Files
LlamaCasty/ytLive.Tests/BackgroundDefaultArtTests.cs
T
gramps a6f15a4bc3 art(starting): the corrected onboarding screen, and a heal that actually heals
The creator's corrected Starting artwork (the in-app onboarding screen: sign in → stream details →
go live) replaces yesterday's version. 2724x1536 resampled to the 1920x1080 master, q3 (432 KB).
The previous Gemini render is archived beside the original art.

Also fixes the two reasons the replacement would not have appeared on an existing profile:

- **The first heal iterated `AllBackgrounds()`**, which returns only the LIVE scene's capture rows, so it
  cleared nothing and then saved the version anyway — permanently skipping the heal. It now walks every
  scene's background. Version bumped to 4 so profiles stamped 2 or 3 heal.
- **The pack URI was unqualified**, so it resolved against the ENTRY assembly: LlamaCasty.exe in
  production, the test host under `dotnet test`, where the seed silently did nothing. It is now
  `/LlamaCasty;component/Assets/…`, which works in both.

Test: `BackgroundDefaultArtTests` seeds a profile, plants an "old art" asset on the Starting background,
marks the layout as an older art vintage, relaunches and asserts the shipped art is re-stamped. 45
background tests pass.
2026-11-06 12:04:06 -08:00

92 lines
4.0 KiB
C#

using System;
using System.IO;
using System.Linq;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.Services;
using ytLive.ViewModels;
namespace ytLive.Tests;
/// <summary>
/// 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 — and the first
/// attempt at a version heal silently did nothing because it iterated `AllBackgrounds()`, which returns
/// only the LIVE scene's rows.
/// </summary>
[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: point the Starting background at a planted
// asset and mark the layout as an older art vintage. The heal must re-stamp the shipped art.
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();
using (var store = new LayoutStore(tempDb))
store.SaveDefaultArtVersion(1);
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);
}
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<ytLive.Models.Source>())
.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());
}
}