173 lines
5.7 KiB
C#
173 lines
5.7 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
|
|
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Validation;
|
|
|
|
/// <summary>
|
|
/// M9-T32b deploy-time validation: a dangling <c>{"$ref":"lib:Name"}</c> in any
|
|
/// validated script parameter/return schema must BLOCK deployment (a
|
|
/// <see cref="ValidationCategory.SchemaReference"/> error naming the missing ref).
|
|
/// A valid ref resolved through the supplied seam passes. The check is inert for
|
|
/// schemas with no <c>$ref</c> (existing behaviour unchanged) and when no resolver
|
|
/// is supplied and there are no refs.
|
|
/// </summary>
|
|
public class SchemaRefValidationTests
|
|
{
|
|
private readonly ValidationService _sut = new();
|
|
|
|
[Fact]
|
|
public void Validate_DanglingRefInParameterDefinitions_BlocksDeploy()
|
|
{
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "CallMe",
|
|
Code = "var x = 1;",
|
|
ParameterDefinitions = """{"$ref":"lib:MissingLib"}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
// Resolver knows nothing → the ref dangles.
|
|
var result = _sut.Validate(config, resolveSchemaRef: _ => null);
|
|
|
|
Assert.False(result.IsValid);
|
|
var error = Assert.Single(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
|
Assert.Contains("MissingLib", error.Message, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_DanglingRefInReturnDefinition_BlocksDeploy()
|
|
{
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "CallMe",
|
|
Code = "var x = 1;",
|
|
ReturnDefinition = """{"$ref":"lib:GoneReturn"}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
var result = _sut.Validate(config, resolveSchemaRef: _ => null);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains(result.Errors, e =>
|
|
e.Category == ValidationCategory.SchemaReference
|
|
&& e.Message.Contains("GoneReturn", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_ValidRef_Passes()
|
|
{
|
|
const string libSchema = """{"type":"object","properties":{"qty":{"type":"integer"}}}""";
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "CallMe",
|
|
Code = "var x = 1;",
|
|
TriggerType = "Call",
|
|
ParameterDefinitions = """{"$ref":"lib:OrderRequest"}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
var result = _sut.Validate(
|
|
config,
|
|
resolveSchemaRef: name => name == "OrderRequest" ? libSchema : null);
|
|
|
|
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_NoRefSchema_NoSchemaReferenceError()
|
|
{
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "CallMe",
|
|
Code = "var x = 1;",
|
|
ParameterDefinitions = """{"type":"object","properties":{"a":{"type":"integer"}}}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
// Even with a resolver present, a schema with no $ref yields no ref errors.
|
|
var result = _sut.Validate(config, resolveSchemaRef: _ => null);
|
|
|
|
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_NoResolverSupplied_NoRegression()
|
|
{
|
|
// The default deploy/design path with no resolver and no $ref schemas must
|
|
// behave exactly as before (no SchemaReference errors).
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Attributes = [new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }],
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "Monitor",
|
|
Code = "var x = 1;",
|
|
ParameterDefinitions = """{"type":"object","properties":{"a":{"type":"integer"}}}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
var result = _sut.Validate(config);
|
|
|
|
Assert.True(result.IsValid);
|
|
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
|
}
|
|
|
|
[Fact]
|
|
public void Validate_CyclicRef_BlocksDeployWithoutOverflow()
|
|
{
|
|
var schemas = new Dictionary<string, string>
|
|
{
|
|
["A"] = """{"$ref":"lib:B"}""",
|
|
["B"] = """{"$ref":"lib:A"}""",
|
|
};
|
|
var config = new FlattenedConfiguration
|
|
{
|
|
InstanceUniqueName = "Instance1",
|
|
Scripts =
|
|
[
|
|
new ResolvedScript
|
|
{
|
|
CanonicalName = "CallMe",
|
|
Code = "var x = 1;",
|
|
ParameterDefinitions = """{"$ref":"lib:A"}""",
|
|
}
|
|
]
|
|
};
|
|
|
|
var result = _sut.Validate(
|
|
config,
|
|
resolveSchemaRef: name => schemas.GetValueOrDefault(name));
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.SchemaReference);
|
|
}
|
|
}
|