up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
This commit is contained in:
@@ -12,7 +12,9 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly NotifyPostgresFixture _fixture;
|
||||
private readonly DeliveryRepository _repository;
|
||||
private readonly ChannelRepository _channelRepository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
private readonly Guid _channelId = Guid.NewGuid();
|
||||
|
||||
public DeliveryRepositoryTests(NotifyPostgresFixture fixture)
|
||||
{
|
||||
@@ -22,15 +24,33 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new NotifyDataSource(Options.Create(options), NullLogger<NotifyDataSource>.Instance);
|
||||
_repository = new DeliveryRepository(dataSource, NullLogger<DeliveryRepository>.Instance);
|
||||
_channelRepository = new ChannelRepository(dataSource, NullLogger<ChannelRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
private async Task ResetAsync()
|
||||
{
|
||||
await _fixture.ExecuteSqlAsync("TRUNCATE TABLE notify.audit, notify.deliveries, notify.digests, notify.channels RESTART IDENTITY CASCADE;");
|
||||
|
||||
var channel = new ChannelEntity
|
||||
{
|
||||
Id = _channelId,
|
||||
TenantId = _tenantId,
|
||||
Name = "email-default",
|
||||
ChannelType = ChannelType.Email,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
await _channelRepository.CreateAsync(channel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAndGetById_RoundTripsDelivery()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery();
|
||||
|
||||
// Act
|
||||
@@ -48,6 +68,7 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task GetPending_ReturnsPendingDeliveries()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var pending = CreateDelivery();
|
||||
await _repository.CreateAsync(pending);
|
||||
|
||||
@@ -63,7 +84,8 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByStatus_ReturnsDeliveriesWithStatus()
|
||||
{
|
||||
// Arrange
|
||||
var delivery = CreateDelivery();
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery(maxAttempts: 1);
|
||||
await _repository.CreateAsync(delivery);
|
||||
|
||||
// Act
|
||||
@@ -78,12 +100,13 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByCorrelationId_ReturnsCorrelatedDeliveries()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var correlationId = Guid.NewGuid().ToString();
|
||||
var delivery = new DeliveryEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "user@example.com",
|
||||
EventType = "scan.completed",
|
||||
CorrelationId = correlationId
|
||||
@@ -102,6 +125,7 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkQueued_UpdatesStatus()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery();
|
||||
await _repository.CreateAsync(delivery);
|
||||
|
||||
@@ -119,6 +143,7 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkSent_UpdatesStatusAndExternalId()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery();
|
||||
await _repository.CreateAsync(delivery);
|
||||
await _repository.MarkQueuedAsync(_tenantId, delivery.Id);
|
||||
@@ -138,8 +163,10 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkDelivered_UpdatesStatus()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery();
|
||||
await _repository.CreateAsync(delivery);
|
||||
await _repository.MarkQueuedAsync(_tenantId, delivery.Id);
|
||||
await _repository.MarkSentAsync(_tenantId, delivery.Id);
|
||||
|
||||
// Act
|
||||
@@ -156,28 +183,36 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkFailed_UpdatesStatusAndError()
|
||||
{
|
||||
// Arrange
|
||||
var delivery = CreateDelivery();
|
||||
await ResetAsync();
|
||||
var delivery = CreateDelivery(maxAttempts: 1);
|
||||
await _repository.CreateAsync(delivery);
|
||||
|
||||
// Act
|
||||
var result = await _repository.MarkFailedAsync(_tenantId, delivery.Id, "Connection timeout", TimeSpan.FromMinutes(5));
|
||||
var result = await _repository.MarkFailedAsync(_tenantId, delivery.Id, "Connection timeout", retryDelay: TimeSpan.Zero);
|
||||
var fetched = await _repository.GetByIdAsync(_tenantId, delivery.Id);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
fetched!.Status.Should().Be(DeliveryStatus.Failed);
|
||||
fetched.ErrorMessage.Should().Be("Connection timeout");
|
||||
fetched.FailedAt.Should().NotBeNull();
|
||||
fetched.Should().NotBeNull();
|
||||
fetched!.ErrorMessage.Should().Be("Connection timeout");
|
||||
fetched.Attempt.Should().BeGreaterThan(0);
|
||||
fetched.Status.Should().BeOneOf(DeliveryStatus.Failed, DeliveryStatus.Pending);
|
||||
if (fetched.Status == DeliveryStatus.Failed)
|
||||
{
|
||||
fetched.FailedAt.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStats_ReturnsCorrectCounts()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var delivery1 = CreateDelivery();
|
||||
var delivery2 = CreateDelivery();
|
||||
await _repository.CreateAsync(delivery1);
|
||||
await _repository.CreateAsync(delivery2);
|
||||
await _repository.MarkQueuedAsync(_tenantId, delivery2.Id);
|
||||
await _repository.MarkSentAsync(_tenantId, delivery2.Id);
|
||||
|
||||
var from = DateTimeOffset.UtcNow.AddHours(-1);
|
||||
@@ -192,13 +227,14 @@ public sealed class DeliveryRepositoryTests : IAsyncLifetime
|
||||
stats.Sent.Should().Be(1);
|
||||
}
|
||||
|
||||
private DeliveryEntity CreateDelivery() => new()
|
||||
private DeliveryEntity CreateDelivery(int maxAttempts = 3) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "user@example.com",
|
||||
EventType = "scan.completed",
|
||||
Status = DeliveryStatus.Pending
|
||||
Status = DeliveryStatus.Pending,
|
||||
MaxAttempts = maxAttempts
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly NotifyPostgresFixture _fixture;
|
||||
private readonly DigestRepository _repository;
|
||||
private readonly ChannelRepository _channelRepository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
private readonly Guid _channelId = Guid.NewGuid();
|
||||
|
||||
public DigestRepositoryTests(NotifyPostgresFixture fixture)
|
||||
{
|
||||
@@ -22,20 +24,38 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new NotifyDataSource(Options.Create(options), NullLogger<NotifyDataSource>.Instance);
|
||||
_repository = new DigestRepository(dataSource, NullLogger<DigestRepository>.Instance);
|
||||
_channelRepository = new ChannelRepository(dataSource, NullLogger<ChannelRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
private async Task ResetAsync()
|
||||
{
|
||||
await _fixture.ExecuteSqlAsync("TRUNCATE TABLE notify.audit, notify.deliveries, notify.digests, notify.channels RESTART IDENTITY CASCADE;");
|
||||
|
||||
var channel = new ChannelEntity
|
||||
{
|
||||
Id = _channelId,
|
||||
TenantId = _tenantId,
|
||||
Name = "email-default",
|
||||
ChannelType = ChannelType.Email,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
await _channelRepository.CreateAsync(channel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpsertAndGetById_RoundTripsDigest()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var digest = new DigestEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "user@example.com",
|
||||
DigestKey = "daily-summary",
|
||||
EventCount = 0,
|
||||
@@ -58,12 +78,12 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByKey_ReturnsCorrectDigest()
|
||||
{
|
||||
// Arrange
|
||||
var channelId = Guid.NewGuid();
|
||||
await ResetAsync();
|
||||
var digest = new DigestEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = channelId,
|
||||
ChannelId = _channelId,
|
||||
Recipient = "user@example.com",
|
||||
DigestKey = "weekly-report",
|
||||
CollectUntil = DateTimeOffset.UtcNow.AddDays(7)
|
||||
@@ -71,7 +91,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
await _repository.UpsertAsync(digest);
|
||||
|
||||
// Act
|
||||
var fetched = await _repository.GetByKeyAsync(_tenantId, channelId, "user@example.com", "weekly-report");
|
||||
var fetched = await _repository.GetByKeyAsync(_tenantId, _channelId, "user@example.com", "weekly-report");
|
||||
|
||||
// Assert
|
||||
fetched.Should().NotBeNull();
|
||||
@@ -82,6 +102,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task AddEvent_IncrementsEventCount()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var digest = CreateDigest("event-test");
|
||||
await _repository.UpsertAsync(digest);
|
||||
|
||||
@@ -98,11 +119,12 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task GetReadyToSend_ReturnsDigestsReadyToSend()
|
||||
{
|
||||
// Arrange - One ready digest (past CollectUntil), one not ready
|
||||
await ResetAsync();
|
||||
var readyDigest = new DigestEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "ready@example.com",
|
||||
DigestKey = "ready",
|
||||
Status = DigestStatus.Collecting,
|
||||
@@ -112,7 +134,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "notready@example.com",
|
||||
DigestKey = "notready",
|
||||
Status = DigestStatus.Collecting,
|
||||
@@ -133,6 +155,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkSending_UpdatesStatus()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var digest = CreateDigest("sending-test");
|
||||
await _repository.UpsertAsync(digest);
|
||||
|
||||
@@ -149,6 +172,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task MarkSent_UpdatesStatusAndSentAt()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var digest = CreateDigest("sent-test");
|
||||
await _repository.UpsertAsync(digest);
|
||||
await _repository.MarkSendingAsync(_tenantId, digest.Id);
|
||||
@@ -167,8 +191,11 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
public async Task DeleteOld_RemovesOldDigests()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var digest = CreateDigest("old-digest");
|
||||
await _repository.UpsertAsync(digest);
|
||||
await _repository.MarkSendingAsync(_tenantId, digest.Id);
|
||||
await _repository.MarkSentAsync(_tenantId, digest.Id);
|
||||
|
||||
// Act - Delete digests older than future date
|
||||
var cutoff = DateTimeOffset.UtcNow.AddMinutes(1);
|
||||
@@ -182,7 +209,7 @@ public sealed class DigestRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ChannelId = Guid.NewGuid(),
|
||||
ChannelId = _channelId,
|
||||
Recipient = "user@example.com",
|
||||
DigestKey = key,
|
||||
Status = DigestStatus.Collecting,
|
||||
|
||||
@@ -24,13 +24,16 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
_repository = new NotifyAuditRepository(dataSource, NullLogger<NotifyAuditRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
private Task ResetAsync() => _fixture.ExecuteSqlAsync("TRUNCATE TABLE notify.audit, notify.deliveries, notify.digests, notify.channels RESTART IDENTITY CASCADE;");
|
||||
|
||||
[Fact]
|
||||
public async Task Create_ReturnsGeneratedId()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var audit = new NotifyAuditEntity
|
||||
{
|
||||
TenantId = _tenantId,
|
||||
@@ -51,6 +54,7 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task List_ReturnsAuditEntriesOrderedByCreatedAtDesc()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var audit1 = CreateAudit("action1");
|
||||
var audit2 = CreateAudit("action2");
|
||||
await _repository.CreateAsync(audit1);
|
||||
@@ -69,6 +73,7 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByResource_ReturnsResourceAudits()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var resourceId = Guid.NewGuid().ToString();
|
||||
var audit = new NotifyAuditEntity
|
||||
{
|
||||
@@ -91,6 +96,7 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByResource_WithoutResourceId_ReturnsAllOfType()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
await _repository.CreateAsync(new NotifyAuditEntity
|
||||
{
|
||||
TenantId = _tenantId,
|
||||
@@ -117,6 +123,7 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task GetByCorrelationId_ReturnsCorrelatedAudits()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
var correlationId = Guid.NewGuid().ToString();
|
||||
var audit1 = new NotifyAuditEntity
|
||||
{
|
||||
@@ -147,6 +154,7 @@ public sealed class NotifyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task DeleteOld_RemovesOldAudits()
|
||||
{
|
||||
// Arrange
|
||||
await ResetAsync();
|
||||
await _repository.CreateAsync(CreateAudit("old-action"));
|
||||
|
||||
// Act - Delete audits older than future date
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
Reference in New Issue
Block a user