Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 17s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m28s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 15m46s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m50s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m18s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
The #74 seeded on-now-next.yml never rendered at transcode time: - format_datetime was called with 2 args; it needs 3 (DateTimeOffset, timeZoneId, format) and does the tz conversion itself, so the Scriban render threw. Drop the NEXT start-time (avoids hardcoding a timezone in a shipped default). - styles had no font_family; CustomFontMapper.TypefaceFromStyle crashes on a null family before its default-font fallback. Add font_family: Noto Sans. Verified live on ersatztv-test via frame capture. Adds a seeder test that deserializes the YAML and asserts base_style resolves + every style sets a font. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.5 KiB
C#
96 lines
3.5 KiB
C#
using System.IO.Abstractions;
|
|
using Testably.Abstractions.Testing;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using ErsatzTV.Infrastructure.Streaming.Graphics;
|
|
using ErsatzTV.Tests.Support;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
using YamlDotNet.Serialization;
|
|
using YamlDotNet.Serialization.NamingConventions;
|
|
using ErsatzTV.Core.Graphics;
|
|
|
|
namespace ErsatzTV.Tests.Infrastructure;
|
|
|
|
[TestFixture]
|
|
public class GraphicsElementSeederTests
|
|
{
|
|
private InMemoryTvContext _db = null!;
|
|
private string _seededPath = null!;
|
|
|
|
[SetUp]
|
|
public async Task SetUp()
|
|
{
|
|
_db = await InMemoryTvContext.CreateAsync();
|
|
_seededPath = Path.Combine(FileSystemLayout.GraphicsElementsTextTemplatesFolder, "on-now-next.yml");
|
|
}
|
|
|
|
[TearDown]
|
|
public async Task TearDown() => await _db.DisposeAsync();
|
|
|
|
[Test]
|
|
public async Task Seeds_File_And_Marker_When_Absent()
|
|
{
|
|
var fs = new MockFileSystem();
|
|
await using TvContext context = _db.CreateContext();
|
|
|
|
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
|
|
|
|
fs.File.Exists(_seededPath).ShouldBeTrue();
|
|
fs.File.ReadAllText(_seededPath).ShouldContain("epg_entries: 2");
|
|
(await context.ConfigElements.AnyAsync(c => c.Key == ConfigElementKey.GraphicsOnNowNextSeeded.Key)).ShouldBeTrue();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Adopts_Existing_File_Untouched()
|
|
{
|
|
var fs = new MockFileSystem();
|
|
fs.Directory.CreateDirectory(FileSystemLayout.GraphicsElementsTextTemplatesFolder);
|
|
await fs.File.WriteAllTextAsync(_seededPath, "name: Operator Custom\nepg_entries: 2\n");
|
|
await using TvContext context = _db.CreateContext();
|
|
|
|
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
|
|
|
|
fs.File.ReadAllText(_seededPath).ShouldContain("Operator Custom");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Does_Not_Resurrect_After_Delete()
|
|
{
|
|
var fs = new MockFileSystem();
|
|
await using TvContext context = _db.CreateContext();
|
|
|
|
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
|
|
fs.File.Delete(_seededPath);
|
|
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
|
|
|
|
fs.File.Exists(_seededPath).ShouldBeFalse();
|
|
}
|
|
|
|
// Guards the #570 render bugs: the seeded YAML must deserialize into a TextGraphicsElement whose
|
|
// base_style names a real style and whose every style sets a font_family — a null font_family
|
|
// crashes CustomFontMapper before its default-font fallback, so the overlay silently never renders.
|
|
[Test]
|
|
public async Task Seeded_Element_Deserializes_With_Resolvable_BaseStyle_And_Font_Families()
|
|
{
|
|
var fs = new MockFileSystem();
|
|
await using TvContext context = _db.CreateContext();
|
|
await GraphicsElementSeeder.SeedOnNowNext(context, fs, CancellationToken.None);
|
|
|
|
string yaml = await fs.File.ReadAllTextAsync(_seededPath);
|
|
IDeserializer deserializer = new DeserializerBuilder()
|
|
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
|
.IgnoreUnmatchedProperties()
|
|
.Build();
|
|
|
|
var element = deserializer.Deserialize<TextGraphicsElement>(yaml);
|
|
|
|
element.ShouldNotBeNull();
|
|
element.Styles.ShouldNotBeEmpty();
|
|
element.Styles.Select(s => s.Name).ShouldContain(element.BaseStyle);
|
|
element.Styles.ShouldAllBe(s => !string.IsNullOrWhiteSpace(s.FontFamily));
|
|
}
|
|
}
|