792f16c2d5
Attribute/alarm/script add (collision check) and update (parent-lock lookup) used GetAllTemplatesForAnalysisAsync, materialising every template, attribute, alarm and script in the library per edit. They now call the new ITemplateEngineRepository.GetTemplateAnalysisClosureAsync: the same untracked, body-free analysis projection, restricted to the template's inheritance chain plus the composition closure of every chain member - exactly what TemplateResolver and CollisionDetector walk (a composed module's own parent chain is never climbed, so it is not loaded). Adds use the edited template's closure, updates the parent's. The walk (Commons TemplateAnalysisClosure) loads one batch per closure depth, requests each id once, terminates on cycles and skips dangling edges. Evidence: - TemplateAnalysisClosureTests: for every template of a graph with chains, nested + diamond compositions, a derived module, cycles, dangling edges and colliding unrelated templates, ResolveAllMembers / DetectCollisions / FindMemberByCanonicalName answer identically from the closure and the whole graph (mutation-checked). - TemplateAnalysisClosureRepositoryTests (SQLite, recorded SQL): 6 templates / 12 attributes loaded instead of 47 / 94, in 4 queries (one per depth), untracked, script bodies never selected. - TemplateServiceTests: two new tests with the whole-graph load wired to throw (fail on the old service); every existing add/update test now runs against the closure via AnalysisClosureMock. ReconcileDescendantsAsync still loads the tracked full graph; not in scope. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
41 lines
1.9 KiB
C#
41 lines
1.9 KiB
C#
using Moq;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests;
|
|
|
|
/// <summary>
|
|
/// Gitea #66: member add/update now asks the repository for a template's analysis closure
|
|
/// rather than the whole analysis graph. Test classes keep seeding the whole graph through
|
|
/// <c>GetAllTemplatesForAnalysisAsync</c>; this wires <c>GetTemplateAnalysisClosureAsync</c>
|
|
/// to serve the real closure walk over that seeded graph, so every existing scenario runs the
|
|
/// service against the reduced set — the same set the production repository returns.
|
|
/// </summary>
|
|
internal static class AnalysisClosureMock
|
|
{
|
|
/// <summary>Wires the closure method to filter whatever the full-graph setup returns.</summary>
|
|
/// <param name="repoMock">The repository mock.</param>
|
|
/// <param name="closureRequests">Optional sink recording each requested closure root.</param>
|
|
public static void ServeClosureFromFullGraph(
|
|
this Mock<ITemplateEngineRepository> repoMock,
|
|
List<int>? closureRequests = null)
|
|
{
|
|
repoMock
|
|
.Setup(r => r.GetTemplateAnalysisClosureAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
|
.Returns((int templateId, CancellationToken ct) =>
|
|
{
|
|
closureRequests?.Add(templateId);
|
|
return TemplateAnalysisClosure.CollectAsync(
|
|
templateId,
|
|
async (ids, token) =>
|
|
{
|
|
var all = await repoMock.Object.GetAllTemplatesForAnalysisAsync(token)
|
|
?? Array.Empty<Template>();
|
|
return all.Where(t => ids.Contains(t.Id)).ToList();
|
|
},
|
|
ct);
|
|
});
|
|
}
|
|
}
|