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:
@@ -277,6 +277,49 @@ $env:MxGateway__Ldap__ServiceAccountPassword = "<service-account-password>"
|
||||
dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~DashboardLdapLiveTests
|
||||
```
|
||||
|
||||
## Client Wire Tests
|
||||
|
||||
Each client's own suite drives the client's public API against a **fake gateway
|
||||
served over a real gRPC transport** — an in-process or loopback server
|
||||
implementing `mxaccess_gateway.v1.MxAccessGateway`. Only the gateway's *behaviour*
|
||||
is canned; the HTTP/2 framing, protobuf serialization, call metadata, and gRPC
|
||||
status codes are genuine. That is the difference from the per-client mocks: a mock
|
||||
substituted for the generated stub (or, in .NET, for `IMxGatewayClientTransport`)
|
||||
proves what the client *intends* to send, never what a server *receives*, so a
|
||||
field the client fails to decode or a header it never actually attaches passes
|
||||
every mock-based test. These tests need no MXAccess, no worker, and no network
|
||||
beyond loopback, so they run in the default suite on every host.
|
||||
|
||||
The shared shape each client's wire test covers:
|
||||
|
||||
- **Round trip** — `OpenSession` → `Invoke` (a `Register`, asserting the decoded
|
||||
`RegisterReply.server_handle`) → `StreamEvents` (asserting the decoded
|
||||
`OnDataChange` fields) → `CloseSession`.
|
||||
- **Auth on the wire** — the `authorization: Bearer <key>` header is asserted as
|
||||
*observed by the server*, on the streaming RPC as well as the unary ones.
|
||||
- **Replay-gap sentinel** — a stream resumed with `after_worker_sequence` opens
|
||||
with the gateway's `replay_gap` sentinel, and the client surfaces it as its
|
||||
typed, non-terminal replay-gap signal rather than a normal event.
|
||||
- **Status mapping** — a real `PERMISSION_DENIED` from the server becomes the
|
||||
client's typed authorization error, not a bare transport exception.
|
||||
|
||||
Per-client harness and command:
|
||||
|
||||
| Client | Harness | Command |
|
||||
|---|---|---|
|
||||
| .NET | `WireFakeGatewayServer` (Kestrel h2c on `127.0.0.1:0`, `MxAccessGatewayBase`) — `MxGatewayClientWireTests` | `dotnet test clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/ZB.MOM.WW.MxGateway.Client.Tests.csproj` |
|
||||
| Python | `FakeGateway` + `serve_gateway` fixture (`grpc.aio` server on `127.0.0.1:0`) — `tests/test_wire_fake_gateway.py` | `python -m pytest` from `clients/python` |
|
||||
| Go | `fakeGatewayServer` + `newBufconnClient` (`grpc.NewServer` over `bufconn`) — `mxgateway/client_session_test.go` | `go test ./...` from `clients/go` |
|
||||
| Rust | `spawn_fake_gateway` (tonic `Server` over a loopback `TcpListener`) — `tests/client_behavior.rs` | `cargo test --workspace` from `clients/rust` |
|
||||
| Java | `TestGatewayService` + `InProcessGateway` (`InProcessServerBuilder`) — `MxGatewayClientSessionTests`; plus `InProcessGatewayHarness` for the CLI tests | `gradle test` from `clients/java` |
|
||||
|
||||
All five run in CI: Go, Rust, Python, and the .NET client tests in the `portable`
|
||||
job, Java in the `java` job.
|
||||
|
||||
Adding an RPC to `mxaccess_gateway.proto` does not automatically extend these —
|
||||
the fake gateways implement only the four session RPCs. Extend the fake in the
|
||||
client whose behaviour changed rather than adding a parallel harness.
|
||||
|
||||
## Client E2E Scripts
|
||||
|
||||
`scripts/discover-testmachine-tags.ps1` queries the ZB Galaxy Repository for the
|
||||
|
||||
Reference in New Issue
Block a user