feat(vtags): wire IHistoryWriter through DriverHostActor (Null default; durable sink infra-gated) (H5d, stillpending §1)
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using Akka.Actor;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.VirtualTags;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// H5d (DI seam): pins that <see cref="DriverHostActor.Props"/> accepts an
|
||||
/// <see cref="IHistoryWriter"/> and threads it through to the REAL spawned
|
||||
/// <see cref="VirtualTagHostActor"/> (the <c>virtualTagHostOverride</c> is deliberately NOT used,
|
||||
/// so the production spawn path that wires <c>historyWriter</c> is exercised).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a construction/threading smoke test, not a record-behaviour test. The record path —
|
||||
/// "a Historize=true result is recorded with the IHistoryWriter under the folder-scoped NodeId" —
|
||||
/// is already covered end-to-end against the host directly by
|
||||
/// <c>VirtualTagHostActorTests.Historized_vtag_result_is_recorded_with_the_history_writer</c> (H5c).
|
||||
/// Driving a real spawned child to emit a synthetic <see cref="VirtualTagActor.EvaluationResult"/>
|
||||
/// from inside <see cref="DriverHostActor"/> would require reaching the auto-named grandchild and a
|
||||
/// live evaluator/mux feed — heavy and brittle for no extra coverage. So here we assert the writer
|
||||
/// threads through cleanly: a real apply carrying a Historize=true Equipment VirtualTag is Applied
|
||||
/// with the real host spawned and the capturing writer wired in, with no construction error.
|
||||
/// </remarks>
|
||||
public sealed class DriverHostActorHistoryWriterTests : RuntimeActorTestBase
|
||||
{
|
||||
private static readonly NodeId TestNode = NodeId.Parse("driver-hw-test");
|
||||
private static readonly RevisionHash RevA = RevisionHash.Parse(new string('a', 64));
|
||||
|
||||
/// <summary>Spawning a real DriverHostActor (no host override, publish actor wired so the real
|
||||
/// VirtualTagHostActor spawns) with a capturing IHistoryWriter applies a Historize=true VirtualTag
|
||||
/// deployment cleanly — proving Props/ctor accept the writer and thread it through the real spawn.</summary>
|
||||
[Fact]
|
||||
public void Props_accepts_history_writer_and_applies_historized_vtag_deployment()
|
||||
{
|
||||
var db = NewInMemoryDbFactory();
|
||||
var deploymentId = SeedDeploymentWithHistorizedVirtualTag(db, RevA);
|
||||
|
||||
var coordinator = CreateTestProbe();
|
||||
// A publish actor MUST be wired for SpawnVirtualTagHost to spawn the real host (its ctor
|
||||
// requires a non-null sink). No virtualTagHostOverride ⇒ the real VirtualTagHostActor is
|
||||
// spawned with _historyWriter threaded in.
|
||||
var publish = CreateTestProbe();
|
||||
var writer = new CapturingHistoryWriter();
|
||||
|
||||
var actor = Sys.ActorOf(DriverHostActor.Props(
|
||||
db, TestNode, coordinator.Ref,
|
||||
localRoles: new HashSet<string> { "driver" },
|
||||
opcUaPublishActor: publish.Ref,
|
||||
virtualTagEvaluator: NullVirtualTagEvaluator.Instance,
|
||||
historyWriter: writer));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
|
||||
// The deployment Applies — the real VirtualTagHostActor spawned and accepted ApplyVirtualTags
|
||||
// with the writer threaded through (a construction/threading failure would have faulted PreStart
|
||||
// or the apply turn). Record behaviour itself is covered by VirtualTagHostActorTests (H5c).
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
}
|
||||
|
||||
private static DeploymentId SeedDeploymentWithHistorizedVirtualTag(
|
||||
IDbContextFactory<OtOpcUaConfigDbContext> db, RevisionHash rev)
|
||||
{
|
||||
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
Scripts = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
ScriptId = "scr-1",
|
||||
SourceCode = "return ctx.GetTag(\"TestMachine_001.TestDouble\").Value;",
|
||||
},
|
||||
},
|
||||
VirtualTags = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
VirtualTagId = "vt-1",
|
||||
EquipmentId = "eq-1",
|
||||
Name = "Doubled",
|
||||
DataType = "Float",
|
||||
ScriptId = "scr-1",
|
||||
Historize = true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var id = DeploymentId.NewId();
|
||||
using var ctx = db.CreateDbContext();
|
||||
ctx.Deployments.Add(new Deployment
|
||||
{
|
||||
DeploymentId = id.Value,
|
||||
RevisionHash = rev.Value,
|
||||
Status = DeploymentStatus.Sealed,
|
||||
CreatedBy = "test",
|
||||
SealedAtUtc = DateTime.UtcNow,
|
||||
ArtifactBlob = artifact,
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>Capturing <see cref="IHistoryWriter"/>: records every Record call so the threading
|
||||
/// smoke test can be tightened later if a record assertion becomes feasible here.</summary>
|
||||
private sealed class CapturingHistoryWriter : IHistoryWriter
|
||||
{
|
||||
public readonly ConcurrentQueue<(string Path, DataValueSnapshot Value)> Calls = new();
|
||||
|
||||
/// <summary>Captures the path + snapshot of a Record call.</summary>
|
||||
/// <param name="path">The virtual tag path.</param>
|
||||
/// <param name="value">The data value snapshot.</param>
|
||||
public void Record(string path, DataValueSnapshot value) => Calls.Enqueue((path, value));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user