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.
95 lines
3.6 KiB
C#
95 lines
3.6 KiB
C#
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,
|
|
};
|
|
}
|