Compare commits

...

6 Commits

Author SHA1 Message Date
Joseph Doherty 4ea2c4fd86 Issue #50: clarify packaging API key placeholders 2026-04-26 21:26:28 -04:00
Joseph Doherty 41a2d70f8f Merge remote-tracking branch 'origin/main' into agent-2/issue-50-client-packaging-documentation 2026-04-26 21:23:06 -04:00
dohertj2 09e01de9c8 Merge pull request #101 from agent-1/issue-49-cross-language-smoke-test-matrix
Issue #49: add cross-language smoke test matrix
2026-04-26 21:24:29 -04:00
Joseph Doherty 79f73e04fd Issue #49: add cross-language smoke matrix 2026-04-26 21:21:49 -04:00
Joseph Doherty f2118f7028 Issue #50: document client packaging 2026-04-26 21:20:43 -04:00
dohertj2 9159f6f093 Merge pull request #99 from agent-1/issue-48-implement-java-client-session-values-errors-and-cli
Issue #48: implement Java client session values errors and CLI
2026-04-26 21:04:24 -04:00
12 changed files with 1138 additions and 1 deletions
+47
View File
@@ -23,6 +23,29 @@ dotnet build clients/dotnet/MxGateway.Client.sln
dotnet test clients/dotnet/MxGateway.Client.sln --no-build
```
## Packaging
Create local library and CLI artifacts from the repository root:
```powershell
$dotnetPackageOutput = Join-Path (Get-Location) 'artifacts/clients/dotnet'
dotnet pack clients/dotnet/MxGateway.Client/MxGateway.Client.csproj -c Release -p:PackageOutputPath="$dotnetPackageOutput"
dotnet publish clients/dotnet/MxGateway.Client.Cli/MxGateway.Client.Cli.csproj -c Release -o artifacts/clients/dotnet/mxgw-dotnet
```
The library package references the shared contracts project at build time. The
published CLI runs from `artifacts/clients/dotnet/mxgw-dotnet`.
## Regenerating Protobuf Bindings
The .NET client uses the generated C# types from
`src/MxGateway.Contracts/Generated`. Regenerate those files through the
contracts project:
```powershell
dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj
```
## Client Usage
`MxGatewayClient` opens a gRPC channel to the gateway and attaches the API key
@@ -109,3 +132,27 @@ dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint htt
optionally writes a value when `--type` and `--value` are supplied, reads a
bounded event stream, and closes the session in a `finally` block. CLI error
output redacts API keys supplied through `--api-key`.
Use TLS options for a secured gateway:
```powershell
dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint https://mxgateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item Area001.Pump001.Speed --json
```
## Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'http://localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'Area001.Pump001.Speed'
dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint $env:MXGATEWAY_ENDPOINT --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
```
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [.NET Client Detailed Design](../../docs/clients-dotnet-csharp-design.md)
+42
View File
@@ -44,6 +44,24 @@ The tests parse the shared JSON fixtures, exercise value and status conversion,
use `bufconn` for fake gateway auth and streaming behavior, and cover CLI JSON
redaction.
## Packaging
Build a local CLI executable from `clients/go`:
```powershell
New-Item -ItemType Directory -Force ../../artifacts/clients/go | Out-Null
go build -o ../../artifacts/clients/go/mxgw-go.exe ./cmd/mxgw-go
```
Install the CLI into the active `GOBIN` or `GOPATH/bin`:
```powershell
go install ./cmd/mxgw-go
```
Other Go modules can consume the library package with the module path
`gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway`.
## Client API
Use `mxgateway.Dial` with `mxgateway.Options` to configure plaintext or TLS
@@ -81,3 +99,27 @@ go run ./cmd/mxgw-go smoke -item Area001.Tag.Value -plaintext -json
Use `-api-key-env MXGATEWAY_API_KEY` or `-api-key <key>` when authentication is
enabled. CLI output redacts the key value and never writes the raw secret.
Use TLS options for a secured gateway:
```powershell
go run ./cmd/mxgw-go smoke -endpoint mxgateway.example.local:5001 -ca-cert C:\certs\mxgateway-ca.pem -server-name-override mxgateway.example.local -api-key-env MXGATEWAY_API_KEY -item Area001.Tag.Value -json
```
## Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'Area001.Tag.Value'
go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
```
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Go Client Detailed Design](../../docs/clients-golang-design.md)
+39
View File
@@ -27,6 +27,15 @@ generated stubs, and generated protobuf messages for parity tests.
application entry point. The CLI supports version, session, command, event
streaming, write, and smoke-test commands with deterministic JSON output.
## Regenerating Protobuf Bindings
Run generation from `clients/java` after the shared `.proto` files or Java
output path changes:
```powershell
gradle :mxgateway-client:generateProto
```
## Client Usage
Create a client with explicit transport and auth options:
@@ -77,6 +86,12 @@ The CLI accepts `--api-key`, `--api-key-env`, `--plaintext`, `--ca-file`,
`--server-name-override`, `--timeout`, and `--json` on gateway commands. JSON
output redacts API keys.
Use TLS options for a secured gateway:
```powershell
gradle :mxgateway-cli:run --args="smoke --endpoint mxgateway.example.local:5001 --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item TestObject.TestInt --json"
```
## Build And Test
Run the Java checks from `clients/java`:
@@ -89,8 +104,32 @@ The build uses the Java 21 Gradle toolchain, compiles generated protobuf/gRPC
code, and runs JUnit 5 tests for the client wrapper, shared behavior fixtures,
in-process gRPC behavior, stream cancellation, and CLI parser/output behavior.
## Packaging
Create local library and CLI artifacts from `clients/java`:
```powershell
gradle :mxgateway-client:jar :mxgateway-cli:installDist
```
The library jar is under `mxgateway-client/build/libs`. The installed CLI
distribution is under `mxgateway-cli/build/install/mxgateway-cli`.
## Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
gradle :mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
```
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Java Client Detailed Design](../../docs/clients-java-design.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
@@ -0,0 +1,280 @@
{
"schemaVersion": 1,
"fixtureSet": "mxaccess-gateway-cross-language-smoke-matrix",
"description": "Documented command matrix for opt-in cross-language client smoke runs against a live gateway.",
"integrationGate": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"defaultInputs": {
"endpointVariable": "MXGATEWAY_ENDPOINT",
"endpointFallback": "localhost:5000",
"apiKeyVariable": "MXGATEWAY_API_KEY",
"itemVariable": "MXGATEWAY_TEST_ITEM",
"itemFallback": "TestChildObject.TestInt",
"eventLimit": 1,
"optionalWriteValueVariable": "MXGATEWAY_TEST_WRITE_VALUE",
"optionalWriteType": "int32"
},
"requiredOperations": [
"open-session",
"register",
"add-item",
"advise",
"stream-events",
"close-session"
],
"optionalOperations": [
"write"
],
"jsonComparison": {
"requiredOutputMode": "json",
"commonFields": [
"language",
"operation",
"sessionId",
"serverHandle",
"itemHandle",
"events",
"closeStatus"
],
"comparisonFields": [
"sessionId",
"serverHandle",
"itemHandle",
"eventCount",
"eventFamily",
"workerSequence",
"protocolStatus",
"hresult",
"statuses"
]
},
"failureOutput": {
"requiredContextFields": [
"language",
"endpoint",
"authContext"
],
"authContext": {
"sourceVariable": "MXGATEWAY_API_KEY",
"redactedValue": "<redacted>",
"forbiddenLiterals": [
"mxgw_visible_secret",
"Bearer mxgw_visible_secret"
]
}
},
"clients": [
{
"language": "dotnet",
"displayName": ".NET",
"workingDirectory": ".",
"integrationSkip": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"failureContextFields": [
"language",
"endpoint",
"authContext"
],
"commands": [
{
"operation": "open-session",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- open-session --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --client-name mxgw-dotnet-smoke --json"
},
{
"operation": "register",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- register --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --client-name mxgw-dotnet-smoke --json"
},
{
"operation": "add-item",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- add-item --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item TestChildObject.TestInt --json"
},
{
"operation": "advise",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- advise --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --json"
},
{
"operation": "stream-events",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- stream-events --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --max-events 1 --json"
},
{
"operation": "close-session",
"command": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- close-session --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --json"
}
],
"optionalWriteCommand": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- write --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --type int32 --value <write-value> --json",
"bundledSmokeCommand": "dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt --json"
},
{
"language": "go",
"displayName": "Go",
"workingDirectory": "clients/go",
"integrationSkip": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"failureContextFields": [
"language",
"endpoint",
"authContext"
],
"commands": [
{
"operation": "open-session",
"command": "go run ./cmd/mxgw-go open-session -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -client-session-name mxgw-go-smoke -plaintext -json"
},
{
"operation": "register",
"command": "go run ./cmd/mxgw-go register -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -client-name mxgw-go-smoke -plaintext -json"
},
{
"operation": "add-item",
"command": "go run ./cmd/mxgw-go add-item -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -server-handle <server-handle> -item TestChildObject.TestInt -plaintext -json"
},
{
"operation": "advise",
"command": "go run ./cmd/mxgw-go advise -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -server-handle <server-handle> -item-handle <item-handle> -plaintext -json"
},
{
"operation": "stream-events",
"command": "go run ./cmd/mxgw-go stream-events -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -limit 1 -plaintext -json"
},
{
"operation": "close-session",
"command": "go run ./cmd/mxgw-go close-session -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -plaintext -json"
}
],
"optionalWriteCommand": "go run ./cmd/mxgw-go write -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -session-id <session-id> -server-handle <server-handle> -item-handle <item-handle> -type int32 -value <write-value> -plaintext -json",
"bundledSmokeCommand": "go run ./cmd/mxgw-go smoke -endpoint localhost:5000 -api-key-env MXGATEWAY_API_KEY -item TestChildObject.TestInt -plaintext -json"
},
{
"language": "rust",
"displayName": "Rust",
"workingDirectory": "clients/rust",
"integrationSkip": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"failureContextFields": [
"language",
"endpoint",
"authContext"
],
"commands": [
{
"operation": "open-session",
"command": "cargo run -p mxgw-cli -- open-session --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --client-name mxgw-rust-smoke --json"
},
{
"operation": "register",
"command": "cargo run -p mxgw-cli -- register --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --client-name mxgw-rust-smoke --json"
},
{
"operation": "add-item",
"command": "cargo run -p mxgw-cli -- add-item --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item TestChildObject.TestInt --json"
},
{
"operation": "advise",
"command": "cargo run -p mxgw-cli -- advise --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --json"
},
{
"operation": "stream-events",
"command": "cargo run -p mxgw-cli -- stream-events --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --max-events 1 --json"
},
{
"operation": "close-session",
"command": "cargo run -p mxgw-cli -- close-session --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --json"
}
],
"optionalWriteCommand": "cargo run -p mxgw-cli -- write --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --value-type int32 --value <write-value> --json",
"bundledSmokeCommand": "cargo run -p mxgw-cli -- smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt --json"
},
{
"language": "python",
"displayName": "Python",
"workingDirectory": "clients/python",
"integrationSkip": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"failureContextFields": [
"language",
"endpoint",
"authContext"
],
"commands": [
{
"operation": "open-session",
"command": "mxgw-py open-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --client-name mxgw-py-smoke --json"
},
{
"operation": "register",
"command": "mxgw-py register --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --client-name mxgw-py-smoke --json"
},
{
"operation": "add-item",
"command": "mxgw-py add-item --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item TestChildObject.TestInt --json"
},
{
"operation": "advise",
"command": "mxgw-py advise --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --json"
},
{
"operation": "stream-events",
"command": "mxgw-py stream-events --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --max-events 1 --json"
},
{
"operation": "close-session",
"command": "mxgw-py close-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --json"
}
],
"optionalWriteCommand": "mxgw-py write --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --type int32 --value <write-value> --json",
"bundledSmokeCommand": "mxgw-py smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt --max-events 1 --json"
},
{
"language": "java",
"displayName": "Java",
"workingDirectory": "clients/java",
"integrationSkip": {
"variable": "MXGATEWAY_INTEGRATION",
"requiredValue": "1"
},
"failureContextFields": [
"language",
"endpoint",
"authContext"
],
"commands": [
{
"operation": "open-session",
"command": "gradle :mxgateway-cli:run --args=\"open-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --client-session-name mxgw-java-smoke --json\""
},
{
"operation": "register",
"command": "gradle :mxgateway-cli:run --args=\"register --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --client-name mxgw-java-smoke --json\""
},
{
"operation": "add-item",
"command": "gradle :mxgateway-cli:run --args=\"add-item --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item TestChildObject.TestInt --json\""
},
{
"operation": "advise",
"command": "gradle :mxgateway-cli:run --args=\"advise --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --json\""
},
{
"operation": "stream-events",
"command": "gradle :mxgateway-cli:run --args=\"stream-events --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --limit 1 --json\""
},
{
"operation": "close-session",
"command": "gradle :mxgateway-cli:run --args=\"close-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --json\""
}
],
"optionalWriteCommand": "gradle :mxgateway-cli:run --args=\"write --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <session-id> --server-handle <server-handle> --item-handle <item-handle> --type int32 --value <write-value> --json\"",
"bundledSmokeCommand": "gradle :mxgateway-cli:run --args=\"smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt --json\""
}
]
}
+47 -1
View File
@@ -47,6 +47,28 @@ 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.
## Packaging
Install the package in editable mode for local development:
```powershell
python -m pip install -e ".[dev]"
```
Build a wheel from `clients/python`:
```powershell
python -m pip wheel . --no-deps --wheel-dir "$env:TEMP\mxgateway-python-wheel"
```
Install the generated wheel into a target environment:
```powershell
python -m pip install <wheel-path>
```
The wheel exposes the `mxgw-py` console script.
## Library Usage
The library is async-first:
@@ -56,7 +78,7 @@ from mxgateway import GatewayClient
async with await GatewayClient.connect(
endpoint="localhost:5000",
api_key="mxgw_example",
api_key="<gateway-api-key>",
plaintext=True,
) as client:
session = await client.open_session(client_session_name="python-client")
@@ -105,3 +127,27 @@ mxgw-py write --session-id <id> --server-handle 1 --item-handle 2 --type int32 -
Use `--api-key` or `--api-key-env MXGATEWAY_API_KEY` to attach API key
metadata. `smoke` opens a session, registers, adds an item, advises, streams a
bounded event count, and closes the session in a `finally` block.
Use TLS options for a secured gateway:
```powershell
mxgw-py smoke --endpoint mxgateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item Object.Attribute --json
```
## Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'Object.Attribute'
mxgw-py smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
```
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Python Client Detailed Design](../../docs/clients-python-design.md)
+29
View File
@@ -38,6 +38,18 @@ cargo clippy --workspace --all-targets -- -D warnings
The build script uses `protoc` from `PATH` or the Windows path recorded in
`../../docs/toolchain-links.md`.
## Packaging
Create local release artifacts from `clients/rust`:
```powershell
cargo build --workspace --release
cargo install --path crates/mxgw-cli --locked --force
```
`cargo check --workspace` regenerates the `tonic` and `prost` modules into
Cargo build output through `build.rs`.
## CLI
The CLI exposes version, session, command, event stream, write, and smoke
@@ -58,6 +70,10 @@ CLI reads the API key from `--api-key` or from `--api-key-env`, which defaults
to `MXGATEWAY_API_KEY`. API keys are redacted by the library option and secret
types.
```powershell
cargo run -p mxgw-cli -- smoke --endpoint https://mxgateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt --json
```
## Library Surface
`ClientOptions` configures endpoint, API key, plaintext or TLS transport,
@@ -83,8 +99,21 @@ preserving the raw message for parity diagnostics. Command replies whose
protocol status is not `PROTOCOL_STATUS_CODE_OK` become `Error::Command` and
retain the raw `MxCommandReply`.
## Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'http://127.0.0.1:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'TestChildObject.TestInt'
cargo run -p mxgw-cli -- smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
```
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Rust Client Detailed Design](../../docs/clients-rust-design.md)
- [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md)
+260
View File
@@ -0,0 +1,260 @@
# Client Packaging
This document defines the clean-checkout commands for building, packaging, and
running the official MXAccess Gateway clients. Use the tool paths and versions
in [Toolchain Links](./toolchain-links.md) when a command is missing from
`PATH`.
## Shared Inputs
All clients generate bindings from the shared protobuf files under
`src/MxGateway.Contracts/Protos`. Regenerate the published client descriptor
after changing either `.proto` file or `clients/proto/proto-inputs.json`:
```powershell
scripts/publish-client-proto-inputs.ps1
scripts/publish-client-proto-inputs.ps1 -Check
```
Generated protobuf and gRPC files are generator output. Do not edit them by
hand.
## Environment
The examples use these common variables:
```powershell
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
```
Use plaintext only for a local gateway. Use TLS when the gateway crosses a
machine boundary or uses a production certificate.
## .NET
The .NET client uses .NET 10 and references
`src/MxGateway.Contracts/MxGateway.Contracts.csproj` for generated C# contract
types. `clients/dotnet/generated` remains reserved for client-local generator
output if the client later decouples from the contracts project.
Regenerate the generated C# contract types:
```powershell
dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj
```
Build and test from the repository root:
```powershell
dotnet build clients/dotnet/MxGateway.Client.sln
dotnet test clients/dotnet/MxGateway.Client.sln --no-build
```
Create local package artifacts:
```powershell
$dotnetPackageOutput = Join-Path (Get-Location) 'artifacts/clients/dotnet'
dotnet pack clients/dotnet/MxGateway.Client/MxGateway.Client.csproj -c Release -p:PackageOutputPath="$dotnetPackageOutput"
dotnet publish clients/dotnet/MxGateway.Client.Cli/MxGateway.Client.Cli.csproj -c Release -o artifacts/clients/dotnet/mxgw-dotnet
```
Run the CLI from source:
```powershell
dotnet run --project clients/dotnet/MxGateway.Client.Cli -- version --json
dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint "http://$env:MXGATEWAY_ENDPOINT" --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint "https://mxgateway.example.local:5001" --tls --ca-file C:\certs\mxgateway-ca.pem --server-name mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
```
## Go
The Go client is the module
`gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go`.
Generated Go files live under `clients/go/internal/generated`.
Regenerate the Go bindings:
```powershell
Push-Location clients/go
./generate-proto.ps1
Pop-Location
```
Build and test from `clients/go`:
```powershell
Push-Location clients/go
go test ./...
go build ./...
go vet ./...
Pop-Location
```
Create a local CLI executable:
```powershell
Push-Location clients/go
New-Item -ItemType Directory -Force ../../artifacts/clients/go | Out-Null
go build -o ../../artifacts/clients/go/mxgw-go.exe ./cmd/mxgw-go
Pop-Location
```
Run the CLI from source:
```powershell
Push-Location clients/go
go run ./cmd/mxgw-go version -json
go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
go run ./cmd/mxgw-go smoke -endpoint mxgateway.example.local:5001 -ca-cert C:\certs\mxgateway-ca.pem -server-name-override mxgateway.example.local -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
Pop-Location
```
## Rust
The Rust workspace builds the `mxgateway-client` library crate and the `mxgw`
CLI crate. `build.rs` generates `tonic` and `prost` modules into Cargo build
output on each build that needs updated protobuf output.
Regenerate and compile Rust bindings:
```powershell
Push-Location clients/rust
cargo check --workspace
Pop-Location
```
Build and test from `clients/rust`:
```powershell
Push-Location clients/rust
cargo fmt --all --check
cargo test --workspace
cargo check --workspace
Pop-Location
```
Create local release artifacts:
```powershell
Push-Location clients/rust
cargo build --workspace --release
cargo install --path crates/mxgw-cli --locked --force
Pop-Location
```
Run the CLI from source:
```powershell
Push-Location clients/rust
cargo run -p mxgw-cli -- version --json
cargo run -p mxgw-cli -- smoke --endpoint "http://127.0.0.1:5000" --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
cargo run -p mxgw-cli -- smoke --endpoint "https://mxgateway.example.local:5001" --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
Pop-Location
```
## Python
The Python package is `mxaccess-gateway-client`. Generated modules live under
`clients/python/src/mxgateway/generated`.
Regenerate the Python bindings:
```powershell
Push-Location clients/python
./generate-proto.ps1
Pop-Location
```
Install, test, and build a wheel from `clients/python`:
```powershell
Push-Location clients/python
python -m pip install -e ".[dev]"
python -m pytest
python -m pip wheel . --no-deps --wheel-dir "$env:TEMP\mxgateway-python-wheel"
Pop-Location
```
Run the CLI from the editable install or with `python -m`:
```powershell
Push-Location clients/python
mxgw-py version --json
mxgw-py smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
mxgw-py smoke --endpoint mxgateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
python -m mxgateway_cli version --json
Pop-Location
```
## Java
The Java workspace uses Gradle, Java 21, `mxgateway-client`, and
`mxgateway-cli`. The Gradle protobuf plugin writes generated Java protobuf and
gRPC sources under `clients/java/src/main/generated`.
Regenerate Java bindings:
```powershell
Push-Location clients/java
gradle :mxgateway-client:generateProto
Pop-Location
```
Build and test from `clients/java`:
```powershell
Push-Location clients/java
gradle test
Pop-Location
```
Create local library and CLI artifacts:
```powershell
Push-Location clients/java
gradle :mxgateway-client:jar :mxgateway-cli:installDist
Pop-Location
```
Run the CLI through Gradle:
```powershell
Push-Location clients/java
gradle :mxgateway-cli:run --args="version --json"
gradle :mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
gradle :mxgateway-cli:run --args="smoke --endpoint mxgateway.example.local:5001 --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
Pop-Location
```
## Integration Tests
Client integration checks are opt-in because they need a live gateway and a
gateway host that can create MXAccess worker sessions. Set the common
environment before running a client smoke:
```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
$env:MXGATEWAY_TEST_CONTEXT = ''
$env:MXGATEWAY_TEST_WRITE_VALUE = '123'
```
Run the bounded `smoke` command for each client against the same item. The
smoke commands open a session, register a client name, add one item, advise it,
and close the session. The .NET and Python smoke commands also read a bounded
event stream; the Go, Rust, and Java smoke commands exercise the command path
and can be paired with their `stream-events` commands after a session is open.
Client-side cancellation or timeout stops waiting for the gateway response. It
does not abort an MXAccess COM call that is already executing on the worker STA.
## Related Documentation
- [Client Proto Generation](./client-proto-generation.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Toolchain Links](./toolchain-links.md)
+98
View File
@@ -0,0 +1,98 @@
# Cross-Language Smoke Matrix
The cross-language smoke matrix defines the documented commands used to compare
official clients against the same live gateway flow. It is a repository
validation fixture and command reference; normal unit tests validate the matrix
shape without connecting to a gateway.
The matrix lives in
`clients/proto/fixtures/smoke/cross-language-smoke-matrix.json`.
## Scope
The matrix covers the supported client languages:
- .NET
- Go
- Rust
- Python
- Java
Each client entry defines commands for the same required operation sequence:
1. `open-session`
2. `register`
3. `add-item`
4. `advise`
5. `stream-events`
6. `close-session`
The optional `write` command is documented separately because writing changes
provider state and should only run when the operator supplies a safe test value.
## Integration Gate
Cross-language smoke execution is opt-in. Runners should skip the matrix unless
this variable is set:
```powershell
$env:MXGATEWAY_INTEGRATION = "1"
```
The shared inputs are:
| Variable | Default | Purpose |
|----------|---------|---------|
| `MXGATEWAY_ENDPOINT` | `localhost:5000` | Gateway endpoint used by client CLIs. |
| `MXGATEWAY_API_KEY` | Empty | API key source for authenticated gateway deployments. |
| `MXGATEWAY_TEST_ITEM` | `TestChildObject.TestInt` | MXAccess item used by `add-item`. |
| `MXGATEWAY_TEST_WRITE_VALUE` | Empty | Enables the optional write step when set by a runner. |
The commands in the matrix use `MXGATEWAY_API_KEY` through each CLI's
`api-key-env` flag. They must not embed bearer tokens or raw API keys.
## JSON Comparison
Every command in the matrix requests JSON output. A runner can compare the
normalized smoke record across languages with these fields:
- language,
- operation,
- session id,
- server handle,
- item handle,
- event count,
- event family,
- worker sequence,
- protocol status,
- HRESULT,
- status arrays,
- close status.
Failure output must include the client language, endpoint, and redacted auth
context. Auth context identifies the source, such as `MXGATEWAY_API_KEY`, but
does not include the secret value.
## Bundled Smoke Commands
Each client also exposes a bundled `smoke` command. Those commands are useful
for quick local checks, but the full cross-language matrix uses explicit
operation commands because not every bundled smoke command streams events yet.
The explicit sequence remains the parity baseline for issue-level validation.
## Validation
Run the matrix shape tests after changing the smoke matrix:
```bash
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~CrossLanguageSmokeMatrixTests
```
Live execution remains a separate opt-in step because it depends on a running
gateway, the installed MXAccess worker path, and provider state.
## Related Documentation
- [Gateway Testing](./GatewayTesting.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Proto Generation](./client-proto-generation.md)
+8
View File
@@ -76,6 +76,13 @@ stdout/stderr lines emitted during the run.
## Focused Commands
Run the cross-language smoke matrix tests after changing the documented client
smoke command list:
```bash
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~CrossLanguageSmokeMatrixTests
```
Run the parity fixture matrix tests after changing the integration parity
scenario list:
@@ -102,6 +109,7 @@ dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
## Related Documentation
- [Cross-Language Smoke Matrix](./CrossLanguageSmokeMatrix.md)
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
- [Gateway Process Design](./gateway-process-design.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
+1
View File
@@ -30,6 +30,7 @@ Shared generation inputs:
- `docs/client-proto-generation.md`
- `docs/ClientBehaviorFixtures.md`
- `docs/ClientPackaging.md`
- `clients/proto/proto-inputs.json`
Language style guides:
+11
View File
@@ -86,6 +86,16 @@ All generators use `src/MxGateway.Contracts/Protos` as the protobuf import
root. The checked-in descriptor is available when a language build prefers a
descriptor input, but the `.proto` files remain canonical.
Use these commands to regenerate language-specific client bindings:
| Client | Command |
|--------|---------|
| .NET | `dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj` |
| Go | `Push-Location clients/go; ./generate-proto.ps1; Pop-Location` |
| Rust | `Push-Location clients/rust; cargo check --workspace; Pop-Location` |
| Python | `Push-Location clients/python; ./generate-proto.ps1; Pop-Location` |
| Java | `Push-Location clients/java; gradle :mxgateway-client:generateProto; Pop-Location` |
.NET generation currently runs through the contracts project:
```powershell
@@ -186,6 +196,7 @@ scripts/validate-client-behavior-fixtures.ps1
- [Protobuf Contracts](./Contracts.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Packaging](./ClientPackaging.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Client Libraries Implementation Plan](./implementation-plan-clients.md)
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
@@ -0,0 +1,276 @@
using System.Text.Json;
namespace MxGateway.Tests.Contracts;
public sealed class CrossLanguageSmokeMatrixTests
{
[Fact]
public void Matrix_DeclaresIntegrationGateAndComparisonShape()
{
using JsonDocument matrix = LoadSmokeMatrix();
JsonElement root = matrix.RootElement;
Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
Assert.Equal("mxaccess-gateway-cross-language-smoke-matrix", root.GetProperty("fixtureSet").GetString());
JsonElement integrationGate = root.GetProperty("integrationGate");
Assert.Equal("MXGATEWAY_INTEGRATION", integrationGate.GetProperty("variable").GetString());
Assert.Equal("1", integrationGate.GetProperty("requiredValue").GetString());
JsonElement defaultInputs = root.GetProperty("defaultInputs");
Assert.Equal("MXGATEWAY_ENDPOINT", defaultInputs.GetProperty("endpointVariable").GetString());
Assert.Equal("localhost:5000", defaultInputs.GetProperty("endpointFallback").GetString());
Assert.Equal("MXGATEWAY_API_KEY", defaultInputs.GetProperty("apiKeyVariable").GetString());
Assert.Equal("MXGATEWAY_TEST_ITEM", defaultInputs.GetProperty("itemVariable").GetString());
AssertRequiredFields(
root.GetProperty("jsonComparison").GetProperty("commonFields"),
"language",
"operation",
"sessionId",
"serverHandle",
"itemHandle",
"events",
"closeStatus");
AssertRequiredFields(
root.GetProperty("failureOutput").GetProperty("requiredContextFields"),
"language",
"endpoint",
"authContext");
JsonElement authContext = root.GetProperty("failureOutput").GetProperty("authContext");
Assert.Equal("MXGATEWAY_API_KEY", authContext.GetProperty("sourceVariable").GetString());
Assert.Equal("<redacted>", authContext.GetProperty("redactedValue").GetString());
AssertForbiddenLiterals(authContext.GetProperty("forbiddenLiterals"));
}
[Fact]
public void Matrix_CoversEverySupportedClientWithEquivalentSmokeSteps()
{
using JsonDocument matrix = LoadSmokeMatrix();
JsonElement root = matrix.RootElement;
string[] requiredOperations = GetStrings(root.GetProperty("requiredOperations"));
Dictionary<string, JsonElement> clientsByLanguage = [];
foreach (JsonElement client in root.GetProperty("clients").EnumerateArray())
{
string language = client.GetProperty("language").GetString()!;
Assert.True(clientsByLanguage.TryAdd(language, client), $"Duplicate smoke client '{language}'.");
Assert.Contains(language, ExpectedLanguages);
AssertClientWorkDirectoryExists(client);
AssertIntegrationSkip(client);
AssertRequiredFields(client.GetProperty("failureContextFields"), "language", "endpoint", "authContext");
AssertSmokeCommands(client, requiredOperations);
AssertOptionalWriteCommand(client.GetProperty("optionalWriteCommand").GetString()!);
AssertCommandUsesJsonAndAuthEnv(client.GetProperty("bundledSmokeCommand").GetString()!);
Assert.Contains("TestChildObject.TestInt", client.GetProperty("bundledSmokeCommand").GetString()!, StringComparison.Ordinal);
}
Assert.Equal(ExpectedLanguages.OrderBy(language => language, StringComparer.Ordinal), clientsByLanguage.Keys.OrderBy(language => language, StringComparer.Ordinal));
}
[Fact]
public void Matrix_KeepsLiveSmokeOptInAndSecretsOutOfCommands()
{
using JsonDocument matrix = LoadSmokeMatrix();
JsonElement root = matrix.RootElement;
string[] forbiddenLiterals = GetStrings(root.GetProperty("failureOutput").GetProperty("authContext").GetProperty("forbiddenLiterals"));
foreach (JsonElement client in root.GetProperty("clients").EnumerateArray())
{
string language = client.GetProperty("language").GetString()!;
AssertIntegrationSkip(client);
foreach (JsonElement commandStep in client.GetProperty("commands").EnumerateArray())
{
AssertNoForbiddenLiterals(language, commandStep.GetProperty("command").GetString()!, forbiddenLiterals);
}
AssertNoForbiddenLiterals(language, client.GetProperty("optionalWriteCommand").GetString()!, forbiddenLiterals);
AssertNoForbiddenLiterals(language, client.GetProperty("bundledSmokeCommand").GetString()!, forbiddenLiterals);
}
}
private static readonly string[] ExpectedLanguages =
[
"dotnet",
"go",
"rust",
"python",
"java",
];
private static void AssertSmokeCommands(
JsonElement client,
string[] requiredOperations)
{
Dictionary<string, JsonElement> commandsByOperation = [];
foreach (JsonElement commandStep in client.GetProperty("commands").EnumerateArray())
{
string operation = commandStep.GetProperty("operation").GetString()!;
string command = commandStep.GetProperty("command").GetString()!;
Assert.True(commandsByOperation.TryAdd(operation, commandStep), $"Duplicate smoke operation '{operation}'.");
AssertCommandUsesJsonAndAuthEnv(command);
Assert.Contains("localhost:5000", command, StringComparison.Ordinal);
AssertOperationPlaceholders(operation, command);
}
Assert.Equal(requiredOperations.OrderBy(operation => operation, StringComparer.Ordinal), commandsByOperation.Keys.OrderBy(operation => operation, StringComparer.Ordinal));
}
private static void AssertOperationPlaceholders(
string operation,
string command)
{
switch (operation)
{
case "open-session":
Assert.Contains("smoke", command, StringComparison.Ordinal);
break;
case "register":
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
Assert.Contains("smoke", command, StringComparison.Ordinal);
break;
case "add-item":
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
Assert.Contains("<server-handle>", command, StringComparison.Ordinal);
Assert.Contains("TestChildObject.TestInt", command, StringComparison.Ordinal);
break;
case "advise":
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
Assert.Contains("<server-handle>", command, StringComparison.Ordinal);
Assert.Contains("<item-handle>", command, StringComparison.Ordinal);
break;
case "stream-events":
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
Assert.True(
command.Contains("--max-events 1", StringComparison.Ordinal)
|| command.Contains("-limit 1", StringComparison.Ordinal)
|| command.Contains("--limit 1", StringComparison.Ordinal),
$"Stream command '{command}' must bound event reads.");
break;
case "close-session":
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
break;
default:
throw new InvalidOperationException($"Unexpected smoke operation '{operation}'.");
}
}
private static void AssertOptionalWriteCommand(string command)
{
AssertCommandUsesJsonAndAuthEnv(command);
Assert.Contains("<session-id>", command, StringComparison.Ordinal);
Assert.Contains("<server-handle>", command, StringComparison.Ordinal);
Assert.Contains("<item-handle>", command, StringComparison.Ordinal);
Assert.Contains("<write-value>", command, StringComparison.Ordinal);
Assert.Contains("int32", command, StringComparison.Ordinal);
}
private static void AssertCommandUsesJsonAndAuthEnv(string command)
{
Assert.True(
command.Contains("--json", StringComparison.Ordinal) || command.Contains("-json", StringComparison.Ordinal),
$"Command '{command}' must request JSON output.");
Assert.Contains("MXGATEWAY_API_KEY", command, StringComparison.Ordinal);
Assert.Contains("api-key-env", command, StringComparison.Ordinal);
}
private static void AssertIntegrationSkip(JsonElement client)
{
JsonElement integrationSkip = client.GetProperty("integrationSkip");
Assert.Equal("MXGATEWAY_INTEGRATION", integrationSkip.GetProperty("variable").GetString());
Assert.Equal("1", integrationSkip.GetProperty("requiredValue").GetString());
}
private static void AssertClientWorkDirectoryExists(JsonElement client)
{
string workingDirectory = client.GetProperty("workingDirectory").GetString()!;
DirectoryInfo repositoryRoot = FindRepositoryRoot();
string fullPath = Path.GetFullPath(Path.Combine(repositoryRoot.FullName, workingDirectory));
Assert.True(Directory.Exists(fullPath), $"Smoke client working directory '{workingDirectory}' must exist.");
Assert.StartsWith(repositoryRoot.FullName, fullPath, StringComparison.OrdinalIgnoreCase);
}
private static void AssertRequiredFields(
JsonElement fields,
params string[] expectedFields)
{
HashSet<string> declared = GetStrings(fields).ToHashSet(StringComparer.Ordinal);
foreach (string expectedField in expectedFields)
{
Assert.Contains(expectedField, declared);
}
}
private static void AssertForbiddenLiterals(JsonElement forbiddenLiterals)
{
string[] values = GetStrings(forbiddenLiterals);
Assert.Contains("mxgw_visible_secret", values);
Assert.Contains("Bearer mxgw_visible_secret", values);
}
private static void AssertNoForbiddenLiterals(
string language,
string command,
string[] forbiddenLiterals)
{
foreach (string forbiddenLiteral in forbiddenLiterals)
{
Assert.DoesNotContain(forbiddenLiteral, command, StringComparison.Ordinal);
}
Assert.DoesNotContain(" --api-key ", command, StringComparison.Ordinal);
Assert.DoesNotContain(" -api-key ", command, StringComparison.Ordinal);
Assert.Contains("api-key-env", command, StringComparison.Ordinal);
Assert.Contains("MXGATEWAY_API_KEY", command, StringComparison.Ordinal);
Assert.False(command.Contains("Bearer ", StringComparison.Ordinal), $"Smoke command for '{language}' must not include bearer tokens.");
}
private static string[] GetStrings(JsonElement array)
{
return array
.EnumerateArray()
.Select(element => element.GetString()!)
.ToArray();
}
private static JsonDocument LoadSmokeMatrix()
{
return JsonDocument.Parse(File.ReadAllText(Path.Combine(GetSmokeFixtureRoot().FullName, "cross-language-smoke-matrix.json")));
}
private static DirectoryInfo GetSmokeFixtureRoot()
{
DirectoryInfo repositoryRoot = FindRepositoryRoot();
return new DirectoryInfo(Path.Combine(repositoryRoot.FullName, "clients", "proto", "fixtures", "smoke"));
}
private static DirectoryInfo FindRepositoryRoot()
{
DirectoryInfo? current = new(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "AGENTS.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.");
}
}