-
Notifications
You must be signed in to change notification settings - Fork 92
Expand file tree
/
Copy pathTestCapabilityContext.cs
More file actions
114 lines (101 loc) · 4.81 KB
/
Copy pathTestCapabilityContext.cs
File metadata and controls
114 lines (101 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
namespace FlowX.Testing;
/// <summary>
/// A <see cref="CapabilityContext"/> for tests: fixed values, every one overridable.
/// </summary>
/// <remarks>
/// <para>
/// Quality goal Q2 says a capability is testable by constructing it and calling it.
/// It is — but <see cref="CapabilityContext"/> is abstract with nine members, so the
/// first thing every consumer wrote was the same thirty-line stub. Ceremony that every
/// user pays is a platform defect, not a user problem.
/// </para>
/// <para>
/// <strong>Every value is fixed, and that is the point.</strong> The clock, the
/// identifiers and the randomness a capability is allowed to reach for all come from
/// this type, so pinning them is what makes a capability test deterministic. It is the
/// same property durable replay depends on, which is why the abstraction exists at all
/// — a capability that reads <see cref="DateTimeOffset.UtcNow"/> instead is untestable
/// here and unreplayable there, for one reason.
/// </para>
/// <para>
/// Constructor parameters rather than object-initialiser syntax, because an override of
/// a get-only abstract property cannot add an <c>init</c> accessor. Named arguments give
/// the same call site: <c>new TestCapabilityContext(idempotencyKey: "key-1")</c>.
/// </para>
/// </remarks>
public sealed class TestCapabilityContext : CapabilityContext
{
private readonly ContextValues _values;
/// <summary>Creates a context. Every parameter has a usable default.</summary>
/// <param name="idempotencyKey">
/// Stable across retries and replays. Pass your own when a test asserts that a
/// capability hands it downstream — which is the behaviour that makes a retry safe.
/// </param>
/// <param name="correlationId">Correlates the operation's records.</param>
/// <param name="tenantId">Resolved from validated claims in production; here, whatever you say.</param>
/// <param name="capabilityId">Identity of the capability under test.</param>
/// <param name="compensatingFor">
/// The capability this one is undoing, when the capability under test is being run as
/// somebody else's compensation. <c>null</c> — the default — is the forward path.
/// </param>
/// <param name="flowInstanceId">Durable instance id, or <c>null</c> for an ephemeral flow.</param>
/// <param name="utcNow">The clock. Defaults to <see cref="DateTimeOffset.UnixEpoch"/>.</param>
/// <param name="budget">Time until the deadline. Defaults to one minute.</param>
/// <param name="firstId">The first value <see cref="NewId"/> returns.</param>
/// <param name="randomSeed">Seed for <see cref="Random"/>. Fixed, so draws repeat.</param>
public TestCapabilityContext(
string idempotencyKey = "test-idempotency-key",
string correlationId = "test-correlation-id",
string? tenantId = null,
string capabilityId = "test.capability",
string? compensatingFor = null,
string? flowInstanceId = null,
DateTimeOffset? utcNow = null,
TimeSpan? budget = null,
Guid? firstId = null,
int randomSeed = 0)
{
_values = new ContextValues(
idempotencyKey,
correlationId,
tenantId,
capabilityId,
compensatingFor,
flowInstanceId,
utcNow,
budget,
firstId,
randomSeed);
}
/// <inheritdoc />
public override string CorrelationId => _values.CorrelationId;
/// <inheritdoc />
public override string? FlowInstanceId => _values.FlowInstanceId;
/// <inheritdoc />
public override string CapabilityId => _values.CapabilityId;
/// <inheritdoc />
public override string? CompensatingFor => _values.CompensatingFor;
/// <inheritdoc />
public override string? TenantId => _values.TenantId;
/// <inheritdoc />
public override string IdempotencyKey => _values.IdempotencyKey;
/// <inheritdoc />
public override DateTimeOffset Deadline => _values.Deadline;
/// <inheritdoc />
/// <remarks>
/// Does not advance. A capability that needs time to move should be given a context
/// that says so, rather than one that changes underneath the assertion.
/// </remarks>
public override DateTimeOffset UtcNow => _values.UtcNow;
/// <inheritdoc />
public override Random Random => _values.Random;
/// <summary>How many identifiers this context has issued.</summary>
/// <remarks>
/// Exposed so a test can assert a capability asked for one — or, more usefully, that
/// it did not, because an id minted mid-flow is a value replay cannot reproduce.
/// </remarks>
public int IdsIssued => _values.IdsIssued;
/// <inheritdoc />
/// <remarks>Distinct on every call and reproducible across runs.</remarks>
public override Guid NewId() => _values.NewId();
}