fn spec() -> serde_json::Value { serde_json::to_value(http_axum::openapi::spec()).unwrap() } fn operations(spec: &serde_json::Value) -> Vec<(String, String, serde_json::Value)> { spec["paths"] .as_object() .unwrap() .iter() .flat_map(|(path, methods)| { methods .as_object() .unwrap() .iter() .map(move |(verb, op)| (verb.to_uppercase(), path.clone(), op.clone())) }) .collect() } #[test] fn every_operation_that_can_succeed_with_a_body_declares_its_shape() { let spec = spec(); let mut untyped = Vec::new(); for (verb, path, op) in operations(&spec) { let responses = op["responses"].as_object().unwrap(); let typed = responses.iter().any(|(code, r)| { code.starts_with('2') && r["content"]["application/json"]["schema"].is_object() }); let bodyless = responses .iter() .any(|(code, r)| code.starts_with('2') && r.get("content").is_none()); let binary = responses.iter().any(|(code, r)| { code.starts_with('2') && r["content"] .as_object() .is_some_and(|c| !c.contains_key("application/json")) }); if !typed && !bodyless && !binary { untyped.push(format!("{verb} {path}")); } } assert!(untyped.is_empty(), "2xx without a schema: {untyped:?}"); } #[test] fn every_operation_documents_a_refusal() { let spec = spec(); let silent: Vec<_> = operations(&spec) .into_iter() .filter(|(_, _, op)| { !op["responses"] .as_object() .unwrap() .keys() .any(|c| c.starts_with('4')) }) .map(|(v, p, _)| format!("{v} {p}")) .collect(); assert!(silent.is_empty(), "no 4xx documented: {silent:?}"); } #[test] fn every_authenticated_operation_documents_401() { let spec = spec(); let silent: Vec<_> = operations(&spec) .into_iter() .filter(|(_, _, op)| op.get("security").is_some() && op["responses"].get("401").is_none()) .map(|(v, p, _)| format!("{v} {p}")) .collect(); assert!(silent.is_empty(), "no 401 documented: {silent:?}"); } #[test] fn every_refusal_is_the_one_error_shape() { let spec = spec(); let mut odd = Vec::new(); for (verb, path, op) in operations(&spec) { for (code, response) in op["responses"].as_object().unwrap() { if !code.starts_with('4') && !code.starts_with('5') { continue; } let named = response["content"]["application/json"]["schema"]["$ref"] .as_str() .unwrap_or_default(); if named != "#/components/schemas/ErrorResponse" { odd.push(format!("{verb} {path} -> {code}")); } } } assert!(odd.is_empty(), "refusals not ErrorResponse: {odd:?}"); }