4d2dfb5b50
MeshGrpcBoundaryTests replaces the MeshClusterClientBoundaryTests deleted in step 1. Three
self-seeded single-member clusters sharing the ActorSystem name, joined ONLY by gRPC, driving
the REAL CentralCommunicationActor -> MeshGrpcCommandDialer -> Kestrel h2c ->
MeshCommandAuthInterceptor -> NodeCommandGrpcService -> NodeCommunicationActor -> EventStream:
1. No_cross_mesh_Akka_association_forms_between_central_and_either_site — the program's own
point, kept as a test rather than a live-gate note: each cluster stays single-member and
none ever holds another's address as a member.
2. One_dispatch_through_central_reaches_a_node_in_every_cluster_mesh — the fan, with each
site holding its OWN per-cluster PSK, so a dial that selected the wrong cluster's key
would be refused at the far side.
3. Falsifiability_control_a_dial_carrying_the_wrong_cluster_key_delivers_nothing — without
it, (2) cannot distinguish "the gRPC boundary carried it" from "something else in the
process did". The same node then accepts the right key, so the silence is the auth gate
and not a dead endpoint.
All three run fully offline — no Docker fixture — and pass.
Comment sweep across every remaining clusterclient|receptionist hit in src/ and tests/:
the program label becomes "mesh gRPC command plane", and every comment that described the
deleted transport as live now describes what actually runs (Dps = pair-local default,
Grpc = cross-mesh). mesh_transport.proto's header/service comments are reworded; no message,
field or enum is touched — the contract is locked. MeshCommActorPathTests is reshaped rather
than retired: the two paths still exist and still matter, but what a mismatch costs is now a
misleading Unavailable from the gRPC ingress rather than a silently dropped receptionist send.
The only surviving mentions of the old names are the ones that must survive: the fail-closed
rules that name "ClusterClient" as a retired value refused at boot, their tests, and this
test class's own account of what it replaces.
50 lines
2.3 KiB
C#
50 lines
2.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Protos;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Protos.MeshTransport.V1;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Commons.Tests;
|
|
|
|
/// <summary>
|
|
/// Contract-lock for the mesh command envelope's oneof. If a fifth command is added
|
|
/// to <c>mesh_transport.proto</c> without adding a matching entry to
|
|
/// <see cref="MeshTransportProtoContract.HandledCases"/>, this test goes red — the single guard
|
|
/// that keeps the wire contract and the handled-case set from silently drifting apart. A drift
|
|
/// here is exactly the failure the whole transport exists to avoid: a command that crosses the
|
|
/// mesh boundary, decodes to a case nobody handles, and is dropped without a trace.
|
|
/// </summary>
|
|
public class MeshTransportProtoContractTests
|
|
{
|
|
[Fact]
|
|
public void EveryOneofVariant_IsAccountedFor()
|
|
{
|
|
var variants = System.Enum.GetValues<CommandEnvelope.CommandOneofCase>()
|
|
.Where(c => c != CommandEnvelope.CommandOneofCase.None).ToArray();
|
|
|
|
variants.ShouldBe(MeshTransportProtoContract.HandledCases, ignoreOrder: true);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_service_base_and_client_types_generate()
|
|
{
|
|
// Referencing these types is the whole assertion — GrpcServices="Both" must emit both halves
|
|
// of both services: central hosts CentralCommandService and dials NodeCommandService, and a
|
|
// driver node does the mirror image, so neither service can ship client-only or server-only.
|
|
typeof(NodeCommandService.NodeCommandServiceBase).ShouldNotBeNull();
|
|
typeof(NodeCommandService.NodeCommandServiceClient).ShouldNotBeNull();
|
|
typeof(CentralCommandService.CentralCommandServiceBase).ShouldNotBeNull();
|
|
typeof(CentralCommandService.CentralCommandServiceClient).ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void The_apply_outcome_enum_reserves_zero_as_unspecified()
|
|
{
|
|
// proto3 decodes an absent enum field as 0. If 0 meant "Applied", a message that lost the
|
|
// field in transit — or was written by a peer that predates it — would report a deployment
|
|
// as successfully applied when nothing was said at all. 0 must stay meaningless.
|
|
((int)ApplyOutcome.Unspecified).ShouldBe(0);
|
|
((int)ApplyOutcome.Applied).ShouldNotBe(0);
|
|
((int)ApplyOutcome.Failed).ShouldNotBe(0);
|
|
}
|
|
}
|