241318e7e5
v2-ci / unit-tests (push) Has been cancelled
Item 5: DraftValidator now emits SqlQueryNotDeclared (always blocking) for a Query tag whose query is absent from the merged driver config's queries map, via the driver's own tag parse. Item 3: StartDeployment appends a non-blocking warning to the accepted message when the Tag entity's AccessLevel and the Sql blob's writable flag disagree. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
77 lines
3.5 KiB
C#
77 lines
3.5 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Validation;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Configuration.Tests;
|
|
|
|
/// <summary>lmxopcua#781 items 5 and 3: Sql deploy checks that need the driver config / Tag entity.</summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class DraftValidatorSqlQueryTests
|
|
{
|
|
private const string Code = SqlTagDeployChecks.UndeclaredQueryCode;
|
|
private const string Declared =
|
|
"""{"queries":{"line":{"sql":"SELECT 1 AS v","keyColumns":[]}}}""";
|
|
|
|
private static string QueryTag(string query, bool? writable = null) =>
|
|
$$"""{"model":"Query","query":"{{query}}","column":"v"{{(writable is null ? "" : $",\"writable\":{writable.ToString()!.ToLowerInvariant()}")}}}""";
|
|
|
|
private static DraftSnapshot Draft(string driverConfig, string tagConfig,
|
|
TagAccessLevel access = TagAccessLevel.Read, string deviceConfig = "{}") => new()
|
|
{
|
|
GenerationId = 1, ClusterId = "c",
|
|
DriverInstances = [new DriverInstance
|
|
{ DriverInstanceId = "di", ClusterId = "c", Name = "sql", DriverType = "Sql", DriverConfig = driverConfig }],
|
|
Devices = [new Device { DeviceId = "dev", DriverInstanceId = "di", Name = "d", DeviceConfig = deviceConfig }],
|
|
Tags = [new Tag { TagId = "t1", DeviceId = "dev", Name = "t", DataType = "Int32",
|
|
AccessLevel = access, TagConfig = tagConfig }],
|
|
};
|
|
|
|
[Fact]
|
|
public void Query_tag_naming_an_undeclared_query_is_a_deploy_error()
|
|
{
|
|
var errors = DraftValidator.Validate(Draft(Declared, QueryTag("nope")));
|
|
errors.ShouldContain(e => e.Code == Code && e.Context == "t1" && e.Message.Contains("'nope'"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_tag_with_no_queries_map_at_all_is_a_deploy_error()
|
|
=> DraftValidator.Validate(Draft("{}", QueryTag("line"))).ShouldContain(e => e.Code == Code);
|
|
|
|
[Fact]
|
|
public void Positive_control_declared_query_is_quiet()
|
|
=> DraftValidator.Validate(Draft(Declared, QueryTag("line"))).ShouldNotContain(e => e.Code == Code);
|
|
|
|
[Fact]
|
|
public void Query_declared_on_the_single_device_config_counts_as_declared()
|
|
=> DraftValidator.Validate(Draft("{}", QueryTag("line"), deviceConfig: Declared))
|
|
.ShouldNotContain(e => e.Code == Code);
|
|
|
|
[Fact]
|
|
public void Query_names_are_ordinal()
|
|
=> DraftValidator.Validate(Draft(Declared, QueryTag("LINE"))).ShouldContain(e => e.Code == Code);
|
|
|
|
[Fact]
|
|
public void Non_query_models_are_not_checked()
|
|
=> DraftValidator.Validate(Draft("{}",
|
|
"""{"model":"KeyValue","table":"t","keyColumn":"k","keyValue":"a","valueColumn":"v"}"""))
|
|
.ShouldNotContain(e => e.Code == Code);
|
|
|
|
[Theory]
|
|
[InlineData(TagAccessLevel.ReadWrite, false, true)]
|
|
[InlineData(TagAccessLevel.Read, true, true)]
|
|
[InlineData(TagAccessLevel.ReadWrite, true, false)]
|
|
[InlineData(TagAccessLevel.Read, false, false)]
|
|
public void Writable_flag_disagreement_warns_and_agreement_is_quiet(TagAccessLevel access, bool writable, bool warns)
|
|
{
|
|
var warnings = SqlTagDeployChecks.WritableMismatchWarnings(Draft(Declared, QueryTag("line", writable), access));
|
|
(warnings.Count == 1).ShouldBe(warns);
|
|
if (warns) warnings[0].ShouldContain("t1");
|
|
}
|
|
|
|
[Fact]
|
|
public void Writable_mismatch_is_never_a_validation_error()
|
|
=> DraftValidator.Validate(Draft(Declared, QueryTag("line", true), TagAccessLevel.Read)).ShouldBeEmpty();
|
|
}
|