test(wizard): add WizardDraftRepository integration tests
This commit is contained in:
+75
@@ -0,0 +1,75 @@
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
|
||||
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
|
||||
|
||||
[CollectionDefinition(Name)]
|
||||
public sealed class WizardDraftRepositoryCollection : ICollectionFixture<WizardDraftRepositoryFixture>
|
||||
{
|
||||
public const string Name = "Wizard draft repository PostgreSQL";
|
||||
}
|
||||
|
||||
public sealed class WizardDraftRepositoryFixture : IAsyncLifetime
|
||||
{
|
||||
private static readonly TimeSpan ContainerTimeout = TimeSpan.FromMinutes(2);
|
||||
private readonly PostgreSqlContainer container = new PostgreSqlBuilder("postgres:17-alpine").Build();
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
return container.StartAsync().WaitAsync(ContainerTimeout);
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return container.DisposeAsync().AsTask().WaitAsync(ContainerTimeout);
|
||||
}
|
||||
|
||||
public async Task<string> CreateSchemaDatabaseAsync()
|
||||
{
|
||||
var databaseName = $"wizard_drafts_{Guid.NewGuid():N}";
|
||||
|
||||
await using (var adminConnection = new NpgsqlConnection(container.GetConnectionString()))
|
||||
{
|
||||
await adminConnection.OpenAsync().WaitAsync(ContainerTimeout);
|
||||
await using var createDatabase = new NpgsqlCommand($"CREATE DATABASE \"{databaseName}\"", adminConnection);
|
||||
await createDatabase.ExecuteNonQueryAsync().WaitAsync(ContainerTimeout);
|
||||
}
|
||||
|
||||
var connectionString = new NpgsqlConnectionStringBuilder(container.GetConnectionString())
|
||||
{
|
||||
Database = databaseName,
|
||||
Timeout = 10,
|
||||
CommandTimeout = 10
|
||||
}.ConnectionString;
|
||||
|
||||
await using (var connection = new NpgsqlConnection(connectionString))
|
||||
{
|
||||
await connection.OpenAsync().WaitAsync(ContainerTimeout);
|
||||
await using var createSchema = new NpgsqlCommand(
|
||||
"""
|
||||
CREATE TABLE wizard_drafts (
|
||||
id UUID PRIMARY KEY,
|
||||
chat_id BIGINT NOT NULL,
|
||||
message_thread_id INT,
|
||||
owner_telegram_id BIGINT NOT NULL,
|
||||
step TEXT NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
draft_message_id BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wizard_drafts_owner
|
||||
ON wizard_drafts(chat_id, message_thread_id, owner_telegram_id);
|
||||
|
||||
CREATE INDEX idx_wizard_drafts_expires
|
||||
ON wizard_drafts(expires_at);
|
||||
""",
|
||||
connection);
|
||||
await createSchema.ExecuteNonQueryAsync().WaitAsync(ContainerTimeout);
|
||||
}
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
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.ChatId, draft.MessageThreadId, draft.OwnerTelegramId, 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.ChatId, draft.MessageThreadId, draft.OwnerTelegramId, 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 loaded = await sut.GetActiveAsync(draft.ChatId, draft.MessageThreadId, ownerTelegramId: draft.OwnerTelegramId + 1, 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.ChatId, fresh.MessageThreadId, fresh.OwnerTelegramId, CancellationToken.None);
|
||||
Assert.NotNull(loadedFresh);
|
||||
}
|
||||
|
||||
private static WizardDraft NewDraft(string step, DateTimeOffset expiresAt) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ChatId = 42,
|
||||
MessageThreadId = null,
|
||||
OwnerTelegramId = 100,
|
||||
Step = step,
|
||||
PayloadJson = "{}",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
ExpiresAt = expiresAt,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user