fix(18): stop ends everything — pills clear, pump/prep failures roll the session back
Creator ruling 2026-09-01 (stuck REC pill after the failed first recording): - StopStream clears RecordPillOn + OnAirPillOn — intent resets with reality. - OnFramePumpFailed: dispatcher-marshalled FULL rollback via StopStream (was: toast + Error-status only when live — record-only sessions zombied with IsRecording=true over a dead encoder; the 20:34 attempt proved it). Toast copy says 'Recording stopped' vs 'stream pipeline stopped'. - Same zombie class in the three go-live prep failure branches: StreamStatus.Error limbo replaced by StopStream() (audio loop + recording + pills unwind; a created broadcast still gets the close-out). - AudioMixer.StopLive made explicitly idempotent (Stop() twice, rollback paths that never reached StartLive). - Seams: OnFramePumpFailed + IsRecording setter internal (InternalsVisibleTo; test pattern mirrors LayoutPathOverride). ONE integration test: SessionTeardownTests (real window + temp DB: pump death with lit pill -> no zombie, no End button, Offline). AudioPipelineTests confirmed to hang STANDALONE (pre-existing, the declared-known audio class) — ai.md test-count paragraph corrected to stop claiming a suite total that cannot currently be measured.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
using ytLive.ViewModels;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Session-teardown integration (2026-09-01, first native launch): a frame-pump death
|
||||
/// must roll the WHOLE session back — record-only sessions used to stay IsRecording=true
|
||||
/// with a dead encoder (no live session to flip StreamStatus), and StopStream never
|
||||
/// cleared the intent pills. "Stop ends everything" (creator ruling): pills slide off
|
||||
/// with the session; re-arming is one click. Real-App + temp-DB pattern.
|
||||
/// </summary>
|
||||
[Collection("RealApp")]
|
||||
public sealed class SessionTeardownTests
|
||||
{
|
||||
private readonly RealAppHost _app;
|
||||
|
||||
public SessionTeardownTests(RealAppHost app) => _app = app;
|
||||
|
||||
[Fact]
|
||||
public void Pump_Failure_Rolls_Back_Recording_And_Clears_Pills()
|
||||
{
|
||||
_app.Run(Run);
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-teardown-{Guid.NewGuid():N}.db");
|
||||
MainViewModel.LayoutPathOverride = tempDb;
|
||||
var window = default(MainWindow);
|
||||
try
|
||||
{
|
||||
using (var schema = new LayoutStore(tempDb)) { }
|
||||
SqliteConnection.ClearAllPools();
|
||||
|
||||
window = new MainWindow();
|
||||
var vm = (MainViewModel)window.DataContext;
|
||||
window.Show();
|
||||
window.UpdateLayout();
|
||||
|
||||
// The exact 2026-09-01 zombie: record-only, intent lit, reality true,
|
||||
// encoder already dead. Sign-in absent — the guard must also prove no
|
||||
// close-out API call is attempted for a record-only session (StopStream
|
||||
// completes without throwing offline).
|
||||
vm.RecordPillOn = true;
|
||||
vm.IsRecording = true;
|
||||
|
||||
vm.OnFramePumpFailed(this, "test: ffmpeg exited");
|
||||
|
||||
Assert.False(vm.IsRecording, "pump death must roll IsRecording back — no zombie session");
|
||||
Assert.False(vm.RecordPillOn, "Stop ends everything — the REC pill slides off with the session");
|
||||
Assert.False(vm.OnAirPillOn, "both pills clear");
|
||||
Assert.False(vm.ShowEndStreamButton, "no End button once nothing is running");
|
||||
Assert.Equal(ytLive.Models.StreamStatus.Offline, vm.StreamStatus);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (window != null) window.Close();
|
||||
MainViewModel.LayoutPathOverride = null;
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(tempDb); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user