using Google.Protobuf.Reflection; using ZB.MOM.WW.Secrets.Replicator.Grpc.V1; namespace ZB.MOM.WW.Secrets.Replicator.Grpc.Tests; /// /// Pins the secrets_hub.v1 service surface to its two read RPCs. /// /// /// /// The narrow contract IS the enforcement. In this topology every update originates at /// central and a site node is a strict follower, so ISecretReplicationHub's third method — /// ApplyReplicatedAsync — is deliberately not mirrored onto the wire. With no write RPC /// there, a mis-wired or compromised site node has no way to push a row into central's store /// regardless of what its authorization happens to allow. That guarantee costs nothing to keep and /// is silently lost the moment a third RPC appears, which is why it is pinned here rather than left /// to the proto's comment. /// /// /// Read off the generated descriptor rather than the .proto text: the descriptor is what the server /// actually binds and the client actually dials, so a method that reached the wire some other way /// would still be caught. /// /// public sealed class SecretsHubContractPinTests { private const string PullOnlyRationale = "The secrets hub is PULL-ONLY by construction: updates originate at central only, and the " + "absence of a write RPC is what enforces it — not authorization, which a mis-wired or " + "compromised follower may hold more of than intended. Adding an RPC to this service " + "retires that guarantee. If a write path is genuinely wanted, it needs its own design " + "decision and its own review, not a passing test."; [Fact] public void The_hub_service_exposes_exactly_the_two_read_rpcs() { string[] rpcs = RpcNames(); Assert.True( rpcs is ["GetManifest", "GetSecrets"], $"secrets_hub.v1 SecretsHub must expose exactly [GetManifest, GetSecrets]; it exposes " + $"[{string.Join(", ", rpcs)}]. {PullOnlyRationale}"); } [Fact] public void No_rpc_on_the_hub_service_streams_in_either_direction() { // A streaming RPC is a channel a follower can write into for as long as it stays open. The // contract is unary today; that is a property worth pinning rather than rediscovering. foreach (MethodDescriptor method in SecretsHub.Descriptor.Methods) { Assert.False( method.IsClientStreaming || method.IsServerStreaming, $"secrets_hub.v1 SecretsHub.{method.Name} is a streaming RPC. {PullOnlyRationale}"); } } [Fact] public void The_service_keeps_its_package_qualified_identity() { // The full name is the wire address every deployed follower dials. Renaming the service or // its package is a silent break for anything already running, so it is pinned alongside the // method set. Assert.Equal("zb.mom.ww.secrets.hub.v1.SecretsHub", SecretsHub.Descriptor.FullName); } private static string[] RpcNames() => [.. SecretsHub.Descriptor.Methods.Select(method => method.Name).Order(StringComparer.Ordinal)]; }