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.
79 lines
3.0 KiB
C#
79 lines
3.0 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession;
|
|
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
|
|
|
|
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
|
|
|
|
/// <summary>
|
|
/// Verifies that <see cref="CreateSessionHandler.SubmitDraftAsync"/> bails
|
|
/// out gracefully when the wizard payload is missing required fields. The
|
|
/// missing-fields path returns before the shared handler is ever called,
|
|
/// so we pass <c>null!</c> for the shared dependency — a NRE on that
|
|
/// branch would itself prove the validation did not fire.
|
|
/// </summary>
|
|
public sealed class CreateSessionHandlerSubmitMissingFieldsTests
|
|
{
|
|
[Fact]
|
|
public async Task SubmitDraftAsync_EmptyPayload_EditsMessageWithMissingFields()
|
|
{
|
|
var drafts = new FakeWizardDraftRepository();
|
|
var messenger = new FakeWizardMessenger();
|
|
|
|
var sut = new CreateSessionHandler(
|
|
drafts,
|
|
shared: null!, // missing-fields path returns before touching the shared handler
|
|
messenger,
|
|
NullLogger<CreateSessionHandler>.Instance);
|
|
|
|
// Empty payload → every required field is missing.
|
|
var draft = NewDraft(WizardStepNames.Confirm, new WizardPayload());
|
|
drafts.Seed(draft);
|
|
|
|
await sut.SubmitDraftAsync(draft, CancellationToken.None);
|
|
|
|
// The wizard message is edited to surface the missing-field error.
|
|
Assert.Single(messenger.Edits);
|
|
var edit = messenger.Edits[0];
|
|
Assert.Equal(long.Parse(draft.ChatId, System.Globalization.CultureInfo.InvariantCulture), edit.ChatId);
|
|
Assert.Contains("Не заполнены", edit.Text, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitDraftAsync_MissingTitleOnly_EditsMessageNamingTitle()
|
|
{
|
|
var drafts = new FakeWizardDraftRepository();
|
|
var messenger = new FakeWizardMessenger();
|
|
|
|
var sut = new CreateSessionHandler(
|
|
drafts,
|
|
shared: null!,
|
|
messenger,
|
|
NullLogger<CreateSessionHandler>.Instance);
|
|
|
|
// All required fields set except Title.
|
|
var payload = new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Single,
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
Visibility = WizardVisibility.Public,
|
|
Single = new WizardSingleInput
|
|
{
|
|
ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
|
|
MaxPlayers = 4,
|
|
},
|
|
};
|
|
var draft = NewDraft(WizardStepNames.Confirm, payload);
|
|
drafts.Seed(draft);
|
|
|
|
await sut.SubmitDraftAsync(draft, CancellationToken.None);
|
|
|
|
Assert.Single(messenger.Edits);
|
|
Assert.Contains("название", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|