fix(secrets): route the unset-token harness through the real hub composition (review drift-risk)

An internal validateEagerly seam on AddZbSecretsGrpcHub replaces the harness's
hand-copied registration fragment, so the deny-all second-layer tests exercise
the production wiring and cannot drift from it.

Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
This commit is contained in:
Joseph Doherty
2026-08-07 06:52:26 -04:00
parent 3e3748b985
commit 5f81e65684
2 changed files with 36 additions and 19 deletions
@@ -216,23 +216,24 @@ internal sealed class SecretsHubTestServer : IAsyncDisposable
/// </summary>
/// <remarks>
/// <c>AddZbSecretsGrpcHub</c> refuses this configuration at registration — which is the intended
/// first line of defence and is pinned elsewhere — so the pieces are wired here the way that
/// extension wires them, minus the eager validation. That is precisely the case the interceptor's
/// deny-all exists for: a host that built the options some other way. Without this path the
/// second layer could only ever be tested against a hand-constructed interceptor, never over the
/// wire.
/// first line of defence and is pinned elsewhere — so this path goes through the REAL composition
/// via its internal <c>validateEagerly: false</c> seam, changing nothing else. That is precisely
/// the case the interceptor's deny-all exists for: a host that built the options some other way.
/// Without this path the second layer could only ever be tested against a hand-constructed
/// interceptor, never over the wire.
/// </remarks>
/// <param name="store">The store the hub would serve.</param>
/// <returns>The running hub.</returns>
public static Task<SecretsHubTestServer> StartWithNoTokenConfiguredAsync(ISecretStore store) =>
StartAsync(store, services =>
{
services.Configure<SecretsGrpcHubServerOptions>(options => options.BearerToken = string.Empty);
services.AddSingleton<SecretsHubAuthInterceptor>();
services.AddGrpc()
.AddServiceOptions<SecretsHubService>(
options => options.Interceptors.Add<SecretsHubAuthInterceptor>());
});
public static Task<SecretsHubTestServer> StartWithNoTokenConfiguredAsync(ISecretStore store)
{
IConfiguration configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>())
.Build();
return StartAsync(store, services =>
SecretsGrpcHubServerExtensions.AddZbSecretsGrpcHubCore(
services, configuration, "Secrets:GrpcHub", validateEagerly: false));
}
private static async Task<SecretsHubTestServer> StartAsync(
ISecretStore store, Action<IServiceCollection> registerHub)