test(auth.apikeys): store-level arg guards + SetEnabledAsync idempotence (review M1/M2)

This commit is contained in:
Joseph Doherty
2026-06-02 03:12:24 -04:00
parent 468959ca8a
commit 290e85cb38
@@ -253,6 +253,73 @@ public sealed class SqliteApiKeyAdminStoreTests : IAsyncLifetime
() => _admin.DeleteAsync(keyId!, CancellationToken.None));
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public async Task SetScopesAsync_NullOrWhitespaceKeyId_ThrowsArgumentException(string? keyId)
{
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _admin.SetScopesAsync(
keyId!,
new HashSet<string>(["read"], StringComparer.Ordinal),
CancellationToken.None));
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public async Task SetEnabledAsync_NullOrWhitespaceKeyId_ThrowsArgumentException(string? keyId)
{
await Assert.ThrowsAnyAsync<ArgumentException>(
() => _admin.SetEnabledAsync(keyId!, enabled: false, DateTimeOffset.UtcNow, CancellationToken.None));
}
[Fact]
public async Task SetScopesAsync_NullScopes_ThrowsArgumentNullException()
{
await _admin.CreateAsync(SampleRecord("key-1"), CancellationToken.None);
await Assert.ThrowsAsync<ArgumentNullException>(
() => _admin.SetScopesAsync("key-1", null!, CancellationToken.None));
}
// --- SetEnabled idempotence ---
[Fact]
public async Task SetEnabledAsync_OnAlreadyActiveKey_ReturnsTrue()
{
await _admin.CreateAsync(SampleRecord("key-1"), CancellationToken.None);
bool result = await _admin.SetEnabledAsync(
"key-1", enabled: true, DateTimeOffset.UtcNow, CancellationToken.None);
Assert.True(result);
ApiKeyRecord? active = await _read.FindActiveByKeyIdAsync("key-1", CancellationToken.None);
Assert.NotNull(active);
Assert.Null(active!.RevokedUtc);
}
[Fact]
public async Task SetEnabledAsync_OnAlreadyDisabledKey_OverwritesTimestamp_ReturnsTrue()
{
await _admin.CreateAsync(SampleRecord("key-1"), CancellationToken.None);
var t1 = new DateTimeOffset(2026, 5, 1, 10, 0, 0, TimeSpan.Zero);
var t2 = new DateTimeOffset(2026, 5, 15, 10, 0, 0, TimeSpan.Zero);
// Disable at t1.
await _admin.SetEnabledAsync("key-1", enabled: false, t1, CancellationToken.None);
// Disable again at a later t2 (idempotent overwrite — no guard on revoked_utc).
bool result = await _admin.SetEnabledAsync("key-1", enabled: false, t2, CancellationToken.None);
Assert.True(result);
IReadOnlyList<ApiKeyListItem> listed = await _admin.ListAsync(CancellationToken.None);
ApiKeyListItem item = Assert.Single(listed, k => k.KeyId == "key-1");
Assert.Equal(t2, item.RevokedUtc);
}
// --- Audit ---
[Fact]