- 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>
79 lines
4 KiB
C#
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);
|
|
}
|
|
}
|