using Akka.Actor;
using Akka.Event;
using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
///
/// Review 02 H5: the Stale behaviour registered no receive for a driver child's value/alarm bursts (nor
/// for a late ArtifactForApply / ApplyWedged pipe-back), so any that landed there hit
/// Unhandled — dead-letter noise and a dropped message with no log naming why. They are now explicit
/// drops. The two private pipe-back records cannot be constructed from a test; the two public child
/// messages are pinned here, and the private pair by the same registration pattern (compile-time).
///
public sealed class DriverHostActorStaleDropTests : RuntimeActorTestBase
{
private static readonly NodeId TestNode = NodeId.Parse("stale-drop-test");
[Fact]
public void Child_value_and_alarm_bursts_landing_in_Stale_are_dropped_not_unhandled()
{
var unhandled = CreateTestProbe();
Sys.EventStream.Subscribe(unhandled.Ref, typeof(UnhandledMessage));
// ThrowingDbFactory ⇒ Bootstrap's catch ⇒ Become(Stale).
var host = Sys.ActorOf(DriverHostActor.Props(
new ThrowingDbFactory(), TestNode, CreateTestProbe().Ref,
localRoles: new HashSet { "driver" }));
// Barrier: Stale answers RouteNodeWrite synchronously, so a reply proves the host is in Stale.
var asker = CreateTestProbe();
host.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref);
asker.ExpectMsg(PresenceBudget).Reason!.ShouldContain("stale");
// Positive control: an unknown message IS reported as unhandled by this host.
host.Tell("not a message the host knows");
unhandled.ExpectMsg(m => ReferenceEquals(m.Recipient, host) || m.Recipient.Equals(host), PresenceBudget);
host.Tell(new DriverInstanceActor.AttributeValueBatchPublished(
"DI-1", new[] { new DriverInstanceActor.AttributeValueItem("Plant/x/y", 1.0, OpcUaStatus.Good, DateTime.UtcNow) }));
host.Tell(new DriverInstanceActor.AttributeAlarmPublished(
"DI-1", new AlarmEventArgs(new Handle(), "Plant/x/alarm", "Plant/x/alarm", "AlarmCondition", "test",
AlarmSeverity.High, DateTime.UtcNow)));
// Barrier again, then the absence window.
host.Tell(new DriverHostActor.RouteNodeWrite("eq-x/speed", 0.0, AddressSpaceRealm.Uns), asker.Ref);
asker.ExpectMsg(PresenceBudget);
unhandled.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
}
private sealed class Handle : IAlarmSubscriptionHandle
{
public string DiagnosticId => "stale-test";
}
private sealed class ThrowingDbFactory : IDbContextFactory
{
public OtOpcUaConfigDbContext CreateDbContext() => throw new InvalidOperationException("db unreachable");
}
}