3a590a0cb7
Scripted-alarm condition state lives in LocalDb (Phase 4); the ConfigDb-backed Ef store + ScriptedAlarmState table are now dead. Removes the test-harness Ef fallback (a DB-backed node with no store now skips the alarm host), deletes the store + entity + model config, and drops the table via migration. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
154 lines
6.7 KiB
C#
154 lines
6.7 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Configuration.Tests;
|
|
|
|
/// <summary>
|
|
/// Verifies the Phase 7 Stream E entities (<see cref="Script"/>, <see cref="VirtualTag"/>,
|
|
/// <see cref="ScriptedAlarm"/>) register correctly in the EF model, map to the expected
|
|
/// tables/columns/indexes, and carry the check constraints the plan decisions call for.
|
|
/// Introspection only — no SQL Server required.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class ScriptingEntitiesTests
|
|
{
|
|
private static OtOpcUaConfigDbContext BuildCtx()
|
|
{
|
|
var options = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
|
|
.UseSqlServer("Server=(local);Database=dummy;Integrated Security=true") // not connected
|
|
.Options;
|
|
return new OtOpcUaConfigDbContext(options);
|
|
}
|
|
|
|
private static Microsoft.EntityFrameworkCore.Metadata.IModel DesignModel(OtOpcUaConfigDbContext ctx)
|
|
=> ctx.GetService<IDesignTimeModel>().Model;
|
|
|
|
/// <summary>Verifies that the Script entity is registered with the expected table name and columns.</summary>
|
|
[Fact]
|
|
public void Script_entity_registered_with_expected_table_and_columns()
|
|
{
|
|
using var ctx = BuildCtx();
|
|
var entity = ctx.Model.FindEntityType(typeof(Script)).ShouldNotBeNull();
|
|
|
|
entity.GetTableName().ShouldBe("Script");
|
|
entity.FindProperty(nameof(Script.ScriptRowId)).ShouldNotBeNull();
|
|
entity.FindProperty(nameof(Script.ScriptId)).ShouldNotBeNull()
|
|
.GetMaxLength().ShouldBe(64);
|
|
entity.FindProperty(nameof(Script.SourceCode)).ShouldNotBeNull()
|
|
.GetColumnType().ShouldBe("nvarchar(max)");
|
|
entity.FindProperty(nameof(Script.SourceHash)).ShouldNotBeNull()
|
|
.GetMaxLength().ShouldBe(64);
|
|
entity.FindProperty(nameof(Script.Language)).ShouldNotBeNull()
|
|
.GetMaxLength().ShouldBe(16);
|
|
}
|
|
|
|
/// <summary>Verifies that the Script entity has a unique logical ID constraint.</summary>
|
|
[Fact]
|
|
public void Script_has_unique_logical_id()
|
|
{
|
|
// v2 live-edit dropped the per-generation qualifier; Script.ScriptId is globally unique.
|
|
using var ctx = BuildCtx();
|
|
var entity = ctx.Model.FindEntityType(typeof(Script)).ShouldNotBeNull();
|
|
entity.GetIndexes().Any(i => i.IsUnique).ShouldBeTrue("Script needs at least one unique index");
|
|
}
|
|
|
|
/// <summary>Verifies that the VirtualTag entity is registered with the expected trigger check constraint.</summary>
|
|
[Fact]
|
|
public void VirtualTag_entity_registered_with_trigger_check_constraint()
|
|
{
|
|
using var ctx = BuildCtx();
|
|
var entity = DesignModel(ctx).FindEntityType(typeof(VirtualTag)).ShouldNotBeNull();
|
|
entity.GetTableName().ShouldBe("VirtualTag");
|
|
|
|
var checks = entity.GetCheckConstraints().Select(c => c.Name).ToArray();
|
|
checks.ShouldContain("CK_VirtualTag_Trigger_AtLeastOne");
|
|
checks.ShouldContain("CK_VirtualTag_TimerInterval_Min");
|
|
}
|
|
|
|
/// <summary>Verifies that the VirtualTag entity has a unique index on its logical key.</summary>
|
|
[Fact]
|
|
public void VirtualTag_has_unique_index_on_logical_key()
|
|
{
|
|
// v2 live-edit dropped the per-generation qualifier on VirtualTag's uniqueness.
|
|
using var ctx = BuildCtx();
|
|
var entity = ctx.Model.FindEntityType(typeof(VirtualTag)).ShouldNotBeNull();
|
|
entity.GetIndexes().Any(i => i.IsUnique).ShouldBeTrue("VirtualTag needs at least one unique index");
|
|
}
|
|
|
|
/// <summary>Verifies that the VirtualTag entity has ChangeTriggered, Historize, and TimerIntervalMs properties.</summary>
|
|
[Fact]
|
|
public void VirtualTag_has_ChangeTriggered_and_Historize_flags()
|
|
{
|
|
using var ctx = BuildCtx();
|
|
var entity = ctx.Model.FindEntityType(typeof(VirtualTag)).ShouldNotBeNull();
|
|
entity.FindProperty(nameof(VirtualTag.ChangeTriggered)).ShouldNotBeNull()
|
|
.ClrType.ShouldBe(typeof(bool));
|
|
entity.FindProperty(nameof(VirtualTag.Historize)).ShouldNotBeNull()
|
|
.ClrType.ShouldBe(typeof(bool));
|
|
entity.FindProperty(nameof(VirtualTag.TimerIntervalMs)).ShouldNotBeNull()
|
|
.ClrType.ShouldBe(typeof(int?));
|
|
}
|
|
|
|
/// <summary>Verifies that the ScriptedAlarm entity is registered with severity and type check constraints.</summary>
|
|
[Fact]
|
|
public void ScriptedAlarm_entity_registered_with_severity_and_type_checks()
|
|
{
|
|
using var ctx = BuildCtx();
|
|
var entity = DesignModel(ctx).FindEntityType(typeof(ScriptedAlarm)).ShouldNotBeNull();
|
|
entity.GetTableName().ShouldBe("ScriptedAlarm");
|
|
|
|
var checks = entity.GetCheckConstraints().Select(c => c.Name).ToArray();
|
|
checks.ShouldContain("CK_ScriptedAlarm_Severity_Range");
|
|
checks.ShouldContain("CK_ScriptedAlarm_AlarmType");
|
|
}
|
|
|
|
/// <summary>Verifies that ScriptedAlarm has HistorizeToAveva defaulted to true per plan decision 15.</summary>
|
|
[Fact]
|
|
public void ScriptedAlarm_has_HistorizeToAveva_default_true_per_plan_decision_15()
|
|
{
|
|
// Defaults live on the CLR default assignment — verify the initializer.
|
|
var alarm = new ScriptedAlarm
|
|
{
|
|
ScriptedAlarmId = "a1",
|
|
EquipmentId = "eq1",
|
|
Name = "n",
|
|
AlarmType = "LimitAlarm",
|
|
MessageTemplate = "m",
|
|
PredicateScriptId = "s1",
|
|
};
|
|
alarm.HistorizeToAveva.ShouldBeTrue();
|
|
alarm.Retain.ShouldBeTrue();
|
|
alarm.Severity.ShouldBe(500);
|
|
alarm.Enabled.ShouldBeTrue();
|
|
}
|
|
|
|
// The ConfigDb-backed ScriptedAlarmState entity + table were retired in per-cluster mesh Phase 4
|
|
// (Task 9): scripted-alarm condition state lives in the replicated LocalDb store. The entity-model
|
|
// tests that covered it are gone with it.
|
|
|
|
/// <summary>Verifies that all new Phase 7 entities are exposed via DbSet properties.</summary>
|
|
[Fact]
|
|
public void All_new_entities_exposed_via_DbSet()
|
|
{
|
|
using var ctx = BuildCtx();
|
|
ctx.Scripts.ShouldNotBeNull();
|
|
ctx.VirtualTags.ShouldNotBeNull();
|
|
ctx.ScriptedAlarms.ShouldNotBeNull();
|
|
}
|
|
|
|
/// <summary>Verifies that the squashed V3Initial migration exists in the assembly.</summary>
|
|
[Fact]
|
|
public void V3Initial_migration_exists_in_assembly()
|
|
{
|
|
// v3 WP1 squashed every prior migration into V3Initial. The migration type carries the
|
|
// design-time snapshot + Up/Down (incl. the scripting tables) EF uses to apply the schema.
|
|
var t = typeof(Migrations.V3Initial);
|
|
t.ShouldNotBeNull();
|
|
}
|
|
}
|