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));
|
() => _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 ---
|
// --- Audit ---
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user