using System; using System.IO; using System.Linq; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Models; using ytLive.ViewModels; namespace ytLive.Tests; /// /// TASK 26 integration: the (+) menu's Webcam row gates on the app-wide webcam /// identity — one camera per app, so the row must stay disabled while ANY scene /// shows the webcam (not just the staged one), and re-enable only after the /// last config anywhere is removed (which also drops the identity). /// Real-App + temp-DB pattern (see BackgroundHealIntegrationTests). /// [Collection("RealApp")] public sealed class WebcamMenuGateTests { [Fact] public void CanAddWebcam_Gates_On_The_AppWide_Webcam_Identity() { _app.Run(Run); } private readonly RealAppHost _app; public WebcamMenuGateTests(RealAppHost app) => _app = app; private void Run() { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-wcgate-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; var window = default(MainWindow); try { using (var schema = new ytLive.Services.LayoutStore(tempDb)) { } SqliteConnection.ClearAllPools(); SeedDatabaseWithWebcam(tempDb); window = new MainWindow(); var vm = (MainViewModel)window.DataContext; var starting = vm.Scenes.Single(s => SceneCatalog.Is(s.Name, SceneCatalog.Starting)); var live = vm.Scenes.Single(s => SceneCatalog.Is(s.Name, SceneCatalog.Live)); vm.StagedScene = live; Assert.True(live.WebcamConfig == null, "Live must not carry the webcam config"); Assert.False(vm.CanAddWebcam, "webcam exists in another scene — Add Webcam must be greyed out app-wide"); vm.StagedScene = starting; Assert.False(vm.CanAddWebcam, "staged scene itself has the webcam"); var config = starting.WebcamConfig!; vm.RemoveSourceCommand.Execute(config); Assert.True(vm.CanAddWebcam, "last webcam config anywhere was removed — identity cleared, picker may run again"); vm.SaveLayoutNow(); using (var connection = new SqliteConnection($"Data Source={tempDb}")) { connection.Open(); using var webcams = connection.CreateCommand(); webcams.CommandText = "SELECT COUNT(*) FROM Webcam;"; Assert.Equal(0L, (long)webcams.ExecuteScalar()!); } } finally { window?.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } /// Five canonical scenes + a webcam identity used by exactly one /// non-staged-by-default scene (Starting), so staging any other screen /// reproduces the old per-scene gate's false "available". private static void SeedDatabaseWithWebcam(string path) { using var connection = new SqliteConnection($"Data Source={path}"); connection.Open(); var scenes = new (string Id, string Name)[] { ("scene-starting", "Starting"), ("scene-live", "Live"), ("scene-brb", "BRB"), ("scene-chat", "Chat"), ("scene-ending", "Ending"), }; foreach (var (id, name) in scenes) { using var cmd = connection.CreateCommand(); cmd.CommandText = "INSERT INTO Scene (Id, Name, HasBackground, SortOrder) VALUES ($id, $name, 1, $order);"; cmd.Parameters.AddWithValue("$id", id); cmd.Parameters.AddWithValue("$name", name); cmd.Parameters.AddWithValue("$order", Array.IndexOf(scenes, (id, name))); cmd.ExecuteNonQuery(); } using (var webcam = connection.CreateCommand()) { webcam.CommandText = "INSERT INTO Webcam (Id, DeviceId, Name) VALUES ('wc-1', 'test-camera', 'Test Cam');"; webcam.ExecuteNonQuery(); } using (var config = connection.CreateCommand()) { config.CommandText = @"INSERT INTO WebcamSceneConfig (SceneId, WebcamId, IsVisible, X, Y, Width, Height, Opacity, SortOrder) VALUES ('scene-starting', 'wc-1', 1, 100, 100, 480, 270, 1, 0);"; config.ExecuteNonQuery(); } } }