using Akka.Actor;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Mesh;
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
///
/// Pins the two mesh comm-actor paths against a really booted host.
///
///
///
/// /user/central-communication and /user/node-communication are where each
/// side's gRPC ingress expects to find its comm actor: the services resolve them by registry
/// key, and these constants are the names the actors are spawned under. Nothing else in the
/// codebase asserts they agree. Renaming either actor would compile, pass every unit test,
/// deploy — and refuse every inbound call as Unavailable ("the node is still
/// starting"), which reads as a boot-window race rather than a permanent misnaming.
///
///
/// Booting the real host is the point. A test that spawned the actors itself would pin the
/// constant against itself and prove nothing about the wiring in
/// WithOtOpcUaControlPlaneSingletons / WithOtOpcUaRuntimeActors.
///
///
public class MeshCommActorPathTests
{
private static async Task AssertActorExistsAsync(ActorSystem system, string path)
{
var identity = await system.ActorSelection(path)
.Ask(new Identify(path), TimeSpan.FromSeconds(10));
identity.Subject.ShouldNotBeNull(
$"{path} is where the central↔node boundary expects its comm actor; nothing else pins "
+ "it, and a mismatch turns every inbound call into a misleading readiness rejection");
}
[Fact]
public async Task Both_mesh_comm_actors_exist_at_their_contract_paths()
{
await using var harness = await TwoNodeClusterHarness.StartAsync();
// Harness nodes carry admin+driver, so BOTH comm actors live on each of them.
await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.CentralCommunication);
await AssertActorExistsAsync(harness.NodeASystem, MeshPaths.NodeCommunication);
await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.CentralCommunication);
await AssertActorExistsAsync(harness.NodeBSystem, MeshPaths.NodeCommunication);
}
// NOT TESTED HERE: that the comm actors are registered per node rather than as cluster
// singletons. Two candidate discriminators were tried and BOTH were invalid:
//
// * "the path resolves on both nodes" — a ClusterSingletonManager is also created on every
// node in the role, so this passes under either shape.
// * "no /singleton child exists" — measured against the KNOWN singleton /user/config-publish,
// that child is null there too, so Akka.Hosting does not lay singletons out this way and
// the assertion proves nothing.
//
// A sabotage that re-registered CentralCommunicationActor via WithSingleton left both green,
// which is what exposed them. Rather than ship an assertion I could not make fail, the property
// is left to live-gate step 8 (stop the OLDEST central; the survivor's comm actor must still
// receive a node ack, which a singleton hosted on the stopped node cannot).
//
// NOT MeshGrpcBoundaryTests: that class proves a command only arrives if the target is
// registered under its registry key, which is necessary but not discriminating — with one node
// per side, per-node and singleton registration look identical.
}