Issue #35: add parity fixture matrix
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
using System.Text.Json;
|
||||
using MxGateway.Contracts;
|
||||
|
||||
namespace MxGateway.Tests.Contracts;
|
||||
|
||||
public sealed class ParityFixtureMatrixTests
|
||||
{
|
||||
[Fact]
|
||||
public void Matrix_DeclaresCurrentProtocolVersionsAndComparisonFields()
|
||||
{
|
||||
using JsonDocument matrix = LoadParityMatrix();
|
||||
JsonElement root = matrix.RootElement;
|
||||
|
||||
Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
|
||||
Assert.Equal("mxaccess-gateway-parity-fixture-matrix", root.GetProperty("fixtureSet").GetString());
|
||||
Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, root.GetProperty("gatewayProtocolVersion").GetUInt32());
|
||||
Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, root.GetProperty("workerProtocolVersion").GetUInt32());
|
||||
|
||||
JsonElement comparisonFormat = root.GetProperty("comparisonFormat");
|
||||
AssertRequiredFields(
|
||||
comparisonFormat.GetProperty("directMxAccess").GetProperty("requiredFields"),
|
||||
"method",
|
||||
"arguments",
|
||||
"returnedValue",
|
||||
"hresult",
|
||||
"statuses",
|
||||
"events");
|
||||
AssertRequiredFields(
|
||||
comparisonFormat.GetProperty("gatewayResult").GetProperty("requiredFields"),
|
||||
"kind",
|
||||
"protocolStatus",
|
||||
"returnValue",
|
||||
"hresult",
|
||||
"statuses",
|
||||
"events");
|
||||
AssertRequiredFields(
|
||||
comparisonFormat.GetProperty("eventFields"),
|
||||
"family",
|
||||
"value",
|
||||
"quality",
|
||||
"sourceTimestamp",
|
||||
"statuses",
|
||||
"workerSequence");
|
||||
AssertRequiredFields(
|
||||
comparisonFormat.GetProperty("comparisonKeys"),
|
||||
"hresult",
|
||||
"statusArrayShape",
|
||||
"statusRawFields",
|
||||
"eventFamilyOrder",
|
||||
"eventPayloadShape",
|
||||
"valueProjection",
|
||||
"rawFallbackMetadata");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Matrix_CoversEveryPublicMxAccessMethod()
|
||||
{
|
||||
using JsonDocument matrix = LoadParityMatrix();
|
||||
JsonElement methodFixtures = matrix.RootElement.GetProperty("methodFixtures");
|
||||
|
||||
Dictionary<string, JsonElement> fixturesByMethod = [];
|
||||
HashSet<string> ids = new(StringComparer.Ordinal);
|
||||
|
||||
foreach (JsonElement fixture in methodFixtures.EnumerateArray())
|
||||
{
|
||||
string id = fixture.GetProperty("id").GetString()!;
|
||||
string method = fixture.GetProperty("method").GetString()!;
|
||||
string commandKind = fixture.GetProperty("commandKind").GetString()!;
|
||||
string status = fixture.GetProperty("status").GetString()!;
|
||||
|
||||
Assert.True(ids.Add(id), $"Duplicate parity fixture id '{id}'.");
|
||||
Assert.True(fixturesByMethod.TryAdd(method, fixture), $"Duplicate parity method '{method}'.");
|
||||
Assert.StartsWith("MX_COMMAND_KIND_", commandKind, StringComparison.Ordinal);
|
||||
Assert.Contains(status, KnownFixtureStatuses);
|
||||
Assert.NotEmpty(fixture.GetProperty("assertions").EnumerateArray());
|
||||
AssertCaptureReferencesAreRelative(fixture.GetProperty("captureReferences"));
|
||||
}
|
||||
|
||||
Assert.Equal(ExpectedPublicMethods.Order(StringComparer.Ordinal), fixturesByMethod.Keys.Order(StringComparer.Ordinal));
|
||||
|
||||
foreach (string method in ExpectedPublicMethods)
|
||||
{
|
||||
JsonElement fixture = fixturesByMethod[method];
|
||||
string status = fixture.GetProperty("status").GetString()!;
|
||||
|
||||
Assert.True(
|
||||
status == "planned_fixture" || status == "documented_gap",
|
||||
$"Method '{method}' must have a planned parity fixture or documented gap.");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Matrix_CoversRequiredParityScenarioGroups()
|
||||
{
|
||||
using JsonDocument matrix = LoadParityMatrix();
|
||||
HashSet<string> knownFixtureIds = GetFixtureIds(matrix.RootElement);
|
||||
Dictionary<string, JsonElement> groupsById = [];
|
||||
|
||||
foreach (JsonElement group in matrix.RootElement.GetProperty("scenarioGroups").EnumerateArray())
|
||||
{
|
||||
string id = group.GetProperty("id").GetString()!;
|
||||
|
||||
Assert.True(groupsById.TryAdd(id, group), $"Duplicate parity scenario group '{id}'.");
|
||||
Assert.NotEmpty(group.GetProperty("description").GetString()!);
|
||||
Assert.NotEmpty(group.GetProperty("fixtureIds").EnumerateArray());
|
||||
AssertCaptureReferencesAreRelative(group.GetProperty("captureReferences"));
|
||||
|
||||
foreach (JsonElement fixtureIdElement in group.GetProperty("fixtureIds").EnumerateArray())
|
||||
{
|
||||
string fixtureId = fixtureIdElement.GetString()!;
|
||||
Assert.Contains(fixtureId, knownFixtureIds);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string requiredGroup in RequiredScenarioGroups)
|
||||
{
|
||||
Assert.True(groupsById.ContainsKey(requiredGroup), $"Missing required parity scenario group '{requiredGroup}'.");
|
||||
}
|
||||
|
||||
AssertScenarioCovers(groupsById["invalid_handles"], "method.remove-item.basic", "method.write.value-status-matrix");
|
||||
AssertScenarioCovers(groupsById["write_statuses"], "method.write.value-status-matrix", "event.on-write-complete.status");
|
||||
AssertScenarioCovers(groupsById["secured_writes"], "method.write-secured.rejection-gap", "method.write-secured2.authenticated");
|
||||
AssertScenarioCovers(groupsById["add_item_context"], "method.add-item2.context", "method.add-buffered-item.context");
|
||||
AssertScenarioCovers(groupsById["buffered_registration"], "method.add-buffered-item.context", "event.on-buffered-data-change.batch-gap");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Matrix_CoversEveryPublicMxAccessEventFamily()
|
||||
{
|
||||
using JsonDocument matrix = LoadParityMatrix();
|
||||
Dictionary<string, JsonElement> fixturesByFamily = [];
|
||||
|
||||
foreach (JsonElement fixture in matrix.RootElement.GetProperty("eventFixtures").EnumerateArray())
|
||||
{
|
||||
string family = fixture.GetProperty("family").GetString()!;
|
||||
string status = fixture.GetProperty("status").GetString()!;
|
||||
|
||||
Assert.True(fixturesByFamily.TryAdd(family, fixture), $"Duplicate parity event family '{family}'.");
|
||||
Assert.Contains(status, KnownFixtureStatuses);
|
||||
Assert.NotEmpty(fixture.GetProperty("assertions").EnumerateArray());
|
||||
AssertCaptureReferencesAreRelative(fixture.GetProperty("captureReferences"));
|
||||
}
|
||||
|
||||
foreach (string eventFamily in ExpectedEventFamilies)
|
||||
{
|
||||
Assert.True(fixturesByFamily.ContainsKey(eventFamily), $"Missing parity fixture for event family '{eventFamily}'.");
|
||||
}
|
||||
|
||||
Assert.Equal("documented_gap", fixturesByFamily["MX_EVENT_FAMILY_OPERATION_COMPLETE"].GetProperty("status").GetString());
|
||||
Assert.Equal("documented_gap", fixturesByFamily["MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE"].GetProperty("status").GetString());
|
||||
}
|
||||
|
||||
private static readonly string[] ExpectedPublicMethods =
|
||||
[
|
||||
"Register",
|
||||
"Unregister",
|
||||
"AddItem",
|
||||
"AddItem2",
|
||||
"RemoveItem",
|
||||
"Advise",
|
||||
"UnAdvise",
|
||||
"AdviseSupervisory",
|
||||
"AddBufferedItem",
|
||||
"SetBufferedUpdateInterval",
|
||||
"Suspend",
|
||||
"Activate",
|
||||
"Write",
|
||||
"Write2",
|
||||
"WriteSecured",
|
||||
"WriteSecured2",
|
||||
"AuthenticateUser",
|
||||
"ArchestrAUserToId",
|
||||
];
|
||||
|
||||
private static readonly string[] ExpectedEventFamilies =
|
||||
[
|
||||
"MX_EVENT_FAMILY_ON_DATA_CHANGE",
|
||||
"MX_EVENT_FAMILY_ON_WRITE_COMPLETE",
|
||||
"MX_EVENT_FAMILY_OPERATION_COMPLETE",
|
||||
"MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE",
|
||||
];
|
||||
|
||||
private static readonly string[] RequiredScenarioGroups =
|
||||
[
|
||||
"invalid_handles",
|
||||
"write_statuses",
|
||||
"secured_writes",
|
||||
"add_item_context",
|
||||
"buffered_registration",
|
||||
];
|
||||
|
||||
private static readonly string[] KnownFixtureStatuses =
|
||||
[
|
||||
"planned_fixture",
|
||||
"documented_gap",
|
||||
];
|
||||
|
||||
private static void AssertRequiredFields(
|
||||
JsonElement fields,
|
||||
params string[] expectedFields)
|
||||
{
|
||||
HashSet<string> declared = fields
|
||||
.EnumerateArray()
|
||||
.Select(field => field.GetString()!)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (string expectedField in expectedFields)
|
||||
{
|
||||
Assert.Contains(expectedField, declared);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertCaptureReferencesAreRelative(JsonElement captureReferences)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (JsonElement captureReference in captureReferences.EnumerateArray())
|
||||
{
|
||||
string path = captureReference.GetString()!;
|
||||
|
||||
Assert.StartsWith("captures/", path, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("\\", path, StringComparison.Ordinal);
|
||||
Assert.False(Path.IsPathRooted(path), $"Capture reference '{path}' must be relative.");
|
||||
count++;
|
||||
}
|
||||
|
||||
Assert.True(count > 0, "Each parity fixture must reference at least one MXAccess capture.");
|
||||
}
|
||||
|
||||
private static void AssertScenarioCovers(
|
||||
JsonElement group,
|
||||
params string[] fixtureIds)
|
||||
{
|
||||
HashSet<string> declared = group
|
||||
.GetProperty("fixtureIds")
|
||||
.EnumerateArray()
|
||||
.Select(fixtureId => fixtureId.GetString()!)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (string fixtureId in fixtureIds)
|
||||
{
|
||||
Assert.Contains(fixtureId, declared);
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> GetFixtureIds(JsonElement root)
|
||||
{
|
||||
HashSet<string> ids = new(StringComparer.Ordinal);
|
||||
|
||||
foreach (JsonElement fixture in root.GetProperty("methodFixtures").EnumerateArray())
|
||||
{
|
||||
ids.Add(fixture.GetProperty("id").GetString()!);
|
||||
}
|
||||
|
||||
foreach (JsonElement fixture in root.GetProperty("eventFixtures").EnumerateArray())
|
||||
{
|
||||
ids.Add(fixture.GetProperty("id").GetString()!);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static JsonDocument LoadParityMatrix()
|
||||
{
|
||||
return JsonDocument.Parse(File.ReadAllText(Path.Combine(GetParityFixtureRoot().FullName, "parity-fixture-matrix.json")));
|
||||
}
|
||||
|
||||
private static DirectoryInfo GetParityFixtureRoot()
|
||||
{
|
||||
DirectoryInfo repositoryRoot = FindRepositoryRoot();
|
||||
|
||||
return new DirectoryInfo(Path.Combine(repositoryRoot.FullName, "clients", "proto", "fixtures", "parity"));
|
||||
}
|
||||
|
||||
private static DirectoryInfo FindRepositoryRoot()
|
||||
{
|
||||
DirectoryInfo? current = new(AppContext.BaseDirectory);
|
||||
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "AGENTS.md"))
|
||||
&& Directory.Exists(Path.Combine(current.FullName, "src"))
|
||||
&& Directory.Exists(Path.Combine(current.FullName, "clients")))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate the repository root from the test output directory.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user