using Omika.App.Local.Diagnostics;
using Omika.Core.Observability;
using Omika.Core.Observability.Outbox;
namespace Omika.Tests.Observability.Outbox;
/// Tests du store SQLite séparé (omika-diagnostics.db) : persistance, rétention, éviction, redémarrage.
public class DiagnosticOutboxSqliteStoreTests : IAsyncLifetime
{
private readonly string _dir = Path.Combine(Path.GetTempPath(), "omika-diag-sqlite-tests", Guid.NewGuid().ToString("N"));
private string DbPath => Path.Combine(_dir, "omika-diagnostics.db");
public Task InitializeAsync()
{
Directory.CreateDirectory(_dir);
return Task.CompletedTask;
}
public Task DisposeAsync()
{
try
{
if (Directory.Exists(_dir))
{
Directory.Delete(_dir, recursive: true);
}
}
catch
{
// Best-effort.
}
return Task.CompletedTask;
}
private static DiagnosticOutboxRow NewRow(TelemetryPriority priority = TelemetryPriority.P3, string payload = "{}") =>
new()
{
EventId = Guid.NewGuid(),
Priority = priority,
CreatedAtUtc = DateTimeOffset.UtcNow,
ExpiresAtUtc = DateTimeOffset.UtcNow.AddDays(7),
PayloadVersion = 1,
PayloadJson = payload,
AttemptCount = 0,
NextAttemptUtc = DateTimeOffset.UtcNow
};
[Fact]
public async Task StoreAndRetrieve_RoundTrip()
{
await using var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath });
DiagnosticOutboxRow row = NewRow(payload: """{"eventId":"00000000-0000-0000-0000-000000000001"}""");
await store.StoreAsync([row]);
IReadOnlyList batch = await store.GetNextBatchAsync(10, 1024 * 1024, DateTimeOffset.UtcNow);
DiagnosticOutboxRow retrieved = Assert.Single(batch);
Assert.Equal(row.EventId, retrieved.EventId);
Assert.Equal(row.PayloadJson, retrieved.PayloadJson);
Assert.Equal(row.Priority, retrieved.Priority);
}
[Fact]
public async Task GetNextBatch_RespectsNextAttemptUtc()
{
await using var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath });
DiagnosticOutboxRow future = NewRow();
future = new DiagnosticOutboxRow
{
EventId = future.EventId,
Priority = future.Priority,
CreatedAtUtc = future.CreatedAtUtc,
ExpiresAtUtc = future.ExpiresAtUtc,
PayloadVersion = future.PayloadVersion,
PayloadJson = future.PayloadJson,
NextAttemptUtc = DateTimeOffset.UtcNow.AddMinutes(5)
};
await store.StoreAsync([future]);
IReadOnlyList batch = await store.GetNextBatchAsync(10, 1024 * 1024, DateTimeOffset.UtcNow);
Assert.Empty(batch);
IReadOnlyList later = await store.GetNextBatchAsync(10, 1024 * 1024, DateTimeOffset.UtcNow.AddMinutes(10));
Assert.Single(later);
}
[Fact]
public async Task DeleteExpired_RemovesOnlyExpired()
{
await using var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath });
DiagnosticOutboxRow expired = NewRow();
expired = new DiagnosticOutboxRow
{
EventId = expired.EventId,
Priority = expired.Priority,
CreatedAtUtc = expired.CreatedAtUtc,
ExpiresAtUtc = DateTimeOffset.UtcNow.AddMinutes(-1),
PayloadVersion = expired.PayloadVersion,
PayloadJson = expired.PayloadJson
};
await store.StoreAsync([expired, NewRow()]);
int removed = await store.DeleteExpiredAsync(DateTimeOffset.UtcNow);
Assert.Equal(1, removed);
Assert.Equal(1, await store.CountAsync());
}
[Fact]
public async Task DeleteOldest_RemovesOldestOfPriority()
{
await using var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath });
DiagnosticOutboxRow old = NewRow(TelemetryPriority.P3);
await store.StoreAsync([old]);
await Task.Delay(10);
DiagnosticOutboxRow newer = NewRow(TelemetryPriority.P3);
await store.StoreAsync([newer]);
int removed = await store.DeleteOldestAsync(TelemetryPriority.P3, 1);
Assert.Equal(1, removed);
Assert.False((await store.GetNextBatchAsync(10, 1024 * 1024, DateTimeOffset.UtcNow)).Any(r => r.EventId == old.EventId));
}
[Fact]
public async Task Restart_PersistsRowsAcrossSessions()
{
DiagnosticOutboxRow row = NewRow(payload: """{"eventId":"00000000-0000-0000-0000-00000000000A"}""");
await using (var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath }))
{
await store.StoreAsync([row]);
}
// « Redémarrage » : nouvelle connexion sur le même fichier.
await using (var store = new DiagnosticOutboxSqliteStore(new DiagnosticOutboxOptions { DatabasePath = DbPath }))
{
IReadOnlyList batch = await store.GetNextBatchAsync(10, 1024 * 1024, DateTimeOffset.UtcNow);
Assert.Equal(row.EventId, Assert.Single(batch).EventId);
}
}
}