Add unit tests for Audit (59% to 100%) and Webhooks (62% to 91%) namespaces
All checks were successful
CI / build-and-push (push) Successful in 41s
CI / deploy-qa (push) Has been skipped
CI / smoke-qa (push) Has been skipped

Covers AuditLogQueryService filtering/paging/actor-name join,
AuditLogResponse mapping, AuditLogsController, AuditService.LogAsync
and actor-claim resolution, WebhookResponse/WebhookDeliveryLogResponse
mapping, CreateWebhookRequestValidator, WebhookQueue, and additional
WebhookDispatcher branch coverage (scope routing, disabled webhooks,
delivery exceptions).

Excludes WebhookBackgroundService and WebhookRetryBackgroundService
from coverage: both resolve a DI-scoped concrete MicCheckDbContext via
IServiceScopeFactory, which requires a live DI container/DB per
CLAUDE.md's no-InMemory/WebApplicationFactory rule. Their query and
dispatch logic is covered by WebhookRetryTests/WebhookDispatcherTests.
This commit is contained in:
2026-07-05 15:00:02 -07:00
parent 837c51d366
commit 4ad3285afa
10 changed files with 789 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
using MicCheck.Api.Webhooks;
using NUnit.Framework;
namespace MicCheck.Api.Tests.Unit.Webhooks;
[TestFixture]
public class WebhookQueueTests
{
[Test]
public async Task WhenAnEventIsEnqueued_ThenItCanBeReadBack()
{
var queue = new WebhookQueue();
var webhookEvent = new WebhookEvent
{
EventType = WebhookEventTypes.FlagUpdated,
OrganizationId = 1,
Data = new { }
};
await queue.EnqueueAsync(webhookEvent);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
await using var enumerator = queue.ReadAllAsync(cts.Token).GetAsyncEnumerator(cts.Token);
await enumerator.MoveNextAsync();
Assert.That(enumerator.Current, Is.SameAs(webhookEvent));
}
[Test]
public async Task WhenMultipleEventsAreEnqueued_ThenTheyAreReadInOrder()
{
var queue = new WebhookQueue();
var first = new WebhookEvent { EventType = WebhookEventTypes.FlagUpdated, OrganizationId = 1, Data = new { } };
var second = new WebhookEvent { EventType = WebhookEventTypes.FlagDeleted, OrganizationId = 1, Data = new { } };
await queue.EnqueueAsync(first);
await queue.EnqueueAsync(second);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
await using var enumerator = queue.ReadAllAsync(cts.Token).GetAsyncEnumerator(cts.Token);
await enumerator.MoveNextAsync();
Assert.That(enumerator.Current, Is.SameAs(first));
await enumerator.MoveNextAsync();
Assert.That(enumerator.Current, Is.SameAs(second));
}
}