using Microsoft.Data.SqlClient; using SQLInsight.Collectors.Instance; using SQLInsight.Core.Data; using SQLInsight.Core.Instances; namespace SQLInsight.IntegrationTests; [Collection(SqlServerTestGroup.Name)] [Trait("Category", "Integration")] public class HighAvailabilityTests(SqlServerFixture sql) { [Fact] public async Task Availability_group_state_is_read_with_least_privilege() { await sql.ExecuteAsSaAsync(""" IF DB_ID(N'AgProbe') IS NULL BEGIN CREATE DATABASE AgProbe; END; """); await sql.ExecuteAsSaAsync(""" IF NOT EXISTS (SELECT 1 FROM sys.availability_groups WHERE name = N'ag_probe') BEGIN ALTER DATABASE AgProbe SET RECOVERY FULL; BACKUP DATABASE AgProbe TO DISK = N'/tmp/agprobe.bak' WITH INIT; IF NOT EXISTS (SELECT 1 FROM sys.symmetric_keys WHERE name = N'##MS_DatabaseMasterKey##') CREATE MASTER KEY ENCRYPTION BY PASSWORD = N'Mk-Pa55word!'; IF NOT EXISTS (SELECT 1 FROM sys.certificates WHERE name = N'ag_probe_cert') CREATE CERTIFICATE ag_probe_cert WITH SUBJECT = N'ag_probe'; IF NOT EXISTS (SELECT 1 FROM sys.endpoints WHERE name = N'hadr_endpoint') CREATE ENDPOINT hadr_endpoint STATE = STARTED AS TCP (LISTENER_PORT = 5022) FOR DATABASE_MIRRORING (ROLE = ALL, AUTHENTICATION = CERTIFICATE ag_probe_cert, ENCRYPTION = REQUIRED ALGORITHM AES); DECLARE @server sysname = CAST(SERVERPROPERTY('ServerName') AS sysname); EXEC (N'CREATE AVAILABILITY GROUP ag_probe WITH (CLUSTER_TYPE = NONE) FOR DATABASE AgProbe REPLICA ON N''' + @server + N''' WITH (ENDPOINT_URL = N''tcp://localhost:5022'', AVAILABILITY_MODE = ASYNCHRONOUS_COMMIT, FAILOVER_MODE = MANUAL, SEEDING_MODE = AUTOMATIC);'); END; """); await using var session = await AgentSession.OpenAsync(sql); Assert.True(session.Capabilities.Has(Capability.HadrEnabled)); var snapshot = await session.CollectAsync(new AvailabilityGroupCollector()); var replica = Assert.Single(snapshot.Replicas, r => r.Group == "ag_probe"); Assert.True(replica.IsLocal); Assert.Equal("PRIMARY", replica.Role); var database = Assert.Single(snapshot.Databases, d => d.Group == "ag_probe"); Assert.Equal("AgProbe", database.Database); Assert.True(database.IsPrimaryReplica); } [Fact] public async Task Failing_agent_job_is_reported_with_the_step_message() { await sql.ExecuteAsSaAsync(""" IF EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = N'SQLInsight probe job') EXEC msdb.dbo.sp_delete_job @job_name = N'SQLInsight probe job'; EXEC msdb.dbo.sp_add_job @job_name = N'SQLInsight probe job'; EXEC msdb.dbo.sp_add_jobstep @job_name = N'SQLInsight probe job', @step_name = N'Fail on purpose', @subsystem = N'TSQL', @command = N'RAISERROR(N''sqlinsight-probe-failure'', 16, 1);'; EXEC msdb.dbo.sp_add_jobserver @job_name = N'SQLInsight probe job'; EXEC msdb.dbo.sp_start_job @job_name = N'SQLInsight probe job'; """); await using var session = await AgentSession.OpenAsync(sql); AgentJob? job = null; for (var attempt = 0; attempt < 60 && job?.LastRunFailed != true; attempt++) { await Task.Delay(1000); var snapshot = await session.CollectAsync(new AgentJobCollector()); Assert.True(snapshot.IsAgentRunning); job = snapshot.Jobs.SingleOrDefault(j => j.Name == "SQLInsight probe job"); } Assert.NotNull(job); Assert.True(job.LastRunFailed); Assert.Equal("Fail on purpose", job.LastFailedStepName); Assert.Contains("sqlinsight-probe-failure", job.LastFailedStepMessage, StringComparison.Ordinal); Assert.True(job.FailuresLastDay >= 1); Assert.NotNull(job.LastRunAt); } }