using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using MicrosoftOptions = Microsoft.Extensions.Options; using StellaOps.TestKit; using StellaOps.Graph.Indexer.Persistence.Postgres.Repositories; using StellaOps.Graph.Indexer.Persistence.Postgres; namespace StellaOps.Graph.Indexer.Persistence.Tests; [Collection(GraphIndexerPostgresCollection.Name)] public sealed class PostgresIdempotencyStoreTests : IAsyncLifetime { private readonly GraphIndexerPostgresFixture _fixture; private readonly PostgresIdempotencyStore _store; public PostgresIdempotencyStoreTests(GraphIndexerPostgresFixture fixture) { _fixture = fixture; var options = fixture.Fixture.CreateOptions(); options.SchemaName = fixture.SchemaName; var dataSource = new GraphIndexerDataSource(MicrosoftOptions.Options.Create(options), NullLogger.Instance); _store = new PostgresIdempotencyStore(dataSource, NullLogger.Instance); } public async Task InitializeAsync() { await _fixture.TruncateAllTablesAsync(); } public Task DisposeAsync() => Task.CompletedTask; [Trait("Category", TestCategories.Unit)] [Fact] public async Task HasSeenAsync_ReturnsFalseForNewToken() { // Arrange var sequenceToken = "seq-" + Guid.NewGuid().ToString("N"); // Act var result = await _store.HasSeenAsync(sequenceToken, CancellationToken.None); // Assert result.Should().BeFalse(); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task MarkSeenAsync_ThenHasSeenAsync_ReturnsTrue() { // Arrange var sequenceToken = "seq-" + Guid.NewGuid().ToString("N"); // Act await _store.MarkSeenAsync(sequenceToken, CancellationToken.None); var result = await _store.HasSeenAsync(sequenceToken, CancellationToken.None); // Assert result.Should().BeTrue(); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task MarkSeenAsync_AllowsDifferentTokens() { // Arrange var token1 = "seq-" + Guid.NewGuid().ToString("N"); var token2 = "seq-" + Guid.NewGuid().ToString("N"); // Act await _store.MarkSeenAsync(token1, CancellationToken.None); await _store.MarkSeenAsync(token2, CancellationToken.None); var seen1 = await _store.HasSeenAsync(token1, CancellationToken.None); var seen2 = await _store.HasSeenAsync(token2, CancellationToken.None); // Assert seen1.Should().BeTrue(); seen2.Should().BeTrue(); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task MarkSeenAsync_IsIdempotent() { // Arrange var sequenceToken = "seq-" + Guid.NewGuid().ToString("N"); // Act - marking same token twice should not throw await _store.MarkSeenAsync(sequenceToken, CancellationToken.None); await _store.MarkSeenAsync(sequenceToken, CancellationToken.None); var result = await _store.HasSeenAsync(sequenceToken, CancellationToken.None); // Assert result.Should().BeTrue(); } }