b5da1c8c57
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
75 lines
2.9 KiB
C#
75 lines
2.9 KiB
C#
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
|
|
|
namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer.Tests.Live;
|
|
|
|
/// <summary>
|
|
/// Live tests for <see cref="SqlServerApiKeyAuditStore"/> that the ported SQLite suite does not
|
|
/// already cover: newest-first ordering under a limit, and the non-positive-limit short circuit.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// xUnit constructs a new test-class instance per test, so each test provisions and drops its own
|
|
/// throwaway schema.
|
|
/// </remarks>
|
|
[Collection("live-sqlserver")]
|
|
public sealed class SqlServerApiKeyAuditStoreLiveTests : IAsyncLifetime
|
|
{
|
|
private readonly string _schema = LiveSqlServer.NewSchemaName();
|
|
|
|
private SqlServerApiKeyAuditStore _audit = null!;
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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<ApiKeyAuditEntry> 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));
|
|
}
|
|
}
|