Build-mismatch restoration now keeps the currently selected webcam identity, restores default scene placements against that camera, and leaves cameras absent when none is selected. The integration regression test pins the creator's camera choice and restored default geometry. 612 tests green, clean build with 0 warnings.
185 lines
7.0 KiB
C#
185 lines
7.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Microsoft.Data.Sqlite;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
public sealed class LayoutStoreSnapshotTests
|
|
{
|
|
[Fact]
|
|
public void SameBuild_OpensCurrentLayout_WithoutChangingDefaultSnapshot()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db");
|
|
const string buildId = "build-a";
|
|
try
|
|
{
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
var factory = MakeScene("Factory title");
|
|
store.Save(new[] { factory }, null, null);
|
|
store.PrepareLayoutSnapshots(buildId);
|
|
|
|
store.Save(new[] { MakeScene("Creator edit") }, null, null);
|
|
}
|
|
|
|
using (var reopened = new LayoutStore(path))
|
|
{
|
|
reopened.PrepareLayoutSnapshots(buildId);
|
|
var current = Assert.Single(reopened.Load());
|
|
Assert.Equal("Creator edit", Assert.Single(current.Elements.OfType<Source>()).Name);
|
|
|
|
using var connection = GetConnection(path);
|
|
using var command = connection.CreateCommand();
|
|
command.CommandText = "SELECT Value FROM LayoutSnapshotState WHERE Key = 'CurrentBuildId';";
|
|
Assert.Equal(buildId, command.ExecuteScalar());
|
|
|
|
command.CommandText = "SELECT Name FROM DefaultSource;";
|
|
Assert.Equal("Factory title", command.ExecuteScalar());
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(path); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DifferentBuild_RestoresTheDefaultSnapshotBeforeLoading()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db");
|
|
try
|
|
{
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Save(new[] { MakeScene("Factory title") }, null, null);
|
|
store.PrepareLayoutSnapshots("build-a");
|
|
store.Save(new[] { MakeScene("Creator edit") }, null, null);
|
|
}
|
|
|
|
using (var reopened = new LayoutStore(path))
|
|
{
|
|
reopened.PrepareLayoutSnapshots("build-b");
|
|
var restored = Assert.Single(reopened.Load());
|
|
Assert.Equal("Factory title", Assert.Single(restored.Elements.OfType<Source>()).Name);
|
|
|
|
using var connection = GetConnection(path);
|
|
using var command = connection.CreateCommand();
|
|
command.CommandText = "SELECT Value FROM LayoutSnapshotState WHERE Key = 'CurrentBuildId';";
|
|
Assert.Equal("build-b", command.ExecuteScalar());
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(path); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ResetOneScene_RestoresItsFactoryLayers_AndKeepsOtherSceneEdits()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db");
|
|
var starting = MakeScene(SceneCatalog.Starting, "Factory starting");
|
|
var brb = MakeScene(SceneCatalog.Brb, "Factory BRB");
|
|
try
|
|
{
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Save(new[] { starting, brb }, null, null);
|
|
store.PrepareLayoutSnapshots("build-a");
|
|
store.Save(new[]
|
|
{
|
|
MakeScene(starting.Id, starting.Name, "Edited starting"),
|
|
MakeScene(brb.Id, brb.Name, "Edited BRB"),
|
|
}, null, null);
|
|
}
|
|
|
|
using (var reopened = new LayoutStore(path))
|
|
{
|
|
reopened.PrepareLayoutSnapshots("build-a");
|
|
Assert.True(reopened.RestoreDefaultScene(starting.Id));
|
|
|
|
var scenes = reopened.Load();
|
|
Assert.Equal("Factory starting", Assert.Single(scenes.Single(s => s.Id == starting.Id).Elements.OfType<Source>()).Name);
|
|
Assert.Equal("Edited BRB", Assert.Single(scenes.Single(s => s.Id == brb.Id).Elements.OfType<Source>()).Name);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(path); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DifferentBuild_RestoresWebcamPlacement_WithoutReplacingChosenCamera()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db");
|
|
var sceneId = Guid.NewGuid().ToString();
|
|
var defaultWebcam = new Webcam { Id = "factory-webcam", DeviceId = "factory-device", Name = "Factory camera" };
|
|
var chosenWebcam = new Webcam { Id = "chosen-webcam", DeviceId = "chosen-device", Name = "Chosen camera" };
|
|
try
|
|
{
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Save(new[] { MakeWebcamScene(sceneId, defaultWebcam.Id, 20) }, defaultWebcam, null);
|
|
store.PrepareLayoutSnapshots("build-a");
|
|
store.Save(new[] { MakeWebcamScene(sceneId, chosenWebcam.Id, 200) }, chosenWebcam, null);
|
|
}
|
|
|
|
using (var reopened = new LayoutStore(path))
|
|
{
|
|
reopened.PrepareLayoutSnapshots("build-b");
|
|
var restored = Assert.Single(reopened.Load());
|
|
var config = Assert.Single(restored.Elements.OfType<WebcamSceneConfig>());
|
|
Assert.Equal(chosenWebcam.DeviceId, reopened.Webcam?.DeviceId);
|
|
Assert.Equal(chosenWebcam.Id, config.WebcamId);
|
|
Assert.Equal(20, config.X);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(path); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
|
|
private static Scene MakeScene(string sourceName)
|
|
=> MakeScene(SceneCatalog.Starting, sourceName);
|
|
|
|
private static Scene MakeScene(string sceneName, string sourceName)
|
|
=> new()
|
|
{
|
|
Name = sceneName,
|
|
Elements = { new Source { Name = sourceName, Type = SourceType.TextOverlay } },
|
|
};
|
|
|
|
private static Scene MakeScene(string id, string sceneName, string sourceName)
|
|
=> new()
|
|
{
|
|
Id = id,
|
|
Name = sceneName,
|
|
Elements = { new Source { Name = sourceName, Type = SourceType.TextOverlay } },
|
|
};
|
|
|
|
private static Scene MakeWebcamScene(string id, string webcamId, double x)
|
|
=> new()
|
|
{
|
|
Id = id,
|
|
Name = SceneCatalog.Live,
|
|
Elements = { new WebcamSceneConfig { WebcamId = webcamId, Name = "Webcam", X = x, Y = 20, Width = 320, Height = 180 } },
|
|
};
|
|
|
|
private static SqliteConnection GetConnection(string path)
|
|
{
|
|
var connection = new SqliteConnection($"Data Source={path}");
|
|
connection.Open();
|
|
return connection;
|
|
}
|
|
}
|