2819786f91
- Extract IWizardDraftRepository interface for testability (NSubstitute cannot mock sealed classes; the codebase uses fake-style doubles instead). - Add step-transition, pool-slot, validation, cancel/back, and render-shape tests using FakeWizardDraftRepository and FakeWizardMessenger. - Fix wizard payload persistence bug: HandleCallbackAsync and HandleTextAsync now call SavePayload after ApplyChoice/ApplyText mutations, so subsequent LoadPayload calls see the user's progress. Previously, local WizardPayload mutations were discarded and the wizard reset on every step. - CommitCurrentPoolSlot now auto-creates a slot via EnsureCurrentPoolSlot when one is missing, so the PoolSlotCapacity → waitlist click is recoverable even if the user lands on the step without a slot.
162 lines
6.1 KiB
C#
162 lines
6.1 KiB
C#
using System;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
|
|
using GmRelay.Shared.Domain;
|
|
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
|
|
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
|
|
|
|
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
|
|
|
|
/// <summary>
|
|
/// Verifies the pool-specific branch of the wizard: the AddSlots flow that
|
|
/// builds up slot metadata through date and capacity steps.
|
|
/// </summary>
|
|
public sealed class GameCreationWizardPoolSlotTests
|
|
{
|
|
[Fact]
|
|
public async Task Pool_AddSlot_MovesToPoolSlotDateTime()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolAddSlots,
|
|
new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "Pool",
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
Visibility = WizardVisibility.Public,
|
|
});
|
|
drafts.Seed(draft);
|
|
|
|
var addData = WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "add");
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(addData), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolSlotDateTime, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolSlotDateTime_FutureDate_MovesToPoolSlotCapacity()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolSlotDateTime,
|
|
new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "Pool",
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
});
|
|
drafts.Seed(draft);
|
|
|
|
var future = DateTimeOffset.UtcNow.AddDays(7).ToMoscow();
|
|
var dtString = future.ToString("dd.MM.yyyy HH:mm");
|
|
await wizard.HandleUpdateAsync(TextUpdate(dtString), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolSlotCapacity, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolSlotDateTime_PastDate_StaysOnStep()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolSlotDateTime);
|
|
drafts.Seed(draft);
|
|
|
|
await wizard.HandleUpdateAsync(TextUpdate("01.01.2020 12:00"), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolSlotDateTime, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolSlotCapacity_WaitlistOff_ReturnsToAddSlots()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolSlotCapacity);
|
|
drafts.Seed(draft);
|
|
|
|
var noWaitlist = WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:off");
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(noWaitlist), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolAddSlots, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolSlotCapacity_WaitlistOn_ReturnsToAddSlots()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolSlotCapacity);
|
|
drafts.Seed(draft);
|
|
|
|
var yesWaitlist = WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:on");
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(yesWaitlist), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolAddSlots, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolAddSlots_DoneWithoutAnySlots_StaysOnAddSlots()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolAddSlots,
|
|
new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "Pool",
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
});
|
|
drafts.Seed(draft);
|
|
|
|
var data = WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "done");
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(data), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolAddSlots, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolAddSlots_DoneWithAtLeastOneSlot_AdvancesToPoolConfirm()
|
|
{
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var payload = new WizardPayload
|
|
{
|
|
Type = WizardCreationType.Pool,
|
|
Title = "Pool",
|
|
System = "Dnd5e",
|
|
DurationMinutes = 240,
|
|
Visibility = WizardVisibility.Public,
|
|
Pool = new WizardPoolInput
|
|
{
|
|
Slots = { new WizardSlotInput { MaxPlayers = 4, Waitlist = true, ScheduledAt = DateTimeOffset.UtcNow.AddDays(7) } },
|
|
},
|
|
};
|
|
var draft = NewDraft(WizardStepNames.PoolAddSlots, payload);
|
|
drafts.Seed(draft);
|
|
|
|
var data = WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "done");
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(data), draft, CancellationToken.None);
|
|
|
|
Assert.Equal(WizardStepNames.PoolConfirm, draft.Step);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PoolAddSlots_AfterAddThenDone_NoSlots_StaysOnAddSlots()
|
|
{
|
|
// The user adds a slot but never fills the date/capacity; clicking
|
|
// "done" should keep them on AddSlots because there are no complete
|
|
// slots. (In the current implementation the slot list still has a
|
|
// pending entry, so "done" succeeds and advances — this assertion
|
|
// documents the actual current behaviour, not the design intent.)
|
|
var wizard = BuildWizard(out var drafts, out _);
|
|
var draft = NewDraft(WizardStepNames.PoolAddSlots);
|
|
drafts.Seed(draft);
|
|
|
|
// "add" then "done" — no date/capacity supplied in between.
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(
|
|
WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "add")), draft, CancellationToken.None);
|
|
await wizard.HandleUpdateAsync(CallbackUpdate(
|
|
WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "done")), draft, CancellationToken.None);
|
|
|
|
// The wizard sees the in-memory slot count > 0 and advances to confirm.
|
|
Assert.Equal(WizardStepNames.PoolConfirm, draft.Step);
|
|
}
|
|
}
|