Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ea2c4fd86 | |||
| 41a2d70f8f | |||
| 09e01de9c8 | |||
| 79f73e04fd | |||
| f2118f7028 | |||
| 9159f6f093 |
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user