feat(#19): добавить ссылку на игру в карточку батча
PR Checks / test-and-build (pull_request) Successful in 3m49s

- SessionBatchDto: добавлено поле JoinLink
- SessionViewItem: добавлено поле JoinLink
- SessionBatchViewBuilder: прокидывание JoinLink из DTO в ViewModel
- CreateSessionHandler, SessionService: обновлены все вызовы конструктора
- TelegramSessionBatchRenderer (Bot + Web): рендеринг ссылки в карточке
- Добавлены тесты на наличие ссылки в рендере
- Все 7 SQL-запросов, загружающих SessionBatchDto, обновлены с join_link AS JoinLink
- Бамп версии до 1.11.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 17:55:24 +03:00
parent ac6e2455a1
commit c0c8f852d2
20 changed files with 54 additions and 32 deletions
@@ -147,6 +147,7 @@ public sealed class TelegramLandingPromisesSmokeTests
string title,
IReadOnlyList<DateTimeOffset> scheduledTimes,
int? maxPlayers,
string joinLink,
SessionNotificationMode notificationMode)
{
Title = title;
@@ -156,7 +157,8 @@ public sealed class TelegramLandingPromisesSmokeTests
Guid.NewGuid(),
scheduledAt.UtcDateTime,
SessionStatus.Planned,
maxPlayers))
maxPlayers,
joinLink))
.ToList();
}
@@ -173,6 +175,7 @@ public sealed class TelegramLandingPromisesSmokeTests
parseResult.Title!,
parseResult.ScheduledTimes,
parseResult.MaxPlayers,
parseResult.Link!,
notificationMode);
scenario.RenderBatch();
@@ -318,7 +321,8 @@ public sealed class TelegramLandingPromisesSmokeTests
session.Id,
session.ScheduledAt,
session.Status,
session.MaxPlayers))
session.MaxPlayers,
session.JoinLink))
.ToList(),
participants
.Select(participant => new ParticipantBatchDto(
@@ -371,7 +375,8 @@ public sealed class TelegramLandingPromisesSmokeTests
Guid Id,
DateTime ScheduledAt,
string Status,
int? MaxPlayers)
int? MaxPlayers,
string JoinLink)
{
public DateTime ScheduledAt { get; set; } = ScheduledAt;
}
@@ -14,9 +14,9 @@ public sealed class SessionBatchViewBuilderTests
var sessions = new[]
{
new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4),
new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null),
new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2)
new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4, "https://example.com/game"),
new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null, ""),
new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2, "https://example.com/game")
};
var participants = new[]
{
@@ -38,7 +38,7 @@ public sealed class SessionBatchViewBuilderTests
public void Build_ShouldCalculatePlayerCounts()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") };
var participants = new[]
{
new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active),
@@ -59,7 +59,7 @@ public sealed class SessionBatchViewBuilderTests
public void Build_ShouldIncludeActionsForActiveSessions()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") };
var participants = Array.Empty<ParticipantBatchDto>();
var result = SessionBatchViewBuilder.Build("Test", sessions, participants);
@@ -76,7 +76,7 @@ public sealed class SessionBatchViewBuilderTests
public void Build_ShouldNotIncludeActionsForCancelledSessions()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Cancelled, null) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") };
var participants = Array.Empty<ParticipantBatchDto>();
var result = SessionBatchViewBuilder.Build("Test", sessions, participants);
@@ -88,7 +88,7 @@ public sealed class SessionBatchViewBuilderTests
public void Build_ShouldMarkWaitlistActionWhenFull()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1, "https://example.com/game") };
var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) };
var result = SessionBatchViewBuilder.Build("Test", sessions, participants);
@@ -101,7 +101,7 @@ public sealed class SessionBatchViewBuilderTests
public void Build_ShouldIncludePlayerUsernames()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, null) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, null, "https://example.com/game") };
var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) };
var result = SessionBatchViewBuilder.Build("Test", sessions, participants);
@@ -16,9 +16,9 @@ public sealed class TelegramSessionBatchRendererTests
var sessions = new[]
{
new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4),
new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null),
new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2)
new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4, "https://example.com/game2"),
new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null, ""),
new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2, "https://example.com/game1")
};
var participants = new[]
{
@@ -35,6 +35,9 @@ public sealed class TelegramSessionBatchRendererTests
Assert.Contains("Charlie", text);
Assert.Contains("Bob", text);
Assert.Contains("Сессия отменена", text);
Assert.Contains("Ссылка на игру", text);
Assert.Contains("https://example.com/game1", text);
Assert.Contains("https://example.com/game2", text);
var buttons = markup.InlineKeyboard.SelectMany(row => row).ToList();
Assert.Equal(4, buttons.Count); // 2 sessions x 2 buttons each
@@ -51,7 +54,7 @@ public sealed class TelegramSessionBatchRendererTests
public void Render_ShouldSkipButtonsForCancelledSessions()
{
var cancelledSessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(cancelledSessionId, DateTime.UtcNow, SessionStatus.Cancelled, null) };
var sessions = new[] { new SessionBatchDto(cancelledSessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
@@ -64,7 +67,7 @@ public sealed class TelegramSessionBatchRendererTests
public void Render_ShouldShowWaitlistButtonWhenFull()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1) };
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1, "https://example.com/game") };
var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) };
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);