using ZB.MOM.WW.Auth.Abstractions.ApiKeys; namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.Tests.Live; /// /// Live tests for that the ported SQLite suite does not /// already cover: newest-first ordering under a limit, and the non-positive-limit short circuit. /// /// /// xUnit constructs a new test-class instance per test, so each test provisions and drops its own /// throwaway schema. /// [Collection("live-sqlserver")] public sealed class SqlServerApiKeyAuditStoreLiveTests : IAsyncLifetime { private readonly string _schema = LiveSqlServer.NewSchemaName(); private SqlServerApiKeyAuditStore _audit = null!; /// public async Task InitializeAsync() { if (!LiveSqlServer.IsEnabled) { return; } var factory = new AuthSqlServerConnectionFactory(LiveSqlServer.OptionsFor(_schema)); await new SqlServerApiKeyStoreMigrator(factory).MigrateAsync(CancellationToken.None); _audit = new SqlServerApiKeyAuditStore(factory); } /// public Task DisposeAsync() => LiveSqlServer.DropSchemaAsync(_schema); [SkippableFact] public async Task Append_ThenListRecent_ReturnsNewestFirstWithinLimit() { Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason); // Every row carries the SAME created_utc, so the newest-first order can only come from the // IDENTITY audit_id the ORDER BY names — the timestamp cannot accidentally supply it. var when = new DateTimeOffset(2026, 7, 1, 0, 0, 0, TimeSpan.Zero); for (int i = 0; i < 6; i++) { await _audit.AppendAsync( new ApiKeyAuditEntry("k", $"e{i}", "10.0.0.1", when, $"detail {i}"), CancellationToken.None); } IReadOnlyList recent = await _audit.ListRecentAsync(3, CancellationToken.None); Assert.Equal(3, recent.Count); Assert.Equal(["e5", "e4", "e3"], recent.Select(e => e.EventType)); Assert.Equal("detail 5", recent[0].Details); Assert.Equal(when, recent[0].CreatedUtc); } [SkippableFact] public async Task ListRecent_NonPositiveLimit_ReturnsEmpty() { Skip.IfNot(LiveSqlServer.IsEnabled, LiveSqlServer.SkipReason); await _audit.AppendAsync( new ApiKeyAuditEntry("k", "created", null, DateTimeOffset.UtcNow, null), CancellationToken.None); // A non-positive limit short circuits WITHOUT touching the database — SELECT TOP (0) would // be legal but SELECT TOP (-1) is a syntax error, so the guard is load-bearing here in a way // it is not for SQLite's LIMIT. Assert.Empty(await _audit.ListRecentAsync(0, CancellationToken.None)); Assert.Empty(await _audit.ListRecentAsync(-5, CancellationToken.None)); } }