using System; using System.Collections.Generic; using System.Linq; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; /// /// TASK 3·20 — the session report is DERIVED from YouTube, never stored: no ledger, no migration, and the /// published promise ("no viewer, member or donor information is retained after the session ends") stays /// literally true. /// Two rules the creator set (2026-11-05): money is hidden unless asked for, and a creator who never /// monetized is never shown a zero. Counts stay neutral so a normal session still has something to read. /// public class SessionRewardReportTests { private static readonly DateTime SessionStart = new(2026, 11, 5, 20, 0, 0, DateTimeKind.Utc); [Fact] public void ParsesBothRewardTypes_WithoutDroppingStickers() { const string json = """ { "items": [ {"id":"sc1","snippet":{"publishedAt":"2026-11-05T20:05:00Z","superChatDetails": {"amountMicros":5000000,"currency":"USD","amountDisplayString":"$5.00","userComment":"hi"}}}, {"id":"ss1","snippet":{"publishedAt":"2026-11-05T20:07:00Z","superStickerDetails": {"amountMicros":2000000,"currency":"EUR","amountDisplayString":"€2.00"}}} ] } """; var events = SuperChatEventService.Parse(json); Assert.Equal(2, events.Count); Assert.Equal(ChatEventKind.SuperChat, events[0].Kind); Assert.Equal(5_000_000, events[0].AmountMicros); Assert.Equal("USD", events[0].Currency); Assert.Equal(ChatEventKind.SuperSticker, events[1].Kind); Assert.Equal("EUR", events[1].Currency); } [Fact] public void TheWindowMakesItASessionReport_NotAChannelReport() { var events = new[] { Event(ChatEventKind.SuperChat, 5_000_000, "USD", SessionStart.AddMinutes(-30)), // before Event(ChatEventKind.SuperChat, 3_000_000, "USD", SessionStart.AddMinutes(10)), Event(ChatEventKind.SuperSticker, 2_000_000, "USD", SessionStart.AddMinutes(20)), }; var summary = SessionRewardSummary.From(events, SessionStart); Assert.Equal(1, summary.SuperChatCount); Assert.Equal(1, summary.SuperStickerCount); Assert.Equal(5_000_000, summary.MicrosByCurrency["USD"]); } [Fact] public void CurrenciesAreNeverSummedTogether() { var events = new[] { Event(ChatEventKind.SuperChat, 5_000_000, "USD", SessionStart.AddMinutes(1)), Event(ChatEventKind.SuperChat, 5_000_000, "EUR", SessionStart.AddMinutes(2)), }; var summary = SessionRewardSummary.From(events, SessionStart); Assert.Equal(5_000_000, summary.MicrosByCurrency["USD"]); Assert.Equal(5_000_000, summary.MicrosByCurrency["EUR"]); Assert.Equal(2, summary.MoneyLines().Count); Assert.Contains(summary.MoneyLines(), line => line.StartsWith("USD $5.00")); Assert.Contains(summary.MoneyLines(), line => line.StartsWith("EUR €5.00")); } [Fact] public void AnEmptySession_HasNoCountsAndNoMoney() { var summary = SessionRewardSummary.From(Array.Empty(), SessionStart); Assert.False(summary.HasEvents); Assert.Empty(summary.MoneyLines()); Assert.Equal(0, summary.TotalEvents); } [Fact] public void AStickerOnlySession_StillCounts() { var summary = SessionRewardSummary.From( new[] { Event(ChatEventKind.SuperSticker, 1_000_000, "USD", SessionStart.AddMinutes(1)) }, SessionStart); Assert.True(summary.HasEvents); Assert.Equal(0, summary.SuperChatCount); Assert.Equal(1, summary.SuperStickerCount); } private static SuperChatEvent Event(ChatEventKind kind, long micros, string currency, DateTime at) => new($"id-{Guid.NewGuid():N}", kind, micros, currency, null, at); }