using System.Threading; using System.Threading.Tasks; using GmRelay.Bot.Infrastructure.Telegram; using GmRelay.Shared.Features.Sessions.CreateSession.Wizard; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Telegram.Bot; using Telegram.Bot.Requests.Abstractions; using Telegram.Bot.Types; using BotCreateSessionHandler = GmRelay.Bot.Features.Sessions.CreateSession.CreateSessionHandler; using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes; namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard; /// /// When the user sends /newsession while a non-expired draft already /// exists, the router delegates the update to the wizard (the wizard owns /// every update while a draft is active). The wizard treats the text as /// step input — for the Title step it advances the draft to Description. /// This is the observable contract that this test pins down. /// public sealed class UpdateRouterResetsDraftOnStaleCommandTests { [Fact] public async Task NewSessionCommand_ExistingDraft_DelegatesToWizard() { var bot = Substitute.For(); var (sut, drafts, messenger) = BuildRouter(bot); var draft = NewDraft(WizardStepNames.Title); drafts.Seed(draft); var update = new Update { Message = new Message { Text = "/newsession", Chat = new Chat { Id = long.Parse(draft.ChatId, System.Globalization.CultureInfo.InvariantCulture) }, From = new User { Id = long.Parse(draft.OwnerId, System.Globalization.CultureInfo.InvariantCulture), FirstName = "GM" }, }, }; await sut.RouteAsync(update, CancellationToken.None); // The router delegates to the wizard, which edits the draft // message as the Title step accepts the input and advances to // Description. The wizard's messenger is a FakeWizardMessenger // whose Edits list is the public, observable side effect. Assert.NotEmpty(messenger.Edits); // The bot.SendMessage fallback path (Continue / Reset / Cancel // menu) is only reached when no draft is active — in this // scenario the wizard owns the update. We assert it was NOT // taken here. await bot.DidNotReceiveWithAnyArgs().SendRequest(default(IRequest)!, default); } private static (UpdateRouter sut, FakeWizardDraftRepository drafts, FakeWizardMessenger messenger) BuildRouter( ITelegramBotClient bot) { var drafts = new FakeWizardDraftRepository(); var messenger = new FakeWizardMessenger(); var wizard = new GameCreationWizard(drafts, messenger, NullLogger.Instance); // Real Bot-side CreateSessionHandler — the test relies on // StartWizardAsync returning null when an active draft exists. // We pass null! for the shared handler since the active-draft // path never touches it. var createSessionHandler = new BotCreateSessionHandler( drafts, shared: null!, messenger, NullLogger.Instance); var sut = new UpdateRouter( rsvpHandler: null!, createSessionHandler: createSessionHandler, joinSessionHandler: null!, leaveSessionHandler: null!, promoteWaitlistedPlayerHandler: null!, cancelSessionHandler: null!, deleteSessionHandler: null!, listSessionsHandler: null!, exportCalendarHandler: null!, initiateRescheduleHandler: null!, rescheduleTimeInputHandler: null!, rescheduleVoteHandler: null!, wizard: wizard, drafts: drafts, bot: bot, configuration: Substitute.For(), logger: NullLogger.Instance); return (sut, drafts, messenger); } }