< Summary

Information
Class: AsiBackbone.Testing.AsiBackboneTestSigningService
Assembly: AsiBackbone.Testing
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Testing/AsiBackboneTestHarness.cs
Line coverage
100%
Covered lines: 3
Uncovered lines: 0
Coverable lines: 3
Total lines: 459
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
SignAsync(...)100%11100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Testing/AsiBackboneTestHarness.cs

#LineLine coverage
 1using AsiBackbone.AspNetCore.Endpoints;
 2using AsiBackbone.Core.Audit;
 3using AsiBackbone.Core.Constraints;
 4using AsiBackbone.Core.Decisions;
 5using AsiBackbone.Core.Evaluation;
 6using AsiBackbone.Core.Outbox;
 7using AsiBackbone.Core.Signing;
 8using AsiBackbone.Storage.InMemory.Outbox;
 9using Microsoft.AspNetCore.Http;
 10using Microsoft.Extensions.DependencyInjection;
 11using Microsoft.Extensions.DependencyInjection.Extensions;
 12
 13namespace AsiBackbone.Testing;
 14
 15/// <summary>
 16/// Marker type for the AsiBackbone.Testing assembly.
 17/// </summary>
 18public sealed class AsiBackboneTestingAssemblyMarker
 19{
 20    private AsiBackboneTestingAssemblyMarker()
 21    {
 22    }
 23}
 24
 25/// <summary>
 26/// Configures deterministic services for exercising AsiBackbone-governed endpoints in tests.
 27/// </summary>
 28public sealed class AsiBackboneTestHarnessOptions
 29{
 30    private readonly Dictionary<string, GovernanceDecision> policyResults = new(StringComparer.Ordinal);
 31
 32    /// <summary>
 33    /// Gets the default policy decision used when no explicit policy result is configured.
 34    /// </summary>
 35    public GovernanceDecision DefaultPolicyDecision { get; private set; } = GovernanceDecision.Allow(policyVersion: "tes
 36
 37    /// <summary>
 38    /// Gets the default capability-grant validation decision used by the test harness.
 39    /// </summary>
 40    public GovernanceDecision DefaultCapabilityGrantDecision { get; private set; } = GovernanceDecision.Allow(policyVers
 41
 42    /// <summary>
 43    /// Gets a value indicating whether every policy marker must have an explicit configured result.
 44    /// </summary>
 45    public bool RequireExplicitPolicyResults { get; private set; }
 46
 47    /// <summary>
 48    /// Gets or sets a value indicating whether the harness registers <see cref="AsiBackboneTestAuditSink" /> as the hos
 49    /// </summary>
 50    public bool RegisterInMemoryAuditSink { get; set; } = true;
 51
 52    /// <summary>
 53    /// Gets or sets a value indicating whether the harness registers the non-durable in-memory governance outbox store.
 54    /// </summary>
 55    public bool RegisterInMemoryOutboxStore { get; set; } = true;
 56
 57    /// <summary>
 58    /// Gets or sets a value indicating whether the harness registers a deterministic no-signature signing service.
 59    /// </summary>
 60    public bool RegisterDeterministicSigningService { get; set; } = true;
 61
 62    /// <summary>
 63    /// Gets the configured deterministic policy results.
 64    /// </summary>
 65    public IReadOnlyDictionary<string, GovernanceDecision> PolicyResults => policyResults;
 66
 67    /// <summary>
 68    /// Uses an allow decision as the default policy result.
 69    /// </summary>
 70    public AsiBackboneTestHarnessOptions AllowAllPolicies()
 71    {
 72        DefaultPolicyDecision = GovernanceDecision.Allow(policyVersion: "test-harness");
 73        RequireExplicitPolicyResults = false;
 74        return this;
 75    }
 76
 77    /// <summary>
 78    /// Uses a deny decision as the default policy result.
 79    /// </summary>
 80    public AsiBackboneTestHarnessOptions DenyAllPolicies(
 81        string code = "test_harness.policy_denied",
 82        string message = "Denied by the AsiBackbone test harness.")
 83    {
 84        ArgumentException.ThrowIfNullOrWhiteSpace(code);
 85        ArgumentException.ThrowIfNullOrWhiteSpace(message);
 86
 87        DefaultPolicyDecision = GovernanceDecision.Deny(code, message, policyVersion: "test-harness");
 88        RequireExplicitPolicyResults = false;
 89        return this;
 90    }
 91
 92    /// <summary>
 93    /// Uses an allow decision as the default capability-grant validation result.
 94    /// </summary>
 95    public AsiBackboneTestHarnessOptions AllowCapabilityGrants()
 96    {
 97        DefaultCapabilityGrantDecision = GovernanceDecision.Allow(policyVersion: "test-harness");
 98        return this;
 99    }
 100
 101    /// <summary>
 102    /// Uses a deny decision as the default capability-grant validation result.
 103    /// </summary>
 104    public AsiBackboneTestHarnessOptions DenyCapabilityGrants(
 105        string code = "test_harness.capability_denied",
 106        string message = "Capability grant denied by the AsiBackbone test harness.")
 107    {
 108        ArgumentException.ThrowIfNullOrWhiteSpace(code);
 109        ArgumentException.ThrowIfNullOrWhiteSpace(message);
 110
 111        DefaultCapabilityGrantDecision = GovernanceDecision.Deny(code, message, policyVersion: "test-harness");
 112        return this;
 113    }
 114
 115    /// <summary>
 116    /// Sets a deterministic result for a policy marker type.
 117    /// </summary>
 118    public AsiBackboneTestHarnessOptions SetPolicyResult<TPolicy>(GovernanceDecision decision)
 119    {
 120        return SetPolicyResult(typeof(TPolicy), decision);
 121    }
 122
 123    /// <summary>
 124    /// Sets a deterministic result for a policy marker type.
 125    /// </summary>
 126    public AsiBackboneTestHarnessOptions SetPolicyResult(Type policyType, GovernanceDecision decision)
 127    {
 128        ArgumentNullException.ThrowIfNull(policyType);
 129        ArgumentNullException.ThrowIfNull(decision);
 130
 131        policyResults[policyType.FullName ?? policyType.Name] = decision;
 132        policyResults[policyType.Name] = decision;
 133        return this;
 134    }
 135
 136    /// <summary>
 137    /// Requires explicit deterministic policy results and configures the supplied marker type.
 138    /// </summary>
 139    public AsiBackboneTestHarnessOptions RequirePolicyResult<TPolicy>(GovernanceDecision decision)
 140    {
 141        return RequirePolicyResult(typeof(TPolicy), decision);
 142    }
 143
 144    /// <summary>
 145    /// Requires explicit deterministic policy results and configures the supplied marker type.
 146    /// </summary>
 147    public AsiBackboneTestHarnessOptions RequirePolicyResult(Type policyType, GovernanceDecision decision)
 148    {
 149        RequireExplicitPolicyResults = true;
 150        return SetPolicyResult(policyType, decision);
 151    }
 152
 153    internal bool TryGetPolicyDecision(string policyToken, out GovernanceDecision decision)
 154    {
 155        return policyResults.TryGetValue(policyToken, out decision!);
 156    }
 157
 158    internal void Validate()
 159    {
 160        ArgumentNullException.ThrowIfNull(DefaultPolicyDecision);
 161        ArgumentNullException.ThrowIfNull(DefaultCapabilityGrantDecision);
 162    }
 163}
 164
 165/// <summary>
 166/// Provides service registration helpers for the test-only AsiBackbone harness.
 167/// </summary>
 168public static class AsiBackboneTestHarnessServiceCollectionExtensions
 169{
 170    /// <summary>
 171    /// Adds the AsiBackbone test harness using default allow-all policy and capability behavior.
 172    /// </summary>
 173    public static IServiceCollection AddAsiBackboneTestHarness(this IServiceCollection services)
 174    {
 175        return services.AddAsiBackboneTestHarness(_ => { });
 176    }
 177
 178    /// <summary>
 179    /// Adds the AsiBackbone test harness using deterministic test-only substitutions.
 180    /// </summary>
 181    public static IServiceCollection AddAsiBackboneTestHarness(
 182        this IServiceCollection services,
 183        Action<AsiBackboneTestHarnessOptions> configure)
 184    {
 185        ArgumentNullException.ThrowIfNull(services);
 186        ArgumentNullException.ThrowIfNull(configure);
 187
 188        AsiBackboneTestHarnessOptions options = new();
 189        configure(options);
 190        options.Validate();
 191
 192        _ = services.AddSingleton(options);
 193        _ = services.AddSingleton<AsiBackboneTestAuditSink>();
 194        _ = services.AddSingleton<IAsiBackbonePolicyEvaluator<AsiBackboneConstraintEvaluationContext>, AsiBackboneTestHa
 195        _ = services.AddSingleton<IAsiBackboneEndpointCapabilityGrantValidator, AsiBackboneTestHarnessEndpointCapability
 196
 197        if (options.RegisterInMemoryAuditSink)
 198        {
 199            _ = services.AddSingleton<IAsiBackboneAuditSink>(static serviceProvider =>
 200                serviceProvider.GetRequiredService<AsiBackboneTestAuditSink>());
 201        }
 202
 203        if (options.RegisterInMemoryOutboxStore)
 204        {
 205            services.TryAddSingleton<InMemoryGovernanceOutboxStore>();
 206            services.TryAddSingleton<IAsiBackboneGovernanceOutboxStore>(static serviceProvider =>
 207                serviceProvider.GetRequiredService<InMemoryGovernanceOutboxStore>());
 208        }
 209
 210        if (options.RegisterDeterministicSigningService)
 211        {
 212            services.TryAddSingleton<IAsiBackboneSigningService, AsiBackboneTestSigningService>();
 213        }
 214
 215        return services;
 216    }
 217}
 218
 219/// <summary>
 220/// Deterministic policy evaluator used by the test harness.
 221/// </summary>
 222/// <remarks>
 223/// Initializes a new instance of the <see cref="AsiBackboneTestHarnessPolicyEvaluator" /> class.
 224/// </remarks>
 225public sealed class AsiBackboneTestHarnessPolicyEvaluator(AsiBackboneTestHarnessOptions options) : IAsiBackbonePolicyEva
 226{
 227    private const string PolicyTypesMetadataKey = "endpoint.policy_types";
 228    private readonly AsiBackboneTestHarnessOptions options = options ?? throw new ArgumentNullException(nameof(options))
 229
 230    /// <inheritdoc />
 231    public ValueTask<GovernanceDecision> EvaluateAsync(
 232        AsiBackboneConstraintEvaluationContext context,
 233        CancellationToken cancellationToken = default)
 234    {
 235        ArgumentNullException.ThrowIfNull(context);
 236        cancellationToken.ThrowIfCancellationRequested();
 237
 238        foreach (string policyToken in ResolvePolicyTokens(context))
 239        {
 240            if (options.TryGetPolicyDecision(policyToken, out GovernanceDecision? decision))
 241            {
 242                return ValueTask.FromResult(AsiBackboneTestHarnessDecisionFactory.WithTelemetry(decision, context));
 243            }
 244        }
 245
 246        return options.RequireExplicitPolicyResults
 247            ? ValueTask.FromResult(GovernanceDecision.Deny(
 248                "test_harness.policy_result.missing",
 249                "The AsiBackbone test harness requires an explicit policy result for this endpoint policy marker.",
 250                correlationId: context.CorrelationId,
 251                policyVersion: context.PolicyVersion,
 252                policyHash: context.PolicyHash))
 253            : ValueTask.FromResult(AsiBackboneTestHarnessDecisionFactory.WithTelemetry(options.DefaultPolicyDecision, co
 254    }
 255
 256    private static IEnumerable<string> ResolvePolicyTokens(AsiBackboneConstraintEvaluationContext context)
 257    {
 258        if (!context.Metadata.TryGetValue(PolicyTypesMetadataKey, out string? policyTypesValue)
 259            || string.IsNullOrWhiteSpace(policyTypesValue))
 260        {
 261            yield break;
 262        }
 263
 264        foreach (string policyToken in policyTypesValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOp
 265        {
 266            if (!string.IsNullOrWhiteSpace(policyToken))
 267            {
 268                yield return policyToken;
 269            }
 270        }
 271    }
 272}
 273
 274/// <summary>
 275/// Deterministic capability-grant validator used by the test harness.
 276/// </summary>
 277/// <remarks>
 278/// Initializes a new instance of the <see cref="AsiBackboneTestHarnessEndpointCapabilityGrantValidator" /> class.
 279/// </remarks>
 280public sealed class AsiBackboneTestHarnessEndpointCapabilityGrantValidator(AsiBackboneTestHarnessOptions options) : IAsi
 281{
 282    private readonly AsiBackboneTestHarnessOptions options = options ?? throw new ArgumentNullException(nameof(options))
 283
 284    /// <inheritdoc />
 285    public ValueTask<GovernanceDecision> ValidateAsync(
 286        HttpContext httpContext,
 287        AsiBackboneEndpointGovernanceDescriptor descriptor,
 288        GovernanceDecision currentDecision,
 289        CancellationToken cancellationToken = default)
 290    {
 291        ArgumentNullException.ThrowIfNull(httpContext);
 292        ArgumentNullException.ThrowIfNull(descriptor);
 293        ArgumentNullException.ThrowIfNull(currentDecision);
 294        cancellationToken.ThrowIfCancellationRequested();
 295
 296        return ValueTask.FromResult(AsiBackboneTestHarnessDecisionFactory.WithTelemetry(
 297            options.DefaultCapabilityGrantDecision,
 298            currentDecision.CorrelationId,
 299            currentDecision.TraceId,
 300            currentDecision.PolicyVersion,
 301            currentDecision.PolicyHash));
 302    }
 303}
 304
 305/// <summary>
 306/// In-memory audit sink with inspection helpers for tests.
 307/// </summary>
 308public sealed class AsiBackboneTestAuditSink : IAsiBackboneAuditSink
 309{
 310    private readonly Lock gate = new();
 311    private readonly List<IAsiBackboneAuditResidue> entries = [];
 312
 313    /// <summary>
 314    /// Gets a snapshot of audit residue captured by the test sink.
 315    /// </summary>
 316    public IReadOnlyList<IAsiBackboneAuditResidue> Entries
 317    {
 318        get
 319        {
 320            lock (gate)
 321            {
 322                return entries.ToArray();
 323            }
 324        }
 325    }
 326
 327    /// <summary>
 328    /// Clears captured audit residue.
 329    /// </summary>
 330    public void Clear()
 331    {
 332        lock (gate)
 333        {
 334            entries.Clear();
 335        }
 336    }
 337
 338    /// <inheritdoc />
 339    public ValueTask WriteAsync(
 340        IAsiBackboneAuditResidue residue,
 341        CancellationToken cancellationToken = default)
 342    {
 343        ArgumentNullException.ThrowIfNull(residue);
 344        cancellationToken.ThrowIfCancellationRequested();
 345
 346        lock (gate)
 347        {
 348            entries.Add(residue);
 349        }
 350
 351        return ValueTask.CompletedTask;
 352    }
 353}
 354
 355/// <summary>
 356/// Deterministic no-signature signing service for tests.
 357/// </summary>
 358public sealed class AsiBackboneTestSigningService : IAsiBackboneSigningService
 359{
 360    /// <inheritdoc />
 361    public ValueTask<SigningResult> SignAsync(
 362        SigningRequest request,
 363        CancellationToken cancellationToken = default)
 364    {
 1365        ArgumentNullException.ThrowIfNull(request);
 1366        cancellationToken.ThrowIfCancellationRequested();
 367
 1368        return ValueTask.FromResult(SigningResult.NoSignature);
 369    }
 370}
 371
 372internal static class AsiBackboneTestHarnessDecisionFactory
 373{
 374    internal static GovernanceDecision WithTelemetry(
 375        GovernanceDecision decision,
 376        AsiBackboneConstraintEvaluationContext context)
 377    {
 378        ArgumentNullException.ThrowIfNull(context);
 379
 380        return WithTelemetry(
 381            decision,
 382            context.CorrelationId,
 383            traceId: null,
 384            context.PolicyVersion,
 385            context.PolicyHash);
 386    }
 387
 388    internal static GovernanceDecision WithTelemetry(
 389        GovernanceDecision decision,
 390        string? correlationId,
 391        string? traceId,
 392        string? policyVersion,
 393        string? policyHash)
 394    {
 395        ArgumentNullException.ThrowIfNull(decision);
 396
 397        string? resolvedCorrelationId = decision.CorrelationId ?? correlationId;
 398        string? resolvedTraceId = decision.TraceId ?? traceId;
 399        string? resolvedPolicyVersion = decision.PolicyVersion ?? policyVersion;
 400        string? resolvedPolicyHash = decision.PolicyHash ?? policyHash;
 401
 402        return decision.Outcome switch
 403        {
 404            GovernanceDecisionOutcome.Allowed => GovernanceDecision.Allow(
 405                resolvedCorrelationId,
 406                resolvedTraceId,
 407                resolvedPolicyVersion,
 408                resolvedPolicyHash),
 409            GovernanceDecisionOutcome.Warning => GovernanceDecision.Warning(
 410                decision.Reasons,
 411                resolvedCorrelationId,
 412                resolvedTraceId,
 413                resolvedPolicyVersion,
 414                resolvedPolicyHash),
 415            GovernanceDecisionOutcome.Denied => GovernanceDecision.Deny(
 416                decision.Reasons,
 417                resolvedCorrelationId,
 418                resolvedTraceId,
 419                resolvedPolicyVersion,
 420                resolvedPolicyHash),
 421            GovernanceDecisionOutcome.Deferred => GovernanceDecision.Defer(
 422                FirstReasonCode(decision, "test_harness.deferred"),
 423                FirstReasonMessage(decision, "Deferred by the AsiBackbone test harness."),
 424                resolvedCorrelationId,
 425                resolvedTraceId,
 426                resolvedPolicyVersion,
 427                resolvedPolicyHash),
 428            GovernanceDecisionOutcome.AcknowledgmentRequired => GovernanceDecision.RequireAcknowledgment(
 429                FirstReasonCode(decision, "test_harness.acknowledgment_required"),
 430                FirstReasonMessage(decision, "Acknowledgment required by the AsiBackbone test harness."),
 431                resolvedCorrelationId,
 432                resolvedTraceId,
 433                resolvedPolicyVersion,
 434                resolvedPolicyHash),
 435            GovernanceDecisionOutcome.EscalationRecommended => GovernanceDecision.Escalate(
 436                FirstReasonCode(decision, "test_harness.escalation_recommended"),
 437                FirstReasonMessage(decision, "Escalation recommended by the AsiBackbone test harness."),
 438                resolvedCorrelationId,
 439                resolvedTraceId,
 440                resolvedPolicyVersion,
 441                resolvedPolicyHash),
 442            _ => decision
 443        };
 444    }
 445
 446    private static string FirstReasonCode(GovernanceDecision decision, string fallback)
 447    {
 448        return decision.Reasons.Count == 0
 449            ? fallback
 450            : decision.Reasons[0].Code;
 451    }
 452
 453    private static string FirstReasonMessage(GovernanceDecision decision, string fallback)
 454    {
 455        return decision.Reasons.Count == 0
 456            ? fallback
 457            : decision.Reasons[0].Message;
 458    }
 459}