sqlinsight-pro/tests/SQLInsight.IntegrationTests/ConfigurationPageTests.cs
Søren Eggert Lundsteen Olsen ab8cfcad97 Configuration page with change log
- new collector instance-config: collation, edition, version, services and
  accounts (sys.dm_server_services, 2008 R2 SP1+), Instant File
  Initialization and Lock Pages in Memory (where the build has the
  columns), tempdb files, default paths; plus a fingerprint of the whole
  setup (sp_configure, trace flags, services, version, tempdb files)
- changes are computed against the previous collection, which the agent
  restores from the repository after a restart; migration 0008 keeps them
  in insight.ConfigurationChanges (as long as resolved findings)
- SQLI-CFG-013 "Konfiguration ændret": information for a day when
  sp_configure, a trace flag or a service account changes (account names
  are sensitive evidence)
- page /instanser/{navn}/konfiguration: instance, CPU and memory, services,
  tempdb, trace flags, non-default sp_configure (default vs. in use vs.
  configured) and the change log; linked from the instance page
- the management report lists configuration changes in the period
- trace flag reading shared by trace-flags and instance-config

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 08:12:26 +02:00

79 lines
4 KiB
C#

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using SQLInsight.Core.Data;
using SQLInsight.Core.Instances;
using SQLInsight.Repository;
namespace SQLInsight.IntegrationTests;
/// <summary>The configuration page and its change log, from stored snapshots.</summary>
[Collection(SqlServerTestGroup.Name)]
[Trait("Category", "Integration")]
public class ConfigurationPageTests(SqlServerFixture sql) : IAsyncLifetime
{
private readonly string _database = "SQLInsightConfig_" + Guid.NewGuid().ToString("N")[..8];
private InsightRepository _repository = null!;
private int _instanceId;
public async Task InitializeAsync()
{
await sql.ExecuteAsSaAsync($"CREATE DATABASE [{_database}];");
_repository = new InsightRepository(sql.DatabaseConnectionString(_database));
await _repository.MigrateAsync();
var info = new InstanceInfo
{
ServerName = "AX01",
Version = SqlServerVersion.Parse("16.0.4135.4"),
ProductLevel = "RTM",
Edition = "Standard Edition",
EngineEdition = EngineEdition.Standard,
};
_instanceId = await _repository.UpsertInstanceAsync(@"AX01\AX30", info, "SqlClient", false, DateTimeOffset.UtcNow);
}
public async Task DisposeAsync() =>
await sql.ExecuteAsSaAsync($"ALTER DATABASE [{_database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{_database}];");
[Fact]
public async Task Page_shows_the_setup_non_default_options_and_the_change_log()
{
var now = DateTimeOffset.UtcNow;
await _repository.SaveCollectedAsync(_instanceId, "configuration", new ConfigurationSnapshot
{
CapturedAt = now,
Options = [new ConfigurationOption("max degree of parallelism", 1, 1), new ConfigurationOption("cost threshold for parallelism", 5, 5)],
});
await _repository.SaveCollectedAsync(_instanceId, "instance-config", new InstanceConfigurationSnapshot
{
CapturedAt = now,
Collation = "Danish_Norwegian_CI_AS",
ServicesAvailable = true,
Services = [new ServiceInfo("SQL Server (AX30)", @"KUNDE\svc-ax30", "Automatic", "Running", true)],
InstantFileInitialization = true,
MemoryModel = "LOCK_PAGES",
TempdbFiles = [new TempdbFile("tempdev", "ROWS", 1024, "64 MB", "ubegrænset")],
Changes = [new ConfigurationChange(ConfigurationFingerprint.SpConfigure, "max degree of parallelism", "0", "1", now)],
});
var changes = await _repository.GetConfigurationChangesAsync(_instanceId, now.AddDays(-1), now.AddMinutes(1));
await using var factory = new WebApplicationFactory<Program>().WithWebHostBuilder(b =>
{
b.UseEnvironment("Development");
b.UseSetting("SQLInsight:Repository:ConnectionString", sql.DatabaseConnectionString(_database));
b.UseSetting("SQLInsight:Web:Authentication", "None");
});
var name = Uri.EscapeDataString(@"AX01\AX30");
var html = await factory.CreateClient().GetStringAsync($"/instanser/{name}/konfiguration");
var instancePage = await factory.CreateClient().GetStringAsync($"/instanser/{name}");
var change = Assert.Single(changes);
Assert.Equal(("0", "1"), (change.OldValue, change.NewValue));
Assert.Contains("Danish_Norwegian_CI_AS", html, StringComparison.Ordinal);
Assert.Contains(@"KUNDE\svc-ax30", html, StringComparison.Ordinal);
Assert.Contains("Lock Pages in Memory", html, StringComparison.Ordinal);
Assert.Contains("Ændringslog", html, StringComparison.Ordinal);
Assert.Contains("max degree of parallelism", html, StringComparison.Ordinal); // non-default (1 vs 0) and in the log
Assert.DoesNotContain("cost threshold for parallelism</td>", html, StringComparison.Ordinal); // default: not listed
Assert.Contains($"/instanser/{name}/konfiguration", instancePage, StringComparison.Ordinal);
}
}