| | | 1 | | using AsiBackbone.Core.Constraints; |
| | | 2 | | using AsiBackbone.Core.Decisions; |
| | | 3 | | using AsiBackbone.Core.Evaluation; |
| | | 4 | | |
| | | 5 | | namespace AsiBackbone.Testing.Contracts; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Reusable contract fixture for <see cref="IAsiBackboneDecisionPolicy{TContext}" /> implementations. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <typeparam name="TContext">The framework-neutral evaluation context type.</typeparam> |
| | | 11 | | public abstract class AsiBackboneDecisionPolicyContract<TContext> |
| | | 12 | | where TContext : IAsiBackboneConstraintEvaluationContext |
| | | 13 | | { |
| | | 14 | | /// <summary> |
| | | 15 | | /// Creates the decision-policy implementation under test. |
| | | 16 | | /// </summary> |
| | | 17 | | /// <returns>The decision-policy implementation to validate.</returns> |
| | | 18 | | protected abstract IAsiBackboneDecisionPolicy<TContext> CreateDecisionPolicy(); |
| | | 19 | | |
| | | 20 | | /// <summary> |
| | | 21 | | /// Creates the context supplied to the decision-policy implementation under test. |
| | | 22 | | /// </summary> |
| | | 23 | | /// <returns>The evaluation context to validate with.</returns> |
| | | 24 | | protected abstract TContext CreateEvaluationContext(); |
| | | 25 | | |
| | | 26 | | /// <summary> |
| | | 27 | | /// Creates the composed decision supplied to the decision-policy implementation under test. |
| | | 28 | | /// </summary> |
| | | 29 | | /// <param name="context">The evaluation context created for the contract run.</param> |
| | | 30 | | /// <returns>The composed decision supplied to the decision policy.</returns> |
| | | 31 | | protected virtual GovernanceDecision CreateComposedDecision(TContext context) |
| | | 32 | | { |
| | 12 | 33 | | ArgumentNullException.ThrowIfNull(context); |
| | | 34 | | |
| | 12 | 35 | | return GovernanceDecision.Allow( |
| | 12 | 36 | | context.CorrelationId, |
| | 12 | 37 | | traceId: null, |
| | 12 | 38 | | context.PolicyVersion, |
| | 12 | 39 | | context.PolicyHash); |
| | | 40 | | } |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// Creates the constraint-result collection supplied to the decision-policy implementation under test. |
| | | 44 | | /// </summary> |
| | | 45 | | /// <returns>The constraint-result collection supplied to the decision policy.</returns> |
| | | 46 | | protected virtual IReadOnlyList<ConstraintEvaluationResult> CreateConstraintResults() |
| | | 47 | | { |
| | 10 | 48 | | return Array.Empty<ConstraintEvaluationResult>(); |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | /// <summary> |
| | | 52 | | /// Verifies that the decision policy returns a safe, non-null decision and preserves supplied decision telemetry. |
| | | 53 | | /// </summary> |
| | | 54 | | /// <param name="cancellationToken">A token that can cancel the contract validation.</param> |
| | | 55 | | /// <returns>The verified governance decision.</returns> |
| | | 56 | | public async ValueTask<GovernanceDecision> VerifyDecisionPolicyReturnsSafeDecisionAsync(CancellationToken cancellati |
| | | 57 | | { |
| | 18 | 58 | | IAsiBackboneDecisionPolicy<TContext> decisionPolicy = CreateDecisionPolicy() |
| | 18 | 59 | | ?? throw new AsiBackboneContractViolationException("Decision-policy contract must provide a decision policy |
| | 16 | 60 | | TContext context = CreateEvaluationContext() |
| | 16 | 61 | | ?? throw new AsiBackboneContractViolationException("Decision-policy contract must provide an evaluation cont |
| | 14 | 62 | | GovernanceDecision composedDecision = CreateComposedDecision(context) |
| | 14 | 63 | | ?? throw new AsiBackboneContractViolationException("Decision-policy contract must provide a composed decisio |
| | 12 | 64 | | IReadOnlyList<ConstraintEvaluationResult> constraintResults = CreateConstraintResults() |
| | 12 | 65 | | ?? throw new AsiBackboneContractViolationException("Decision-policy contract must provide a constraint-resul |
| | | 66 | | |
| | | 67 | | try |
| | | 68 | | { |
| | 10 | 69 | | GovernanceDecision decision = await decisionPolicy.ApplyAsync(context, composedDecision, constraintResults, |
| | 2 | 70 | | return AsiBackboneDecisionContract.VerifyTelemetryFromContext(decision, context, "Decision-policy decision") |
| | | 71 | | } |
| | 2 | 72 | | catch (OperationCanceledException) |
| | | 73 | | { |
| | 2 | 74 | | throw; |
| | | 75 | | } |
| | 2 | 76 | | catch (AsiBackboneContractViolationException) |
| | | 77 | | { |
| | 2 | 78 | | throw; |
| | | 79 | | } |
| | 4 | 80 | | catch (Exception exception) |
| | | 81 | | { |
| | 4 | 82 | | throw new AsiBackboneContractViolationException( |
| | 4 | 83 | | "Decision-policy implementations must return safe decisions instead of bypassing the policy pipeline by |
| | 4 | 84 | | exception); |
| | | 85 | | } |
| | 2 | 86 | | } |
| | | 87 | | } |