< Summary

Information
Class: AsiBackbone.Testing.Contracts.GovernanceDecisionContract
Assembly: AsiBackbone.Testing
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Testing/Contracts/GovernanceDecisionContract.cs
Line coverage
100%
Covered lines: 58
Uncovered lines: 0
Coverable lines: 58
Total lines: 183
Line coverage: 100%
Branch coverage
100%
Covered branches: 50
Total branches: 50
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Testing/Contracts/GovernanceDecisionContract.cs

#LineLine coverage
 1using AsiBackbone.Core.Audit;
 2using AsiBackbone.Core.Constraints;
 3using AsiBackbone.Core.Decisions;
 4using AsiBackbone.Core.Results;
 5
 6namespace AsiBackbone.Testing.Contracts;
 7
 8/// <summary>
 9/// Provides reusable safe-collapse assertions for governance decisions and decision receipt.
 10/// </summary>
 11public static class GovernanceDecisionContract
 12{
 13    /// <summary>
 14    /// Verifies that a governance decision is present and carries the minimum shape required for safe downstream handli
 15    /// </summary>
 16    /// <param name="decision">The decision returned by an implementation under test.</param>
 17    /// <param name="contractName">The human-readable contract name used in failure messages.</param>
 18    /// <returns>The verified decision.</returns>
 19    public static GovernanceDecision VerifySafeDecision(
 20        GovernanceDecision? decision,
 21        string contractName = "Governance decision")
 22    {
 2723        if (decision is null)
 24        {
 225            throw new GovernanceContractViolationException($"{contractName} must return a decision and must never return
 26        }
 27
 2528        VerifySupportedOutcome(decision, contractName);
 29
 2430        if (RequiresReasonCodes(decision) && decision.Reasons.Count == 0)
 31        {
 532            throw new GovernanceContractViolationException($"{contractName} outcome '{decision.Outcome}' must include at
 33        }
 34
 5435        for (int index = 0; index < decision.Reasons.Count; index++)
 36        {
 1137            OperationReason reason = decision.Reasons[index] ?? throw new GovernanceContractViolationException($"{contra
 38
 1039            if (string.IsNullOrWhiteSpace(reason.Code))
 40            {
 141                throw new GovernanceContractViolationException($"{contractName} contains a reason with an empty code at 
 42            }
 43
 944            if (string.IsNullOrWhiteSpace(reason.Message))
 45            {
 146                throw new GovernanceContractViolationException($"{contractName} contains a reason with an empty message 
 47            }
 48        }
 49
 1650        return decision;
 51    }
 52
 53    /// <summary>
 54    /// Verifies decision shape, context correlation propagation, and policy telemetry presence when supplied.
 55    /// </summary>
 56    /// <typeparam name="TContext">The framework-neutral evaluation context type.</typeparam>
 57    /// <param name="decision">The decision returned by an implementation under test.</param>
 58    /// <param name="context">The context supplied to the implementation under test.</param>
 59    /// <param name="contractName">The human-readable contract name used in failure messages.</param>
 60    /// <returns>The verified decision.</returns>
 61    public static GovernanceDecision VerifyTelemetryFromContext<TContext>(
 62        GovernanceDecision? decision,
 63        TContext context,
 64        string contractName = "Governance decision")
 65        where TContext : IGovernanceEvaluationContext
 66    {
 867        ArgumentNullException.ThrowIfNull(context);
 68
 769        GovernanceDecision verifiedDecision = VerifySafeDecision(decision, contractName);
 670        VerifyCorrelationTelemetryValue(context.CorrelationId, verifiedDecision.CorrelationId, contractName);
 571        VerifyTelemetryPresence(context.PolicyVersion, verifiedDecision.PolicyVersion, "policy version", contractName);
 472        VerifyTelemetryPresence(context.PolicyHash, verifiedDecision.PolicyHash, "policy hash", contractName);
 373        return verifiedDecision;
 74    }
 75
 76    /// <summary>
 77    /// Verifies that a known invalid capability-grant scenario does not produce an allow decision.
 78    /// </summary>
 79    /// <param name="decision">The decision returned by the capability validator for a known invalid scenario.</param>
 80    /// <param name="contractName">The human-readable contract name used in failure messages.</param>
 81    /// <returns>The verified decision.</returns>
 82    public static GovernanceDecision VerifyInvalidCapabilityGrantDoesNotAllow(
 83        GovernanceDecision? decision,
 84        string contractName = "Invalid capability grant")
 85    {
 486        GovernanceDecision verifiedDecision = VerifySafeDecision(decision, contractName);
 87
 488        return verifiedDecision.IsAllowed
 489            ? throw new GovernanceContractViolationException($"{contractName} must not return Allow for an invalid or un
 490            : verifiedDecision;
 91    }
 92
 93    /// <summary>
 94    /// Verifies that decision receipt contains the minimum identity, operation, outcome, and policy telemetry shape.
 95    /// </summary>
 96    /// <param name="receipt">The decision receipt to verify.</param>
 97    /// <param name="contractName">The human-readable contract name used in failure messages.</param>
 98    /// <returns>The verified decision receipt.</returns>
 99    public static IDecisionReceipt VerifyDecisionReceipt(
 100        IDecisionReceipt? receipt,
 101        string contractName = "Decision receipt")
 102    {
 14103        if (receipt is null)
 104        {
 1105            throw new GovernanceContractViolationException($"{contractName} must not be null.");
 106        }
 107
 13108        VerifyRequiredString(receipt.EventId, "event ID", contractName);
 11109        VerifyRequiredString(receipt.ActorId, "actor ID", contractName);
 10110        VerifyRequiredString(receipt.OperationName, "operation name", contractName);
 9111        VerifyRequiredString(receipt.Outcome, "outcome", contractName);
 112
 8113        if (receipt.ReasonCodes is null)
 114        {
 1115            throw new GovernanceContractViolationException($"{contractName} reason-code collection must not be null.");
 116        }
 117
 16118        for (int index = 0; index < receipt.ReasonCodes.Count; index++)
 119        {
 2120            if (string.IsNullOrWhiteSpace(receipt.ReasonCodes[index]))
 121            {
 1122                throw new GovernanceContractViolationException($"{contractName} contains an empty reason code at index {
 123            }
 124        }
 125
 6126        return receipt.Metadata is null
 6127            ? throw new GovernanceContractViolationException($"{contractName} metadata collection must not be null.")
 6128            : receipt;
 129    }
 130
 131    private static void VerifySupportedOutcome(GovernanceDecision decision, string contractName)
 132    {
 25133        if (decision.Outcome is not GovernanceDecisionOutcome.Allowed
 25134            and not GovernanceDecisionOutcome.Warning
 25135            and not GovernanceDecisionOutcome.Denied
 25136            and not GovernanceDecisionOutcome.Deferred
 25137            and not GovernanceDecisionOutcome.AcknowledgmentRequired
 25138            and not GovernanceDecisionOutcome.EscalationRecommended)
 139        {
 1140            throw new GovernanceContractViolationException($"{contractName} returned unsupported outcome '{decision.Outc
 141        }
 24142    }
 143
 144    private static bool RequiresReasonCodes(GovernanceDecision decision)
 145    {
 24146        return decision.Outcome is GovernanceDecisionOutcome.Warning
 24147            or GovernanceDecisionOutcome.Denied
 24148            or GovernanceDecisionOutcome.Deferred
 24149            or GovernanceDecisionOutcome.AcknowledgmentRequired
 24150            or GovernanceDecisionOutcome.EscalationRecommended;
 151    }
 152
 153    private static void VerifyCorrelationTelemetryValue(
 154        string? expected,
 155        string? actual,
 156        string contractName)
 157    {
 6158        if (!string.IsNullOrWhiteSpace(expected) && !string.Equals(expected, actual, StringComparison.Ordinal))
 159        {
 1160            throw new GovernanceContractViolationException($"{contractName} must preserve the supplied correlation ID wh
 161        }
 5162    }
 163
 164    private static void VerifyTelemetryPresence(
 165        string? expected,
 166        string? actual,
 167        string telemetryName,
 168        string contractName)
 169    {
 9170        if (!string.IsNullOrWhiteSpace(expected) && string.IsNullOrWhiteSpace(actual))
 171        {
 2172            throw new GovernanceContractViolationException($"{contractName} must include a {telemetryName} when one is s
 173        }
 7174    }
 175
 176    private static void VerifyRequiredString(string? value, string name, string contractName)
 177    {
 43178        if (string.IsNullOrWhiteSpace(value))
 179        {
 5180            throw new GovernanceContractViolationException($"{contractName} must include a non-empty {name}.");
 181        }
 38182    }
 183}