perf(adminui): /scripts lists a projection and loads source per dialog (R04 P8)
The list loaded every script entity including SourceCode to render names and
hashes. ScriptListQueries projects {ScriptId, Name, Language, SourceHash} for
the list and fetches one script's source when its Source dialog opens, with a
generation guard against a superseded open.
Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
|
||||
/// <summary>R04 P8: the <c>/scripts</c> list is a projection without source; source loads per dialog.</summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptListQueriesTests
|
||||
{
|
||||
private static OtOpcUaConfigDbContext NewDb(string name) =>
|
||||
new(new DbContextOptionsBuilder<OtOpcUaConfigDbContext>().UseInMemoryDatabase(name).Options);
|
||||
|
||||
private static async Task<string> 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<Script>");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user