Restore policy simulation history compatibility

This commit is contained in:
master
2026-03-10 00:42:18 +02:00
parent ac544c0064
commit 1df79ac75e
4 changed files with 1050 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Policy.Gateway.Tests;
public sealed class PolicySimulationEndpointsTests : IClassFixture<TestPolicyGatewayFactory>
{
private readonly HttpClient _client;
public PolicySimulationEndpointsTests(TestPolicyGatewayFactory factory)
{
_client = factory.CreateClient();
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task ShadowConfig_ReturnsDeterministicCompatibilityShape()
{
var response = await _client.GetAsync("/policy/shadow/config", TestContext.Current.CancellationToken);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.True(payload.TryGetProperty("enabled", out _));
Assert.Equal("policy-pack-shadow-001", payload.GetProperty("shadowPackId").GetString());
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task EnableShadowMode_UpdatesConfigAndResults()
{
var enableResponse = await _client.PostAsJsonAsync(
"/policy/shadow/enable",
new
{
enabled = true,
shadowPackId = "policy-pack-shadow-qa",
shadowVersion = 7,
trafficPercentage = 40
},
TestContext.Current.CancellationToken);
enableResponse.EnsureSuccessStatusCode();
var resultsResponse = await _client.GetAsync("/policy/shadow/results?limit=5", TestContext.Current.CancellationToken);
resultsResponse.EnsureSuccessStatusCode();
var payload = await resultsResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.True(payload.GetProperty("config").GetProperty("enabled").GetBoolean());
Assert.Equal(5, payload.GetProperty("comparisons").GetArrayLength());
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task SimulationLifecycle_CreateListGetCancel()
{
var createResponse = await _client.PostAsJsonAsync(
"/policy/simulations",
new
{
policyPackId = "policy-pack-qa",
policyVersion = 4,
includeExplain = true,
diffAgainstActive = true
},
TestContext.Current.CancellationToken);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var simulationId = created.GetProperty("simulationId").GetString();
Assert.False(string.IsNullOrWhiteSpace(simulationId));
var listResponse = await _client.GetAsync("/policy/simulations?limit=10", TestContext.Current.CancellationToken);
listResponse.EnsureSuccessStatusCode();
var list = await listResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.True(list.GetProperty("items").EnumerateArray().Any(item => item.GetProperty("simulationId").GetString() == simulationId));
var getResponse = await _client.GetAsync($"/policy/simulations/{simulationId}", TestContext.Current.CancellationToken);
getResponse.EnsureSuccessStatusCode();
var detail = await getResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.Equal("completed", detail.GetProperty("status").GetString());
var cancelResponse = await _client.PostAsJsonAsync($"/policy/simulations/{simulationId}/cancel", new { }, TestContext.Current.CancellationToken);
cancelResponse.EnsureSuccessStatusCode();
var cancelledResponse = await _client.GetAsync($"/policy/simulations/{simulationId}", TestContext.Current.CancellationToken);
cancelledResponse.EnsureSuccessStatusCode();
var cancelled = await cancelledResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.Equal("cancelled", cancelled.GetProperty("status").GetString());
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task SimulationHistory_ReturnsCompatibilityEntriesAndSupportsPinnedFilter()
{
var response = await _client.GetAsync("/policy/simulations/history?page=1&pageSize=20", TestContext.Current.CancellationToken);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var items = payload.GetProperty("items");
Assert.True(items.GetArrayLength() >= 3);
Assert.Equal("sim-001", items[0].GetProperty("simulationId").GetString());
Assert.Equal("sha256:abc123def456789", items[0].GetProperty("resultHash").GetString());
Assert.True(items[0].GetProperty("pinned").GetBoolean());
var pinnedResponse = await _client.GetAsync("/policy/simulations/history?pinnedOnly=true&page=1&pageSize=20", TestContext.Current.CancellationToken);
pinnedResponse.EnsureSuccessStatusCode();
var pinnedPayload = await pinnedResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var pinnedItems = pinnedPayload.GetProperty("items");
Assert.All(pinnedItems.EnumerateArray(), item => Assert.True(item.GetProperty("pinned").GetBoolean()));
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task SimulationCompare_ReturnsDeterministicDiffShape()
{
var response = await _client.GetAsync("/policy/simulations/compare?baseId=sim-001&compareId=sim-002", TestContext.Current.CancellationToken);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.Equal("sim-001", payload.GetProperty("baseSimulationId").GetString());
Assert.Equal("sim-002", payload.GetProperty("compareSimulationId").GetString());
Assert.False(payload.GetProperty("resultsMatch").GetBoolean());
Assert.NotEmpty(payload.GetProperty("changed").EnumerateArray());
Assert.True(payload.GetProperty("matchPercentage").GetDouble() < 100);
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public async Task SimulationVerifyAndPin_PreserveHistoryActions()
{
var verifyResponse = await _client.PostAsJsonAsync("/policy/simulations/sim-001/verify", new { }, TestContext.Current.CancellationToken);
verifyResponse.EnsureSuccessStatusCode();
var verifyPayload = await verifyResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.True(verifyPayload.GetProperty("isReproducible").GetBoolean());
Assert.Equal(
verifyPayload.GetProperty("originalHash").GetString(),
verifyPayload.GetProperty("replayHash").GetString());
using var patchRequest = new HttpRequestMessage(HttpMethod.Patch, "/policy/simulations/sim-002")
{
Content = JsonContent.Create(new { pinned = true })
};
var patchResponse = await _client.SendAsync(patchRequest, TestContext.Current.CancellationToken);
patchResponse.EnsureSuccessStatusCode();
var historyResponse = await _client.GetAsync("/policy/simulations/history?pinnedOnly=true&page=1&pageSize=20", TestContext.Current.CancellationToken);
historyResponse.EnsureSuccessStatusCode();
var historyPayload = await historyResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
Assert.Contains(
historyPayload.GetProperty("items").EnumerateArray().Select(item => item.GetProperty("simulationId").GetString()),
simulationId => string.Equals(simulationId, "sim-002", StringComparison.OrdinalIgnoreCase));
}
}