using System; using System.IO; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Services; using ytLive.ViewModels; namespace ytLive.Tests; /// /// Record-OR-live (creator ruling 2026-09-01): the REC and ON-AIR pills are /// radio-exclusive — arming one clears the other, so a session can never dual-encode /// to both the YouTube ingest and a local file at once (a concurrent disk write /// streamed from the same sustained Kbps cripples the stream on mid-range chassis). /// The dual-block encoder machinery in FfmpegArgs stays generic; the constraint /// lives at the pills. /// [Collection("RealApp")] public sealed class PillRadioTests { private readonly RealAppHost _app; public PillRadioTests(RealAppHost app) => _app = app; [Fact] public void Arming_One_Output_Pill_Clears_The_Other() { _app.Run(Run); } private void Run() { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-pillradio-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; try { using (var schema = new LayoutStore(tempDb)) { } SqliteConnection.ClearAllPools(); var vm = new MainViewModel(); vm.IsConnected = true; // the ON-AIR setter requires a sign-in // Arm REC first, then ON-AIR → REC must clear. vm.RecordPillOn = true; vm.OnAirPillOn = true; Assert.False(vm.RecordPillOn, "arming ON-AIR must clear the REC pill"); Assert.True(vm.OnAirPillOn); // Arm REC while ON-AIR is lit → ON-AIR must clear. vm.RecordPillOn = true; Assert.False(vm.OnAirPillOn, "arming REC must clear the ON-AIR pill"); Assert.True(vm.RecordPillOn); // A single armed pill still makes the Start button actionable. Assert.True(vm.CanStartSession); } finally { MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } }