sqlinsight-pro/tests/SQLInsight.IntegrationTests/SqlServerFixture.cs
Claude 15907401a9
Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
Fase 2: Always On availability groups and SQL Agent jobs
- Availability group collector (replica and database replica states,
  queues, commit times) and rules: SQLI-AG-001 unhealthy/disconnected
  replica or suspended data movement (scripts HADR RESUME), SQLI-AG-002
  secondary behind (estimated data loss and redo time)
- SQL Agent collector using only granted msdb tables (no EXECUTE on msdb
  functions): SQLI-JOB-001 failed job with the failing step's message
  (backup/CHECKDB jobs are High), SQLI-JOB-002 SQL Agent not running
- Integration tests create a real availability group (CLUSTER_TYPE NONE)
  and a failing job in the test container (HADR and Agent enabled)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
2026-09-29 07:53:49 +00:00

81 lines
3.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Data.SqlClient;
using Testcontainers.MsSql;
namespace SQLInsight.IntegrationTests;
/// <summary>
/// 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 <c>SQLINSIGHT_MSSQL_IMAGE</c> so CI can run the same tests against 2017–2025.
/// </summary>
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;
// HADR and SQL Agent on, so availability group and job collectors run against the real DMVs.
private readonly MsSqlContainer _container = new MsSqlBuilder(Image)
.WithEnvironment("MSSQL_ENABLE_HADR", "1")
.WithEnvironment("MSSQL_AGENT_ENABLED", "true")
.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<string> 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<SqlServerFixture>
{
public const string Name = "SQL Server";
}