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 */ }
}
}
}