using System; using System.IO; using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Models; using ytLive.Services; using ytLive.ViewModels; namespace ytLive.Tests; /// /// TASK 39 Good Dog: the YPP pull-out, one integration test, real-app + temp-DB /// pattern (mirrors BroadcastPullOutTests / WindowBackdropIntegrationTests). /// Two sections under one [Fact]: (1) a connected channel captures a snapshot /// through the VM — progress props populate, the snapshot persists to SQLite, /// the manual checklist round-trips the Settings store, and the drawer commands /// toggle; (2) a real MainWindow enforces one-open-at-a-time exclusivity between /// the Stream Settings and YPP drawers. /// [Collection("RealApp")] public sealed class YppPullOutTests { private readonly RealAppHost _app; public YppPullOutTests(RealAppHost app) => _app = app; /// Returns canned data with no network — the fake bypasses every API call. private sealed class FakeChannelStats : ChannelStatsService { public FakeChannelStats(YppStatSnapshot snapshot) : base(new YouTubeAuthService("test-client", "test-secret")) => _snapshot = snapshot; private readonly YppStatSnapshot _snapshot; public override Task CaptureCurrentAsync() => Task.FromResult(_snapshot); } [Fact] public void Ypp_LoadsSnapshot_PersistsChecklist_And_DrawerExcludesBroadcast() { _app.Run(Run); } private void Run() { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-ypp-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; MainWindow? window = default; try { using (var schema = new LayoutStore(tempDb)) { } SqliteConnection.ClearAllPools(); // ─── Section 1: connected VM captures + persists ─── var snapshot = new YppStatSnapshot { CapturedAtUtc = DateTime.UtcNow, SubscriberCount = 350, VideoCount = 42, TotalViews = 12_345, UploadsLast90Days = 2, CommunityGuidelinesGoodStanding = true, CopyrightStrikesGoodStanding = true, ContentIdClaimsGoodStanding = true, OverallGoodStanding = true, }; var vm = new YppTrackerViewModel( () => new LayoutStore(tempDb), () => new FakeChannelStats(snapshot), () => true, () => "Test Channel"); vm.IsDrawerOpen = true; Assert.True(vm.IsDrawerOpen); vm.IsDrawerOpen = false; vm.RefreshAsync().GetAwaiter().GetResult(); Assert.True(vm.HasSnapshot); Assert.Equal(350, vm.SubscriberCount); Assert.Equal(2, vm.UploadsLast90Days); Assert.True(vm.OverallGoodStanding); Assert.Equal("Test Channel", vm.DisplayName); Assert.Equal("350 / 500", vm.Tier1SubsText); Assert.Equal("350 / 1,000", vm.Tier2SubsText); Assert.Equal(0.7, vm.Tier1SubsProgress, 3); Assert.Equal(154, vm.Tier1SubsBarWidth, 0); Assert.True(vm.Tier2SubsBarWidth < vm.Tier1SubsBarWidth); Assert.StartsWith("Updated today", vm.LastUpdatedText); // The snapshot persisted so a later analytics slice has history. using (var reader = new LayoutStore(tempDb)) { var history = reader.LoadYppSnapshots(); Assert.Single(history); Assert.Equal(350, history[0].SubscriberCount); Assert.Equal(2, history[0].UploadsLast90Days); } // The self-reported checklist round-trips the Settings store. vm.TwoFactorEnabled = true; vm.AdSenseLinked = false; using (var reader = new LayoutStore(tempDb)) { var (twoFactor, adsense) = reader.LoadYppChecklist(); Assert.True(twoFactor); Assert.False(adsense); } // ─── Section 2: real window enforces one-open-at-a-time ─── window = new MainWindow(); var model = (MainViewModel)window.DataContext; Assert.False(model.BroadcastForm.IsDrawerOpen); Assert.False(model.Ypp.IsDrawerOpen); model.Ypp.ToggleDrawerCommand.Execute(null); Assert.True(model.Ypp.IsDrawerOpen); Assert.False(model.BroadcastForm.IsDrawerOpen, "opening YPP must leave Stream Settings closed"); model.BroadcastForm.ToggleDrawerCommand.Execute(null); Assert.True(model.BroadcastForm.IsDrawerOpen); Assert.False(model.Ypp.IsDrawerOpen, "opening Stream Settings must close YPP"); model.BroadcastForm.ToggleDrawerCommand.Execute(null); Assert.False(model.BroadcastForm.IsDrawerOpen); model.Ypp.ToggleDrawerCommand.Execute(null); Assert.True(model.Ypp.IsDrawerOpen); model.Ypp.CloseDrawerCommand.Execute(null); Assert.False(model.Ypp.IsDrawerOpen); } finally { window?.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } }