using System; using System.Threading; using System.Threading.Tasks; using GmRelay.Shared.Features.Sessions.CreateSession.Wizard; using Npgsql; namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard; [Collection(WizardDraftRepositoryCollection.Name)] public sealed class WizardDraftRepositoryTests(WizardDraftRepositoryFixture fixture) { [Fact] public async Task UpsertAsync_InsertThenUpdate_PreservesSingleRow() { var connectionString = await fixture.CreateSchemaDatabaseAsync(); await using var dataSource = NpgsqlDataSource.Create(connectionString); var sut = new WizardDraftRepository(dataSource); var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1)); await sut.UpsertAsync(draft, CancellationToken.None); draft.Step = "Title"; draft.UpdatedAt = DateTimeOffset.UtcNow.AddSeconds(1); await sut.UpsertAsync(draft, CancellationToken.None); var loaded = await sut.GetActiveAsync(draft.Platform, draft.OwnerId, CancellationToken.None); Assert.NotNull(loaded); Assert.Equal("Title", loaded!.Step); } [Fact] public async Task GetActiveAsync_ExpiredDraft_ReturnsNull() { var connectionString = await fixture.CreateSchemaDatabaseAsync(); await using var dataSource = NpgsqlDataSource.Create(connectionString); var sut = new WizardDraftRepository(dataSource); var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddMinutes(-1)); await sut.UpsertAsync(draft, CancellationToken.None); var loaded = await sut.GetActiveAsync(draft.Platform, draft.OwnerId, CancellationToken.None); Assert.Null(loaded); } [Fact] public async Task GetActiveAsync_DifferentOwner_ReturnsNull() { var connectionString = await fixture.CreateSchemaDatabaseAsync(); await using var dataSource = NpgsqlDataSource.Create(connectionString); var sut = new WizardDraftRepository(dataSource); var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1)); await sut.UpsertAsync(draft, CancellationToken.None); var otherOwner = (long.Parse(draft.OwnerId, System.Globalization.CultureInfo.InvariantCulture) + 1) .ToString(System.Globalization.CultureInfo.InvariantCulture); var loaded = await sut.GetActiveAsync(draft.Platform, otherOwner, CancellationToken.None); Assert.Null(loaded); } [Fact] public async Task DeleteExpiredAsync_DeletesOnlyExpired() { var connectionString = await fixture.CreateSchemaDatabaseAsync(); await using var dataSource = NpgsqlDataSource.Create(connectionString); var sut = new WizardDraftRepository(dataSource); var fresh = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1)); var stale = NewDraft("Type", DateTimeOffset.UtcNow.AddMinutes(-1)); stale.Id = Guid.NewGuid(); await sut.UpsertAsync(fresh, CancellationToken.None); await sut.UpsertAsync(stale, CancellationToken.None); var deleted = await sut.DeleteExpiredAsync(CancellationToken.None); Assert.Equal(1, deleted); var loadedFresh = await sut.GetActiveAsync(fresh.Platform, fresh.OwnerId, CancellationToken.None); Assert.NotNull(loadedFresh); } private static WizardDraft NewDraft(string step, DateTimeOffset expiresAt) => new() { Id = Guid.NewGuid(), ChatId = "42", MessageThreadId = null, OwnerId = "100", Platform = "Telegram", Step = step, PayloadJson = "{}", CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow, ExpiresAt = expiresAt, }; }