using System.Text.Json; using Xunit; using ytLive.Services; namespace ytLive.Tests; /// /// Broadcast-binding diagnostics (2026-10-04). Two test streams proved the RTMP push /// is healthy — ffmpeg delivered 101,229 KiB video + 1,419 KiB audio over 112 seconds /// with no socket error — yet the broadcast never left `ready` and close-out returned /// 403 `invalidTransition`. Google's docs say enableAutoStart fires "when you /// start streaming video on the bound live stream", so the one thing worth asserting /// about the request we send is that we know which stream we think we are bound to. /// public class BroadcastBindingTests { /// The comparison that decides whether the broadcast is even watching the /// feed we are pushing. A reusable stream happily accepts bytes for an unbound /// broadcast, so ingest succeeding proves nothing on its own — a MISMATCH or /// NO-BINDING would look identical from the RTMP side and would hang forever. /// "unknown" is the honest answer when the app has no stream id to compare against, /// so a missing expectation is never silently reported as a match. [Theory] [InlineData("stream-abc", "stream-abc", "yes")] [InlineData("stream-abc", "stream-xyz", "MISMATCH")] [InlineData(null, "stream-abc", "NO-BINDING")] [InlineData("", "stream-abc", "NO-BINDING")] [InlineData("stream-abc", null, "unknown")] [InlineData("stream-abc", "", "unknown")] public void BoundMatches_ComparesTheBroadcastAgainstTheStreamWePush(string? bound, string? expected, string verdict) { Assert.Equal(verdict, YouTubeStreamService.BoundMatches(bound, expected)); } /// The regression the first diagnostic shipped with. `liveBroadcasts.list` /// returns a LIST wrapper, so the broadcast sits at `items[0]`; reading the response /// root reads the wrapper, every field comes back empty, and the binding check then /// reports a confident `NO-BINDING` from a parse that never happened. The first real /// test run produced exactly that — three lines of fabricated alarm. `FirstItem` must /// descend, and must return `default` (not throw, not an empty object) when there is /// genuinely no item, so the caller can report "unread" instead of guessing. [Theory] [InlineData("{\"kind\":\"youtube#liveBroadcastListResponse\",\"items\":[{\"id\":\"bc1\",\"status\":{\"lifeCycleStatus\":\"ready\"}}]}", "bc1")] [InlineData("{\"items\":[]}", null)] [InlineData("{\"kind\":\"youtube#liveBroadcastListResponse\"}", null)] public void FirstItem_DescendsIntoTheListWrapper_AndReportsAbsenceHonestly(string listJson, string? expectedId) { using var doc = JsonDocument.Parse(listJson); var item = YouTubeStreamService.FirstItem(doc.RootElement); if (expectedId == null) { Assert.Equal(JsonValueKind.Undefined, item.ValueKind); return; } Assert.Equal(expectedId, item.GetProperty("id").GetString()); } }