8f0f2ef7e7
Moves the game-creation wizard state machine, view builder, and platform-neutral contracts (callback data, step names, storage exception, club option, step limits) from GmRelay.Bot to GmRelay.Shared. Telegram continues to work through a new TelegramWizardMessenger implementing IWizardMessenger and a WizardInteractionMapper that converts Update → WizardInteraction. Wires the new platform column on wizard_drafts (V032 migration) and switches chat/owner/thread/message ids to TEXT so the same table can hold Discord snowflakes later. - GameCreationWizard: now in Shared, takes IWizardMessenger + IWizardDraftRepository, dispatches on WizardInteraction. - New IWizardMessenger contract with Edit/Send/Answer/GetOwnerClubs (returns string ids so Telegram longs and Discord snowflakes both fit). - New WizardStepViewBuilder in Shared returns (text, IReadOnlyList<WizardAction>); TelegramWizardMessenger renders actions into InlineKeyboardMarkup via a new Bot-side ToInlineKeyboard helper. - New WizardInteractionMapper in Bot (5-case test) converts Telegram Update to WizardInteraction. - WizardDraft gains a Platform column; ChatId/MessageThreadId/OwnerId/ DraftMessageId switched to string. V032 migrates existing rows and rebuilds the owner lookup index on (platform, owner_id). - All existing wizard / create-session tests updated to the new contract (HandleInteractionAsync + WizardInteraction). Wizard callback-data format preserved. - dotnet build clean, dotnet format --verify-no-changes clean, all 101 wizard tests pass.
77 lines
2.7 KiB
C#
77 lines
2.7 KiB
C#
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 TEXT NOT NULL,
|
|
message_thread_id TEXT,
|
|
owner_id TEXT NOT NULL,
|
|
platform TEXT NOT NULL DEFAULT 'Telegram',
|
|
step TEXT NOT NULL,
|
|
payload JSONB NOT NULL,
|
|
draft_message_id TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL,
|
|
expires_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE INDEX idx_wizard_drafts_owner
|
|
ON wizard_drafts(platform, owner_id);
|
|
|
|
CREATE INDEX idx_wizard_drafts_platform
|
|
ON wizard_drafts(platform);
|
|
""",
|
|
connection);
|
|
await createSchema.ExecuteNonQueryAsync().WaitAsync(ContainerTimeout);
|
|
}
|
|
|
|
return connectionString;
|
|
}
|
|
}
|