fix(bot,discord): в /newsession нельзя задать сессию без лимита мест #123

Closed
opened 2026-06-08 18:16:14 +03:00 by Toutsu · 1 comment
Owner

Проблема

В Telegram-визарде создания сессий (/newsession) на шаге «Лимит мест» пользователь не мог задать сессию без лимита. Бот отвечал «Не заполнены поля: лимит мест» (см. скриншот), даже когда пользователь пытался нажать «Без waitlist», полагая, что это «без лимита».

Скриншот

Скриншот из Telegram: при выборе «Без waitlist» на шаге Capacity бот отвечает « Не заполнены поля: лимит мест» (10:30 на скриншоте пользователя).

Корневые причины

  1. В WizardStepViewBuilder.BuildCapacity (Telegram) и DiscordWizardStep.RenderCapacity / RenderPoolSlotCapacity (Discord) не было кнопки «Без лимита» — были только кнопки waitlist. Кнопка waitlist:off молча перепрыгивала на следующий шаг (ApplyCapacityChoice), не устанавливая MaxPlayers — то есть оставляла null.

  2. В BuildCommand (Telegram: CreateSessionHandler.cs:173, Discord: DiscordWizardSubmitter.cs:137) стоял ?? 0 — если бы null MaxPlayers дошёл до команды (через гонку или старый draft), он превратился бы в 0, что нарушает DB CHECK ck_sessions_max_players (V006__add_session_capacity_waitlist.sql: max_players IS NULL OR max_players > 0).

  3. DB и контракт CreateSessionCommand.MaxPlayers: int? уже корректно поддерживали NULL (тест CreateSessionCommandContractTests.cs:16, рендер SessionBatchViewBuilderTests.cs:140, web-форма EditSession.razor:56 с placeholder="Без лимита"). Сломан был только wizard-флоу.

Решение

Добавлена кнопка ♾ Без лимита (callback value: no_limit) в:

  • WizardStepViewBuilder.BuildCapacity (Telegram) — src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStepViewBuilder.cs
  • DiscordWizardStep.RenderCapacity (Discord) — src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs
  • DiscordWizardStep.RenderPoolSlotCapacity (Discord) — src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs

Обработка no_limit:

  • В общем GameCreationWizard.ApplyCapacityChoice (shared) — src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs:301-307 — ветка "no_limit" => (Visibility, SetMaxPlayers(p, null)).
  • SetMaxPlayers переведён с int v на int? v для поддержки null.
  • BuildCommand в обоих submitter'ах принимает int? maxPlayers, ?? 0 убран.

UX-улучшение: в Discord-embed подтверждения (BuildConfirmDescription) при MaxPlayers = null теперь явно пишется 👥 Без лимита, waitlist вкл/выкл вместо игнорирования поля.

Тесты

Добавлено 9 регрессионных тестов:

Telegram (5):

  • WizardStepRenderTests.CapacityStep_HasWaitlistButtons — добавлена проверка наличия кнопки «Без лимита»
  • GameCreationWizardStepTransitionsTests.NoLimitCapacityButton_AdvancesToVisibility_AndLeavesMaxPlayersNull — что no_limit ведёт на Visibility и оставляет MaxPlayers = null в JSON draft'а
  • GameCreationWizardStepTransitionsTests.ChoiceCallback_AdvancesToExpectedStep[Capacity/no_limit] — Theory-кейс
  • CreateSessionHandlerBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsNull_PropagatesNull — null доходит до команды
  • CreateSessionHandlerBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsSet_PropagatesValue — число доходит

Discord (3 + 1):

  • DiscordWizardStepCapacityRenderTests.RenderCapacity_ContainsNoLimitButton
  • DiscordWizardStepCapacityRenderTests.RenderPoolSlotCapacity_ContainsNoLimitButton
  • DiscordWizardStepCapacityRenderTests.RenderCapacity_NoLimitButton_HasChoiceCustomIdForNoLimit
  • DiscordWizardSubmitterBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsNull_PropagatesNull
  • DiscordWizardSubmitterBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsSet_PropagatesValue

Также добавлен InternalsVisibleTo("GmRelay.Bot.Tests") в src/GmRelay.DiscordBot/Properties/AssemblyInfo.cs (для unit-тестов internal BuildCommand).

Верификация

  • dotnet test — 601 пройдено, 2 пропущены (Testcontainers, как до фикса)
  • dotnet format --verify-no-changes — чист
  • dotnet build — без warning'ов
  • dotnet list package --vulnerable — чист
  • packages.lock.json не изменялись (новых пакетов нет)

Аудит follow-up

Проверил, не страдает ли тем же багом Web-форма: нет. EditSession.razor:56 уже использует <InputNumber @bind-Value="model.MaxPlayers" min="1" placeholder="Без лимита" /> с int?, а AuthorizedSessionService.cs:506 отвергает только <= 0, null допустим. Контракт int? MaxPlayers уже полностью поддержан.

Также проверил пул-флоу: WizardSlotInput.MaxPlayers: int (не nullable) — слоты пула by design всегда с лимитом 1..50, MaxPlayersForPool корректно работает с int. Архитектурное изменение здесь не требуется.

## Проблема В Telegram-визарде создания сессий (`/newsession`) на шаге «Лимит мест» пользователь не мог задать сессию без лимита. Бот отвечал «Не заполнены поля: лимит мест» (см. скриншот), даже когда пользователь пытался нажать «Без waitlist», полагая, что это «без лимита». ### Скриншот Скриншот из Telegram: при выборе «Без waitlist» на шаге Capacity бот отвечает «❌ Не заполнены поля: лимит мест» (10:30 на скриншоте пользователя). ## Корневые причины 1. **В `WizardStepViewBuilder.BuildCapacity` (Telegram) и `DiscordWizardStep.RenderCapacity` / `RenderPoolSlotCapacity` (Discord) не было кнопки «Без лимита»** — были только кнопки waitlist. Кнопка `waitlist:off` молча перепрыгивала на следующий шаг (`ApplyCapacityChoice`), не устанавливая `MaxPlayers` — то есть оставляла `null`. 2. **В `BuildCommand` (Telegram: `CreateSessionHandler.cs:173`, Discord: `DiscordWizardSubmitter.cs:137`) стоял `?? 0`** — если бы `null` MaxPlayers дошёл до команды (через гонку или старый draft), он превратился бы в `0`, что нарушает DB CHECK `ck_sessions_max_players` (`V006__add_session_capacity_waitlist.sql`: `max_players IS NULL OR max_players > 0`). 3. **DB и контракт `CreateSessionCommand.MaxPlayers: int?` уже корректно поддерживали `NULL`** (тест `CreateSessionCommandContractTests.cs:16`, рендер `SessionBatchViewBuilderTests.cs:140`, web-форма `EditSession.razor:56` с `placeholder="Без лимита"`). Сломан был только wizard-флоу. ## Решение Добавлена кнопка `♾ Без лимита` (`callback value: no_limit`) в: - `WizardStepViewBuilder.BuildCapacity` (Telegram) — `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStepViewBuilder.cs` - `DiscordWizardStep.RenderCapacity` (Discord) — `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs` - `DiscordWizardStep.RenderPoolSlotCapacity` (Discord) — `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs` Обработка `no_limit`: - В общем `GameCreationWizard.ApplyCapacityChoice` (shared) — `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs:301-307` — ветка `"no_limit" => (Visibility, SetMaxPlayers(p, null))`. - `SetMaxPlayers` переведён с `int v` на `int? v` для поддержки null. - `BuildCommand` в обоих submitter'ах принимает `int? maxPlayers`, `?? 0` убран. UX-улучшение: в Discord-embed подтверждения (`BuildConfirmDescription`) при `MaxPlayers = null` теперь явно пишется `👥 Без лимита, waitlist вкл/выкл` вместо игнорирования поля. ## Тесты Добавлено 9 регрессионных тестов: Telegram (5): - `WizardStepRenderTests.CapacityStep_HasWaitlistButtons` — добавлена проверка наличия кнопки «Без лимита» - `GameCreationWizardStepTransitionsTests.NoLimitCapacityButton_AdvancesToVisibility_AndLeavesMaxPlayersNull` — что `no_limit` ведёт на `Visibility` и оставляет `MaxPlayers = null` в JSON draft'а - `GameCreationWizardStepTransitionsTests.ChoiceCallback_AdvancesToExpectedStep[Capacity/no_limit]` — Theory-кейс - `CreateSessionHandlerBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsNull_PropagatesNull` — null доходит до команды - `CreateSessionHandlerBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsSet_PropagatesValue` — число доходит Discord (3 + 1): - `DiscordWizardStepCapacityRenderTests.RenderCapacity_ContainsNoLimitButton` - `DiscordWizardStepCapacityRenderTests.RenderPoolSlotCapacity_ContainsNoLimitButton` - `DiscordWizardStepCapacityRenderTests.RenderCapacity_NoLimitButton_HasChoiceCustomIdForNoLimit` - `DiscordWizardSubmitterBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsNull_PropagatesNull` - `DiscordWizardSubmitterBuildCommandTests.BuildCommand_WhenSingleMaxPlayersIsSet_PropagatesValue` Также добавлен `InternalsVisibleTo("GmRelay.Bot.Tests")` в `src/GmRelay.DiscordBot/Properties/AssemblyInfo.cs` (для unit-тестов internal `BuildCommand`). ## Верификация - `dotnet test` — 601 пройдено, 2 пропущены (Testcontainers, как до фикса) - `dotnet format --verify-no-changes` — чист - `dotnet build` — без warning'ов - `dotnet list package --vulnerable` — чист - `packages.lock.json` не изменялись (новых пакетов нет) ## Аудит follow-up Проверил, не страдает ли тем же багом Web-форма: **нет**. `EditSession.razor:56` уже использует `<InputNumber @bind-Value="model.MaxPlayers" min="1" placeholder="Без лимита" />` с `int?`, а `AuthorizedSessionService.cs:506` отвергает только `<= 0`, null допустим. Контракт `int? MaxPlayers` уже полностью поддержан. Также проверил пул-флоу: `WizardSlotInput.MaxPlayers: int` (не nullable) — слоты пула by design всегда с лимитом 1..50, `MaxPlayersForPool` корректно работает с `int`. Архитектурное изменение здесь не требуется.
Author
Owner

Реализовано в PR #124 и выпущено в v3.9.3.

Спасибо за подробный скриншот — он сразу указал на нужный шаг визарда.

Реализовано в [PR #124](https://git.codeanddice.ru/Toutsu/GmRelayBot/pulls/124) и выпущено в [v3.9.3](https://git.codeanddice.ru/Toutsu/GmRelayBot/releases/tag/v3.9.3). Спасибо за подробный скриншот — он сразу указал на нужный шаг визарда.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Toutsu/GmRelayBot#123