Add write, parity, auth, and parallel coverage to client e2e matrix
Close the notable gaps in scripts/run-client-e2e-tests.ps1: - Write round-trip: write a per-client sentinel value to a configurable writable attribute, then assert it is echoed back through the event stream. Extends the Rust mxgw-cli stream-events output with full per-event JSON (itemHandle + protojson-shaped value) so all five language clients run an identical value compare. - Parity: assert an invalid item handle and an unknown session id are rejected rather than silently succeeding. - Auth rejection: assert open-session is rejected with a missing API key and, when -RejectScopeApiKeyEnv is supplied, with an insufficient-scope key. - Parallel: -Parallel runs each language client as an isolated child process and merges their JSON reports. Update docs/GatewayTesting.md for the new phases and flags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,12 +17,12 @@ use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use futures_util::StreamExt;
|
||||
use mxgateway_client::generated::galaxy_repository::v1::DeployEvent;
|
||||
use mxgateway_client::generated::mxaccess_gateway::v1::{
|
||||
CloseSessionRequest, MxCommand, MxCommandKind, MxCommandRequest, OpenSessionRequest,
|
||||
PingCommand, StreamEventsRequest,
|
||||
CloseSessionRequest, MxCommand, MxCommandKind, MxCommandRequest, MxEvent,
|
||||
MxValue as ProtoMxValue, OpenSessionRequest, PingCommand, StreamEventsRequest,
|
||||
};
|
||||
use mxgateway_client::{
|
||||
ApiKey, ClientOptions, Error, GalaxyClient, GatewayClient, MxValue, CLIENT_VERSION,
|
||||
GATEWAY_PROTOCOL_VERSION, WORKER_PROTOCOL_VERSION,
|
||||
ApiKey, ClientOptions, Error, GalaxyClient, GatewayClient, MxValue, MxValueProjection,
|
||||
CLIENT_VERSION, GATEWAY_PROTOCOL_VERSION, WORKER_PROTOCOL_VERSION,
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
@@ -451,7 +451,7 @@ async fn run(cli: Cli) -> Result<(), Error> {
|
||||
after_worker_sequence,
|
||||
})
|
||||
.await?;
|
||||
let mut events = Vec::new();
|
||||
let mut events: Vec<Value> = Vec::new();
|
||||
let mut event_count = 0usize;
|
||||
while event_count < max_events {
|
||||
let Some(event) = stream.next().await else {
|
||||
@@ -460,21 +460,17 @@ async fn run(cli: Cli) -> Result<(), Error> {
|
||||
let event = event?;
|
||||
event_count += 1;
|
||||
if jsonl {
|
||||
println!(
|
||||
"{}",
|
||||
json!({
|
||||
"workerSequence": event.worker_sequence,
|
||||
"family": event.family,
|
||||
})
|
||||
);
|
||||
println!("{}", event_to_json(&event));
|
||||
} else if json {
|
||||
events.push(event);
|
||||
events.push(event_to_json(&event));
|
||||
} else {
|
||||
println!("{} {}", event.worker_sequence, event.family);
|
||||
}
|
||||
}
|
||||
if json {
|
||||
println!("{}", json!({ "eventCount": event_count }));
|
||||
// `eventCount` is preserved for back-compat; `events` carries
|
||||
// the per-event detail the cross-language e2e matrix compares.
|
||||
println!("{}", json!({ "eventCount": event_count, "events": events }));
|
||||
}
|
||||
}
|
||||
Command::Write {
|
||||
@@ -841,6 +837,44 @@ fn print_deploy_event(event: &DeployEvent, use_json: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a streamed [`MxEvent`] as a JSON object. The scalar value is
|
||||
/// projected into protojson-style `*Value` keys so the cross-language e2e
|
||||
/// matrix can extract and compare event values uniformly across all five
|
||||
/// client CLIs.
|
||||
fn event_to_json(event: &MxEvent) -> Value {
|
||||
json!({
|
||||
"family": event.family,
|
||||
"sessionId": event.session_id,
|
||||
"serverHandle": event.server_handle,
|
||||
"itemHandle": event.item_handle,
|
||||
"quality": event.quality,
|
||||
"workerSequence": event.worker_sequence,
|
||||
"value": event.value.as_ref().map(event_value_to_json),
|
||||
})
|
||||
}
|
||||
|
||||
/// Project an [`MxValue`] into a protojson-shaped JSON object whose single
|
||||
/// key names the scalar kind (`int32Value`, `stringValue`, ...), matching
|
||||
/// the protobuf-JSON the .NET/Go/Java CLIs emit.
|
||||
fn event_value_to_json(value: &ProtoMxValue) -> Value {
|
||||
match MxValue::from_proto(value.clone()).projection() {
|
||||
MxValueProjection::Bool(inner) => json!({ "boolValue": inner }),
|
||||
MxValueProjection::Int32(inner) => json!({ "int32Value": inner }),
|
||||
// protojson renders 64-bit integers as strings; mirror that here.
|
||||
MxValueProjection::Int64(inner) => json!({ "int64Value": inner.to_string() }),
|
||||
MxValueProjection::Float(inner) => json!({ "floatValue": inner }),
|
||||
MxValueProjection::Double(inner) => json!({ "doubleValue": inner }),
|
||||
MxValueProjection::String(inner) => json!({ "stringValue": inner }),
|
||||
MxValueProjection::Timestamp(ts) => {
|
||||
json!({ "timestampValue": { "seconds": ts.seconds, "nanos": ts.nanos } })
|
||||
}
|
||||
MxValueProjection::Array(_) => json!({ "arrayValue": {} }),
|
||||
MxValueProjection::Raw(bytes) => json!({ "rawValue": { "byteCount": bytes.len() } }),
|
||||
MxValueProjection::Null => json!({ "isNull": true }),
|
||||
MxValueProjection::Unset => Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a small but practically-complete subset of RFC3339:
|
||||
/// `YYYY-MM-DDTHH:MM:SS[.fffffffff][Z|+HH:MM|-HH:MM]`. Returns the
|
||||
/// corresponding `prost_types::Timestamp` (Unix seconds + nanoseconds).
|
||||
|
||||
Reference in New Issue
Block a user