Merge branch 'wt/review-002-cov-deployment-knobs'

# Conflicts:
#	src/gateway/routes.rs
This commit is contained in:
2026-08-31 01:55:12 +00:00
6 changed files with 300 additions and 0 deletions
+65
View File
@@ -1604,6 +1604,38 @@ mod tests {
}
}
#[test]
fn array_of_ref_resolves_each_item() {
// The array branch of `resolve_refs_bounded` (the arm a coverage
// pass initially mis-flagged): `$ref`s inside `items` resolve to
// the component schema, not pass through verbatim.
let spec = schema_test_spec(json!({
"Tag": {"type": "string", "minLength": 1},
"Tags": {"type": "array", "items": {"$ref": "#/components/schemas/Tag"}}
}));
let schema = spec
.components
.as_ref()
.expect("test spec has schemas")
.schemas
.get("Tags")
.expect("test schema present")
.clone();
let resolved = spec
.resolve_refs_recursive(&schema)
.expect("array-of-$ref is acyclic and must resolve");
assert_eq!(resolved["type"], "array");
assert_eq!(
resolved["items"],
serde_json::json!({"type": "string", "minLength": 1}),
"the items $ref must expand to the Tag component schema"
);
let resolved_twice = spec
.resolve_refs_recursive(&schema)
.expect("second resolution hits the memo");
assert_eq!(resolved, resolved_twice);
}
#[test]
fn cycle_via_all_of_errors() {
let spec = schema_test_spec(json!({
@@ -1839,6 +1871,39 @@ mod tests {
}
}
#[test]
fn simple_style_with_explode_true_is_rejected() {
let doc = r##"{
"openapi": "3.0.0",
"info": {"title": "T", "version": "1"},
"paths": {
"/f": {"get": {
"operationId": "f",
"parameters": [{
"name": "X-Id",
"in": "header",
"style": "simple",
"explode": true,
"schema": {"type": "string"}
}],
"responses": {"200": {"content": {"application/json": {"schema": {}}}}}
}}
}
}"##;
match OpenAPISpec::from_json(doc) {
Err(AdapterError::SchemaParse { message }) => {
assert!(
message.contains("simple") && message.contains("explode"),
"the error must name the simple+explode conflict: {message}"
);
assert!(message.contains("X-Id"), "message was: {message}");
assert!(message.contains("OAI-06"), "message was: {message}");
}
Ok(_) => panic!("simple+explode=true has no wire meaning here; must fail loudly"),
other => panic!("expected SchemaParse, got {other:?}"),
}
}
#[test]
fn default_style_and_explode_forms_still_import() {
let doc = r##"{
+67
View File
@@ -1098,6 +1098,73 @@ mod tests {
);
}
#[tokio::test]
async fn schema_tool_without_arguments_is_invalid_input() {
let registry = full_registry_with_ops(vec![(
"fs/readFile".to_string(),
OperationType::Query,
AccessControl::default(),
)]);
let gateway = ToMcpGateway::new(dispatch(registry));
let result = invoke_tool(&gateway, "schema", None, None).await;
assert_eq!(result.is_error, Some(true));
let structured = result.structured_content.expect("structured error present");
assert_eq!(
structured.get("code"),
Some(&Value::String("INVALID_INPUT".to_string()))
);
let message = structured
.get("message")
.and_then(Value::as_str)
.expect("message");
assert!(
message.contains("`name` is required"),
"missing tool arguments must name the required field: {message}"
);
}
#[tokio::test]
async fn batch_tool_without_calls_argument_is_invalid_input_without_dispatching() {
let registry = full_registry_with_ops(vec![(
"echo/run".to_string(),
OperationType::Query,
AccessControl::default(),
)]);
let gateway = ToMcpGateway::new(dispatch(registry));
let none_args = invoke_tool(&gateway, "batch", None, None).await;
assert_eq!(none_args.is_error, Some(true));
let structured = none_args
.structured_content
.expect("structured error present");
assert_eq!(
structured.get("code"),
Some(&Value::String("INVALID_INPUT".to_string()))
);
assert_eq!(structured.get("retryable"), Some(&Value::Bool(false)));
let message = structured
.get("message")
.and_then(Value::as_str)
.expect("message");
assert!(
message.contains("`calls` is required"),
"missing batch arguments must name the required field: {message}"
);
let mut args = Map::new();
args.insert("calls".to_string(), Value::String("nope".to_string()));
let non_array = invoke_tool(&gateway, "batch", Some(args), None).await;
assert_eq!(non_array.is_error, Some(true));
let structured = non_array
.structured_content
.expect("structured error present");
assert_eq!(
structured.get("code"),
Some(&Value::String("INVALID_INPUT".to_string()))
);
}
#[tokio::test]
async fn call_returns_structured_error_for_call_error() {
let registry = full_registry_with_ops(vec![]);