Keep -32602 and existing validation order; tell callers to send the object itself. Cover host routing, stdout fast paths, SDK calls and generated dispatch. Card: SAU-317
This commit is contained in:
+107
-2
@@ -500,14 +500,119 @@ async fn unimplemented_core_methods_return_method_not_found() {
|
||||
async fn params_must_be_objects() {
|
||||
let w = TestWorld::new();
|
||||
let h = w.start().await;
|
||||
for (params, kind) in [
|
||||
(json!("{\"text\":\"hello\"}"), "a string"),
|
||||
(json!([]), "an array"),
|
||||
(json!(42), "a number"),
|
||||
(json!(null), "null"),
|
||||
(json!(true), "a boolean"),
|
||||
] {
|
||||
let expected = json!({
|
||||
"code": -32602,
|
||||
"message": format!("params must be a JSON object, got {kind}. Send the object itself, not a JSON-encoded string.")
|
||||
});
|
||||
for method in ["probe.echo", "core.ps"] {
|
||||
assert_eq!(h.call(method, params.clone()).await.unwrap_err(), expected);
|
||||
}
|
||||
assert_eq!(
|
||||
h.call("core.call", json!({"method":"probe.echo", "params":params}))
|
||||
.await
|
||||
.unwrap_err(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
let object = json!({"text":"hello"});
|
||||
assert_eq!(call(&h, "probe.echo", object.clone()).await, object);
|
||||
assert_eq!(
|
||||
h.call("probe.echo", json!([])).await.unwrap_err()["code"],
|
||||
-32602
|
||||
call(
|
||||
&h,
|
||||
"core.call",
|
||||
json!({"method":"probe.echo", "params":object})
|
||||
)
|
||||
.await,
|
||||
object
|
||||
);
|
||||
assert_eq!(
|
||||
call(&h, "core.call", json!({"method":"probe.echo"})).await,
|
||||
json!({})
|
||||
);
|
||||
|
||||
h.stop().await;
|
||||
}
|
||||
|
||||
/// PROTOCOL 3.1: stdout fast paths reject non-objects without changing defaults.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn stdout_params_must_be_objects() {
|
||||
let w = TestWorld::new();
|
||||
let h = w.start().await;
|
||||
let mut notifications = h.subscribe();
|
||||
for method in ["core.notify", "core.unlock", "probe.echo"] {
|
||||
for (params, kind) in [
|
||||
(json!("{}"), "a string"),
|
||||
(json!([]), "an array"),
|
||||
(json!(1.5), "a number"),
|
||||
(json!(null), "null"),
|
||||
(json!(false), "a boolean"),
|
||||
] {
|
||||
let response = call(
|
||||
&h,
|
||||
"probe.raw_call",
|
||||
json!({
|
||||
"jsonrpc":"2.0", "id":1, "method":method, "params":params
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response["id"], 1);
|
||||
assert_eq!(
|
||||
response["error"],
|
||||
json!({
|
||||
"code": -32602,
|
||||
"message": format!("params must be a JSON object, got {kind}. Send the object itself, not a JSON-encoded string.")
|
||||
})
|
||||
);
|
||||
}
|
||||
let params = match method {
|
||||
"core.notify" => json!({"name":"valid", "params":{"n":1}}),
|
||||
"core.unlock" => json!({"path":"/state/not-held"}),
|
||||
_ => json!({"n":1}),
|
||||
};
|
||||
let response = call(
|
||||
&h,
|
||||
"probe.raw_call",
|
||||
json!({
|
||||
"jsonrpc":"2.0", "id":1, "method":method, "params":params
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
response["result"],
|
||||
if method == "probe.echo" {
|
||||
params
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
);
|
||||
assert!(response.get("error").is_none());
|
||||
let omitted = call(
|
||||
&h,
|
||||
"probe.raw_call",
|
||||
json!({
|
||||
"jsonrpc":"2.0", "id":1, "method":method
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
match method {
|
||||
"core.notify" => {
|
||||
assert_eq!(omitted["error"]["message"], "name must be a string");
|
||||
assert_eq!(notifications.try_recv().unwrap()["params"]["name"], "valid");
|
||||
}
|
||||
"core.unlock" => assert_eq!(omitted["error"]["message"], "path must be a string"),
|
||||
_ => assert_eq!(omitted["result"], json!({})),
|
||||
}
|
||||
}
|
||||
h.stop().await;
|
||||
}
|
||||
|
||||
/// PROTOCOL 3.9: exit without answer reports status and last logs.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn exit_without_answer_reports_status_and_last_logs() {
|
||||
|
||||
@@ -470,6 +470,10 @@ fn dispatch(request: &Value, lines: &mut Lines) -> Option<Value> {
|
||||
"network" => network(params),
|
||||
"read" => read_file(params),
|
||||
"write" => write_file(params),
|
||||
"raw_call" => {
|
||||
send(params.clone());
|
||||
receive(lines)
|
||||
}
|
||||
"call" => outgoing_call(params, lines),
|
||||
"lock" => lock(params, lines),
|
||||
_ => {
|
||||
|
||||
Reference in New Issue
Block a user