using System.Text.Json; using ZB.MOM.WW.MxGateway.Contracts; namespace ZB.MOM.WW.MxGateway.Tests.Contracts; public sealed class ParityFixtureMatrixTests { /// Verifies that the parity matrix declares current protocol versions and comparison fields. [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"); } /// Verifies that the parity matrix covers every public MXAccess method. [Fact] public void Matrix_CoversEveryPublicMxAccessMethod() { using JsonDocument matrix = LoadParityMatrix(); JsonElement methodFixtures = matrix.RootElement.GetProperty("methodFixtures"); Dictionary fixturesByMethod = []; HashSet 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."); } } /// Verifies that the parity matrix covers required scenario groups. [Fact] public void Matrix_CoversRequiredParityScenarioGroups() { using JsonDocument matrix = LoadParityMatrix(); HashSet knownFixtureIds = GetFixtureIds(matrix.RootElement); Dictionary 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"); } /// Verifies that the parity matrix covers every public MXAccess event family. [Fact] public void Matrix_CoversEveryPublicMxAccessEventFamily() { using JsonDocument matrix = LoadParityMatrix(); Dictionary 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 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 declared = group .GetProperty("fixtureIds") .EnumerateArray() .Select(fixtureId => fixtureId.GetString()!) .ToHashSet(StringComparer.Ordinal); foreach (string fixtureId in fixtureIds) { Assert.Contains(fixtureId, declared); } } private static HashSet GetFixtureIds(JsonElement root) { HashSet 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, "CLAUDE.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."); } }