diff --git a/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs b/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs index 7eb99edc..e56b7c2e 100644 --- a/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs +++ b/tests/GameCult.Mesh.Quic.Native.Tests/CultMeshNativeQuicRealtimeTransportTests.cs @@ -13,11 +13,14 @@ namespace GameCult.Mesh.Quic.Native.Tests; public sealed class CultMeshNativeQuicRealtimeTransportTests { [Test] - public async Task NativeConnectorReceivesFromExternalManagedProvider() + public async Task NativeConnectorReceivesFromExternalProvider() { + // The provider on the other end is no longer necessarily the C# + // managed one (Cut 5 adds a TypeScript provider on this same lane); + // the env var name stays, since it names the endpoint, not the peer. var endpoint = Environment.GetEnvironmentVariable("CULTMESH_NATIVE_EXTERNAL_ENDPOINT"); if (string.IsNullOrWhiteSpace(endpoint)) - Assert.Ignore("Set CULTMESH_NATIVE_EXTERNAL_ENDPOINT to exercise cross-process native/managed parity."); + Assert.Ignore("Set CULTMESH_NATIVE_EXTERNAL_ENDPOINT to exercise cross-process native/external-provider parity."); var connector = new CultMeshNativeQuicRealtimeTransportConnector(); using var client = await connector.ConnectAsync( diff --git a/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs b/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs index ef739411..1ff6fd20 100644 --- a/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs +++ b/tests/GameCult.Mesh.Quic.Tests/CultMeshQuicRealtimeTransportTests.cs @@ -159,6 +159,49 @@ public sealed class CultMeshQuicRealtimeTransportTests session.State.Status.Should().Be(CultMeshSessionStatus.Offline); } + /// + /// Cut 5's cross-process lane: an external provider (normally the + /// TypeScript `cultmesh-quic-peer.ts serve` peer started by + /// `cultnet-interop.test.ts`) is dialed by the plain managed connector, + /// using only the endpoint's own `cert-sha256` pin — no + /// — + /// exactly as an ordinary consumer would. Prints the received frame's + /// re-encoding as hex so the harness can compare it against the same + /// frame's TypeScript-side encodeRealtimeFrame output (golden + /// bytes across processes, closing "TypeScript encodings decode in C#" + /// by process, not by shared file). + /// + [Test] + public async Task ManagedConnectorReceivesFromExternalProvider() + { + var endpoint = Environment.GetEnvironmentVariable("CULTMESH_NATIVE_EXTERNAL_ENDPOINT"); + if (string.IsNullOrWhiteSpace(endpoint)) + Assert.Ignore("Set CULTMESH_NATIVE_EXTERNAL_ENDPOINT to exercise cross-process managed/external-provider parity."); + + var connector = new CultMeshQuicRealtimeTransportConnector(); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var transport = await connector.ConnectAsync( + new CultMeshTransportCandidate(endpoint!), + new CultMeshSessionTarget("interop", "interop.ts-provider"), + timeout.Token); + try + { + var frame = await transport.ReceiveAsync(timeout.Token); + + transport.TransportId.Should().Be("msquic-realtime"); + frame.SchemaId.Should().NotBeNullOrWhiteSpace(); + frame.BodyId.Should().NotBeNullOrWhiteSpace(); + frame.Payload.Should().NotBeEmpty(); + + var hex = Convert.ToHexString(CultMeshRealtimeWireProtocol.EncodeFrame(frame)); + TestContext.Out.WriteLine($"CULTMESH_GOLDEN_HEX={hex}"); + } + finally + { + transport.Dispose(); + } + } + private static CultMeshSessionManager CreateSessions( CultMeshQuicRealtimeServer server, out IdentityObservation validatedIdentity)