ci / check (push) Failing after 2m38s
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
187 lines
5.2 KiB
Rust
187 lines
5.2 KiB
Rust
//! Native checks for generated schemas, dispatch, and missing core transports.
|
|
use sausage_plugin::serde_json::json;
|
|
use sausage_plugin::{Ctx, Interface, Result, rpc, schema};
|
|
/// The addition parameters.
|
|
#[schema]
|
|
struct Add {
|
|
/// The first integer.
|
|
a: i32,
|
|
/// The second integer.
|
|
b: i32,
|
|
}
|
|
/// A type with different serialize and deserialize contracts.
|
|
#[schema]
|
|
struct Defaulted {
|
|
/// A value which the provider defaults.
|
|
#[serde(default)]
|
|
value: String,
|
|
}
|
|
/// A documented string enum.
|
|
#[schema]
|
|
enum Choice {
|
|
/// The first choice.
|
|
First,
|
|
/// The second choice.
|
|
Second,
|
|
}
|
|
struct Plugin;
|
|
#[rpc(endpoints(control=Plugin))]
|
|
impl Plugin {
|
|
/// Return the same named object without a contract change.
|
|
fn identity(&self, p: Add) -> Result<Add> {
|
|
Ok(p)
|
|
}
|
|
/// Choose the first value.
|
|
fn choice(&self) -> Result<Choice> {
|
|
Ok(Choice::First)
|
|
}
|
|
/// Round-trip the defaulted object.
|
|
fn defaults(&self, p: Defaulted) -> Result<Defaulted> {
|
|
Ok(p)
|
|
}
|
|
/// Add integers.
|
|
///
|
|
/// Return their sum.
|
|
fn add(&self, p: Add) -> Result<i32> {
|
|
Ok(p.a + p.b)
|
|
}
|
|
}
|
|
#[test]
|
|
fn discovery_and_dispatch() {
|
|
let doc = Plugin.discover();
|
|
assert!(doc["components"]["schemas"].get("Empty").is_none());
|
|
let choice = doc["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|m| m["name"] == "choice")
|
|
.unwrap();
|
|
assert!(choice.get("x-sausage-params").is_none());
|
|
assert!(
|
|
doc["x-sausage-endpoints"]["control"]["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|m| m["name"] == "add")
|
|
);
|
|
assert!(doc["components"]["schemas"].get("AddResult").is_none());
|
|
assert_eq!(
|
|
doc["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|m| m["name"] == "identity")
|
|
.unwrap()["result"]["schema"]["$ref"],
|
|
"#/components/schemas/Add"
|
|
);
|
|
assert!(
|
|
doc["components"]["schemas"]["Defaulted"]
|
|
.get("required")
|
|
.is_none()
|
|
);
|
|
assert_eq!(
|
|
doc["components"]["schemas"]["DefaultedResult"]["required"],
|
|
json!(["value"])
|
|
);
|
|
assert_eq!(
|
|
doc["components"]["schemas"]["Choice"]["oneOf"][0]["description"],
|
|
json!("The first choice.")
|
|
);
|
|
let add = doc["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|m| m["name"] == "add")
|
|
.unwrap();
|
|
assert_eq!(add["summary"], "Add integers.");
|
|
assert_eq!(
|
|
doc["components"]["schemas"]["Add"]["description"],
|
|
"The addition parameters."
|
|
);
|
|
assert_eq!(add["params"][0]["description"], "The first integer.");
|
|
eprintln!("DISCOVER={doc}");
|
|
assert_eq!(
|
|
sausage_plugin::block_on(Plugin.dispatch("add", json!({"a":2,"b":3}), &Ctx::default()))
|
|
.unwrap(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
sausage_plugin::block_on(Plugin.dispatch("add", json!({}), &Ctx::default()))
|
|
.unwrap_err()
|
|
.code,
|
|
-32602
|
|
);
|
|
let internal: sausage_plugin::Error = sausage_plugin::serde_json::from_str::<i32>("bad")
|
|
.unwrap_err()
|
|
.into();
|
|
assert_eq!(internal.code, -32603);
|
|
}
|
|
#[sausage_plugin::test]
|
|
async fn params_must_be_objects() {
|
|
for (params, kind) in [
|
|
(json!("{\"a\":2,\"b\":3}"), "a string"),
|
|
(json!([]), "an array"),
|
|
(json!(42), "a number"),
|
|
(json!(null), "null"),
|
|
(json!(true), "a boolean"),
|
|
] {
|
|
let message = format!(
|
|
"params must be a JSON object, got {kind}. Send the object itself, not a JSON-encoded string."
|
|
);
|
|
let outgoing = sausage_plugin::call("plugin.add", params.clone())
|
|
.await
|
|
.unwrap_err();
|
|
assert_eq!(outgoing.code, -32602);
|
|
assert_eq!(outgoing.message, message);
|
|
for method in ["add", "choice", "unknown"] {
|
|
let incoming = Plugin
|
|
.dispatch(method, params.clone(), &Ctx::default())
|
|
.await
|
|
.unwrap_err();
|
|
assert_eq!(incoming.code, -32602);
|
|
assert_eq!(incoming.message, message);
|
|
}
|
|
}
|
|
assert_eq!(
|
|
Plugin
|
|
.dispatch("add", json!({"a":2,"b":3}), &Ctx::default())
|
|
.await
|
|
.unwrap(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
Plugin
|
|
.dispatch("defaults", json!({}), &Ctx::default())
|
|
.await
|
|
.unwrap(),
|
|
json!({"value":""})
|
|
);
|
|
// An object reaches transport validation rather than the params rejection.
|
|
let outgoing = sausage_plugin::call("plugin.add", json!({"a":2,"b":3}))
|
|
.await
|
|
.unwrap_err();
|
|
assert!(outgoing.message.contains("no core transport"));
|
|
}
|
|
|
|
#[sausage_plugin::test]
|
|
async fn missing_core_fails_without_wait() {
|
|
assert!(
|
|
Ctx::default()
|
|
.notify("progress", json!({}))
|
|
.unwrap_err()
|
|
.message
|
|
.contains("no core transport")
|
|
);
|
|
assert!(
|
|
sausage_plugin::call("core.lock", json!({}))
|
|
.await
|
|
.unwrap_err()
|
|
.message
|
|
.contains("no core transport")
|
|
);
|
|
}
|
|
#[test]
|
|
fn compile_fail() {
|
|
trybuild::TestCases::new().compile_fail("tests/ui/*.rs");
|
|
}
|