fix(mgmt): pass sharedScripts to design-time template $ref validation (#259)

This commit is contained in:
Joseph Doherty
2026-06-19 02:21:15 -04:00
parent d844405cec
commit 454e47ea38
2 changed files with 79 additions and 1 deletions
@@ -590,9 +590,23 @@ public class ManagementActor : ReceiveActor
var schemaLibrary = sharedSchemas.ToDictionary(s => s.Name, s => s.SchemaJson, StringComparer.Ordinal);
Func<string, string?> resolveSchemaRef = name => schemaLibrary.GetValueOrDefault(name);
// #259: also pass shared scripts so a dangling $ref in a SHARED script's schema is
// caught at design-time (not only on the deploy path). Mirror the deploy-path mapping
// in FlatteningPipeline: SharedScript entity → ResolvedScript (name + schema fields).
var sharedScriptEntities = await repo.GetAllSharedScriptsAsync();
var resolvedSharedScripts = sharedScriptEntities
.Select(s => new Commons.Types.Flattening.ResolvedScript
{
CanonicalName = s.Name,
Code = s.Code,
ParameterDefinitions = s.ParameterDefinitions,
ReturnDefinition = s.ReturnDefinition
})
.ToList();
// Run full validation pipeline (collisions, script compilation, trigger refs, bindings)
var validationService = new TemplateEngine.Validation.ValidationService();
var validationResult = validationService.Validate(flatConfig, resolveSchemaRef: resolveSchemaRef);
var validationResult = validationService.Validate(flatConfig, resolvedSharedScripts, resolveSchemaRef: resolveSchemaRef);
// Also detect naming collisions across the inheritance/composition graph
var svc = sp.GetRequiredService<TemplateService>();
@@ -5,6 +5,8 @@ using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
@@ -2378,4 +2380,66 @@ public class ManagementActorTests : TestKit, IDisposable
// The off-site template (42) must never be scanned.
_templateRepo.DidNotReceive().GetTemplateByIdAsync(42, Arg.Any<CancellationToken>());
}
// ========================================================================
// #259: design-time ValidateTemplateCommand passes sharedScripts so a
// dangling $ref in a SHARED script's schema is caught at design time, not
// only on the deploy path.
// ========================================================================
[Fact]
public void ValidateTemplate_DanglingRefInSharedScript_ReturnsInvalidResult()
{
// Arrange
var template = new Template("T1") { Id = 1 };
_templateRepo.GetTemplateWithChildrenAsync(1, Arg.Any<CancellationToken>())
.Returns(template);
_templateRepo.GetAttributesByTemplateIdAsync(1, Arg.Any<CancellationToken>())
.Returns(new List<TemplateAttribute>());
_templateRepo.GetAlarmsByTemplateIdAsync(1, Arg.Any<CancellationToken>())
.Returns(new List<TemplateAlarm>());
_templateRepo.GetScriptsByTemplateIdAsync(1, Arg.Any<CancellationToken>())
.Returns(new List<TemplateScript>());
// Shared script whose parameter schema contains a dangling $ref.
_templateRepo.GetAllSharedScriptsAsync(Arg.Any<CancellationToken>())
.Returns(new List<SharedScript>
{
new SharedScript("ComputeOrder", "var x = 1;")
{
ParameterDefinitions = """{"$ref":"lib:MissingSharedSchema"}"""
}
});
// Schema library is empty → the $ref cannot be resolved and dangles.
var schemaRepo = Substitute.For<ISharedSchemaRepository>();
schemaRepo.ListAsync(Arg.Any<CancellationToken>())
.Returns(new List<SharedSchema>());
_services.AddScoped(_ => schemaRepo);
// TemplateService is required by the collision-detection step inside HandleValidateTemplate.
_templateRepo.GetTemplateByIdAsync(1, Arg.Any<CancellationToken>())
.Returns(template);
_templateRepo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
.Returns(new List<Template> { template });
_services.AddScoped<TemplateService>();
var actor = CreateActor();
var envelope = Envelope(new ValidateTemplateCommand(1), "Designer");
// Act
actor.Tell(envelope);
// Assert: the handler returns ManagementSuccess whose JSON payload signals an invalid
// ValidationResult with a SchemaReference error naming the missing library entry.
var msg = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(10));
Assert.Equal(envelope.CorrelationId, msg.CorrelationId);
// The ValidationResult is JSON-serialised into JsonData; check key fields in the raw JSON
// (consistent with how other ManagementActorTests verify structured payloads).
// ValidationCategory is serialised as an integer; SchemaReference is ordinal 15.
Assert.Contains("\"isValid\":false", msg.JsonData, StringComparison.Ordinal);
Assert.Contains("MissingSharedSchema", msg.JsonData, StringComparison.Ordinal);
Assert.Contains("\"category\":15", msg.JsonData, StringComparison.Ordinal);
}
}