- SessionBatchViewBuilder в Shared собирает нейтральную view model - TelegramSessionBatchRenderer в Bot/Web рендерит HTML + InlineKeyboardMarkup - DiscordSessionBatchRenderer заглушка подготовлена - BatchMessageEditor перенесён из Shared в Bot/Web - Удалён SessionBatchRenderer, убран Telegram.Bot из Shared.csproj - Обновлены все вызовы (7 handler-ов + Web SessionService + smoke tests) - Новые тесты на builder и Telegram renderer
9.0 KiB
Issue #19: Выровнять /newsession с batch-сценарием лендинга
Goal: Сделать
/newsessionатомарным: либо весь batch создаётся целиком, либо ничего — с понятным сообщением об ошибках. Убрать создание частичных сессий при наличии любых ошибок ввода.
Architecture: Изменить NewSessionParseResult.IsValid так, чтобы он учитывал ЛЮБЫЕ parse-ошибки (некорректные даты, лимиты, повторы, прошедшие даты). Обновить CreateSessionHandler, чтобы при !IsValid отправлялось одно детальное сообщение с перечислением ошибок и help-шаблоном, и creation прерывался до транзакции БД.
Tech Stack: C#, .NET, Dapper, Telegram.Bot, xUnit
Task 1: Добавить HasErrors и ужесточить IsValid
Objective: Запретить частичное создание: IsValid == false, если есть хоть одна ошибка парсинга.
Files:
- Modify:
src/GmRelay.Bot/Features/Sessions/CreateSession/NewSessionCommandParser.cs - Test:
tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/NewSessionCommandParserTests.cs
Step 1: RED — написать failing test
Добавить в конец NewSessionCommandParserTests.cs:
[Fact]
public void Parse_ShouldBeInvalid_WhenAnyErrorsPresent()
{
var nowUtc = new DateTimeOffset(2026, 4, 23, 12, 0, 0, TimeSpan.Zero);
var text = """
Название: Delta Green
Время: 25.04.2026 19:30
Время: 31.04.2026 19:30
Ссылка: https://example.test/dg
""";
var result = NewSessionCommandParser.Parse(text, nowUtc);
Assert.True(result.HasErrors);
Assert.False(result.IsValid);
}
Step 2: Run test and verify RED
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "FullyQualifiedName~NewSessionCommandParserTests" -v n
Expected: HasErrors property not found → compile error or FAIL.
Step 3: GREEN — minimal implementation
В NewSessionCommandParser.cs, в NewSessionParseResult, заменить IsValid на:
public bool HasErrors =>
PastTimeInputs.Count > 0 ||
InvalidTimeInputs.Count > 0 ||
InvalidSeatLimitInputs.Count > 0 ||
InvalidRecurringInputs.Count > 0;
public bool IsValid =>
!string.IsNullOrWhiteSpace(Title) &&
!string.IsNullOrWhiteSpace(Link) &&
ScheduledTimes.Count > 0 &&
!HasErrors;
Step 4: Run test and verify GREEN
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "FullyQualifiedName~NewSessionCommandParserTests" -v n
Expected: 7 passed (включая новый).
Step 5: Commit
git add src/GmRelay.Bot/Features/Sessions/CreateSession/NewSessionCommandParser.cs
git add tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/NewSessionCommandParserTests.cs
git commit -m "feat(#19): add HasErrors to prevent partial batch creation"
Task 2: Обновить существующий тест на Past/Invalid times
Objective: Старый тест ожидал IsValid == true при partial invalid — теперь это баг, нужно обновить assertion.
Files:
- Modify:
tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/NewSessionCommandParserTests.cs:114
Step 1: Write failing expectation first
Заменить в методе Parse_ShouldCollectPastAndInvalidTimes (примерно строка 114):
// Было:
Assert.True(result.IsValid);
// Стало:
Assert.False(result.IsValid);
Step 2: Run test and verify RED
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "Parse_ShouldCollectPastAndInvalidTimes" -v n
Expected: FAIL — expected False, actual True.
Step 3: Fix already done in Task 1
Task 1 уже изменил IsValid. Перезапустить тест.
Step 4: Run test and verify GREEN
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "Parse_ShouldCollectPastAndInvalidTimes" -v n
Expected: PASS.
Step 5: Commit
git add tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/NewSessionCommandParserTests.cs
git commit -m "test(#19): update assertion - partial invalid means invalid"
Task 3: Обновить CreateSessionHandler для атомарной валидации и единого сообщения об ошибках
Objective: Убрать разбросанные warning-сообщения. При любых ошибках — одно сообщение с перечнем ошибок + help-шаблон. Не создавать сессии.
Files:
- Modify:
src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs
Step 1: RED — напишем интеграционный поведенческий тест (опционально, landing test уже покрывает happy path)
Просто запустим landing promise smoke test и убедимся, что он ещё green (happy path не сломан).
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "TelegramLandingPromisesSmokeTests" -v n
Expected: PASS.
Step 2: GREEN — minimal change in handler
В CreateSessionHandler.cs, метод HandleAsync, заменить начало (строки 19–59) на:
var parseResult = NewSessionCommandParser.Parse(message.Text ?? message.Caption, DateTimeOffset.UtcNow);
var errorMessages = new List<string>();
foreach (var timeInput in parseResult.PastTimeInputs)
errorMessages.Add($"⚠️ Дата {timeInput} находится в прошлом и будет пропущена.");
foreach (var timeInput in parseResult.InvalidTimeInputs)
errorMessages.Add($"⚠️ Некорректный формат времени '{timeInput}'. Пропущено.");
foreach (var seatLimitInput in parseResult.InvalidSeatLimitInputs)
errorMessages.Add($"⚠️ Некорректный лимит мест '{seatLimitInput}'. Укажите целое число больше 0.");
foreach (var recurringInput in parseResult.InvalidRecurringInputs)
errorMessages.Add($"⚠️ Некорректный повтор расписания '{recurringInput}'. Укажите число игр 1–52 и шаг 1–365 дней.");
if (!parseResult.IsValid)
{
var helpText = """
❌ Не удалось распознать формат. Пожалуйста, используйте шаблон:
/newsession
Название: My Game
Время: 15.05.2026 19:30
Время: 22.05.2026 19:30
Мест: 4
Ссылка: https://link
Картинка: https://cover
Для повтора можно указать одну дату и строки:
Игр: 4
Интервал: 7
""";
if (errorMessages.Count > 0)
{
helpText = string.Join('\n', errorMessages) + "\n\n" + helpText;
}
await botClient.SendMessage(
chatId: message.Chat.Id,
text: helpText,
cancellationToken: cancellationToken);
return;
}
Step 3: Build and verify
cd /repo && dotnet build src/GmRelay.Bot
Expected: Build succeeded.
Step 4: Run all relevant tests
cd /repo && dotnet test tests/GmRelay.Bot.Tests --filter "FullyQualifiedName~CreateSession|FullyQualifiedName~Landing" -v n
Expected: All passed.
Step 5: Commit
git add src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs
git commit -m "feat(#19): atomic validation with detailed error message in /newsession"
Task 4: Проверить полный test suite на регрессии
Objective: Убедиться, что изменения не сломали существующие тесты.
Files:
- Test: все тесты проекта
GmRelay.Bot.Tests
Step 1: Run full test suite
cd /repo && dotnet test tests/GmRelay.Bot.Tests -v n
Expected: All passed.
Step 2: Commit (if any test baseline updated)
Если всё зелёное — commit message:
git commit --allow-empty -m "test(#19): verify full suite green after atomic validation"
Verification Checklist
NewSessionCommandParser.ParseвозвращаетIsValid = falseпри любых ошибках (past/invalid times, seat limits, recurring).HasErrorstrue ↔ есть хотя бы одна ошибка.CreateSessionHandlerне открывает БД-транзакцию, если!IsValid.- Пользователь получает одно сообщение с перечислением ошибок + help.
- Landing promise smoke test проходит (happy path не сломан).
- Полный test suite зелёный.