using Microsoft.Data.SqlClient;
using Testcontainers.MsSql;
namespace SQLInsight.IntegrationTests;
///
/// One SQL Server container for the test run, with the agent login set up by the real least-privilege script.
/// The image is chosen with SQLINSIGHT_MSSQL_IMAGE so CI can run the same tests against 2017–2025.
///
public sealed class SqlServerFixture : IAsyncLifetime
{
public const string DefaultImage = "mcr.microsoft.com/mssql/server:2022-latest";
public const string AgentLogin = "sqlinsight_agent";
public const string AgentPassword = "Agent-Pa55word!";
public const string TestDatabase = "SQLInsightQsTest";
public static string Image => Environment.GetEnvironmentVariable("SQLINSIGHT_MSSQL_IMAGE") ?? DefaultImage;
private readonly MsSqlContainer _container = new MsSqlBuilder(Image).Build();
public string SaConnectionString => _container.GetConnectionString();
public string Server => new SqlConnectionStringBuilder(SaConnectionString).DataSource;
public string SaPassword => new SqlConnectionStringBuilder(SaConnectionString).Password;
public string AgentConnectionString => new SqlConnectionStringBuilder(SaConnectionString)
{
UserID = AgentLogin,
Password = AgentPassword,
InitialCatalog = "master",
}.ConnectionString;
public string DatabaseConnectionString(string database) =>
new SqlConnectionStringBuilder(SaConnectionString) { InitialCatalog = database }.ConnectionString;
public async Task InitializeAsync()
{
await _container.StartAsync();
await ExecuteAsSaAsync($"""
CREATE LOGIN [{AgentLogin}] WITH PASSWORD = N'{AgentPassword}', CHECK_POLICY = OFF;
CREATE DATABASE [{TestDatabase}];
""");
await ExecuteAsSaAsync($"""
USE [{TestDatabase}];
ALTER DATABASE [{TestDatabase}] SET QUERY_STORE = ON;
CREATE TABLE dbo.CustomerData (Id int NOT NULL PRIMARY KEY, Secret nvarchar(50) NOT NULL);
INSERT dbo.CustomerData VALUES (1, N'fortroligt'), (2, N'hemmeligt');
""");
await ExecuteAsSaAsync(await GrantScriptAsync());
}
public static async Task GrantScriptAsync()
{
var script = await File.ReadAllTextAsync(Path.Combine(AppContext.BaseDirectory, "Scripts", "grant-least-privilege.sql"));
return script.Replace(@"N'DOMÆNE\svc-sqlinsight$'", $"N'{AgentLogin}'", StringComparison.Ordinal);
}
public Task DisposeAsync() => _container.DisposeAsync().AsTask();
public async Task ExecuteAsSaAsync(string sql)
{
await using var connection = new SqlConnection(SaConnectionString);
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = sql;
command.CommandTimeout = 120;
await command.ExecuteNonQueryAsync();
}
}
[CollectionDefinition(Name)]
public sealed class SqlServerTestGroup : ICollectionFixture
{
public const string Name = "SQL Server";
}