using Microsoft.EntityFrameworkCore; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptList; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptList; /// R04 P8: the /scripts list is a projection without source; source loads per dialog. [Trait("Category", "Unit")] public sealed class ScriptListQueriesTests { private static OtOpcUaConfigDbContext NewDb(string name) => new(new DbContextOptionsBuilder().UseInMemoryDatabase(name).Options); private static async Task SeedAsync() { var name = Guid.NewGuid().ToString("N"); await using var db = NewDb(name); db.Scripts.AddRange( new Script { ScriptId = "s-b", Name = "Beta", SourceCode = "return 2;", SourceHash = "hb" }, new Script { ScriptId = "s-a", Name = "Alpha", SourceCode = "return 1;", SourceHash = "ha", Language = "CSharp" }); await db.SaveChangesAsync(); return name; } [Fact] public async Task LoadRowsAsync_returns_name_ordered_rows_without_source() { var name = await SeedAsync(); await using var db = NewDb(name); var rows = await ScriptListQueries.LoadRowsAsync(db); rows.Select(r => r.ScriptId).ShouldBe(["s-a", "s-b"]); rows[0].ShouldBe(new ScriptListRow("s-a", "Alpha", "CSharp", "ha")); // The row type has no source member at all — the list cannot load it by accident. typeof(ScriptListRow).GetProperty("SourceCode").ShouldBeNull(); } [Fact] public async Task LoadSourceAsync_returns_one_scripts_source_or_null_when_gone() { var name = await SeedAsync(); await using var db = NewDb(name); (await ScriptListQueries.LoadSourceAsync(db, "s-b")).ShouldBe("return 2;"); (await ScriptListQueries.LoadSourceAsync(db, "missing")).ShouldBeNull(); } [Fact] public void Scripts_page_uses_the_projection() { var src = AdminUiSource.Read("Components", "Pages", "Scripts.razor"); src.ShouldContain("ScriptListQueries.LoadRowsAsync("); src.ShouldNotContain("List