Skip to content

Commit f557860

Browse files
committed
test(redis): split keyspace scenarios
1 parent ade4121 commit f557860

9 files changed

Lines changed: 215 additions & 149 deletions
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Keys;
4+
using Xunit;
5+
6+
namespace ModularityKit.Mutator.Governance.Redis.Tests.Keys;
7+
8+
public sealed partial class RedisMutationRequestKeyspaceTests
9+
{
10+
[Fact]
11+
public void Enumerate_indexes_includes_pending_indexes_only_for_pending_requests()
12+
{
13+
var keyspace = RedisMutationRequestKeyspaceTestSupport.CreateKeyspace();
14+
15+
var request = new MutationRequest
16+
{
17+
RequestId = "req-42",
18+
Scope = new MutationRequestScopeDetails
19+
{
20+
StateId = "tenant-42",
21+
StateType = "IamRoleState",
22+
MutationType = "GrantRoleMutation"
23+
},
24+
Lifecycle = new MutationRequestLifecycleDetails
25+
{
26+
Status = MutationRequestStatus.Pending,
27+
PendingReason = PendingMutationReason.Approval
28+
}
29+
};
30+
31+
var keys = keyspace.EnumerateIndexes(request).Select(key => key.ToString()).ToArray();
32+
33+
Assert.Contains("mk:gov:requests:ids", keys);
34+
Assert.Contains("mk:gov:states:tenant-42:requests", keys);
35+
Assert.Contains("mk:gov:status:pending:requests", keys);
36+
Assert.Contains("mk:gov:pending:requests", keys);
37+
Assert.Contains("mk:gov:pending:approval:requests", keys);
38+
}
39+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Keys;
4+
using Xunit;
5+
6+
namespace ModularityKit.Mutator.Governance.Redis.Tests.Keys;
7+
8+
public sealed partial class RedisMutationRequestKeyspaceTests
9+
{
10+
[Fact]
11+
public void Builds_expected_index_keys_for_state_status_and_pending_reason()
12+
{
13+
var keyspace = RedisMutationRequestKeyspaceTestSupport.CreateKeyspace();
14+
15+
Assert.Equal("mk:gov:states:tenant-42:requests", keyspace.RequestsByStateId("tenant-42").ToString());
16+
Assert.Equal("mk:gov:status:pending:requests", keyspace.RequestsByStatus(MutationRequestStatus.Pending).ToString());
17+
Assert.Equal("mk:gov:pending:requests", keyspace.PendingRequestIds().ToString());
18+
Assert.Equal(
19+
"mk:gov:pending:approval:requests",
20+
keyspace.PendingRequestIds(PendingMutationReason.Approval).ToString());
21+
}
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Governance.Redis;
4+
using ModularityKit.Mutator.Governance.Redis.Keys;
5+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Keys;
6+
using Xunit;
7+
8+
namespace ModularityKit.Mutator.Governance.Redis.Tests.Keys;
9+
10+
/// <summary>
11+
/// Verifies Redis mutation request key construction for provider scenarios.
12+
/// </summary>
13+
public sealed partial class RedisMutationRequestKeyspaceTests
14+
{
15+
/// <summary>
16+
/// Verifies request data and identity keys derived from the configured prefix.
17+
/// </summary>
18+
[Fact]
19+
public void Builds_expected_request_keys_from_prefix()
20+
{
21+
var keyspace = RedisMutationRequestKeyspaceTestSupport.CreateKeyspace();
22+
23+
Assert.Equal("mk:gov:requests:ids", keyspace.RequestIds().ToString());
24+
Assert.Equal("mk:gov:requests:req-42:data", keyspace.RequestData("req-42").ToString());
25+
Assert.Equal("mk:gov:requests:req-42:revision", keyspace.RequestRevision("req-42").ToString());
26+
}
27+
}

Tests/ModularityKit.Mutator.Governance.Redis.Tests/Keys/RedisMutationRequestKeyspaceTests.cs

Lines changed: 0 additions & 73 deletions
This file was deleted.

Tests/ModularityKit.Mutator.Governance.Redis.Tests/ModularityKit.Mutator.Governance.Redis.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
<IsPackable>false</IsPackable>
8+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
9+
<NoWarn>$(NoWarn);1591</NoWarn>
810
</PropertyGroup>
911

1012
<ItemGroup>
Lines changed: 3 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
using ModularityKit.Mutator.Abstractions.Context;
2-
using ModularityKit.Mutator.Abstractions.Effects;
31
using ModularityKit.Mutator.Abstractions.Intent;
4-
using ModularityKit.Mutator.Abstractions.Policies;
5-
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
6-
using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory;
72
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
83
using ModularityKit.Mutator.Governance.Redis.Serialization;
4+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Serialization;
5+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Serialization.Models;
96
using Xunit;
107

118
namespace ModularityKit.Mutator.Governance.Redis.Tests.Serialization;
@@ -15,71 +12,7 @@ public sealed class RedisMutationRequestSerializerTests
1512
[Fact]
1613
public void Roundtrip_preserves_request_shape_needed_by_governance_runtime()
1714
{
18-
var request = MutationRequestFactory.PendingApproval(
19-
stateId: "tenant-42:roles",
20-
stateType: "IamRoleState",
21-
mutationType: "GrantRoleMutation",
22-
intent: new MutationIntent
23-
{
24-
OperationName = "GrantRole",
25-
Category = "Security",
26-
Description = "Grant elevated access",
27-
Tags = new HashSet<string> { "security", "urgent" },
28-
EstimatedBlastRadius = BlastRadius.Module,
29-
Metadata = new Dictionary<string, object>
30-
{
31-
["risk-owner"] = "platform"
32-
}
33-
},
34-
context: MutationContext.User("requester-1", "Requester One", "Need emergency access") with
35-
{
36-
StateId = "tenant-42:roles",
37-
Metadata = new Dictionary<string, object>
38-
{
39-
["source"] = "tests"
40-
}
41-
},
42-
requirements:
43-
[
44-
new PolicyRequirement
45-
{
46-
Type = "Approval",
47-
Description = "Requires security approval",
48-
Data = new Dictionary<string, object>
49-
{
50-
["Approver"] = "security-lead",
51-
["Reason"] = "Elevated role",
52-
["StepOrder"] = 1L,
53-
["RequiredApprovals"] = 1L
54-
}
55-
}
56-
],
57-
expectedStateVersion: "v10",
58-
metadata: new Dictionary<string, object>
59-
{
60-
["team"] = "security",
61-
["priority"] = "high"
62-
})
63-
with
64-
{
65-
Lifecycle = new MutationRequestLifecycleDetails
66-
{
67-
Status = MutationRequestStatus.Pending,
68-
PendingReason = PendingMutationReason.Approval,
69-
CreatedAt = new DateTimeOffset(2026, 6, 25, 9, 0, 0, TimeSpan.Zero),
70-
UpdatedAt = new DateTimeOffset(2026, 6, 25, 9, 5, 0, TimeSpan.Zero)
71-
},
72-
SideEffects =
73-
[
74-
SideEffect.Critical(
75-
type: "WorkflowRejected",
76-
description: "Workflow rejection requires action",
77-
data: new RedisGovernanceSideEffectData
78-
{
79-
Ticket = "INC-42"
80-
})
81-
]
82-
};
15+
var request = RedisMutationRequestSerializerRequestFactory.CreateRoundtripRequest();
8316

8417
var json = RedisMutationRequestSerializer.Serialize(request);
8518
var roundtrip = RedisMutationRequestSerializer.Deserialize(json);
@@ -104,10 +37,4 @@ public void Roundtrip_preserves_request_shape_needed_by_governance_runtime()
10437
Assert.Equal(request.Lifecycle.CreatedAt, roundtrip.Lifecycle.CreatedAt);
10538
Assert.Equal(request.Lifecycle.UpdatedAt, roundtrip.Lifecycle.UpdatedAt);
10639
}
107-
108-
[SideEffectDataContract("redis.governance.side-effect", 1)]
109-
private sealed record RedisGovernanceSideEffectData
110-
{
111-
public required string Ticket { get; init; }
112-
}
11340
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using ModularityKit.Mutator.Governance.Redis.Configuration;
2+
using ModularityKit.Mutator.Governance.Redis.Keys;
3+
4+
namespace ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Keys;
5+
6+
/// <summary>
7+
/// Creates Redis mutation request keyspace fixtures for key-centric tests.
8+
/// </summary>
9+
internal static class RedisMutationRequestKeyspaceTestSupport
10+
{
11+
/// <summary>
12+
/// Creates a keyspace with the default provider prefix used by tests.
13+
/// </summary>
14+
public static RedisMutationRequestKeyspace CreateKeyspace(string keyPrefix = "mk:gov")
15+
=> new(new RedisMutationRequestStoreOptions
16+
{
17+
KeyPrefix = keyPrefix
18+
});
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using ModularityKit.Mutator.Abstractions.Effects;
2+
3+
namespace ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Serialization.Models;
4+
5+
/// <summary>
6+
/// Side-effect payload used by the serializer roundtrip fixture.
7+
/// </summary>
8+
[SideEffectDataContract("redis.governance.side-effect", 1)]
9+
internal sealed record RedisGovernanceSideEffectData
10+
{
11+
/// <summary>
12+
/// Gets the external ticket reference carried by the side effect.
13+
/// </summary>
14+
public required string Ticket { get; init; }
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using ModularityKit.Mutator.Abstractions.Context;
2+
using ModularityKit.Mutator.Abstractions.Effects;
3+
using ModularityKit.Mutator.Abstractions.Intent;
4+
using ModularityKit.Mutator.Abstractions.Policies;
5+
using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model;
6+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory;
7+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
8+
using ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Serialization.Models;
9+
10+
namespace ModularityKit.Mutator.Governance.Redis.Tests.TestSupport.Serialization;
11+
12+
/// <summary>
13+
/// Creates the governed request fixture used by serializer tests.
14+
/// </summary>
15+
internal static class RedisMutationRequestSerializerRequestFactory
16+
{
17+
/// <summary>
18+
/// Creates a request that exercises the Redis serializer roundtrip path.
19+
/// </summary>
20+
public static MutationRequest CreateRoundtripRequest()
21+
{
22+
return MutationRequestFactory.PendingApproval(
23+
stateId: "tenant-42:roles",
24+
stateType: "IamRoleState",
25+
mutationType: "GrantRoleMutation",
26+
intent: new MutationIntent
27+
{
28+
OperationName = "GrantRole",
29+
Category = "Security",
30+
Description = "Grant elevated access",
31+
Tags = new HashSet<string> { "security", "urgent" },
32+
EstimatedBlastRadius = BlastRadius.Module,
33+
Metadata = new Dictionary<string, object>
34+
{
35+
["risk-owner"] = "platform"
36+
}
37+
},
38+
context: MutationContext.User("requester-1", "Requester One", "Need emergency access") with
39+
{
40+
StateId = "tenant-42:roles",
41+
Metadata = new Dictionary<string, object>
42+
{
43+
["source"] = "tests"
44+
}
45+
},
46+
requirements:
47+
[
48+
new PolicyRequirement
49+
{
50+
Type = "Approval",
51+
Description = "Requires security approval",
52+
Data = new Dictionary<string, object>
53+
{
54+
["Approver"] = "security-lead",
55+
["Reason"] = "Elevated role",
56+
["StepOrder"] = 1L,
57+
["RequiredApprovals"] = 1L
58+
}
59+
}
60+
],
61+
expectedStateVersion: "v10",
62+
metadata: new Dictionary<string, object>
63+
{
64+
["team"] = "security",
65+
["priority"] = "high"
66+
})
67+
with
68+
{
69+
Lifecycle = new MutationRequestLifecycleDetails
70+
{
71+
Status = MutationRequestStatus.Pending,
72+
PendingReason = PendingMutationReason.Approval,
73+
CreatedAt = new DateTimeOffset(2026, 6, 25, 9, 0, 0, TimeSpan.Zero),
74+
UpdatedAt = new DateTimeOffset(2026, 6, 25, 9, 5, 0, TimeSpan.Zero)
75+
},
76+
SideEffects =
77+
[
78+
SideEffect.Critical(
79+
type: "WorkflowRejected",
80+
description: "Workflow rejection requires action",
81+
data: new RedisGovernanceSideEffectData
82+
{
83+
Ticket = "INC-42"
84+
})
85+
]
86+
};
87+
}
88+
}

0 commit comments

Comments
 (0)