Rename and add the C# sides of Cut 5's external-provider lane

NativeConnectorReceivesFromExternalManagedProvider -> NativeConnectorReceivesFromExternalProvider:
CULTMESH_NATIVE_EXTERNAL_ENDPOINT's peer is no longer necessarily the C#
managed provider now that Cut 5 adds a TypeScript one on the same lane; the
env var name stays, since it names the endpoint, not the peer.

Add ManagedConnectorReceivesFromExternalProvider, gated by the same env var:
dials an external provider with the plain managed connector and no
ValidateProviderCertificate (so acceptance runs on the endpoint's own
cert-sha256 pin alone, like an ordinary consumer), and prints the received
frame's CultMeshRealtimeWireProtocol re-encoding as hex for the harness's
golden-bytes comparison against the TypeScript peer's own encoding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-23 15:06:15 +02:00
commit 962162e7c6
2 changed files with 48 additions and 2 deletions

View file

@ -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(

View file

@ -159,6 +159,49 @@ public sealed class CultMeshQuicRealtimeTransportTests
session.State.Status.Should().Be(CultMeshSessionStatus.Offline);
}
/// <summary>
/// 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
/// <see cref="CultMeshQuicRealtimeConnectorOptions.ValidateProviderCertificate"/> —
/// 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 <c>encodeRealtimeFrame</c> output (golden
/// bytes across processes, closing "TypeScript encodings decode in C#"
/// by process, not by shared file).
/// </summary>
[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)