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)];
}