Skip to content

Commit c0e0837

Browse files
committed
test(core): cover mixed sync and async policy evaluation
1 parent 359e711 commit c0e0837

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

Tests/ModularityKit.Mutator.Tests/Runtime/MutationEnginePolicyTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ public async Task ExecuteAsync_uses_sync_policy_path_without_async_override()
7676
Assert.Equal("updated", result.NewState!.Value);
7777
}
7878

79+
[Fact]
80+
public async Task ExecuteAsync_allows_sync_and_async_policies_to_coexist_without_ambiguous_ordering()
81+
{
82+
var engine = CreateEngine();
83+
var observed = new List<string>();
84+
85+
engine.RegisterPolicy(new ObservedSyncAllowPolicy(observed));
86+
engine.RegisterPolicy(new ObservedAsyncAllowPolicy(observed));
87+
88+
var result = await engine.ExecuteAsync(new SampleMutation(), new SampleState("initial"));
89+
90+
Assert.True(result.IsSuccess);
91+
Assert.Equal(["async", "sync"], observed);
92+
}
93+
7994
private static IMutationEngine CreateEngine(Action<MutationEngineOptions>? configure = null)
8095
{
8196
var services = new ServiceCollection();
@@ -113,6 +128,36 @@ public PolicyDecision Evaluate(IMutation<SampleState> mutation, SampleState stat
113128
=> PolicyDecision.Allow(Name, "Synchronous policy allowed the mutation.");
114129
}
115130

131+
private sealed class ObservedSyncAllowPolicy(List<string> observed) : IMutationPolicy<SampleState>
132+
{
133+
public string Name => "ObservedSyncAllow";
134+
public int Priority => 10;
135+
public string? Description => "Records synchronous policy evaluation order.";
136+
137+
public PolicyDecision Evaluate(IMutation<SampleState> mutation, SampleState state)
138+
{
139+
observed.Add("sync");
140+
return PolicyDecision.Allow(Name, "Synchronous policy allowed the mutation.");
141+
}
142+
}
143+
144+
private sealed class ObservedAsyncAllowPolicy(List<string> observed) : IMutationPolicy<SampleState>
145+
{
146+
public string Name => "ObservedAsyncAllow";
147+
public int Priority => 100;
148+
public string? Description => "Records asynchronous policy evaluation order.";
149+
150+
public async Task<PolicyDecision> EvaluateAsync(
151+
IMutation<SampleState> mutation,
152+
SampleState state,
153+
CancellationToken cancellationToken = default)
154+
{
155+
await Task.Delay(10, cancellationToken);
156+
observed.Add("async");
157+
return PolicyDecision.Allow(Name, "Asynchronous policy allowed the mutation.");
158+
}
159+
}
160+
116161
private sealed class AsyncBlockingPolicy : IMutationPolicy<SampleState>
117162
{
118163
public string Name => "AsyncBlocking";

src/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,20 @@ Core runtime concurrency is controlled by `MutationEngineOptions.MaxConcurrentMu
109109
- implement `Evaluate(...)` for lightweight in-process rules
110110
- implement `EvaluateAsync(..., CancellationToken)` for external identity, ticketing, quota, or compliance checks
111111
- the runtime evaluates policies in descending `Priority` order
112+
- sync and async policies can be registered together; they participate in the same priority-ordered pipeline
112113
- when `MutationEngineOptions.PolicyEvaluationTimeout` is set, the timeout is applied per policy evaluation
113114
- caller cancellation still flows through unchanged
114115
- policy failures are surfaced as `PolicyEvaluationException`
115116
- policy timeouts are surfaced as `PolicyEvaluationTimeoutException`
116117

118+
Typical integration cases include:
119+
120+
- external approval evidence checks
121+
- actor/identity resolution against IAM or directory systems
122+
- ticket status validation before execution
123+
- quota lookups in remote control planes
124+
- compliance or risk verification before commit
125+
117126
```csharp
118127
public sealed class RequireApprovedTicketPolicy : IMutationPolicy<QuotaState>
119128
{

0 commit comments

Comments
 (0)