using System; using System.IO; using System.Linq; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Services; using ytLive.ViewModels; namespace ytLive.Tests; /// /// 2026-11-06: replacing a bundled background reached new installs only. Default art is hash-keyed into the /// layout database on first seed, so an existing profile kept the old image for ever. A version-stamp heal /// failed twice (first it iterated `AllBackgrounds()`, which returns only the LIVE scene's rows; then the /// constant and the embedded resource were built at different moments). The seed now compares BYTES, which /// is what this test pins: a profile already pointing at other art is re-stamped without any version bump. /// [Collection("RealApp")] public sealed class BackgroundDefaultArtTests { private readonly RealAppHost _app; public BackgroundDefaultArtTests(RealAppHost app) => _app = app; [Fact] public void ReplacingTheBundledArt_ReSeedsAnExistingProfile() => _app.Run(() => { // The bundled art is a WPF resource, so this needs a real Application to resolve the pack URI. var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-art-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; try { // First launch: the seeded art is whatever ships today. var first = new MainViewModel(); var firstId = StartingBackgroundId(first); Assert.False(string.IsNullOrWhiteSpace(firstId), "the bundled Starting art never seeded"); var firstBytes = AssetLength(tempDb, firstId!); // Simulate a profile stamped with the OLD art. No version flag is touched — the seed has to // notice the bytes are not the shipped ones by itself. using (var connection = new SqliteConnection($"Data Source={tempDb}")) { connection.Open(); using var plant = connection.CreateCommand(); plant.CommandText = """ INSERT OR REPLACE INTO Asset (Id, Hash, Data, PixelWidth, PixelHeight) VALUES ('old-art', 'deadbeef', zeroblob(1024), 1920, 1080); UPDATE Source SET AssetId = 'old-art' WHERE IsBackground = 1; """; plant.ExecuteNonQuery(); } SqliteConnection.ClearAllPools(); var second = new MainViewModel(); var secondId = StartingBackgroundId(second); Assert.NotNull(secondId); // Re-stamped to the SHIPPED art — which, being the same bytes, is the same asset row the // first launch created. What matters is that the planted "old art" is gone. Assert.NotEqual("old-art", secondId); Assert.Equal(firstId, secondId); Assert.Equal(File.ReadAllBytes(Path.Combine( AppContext.BaseDirectory, "..", "..", "..", "..", "Assets", "starting-background.jpg")).Length, AssetLength(tempDb, secondId!)); Assert.NotEqual(firstBytes, 0); // Idempotent: a third launch finds the shipped bytes already in place and must not re-seed. var third = new MainViewModel(); Assert.Equal(secondId, StartingBackgroundId(third)); } finally { MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } }); private static string? StartingBackgroundId(MainViewModel vm) => vm.Scenes .Where(scene => ytLive.Models.SceneCatalog.Is(scene.Name, ytLive.Models.SceneCatalog.Starting)) .SelectMany(scene => scene.Elements.OfType()) .FirstOrDefault(element => element.IsBackground)?.AssetId; private static int AssetLength(string dbPath, string assetId) { using var connection = new SqliteConnection($"Data Source={dbPath}"); connection.Open(); using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT length(Data) FROM Asset WHERE Id = $id;"; cmd.Parameters.AddWithValue("$id", assetId); return Convert.ToInt32(cmd.ExecuteScalar()); } }