Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79f73e04fd | |||
| 9159f6f093 |
@@ -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\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
## 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
|
Run the parity fixture matrix tests after changing the integration parity
|
||||||
scenario list:
|
scenario list:
|
||||||
|
|
||||||
@@ -102,6 +109,7 @@ dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
|
|||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Cross-Language Smoke Matrix](./CrossLanguageSmokeMatrix.md)
|
||||||
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
|
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
|
||||||
- [Gateway Process Design](./gateway-process-design.md)
|
- [Gateway Process Design](./gateway-process-design.md)
|
||||||
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
|
- [Worker Frame Protocol](./WorkerFrameProtocol.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