test(auth.apikeys): store-level arg guards + SetEnabledAsync idempotence (review M1/M2)
This commit is contained in:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user