test(tst-24): drive the .NET and Python clients against real in-process gRPC servers
TST-24 asked for per-client wire tests against a fake gateway. An audit first corrected the finding's premise: Go, Rust, and Java already had them — bufconn, a loopback tonic server, and InProcessServerBuilder respectively — each already asserting the round trip, the server-observed bearer header, and the ReplayGap sentinel. The two genuine gaps were .NET (every test substituted the transport interface; the test project had no server package at all) and Python (stub monkeypatching everywhere but one opt-in TLS test). Both now serve mxaccess_gateway.v1.MxAccessGateway over a real transport — Kestrel h2c and grpc.aio, each on an ephemeral loopback port — and drive the ordinary public client API against it. Only the gateway's behaviour is canned; the framing, serialization, metadata, and status codes are genuine. Four shapes each: full round trip with every reply field asserted, the authorization header as received by the server (including on the streaming RPC), the ReplayGap sentinel surfaced as the client's typed signal, and a real PERMISSION_DENIED mapping to the typed authorization error. The .NET client was only ever compiled in CI, never tested, so the portable job gains a dotnet test step. Fixes a bug the new tests caught on their first run: Python's connect() built the grpc.aio channel inside asyncio.to_thread, and a grpc.aio channel binds to the event loop current on the constructing thread, so every non-stub connection raised 'There is no current event loop in thread'. No mock-based test could see it, and the test guarding the off-loop behaviour patched create_channel and so asserted the bug. Split resolve_channel_security (blocking TOFU probe, off-loop) from create_channel (on-loop); the guard tests now assert both halves.
This commit is contained in:
@@ -47,6 +47,19 @@ The tests import the generated gateway and worker stubs, run fake async gateway
|
||||
stubs, verify API key metadata, exercise stream cancellation, load shared value
|
||||
and command fixtures, and check deterministic CLI output.
|
||||
|
||||
`tests/test_wire_fake_gateway.py` is the one suite that does **not** substitute a
|
||||
stub: it serves a canned `MxAccessGatewayServicer` from a real `grpc.aio` server
|
||||
on an ephemeral loopback port and drives the ordinary `GatewayClient` API against
|
||||
it. Only the gateway's behaviour is canned — the HTTP/2 framing, protobuf
|
||||
serialization, `authorization` metadata, and gRPC status codes are genuine, so it
|
||||
catches decode and metadata breaks a stub fake cannot see. No MXAccess, no worker,
|
||||
no TLS, so it runs in the default suite. See `docs/GatewayTesting.md`
|
||||
(Client Wire Tests) for the cross-client pattern.
|
||||
|
||||
```powershell
|
||||
python -m pytest tests/test_wire_fake_gateway.py
|
||||
```
|
||||
|
||||
## Packaging
|
||||
|
||||
Install the package in editable mode for local development:
|
||||
@@ -398,6 +411,15 @@ point: the `require_certificate_validation=True` keyword on
|
||||
`--require-certificate-validation` CLI flag. See
|
||||
[Gateway Configuration](../../docs/GatewayConfiguration.md#automatic-self-signed-certificate).
|
||||
|
||||
Channel construction is split in two: `resolve_channel_security(options)` performs
|
||||
the blocking part (the trust-on-first-use certificate probe) and
|
||||
`create_channel(options, security=...)` builds the channel. The async `connect`
|
||||
classmethods run the first off the event loop and the second on it, because a
|
||||
`grpc.aio` channel binds to the event loop current on the constructing thread —
|
||||
building it inside `asyncio.to_thread` raises
|
||||
`RuntimeError: There is no current event loop in thread 'asyncio_N'`. Callers that
|
||||
build their own channel should keep `create_channel` on the loop thread.
|
||||
|
||||
## CLI
|
||||
|
||||
The CLI emits deterministic JSON for automation:
|
||||
|
||||
Reference in New Issue
Block a user