Files
GmRelayBot/tests/GmRelay.Bot.Tests/Rendering/DiscordSessionBatchRendererTests.cs
T
Toutsu 56aeca5288
Deploy Telegram Bot / build-and-push (push) Successful in 5m53s
Deploy Telegram Bot / scan-images (push) Successful in 3m6s
Deploy Telegram Bot / deploy (push) Successful in 29s
fix(discord): sanitize embed join links
2026-05-26 13:57:11 +03:00

230 lines
10 KiB
C#

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<ButtonProperties>().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<ParticipantBatchDto>();
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<ButtonProperties>().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<ParticipantBatchDto>();
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<ParticipantBatchDto>();
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<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, actionRows) = DiscordSessionBatchRenderer.Render(view);
Assert.Single(embeds);
Assert.Single(actionRows); // not cancelled → actions present
}
[Fact]
public void Render_ShouldUseBlueColorForConfirmedSessions()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Confirmed, 4, "https://example.com/game") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
Assert.Equal(0x5865F2, embeds[0].Color.RawValue);
}
[Fact]
public void Render_ShouldShowEmptyPlayerDescription()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
Assert.Contains("Пока никто не записался", embeds[0].Description);
}
[Fact]
public void Render_ShouldSetEmbedUrlWhenJoinLinkPresent()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
Assert.Equal("https://example.com/game", embeds[0].Url);
}
[Fact]
public void Render_ShouldNormalizeBareDomainJoinLinkForEmbedUrl()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "mobaxterm.mobatek.net/game") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
Assert.Equal("https://mobaxterm.mobatek.net/game", embeds[0].Url);
}
[Fact]
public void Render_ShouldNotSetEmbedUrlWhenJoinLinkIsNotHttpUrl()
{
var sessionId = Guid.NewGuid();
var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "test") };
var participants = Array.Empty<ParticipantBatchDto>();
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
Assert.Null(embeds[0].Url);
}
[Fact]
public void Render_ShouldEmbedCorrectFieldValues()
{
var sessionId = Guid.NewGuid();
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),
new ParticipantBatchDto(sessionId, "Bob", null, ParticipantRegistrationStatus.Waitlisted)
};
var view = SessionBatchViewBuilder.Build("Test", sessions, participants);
var (embeds, _) = DiscordSessionBatchRenderer.Render(view);
var embed = embeds[0];
var fields = embed.Fields!.ToList();
Assert.Equal(3, fields.Count);
Assert.Equal("👥 Заполненность", fields[0].Name);
Assert.Equal("1/4", fields[0].Value);
Assert.Equal("⏳ Лист ожидания", fields[1].Name);
Assert.Equal("1", fields[1].Value);
Assert.Equal("📊 Статус", fields[2].Name);
Assert.Equal("Запланирована", fields[2].Value);
}
}