using GmRelay.DiscordBot.Rendering; using GmRelay.Shared.Domain; using GmRelay.Shared.Rendering; using NetCord; using NetCord.Rest; namespace GmRelay.Bot.Tests.Rendering; public sealed class DiscordSessionBatchRendererTests { [Fact] public void Render_ShouldProduceEmbedsAndButtonsForMultipleSessions() { var firstSessionId = Guid.NewGuid(); var secondSessionId = Guid.NewGuid(); var cancelledSessionId = Guid.NewGuid(); var sessions = new[] { 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[] { new ParticipantBatchDto(secondSessionId, "Alice", "alice", ParticipantRegistrationStatus.Active), new ParticipantBatchDto(secondSessionId, "Charlie", null, ParticipantRegistrationStatus.Waitlisted), new ParticipantBatchDto(cancelledSessionId, "Bob", null, ParticipantRegistrationStatus.Active) }; var view = SessionBatchViewBuilder.Build("Campaign", sessions, participants); var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(view); Assert.Equal(3, embeds.Count); Assert.Equal(2, actionRows.Count); // cancelled skipped // Embed titles contain game title and Moscow date Assert.Contains(embeds, e => e.Title!.Contains("Campaign") && e.Title.Contains("26")); Assert.Contains(embeds, e => e.Title!.Contains("Campaign") && e.Title.Contains("27")); Assert.Contains(embeds, e => e.Title!.Contains("Campaign") && e.Title.Contains("28")); // Cancelled session embed description indicates cancellation var cancelledEmbed = embeds.First(e => e.Description!.Contains("отменена") || e.Description.Contains("Отменена")); Assert.NotNull(cancelledEmbed); // Active session embeds contain player names Assert.Contains(embeds, e => e.Description!.Contains("Alice")); Assert.Contains(embeds, e => e.Description!.Contains("Charlie")); // Buttons for active sessions var allButtons = actionRows.SelectMany(r => r).OfType().ToList(); Assert.Contains(allButtons, b => b.CustomId == $"join_session:{firstSessionId}"); Assert.Contains(allButtons, b => b.CustomId == $"leave_session:{firstSessionId}"); Assert.Contains(allButtons, b => b.CustomId == $"join_session:{secondSessionId}"); Assert.Contains(allButtons, b => b.CustomId == $"leave_session:{secondSessionId}"); } [Fact] public void Render_ShouldSkipActionRowsForCancelledSessions() { var cancelledSessionId = Guid.NewGuid(); var sessions = new[] { new SessionBatchDto(cancelledSessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") }; var participants = Array.Empty(); var view = SessionBatchViewBuilder.Build("Test", sessions, participants); var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(view); Assert.Single(embeds); Assert.Empty(actionRows); } [Fact] public void Render_ShouldShowWaitlistButtonWhenFull() { var sessionId = Guid.NewGuid(); 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); var (_, actionRows) = DiscordSessionBatchRenderer.Render(view); var buttons = actionRows.SelectMany(r => r).OfType().ToList(); var joinButton = buttons.First(b => b.CustomId == $"join_session:{sessionId}"); Assert.Contains("ожидания", joinButton.Label); } [Fact] public void Render_ShouldUseRedColorForCancelledSessions() { var sessionId = Guid.NewGuid(); var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") }; var participants = Array.Empty(); var view = SessionBatchViewBuilder.Build("Test", sessions, participants); var (embeds, _) = DiscordSessionBatchRenderer.Render(view); Assert.Equal(0xED4245, embeds[0].Color.RawValue); } [Fact] public void Render_ShouldUseGreenColorForOpenSessions() { var sessionId = Guid.NewGuid(); var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") }; var participants = Array.Empty(); var view = SessionBatchViewBuilder.Build("Test", sessions, participants); var (embeds, _) = DiscordSessionBatchRenderer.Render(view); Assert.Equal(0x57F287, embeds[0].Color.RawValue); } [Fact] public void Render_ShouldUseYellowColorForFullSessions() { var sessionId = Guid.NewGuid(); 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); var (embeds, _) = DiscordSessionBatchRenderer.Render(view); Assert.Equal(0xFEE75C, embeds[0].Color.RawValue); } [Fact] public void Render_ShouldHandleRescheduleStatus() { var sessionId = Guid.NewGuid(); var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, "Rescheduled", 4, "") }; var participants = Array.Empty(); var view = SessionBatchViewBuilder.Build("Test", sessions, participants); var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(view); Assert.Single(embeds); Assert.Single(actionRows); // not cancelled → actions present } }