using System; using System.Net; using System.Net.Http; using System.Text.Json; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; /// /// TASK: broadcast metadata pull-out + launch geometry. One integration slice: /// (1) pull-out edits persist immediately to the Settings store and re-load, /// (2) a real MainWindow restores saved size/position, clamping off-screen or /// below-minimum values into the primary work area, (3) UpdateBroadcast PATCHes /// liveBroadcasts?part=snippet,status with the full editable field set. /// [Collection("RealApp")] public sealed class BroadcastPullOutTests { public BroadcastPullOutTests(RealAppHost app) => _app = app; private readonly RealAppHost _app; [Fact] public void Metadata_Persists_WindowRestores_Clamped_And_UpdatePatchesRemote() { _app.Run(Run); } private sealed class StubHandler : HttpMessageHandler { public HttpRequestMessage? Request; public string? Body; protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Request = request; Body = request.Content?.ReadAsStringAsync(cancellationToken).GetAwaiter().GetResult(); return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"id\":\"bc123\"}", System.Text.Encoding.UTF8, "application/json"), }); } } private void Run() { var tempDb = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"ytLlive-pullout-{Guid.NewGuid():N}.db"); ViewModels.MainViewModel.LayoutPathOverride = tempDb; MainWindow? window = default; try { using (var schema = new LayoutStore(tempDb)) { } Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); var vm = new ViewModels.MainViewModel(); vm.BroadcastForm.Title = "Hello"; vm.BroadcastForm.Description = "World"; vm.BroadcastForm.TagsCsv = "a, b ,c"; vm.BroadcastForm.Visibility = "public"; vm.BroadcastForm.MadeForKids = true; using (var reader = new LayoutStore(tempDb)) { var meta = reader.LoadBroadcastMetadata(); Assert.Equal("Hello", meta.Title); Assert.Equal("World", meta.Description); Assert.Equal("a, b ,c", meta.TagsCsv); Assert.Equal("public", meta.Visibility); Assert.True(meta.MadeForKids); } vm.BroadcastForm.Load(); Assert.Equal("Hello", vm.BroadcastForm.Title); Assert.False(vm.BroadcastForm.IsDrawerOpen); vm.BroadcastForm.OpenDrawerCommand.Execute(null); Assert.True(vm.BroadcastForm.IsDrawerOpen); vm.BroadcastForm.CloseDrawerCommand.Execute(null); Assert.False(vm.BroadcastForm.IsDrawerOpen); using (var writer = new LayoutStore(tempDb)) writer.SaveWindowState(100, 50, 1500, 900, "Normal"); window = new MainWindow(); Assert.Equal(1500, window.Width); Assert.Equal(900, window.Height); Assert.Equal(100, window.Left); Assert.Equal(50, window.Top); window.Close(); window = null; using (var writer = new LayoutStore(tempDb)) writer.SaveWindowState(-99999, -99999, 800, 500, "Normal"); window = new MainWindow(); var work = System.Windows.SystemParameters.WorkArea; Assert.Equal(1366, window.Width); Assert.Equal(768, window.Height); Assert.InRange(window.Left, work.Left - window.Width + 120, work.Right - 120); Assert.InRange(window.Top, work.Top, work.Bottom - 80); var auth = new YouTubeAuthService("test-client", "test-secret"); auth.SetSession(new YouTubeChannel { AccessToken = "token", TokenExpiry = DateTime.UtcNow.AddHours(1), }); var stub = new StubHandler(); var service = new YouTubeStreamService(auth, new HttpClient(stub)); var error = service.UpdateBroadcast("bc123", new BroadcastMetadata { Title = "T", Description = "D", TagsCsv = "x,y", Visibility = "Public", MadeForKids = true, ScheduledStartTime = new DateTime(2026, 8, 24, 12, 0, 0, DateTimeKind.Utc), }).GetAwaiter().GetResult(); Assert.Null(error); Assert.NotNull(stub.Request); Assert.Equal(HttpMethod.Put, stub.Request!.Method); Assert.Equal("https://www.googleapis.com/youtube/v3/liveBroadcasts?part=snippet,status", stub.Request.RequestUri!.ToString()); using var doc = JsonDocument.Parse(stub.Body!); var root = doc.RootElement; Assert.Equal("bc123", root.GetProperty("id").GetString()); Assert.Equal("T", root.GetProperty("snippet").GetProperty("title").GetString()); Assert.Equal(2, root.GetProperty("snippet").GetProperty("tags").GetArrayLength()); Assert.Equal("public", root.GetProperty("status").GetProperty("privacyStatus").GetString()); Assert.True(root.GetProperty("status").GetProperty("selfDeclaredMadeForKids").GetBoolean()); Assert.Equal(new DateTime(2026, 8, 24, 12, 0, 0, DateTimeKind.Utc).ToString("o"), root.GetProperty("snippet").GetProperty("scheduledStartTime").GetString()); } finally { window?.Close(); ViewModels.MainViewModel.LayoutPathOverride = null; Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools(); try { System.IO.File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } }