sqlinsight-pro/tests/SQLInsight.IntegrationTests/HighAvailabilityTests.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

82 lines
4.1 KiB
C#

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<AvailabilityGroupSnapshot>(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<AgentJobSnapshot>(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);
}
}