Files
movies-diary/crates/adapters/http-axum/tests/openapi_contract_test.rs

95 lines
2.8 KiB
Rust

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 the_spec_is_not_vacuous() {
let spec = spec();
assert!(!operations(&spec).is_empty());
assert!(spec["components"]["schemas"]["ErrorResponse"].is_object());
}
#[test]
fn every_2xx_json_response_declares_a_schema() {
let spec = spec();
let mut untyped = Vec::new();
for (verb, path, op) in operations(&spec) {
for (code, r) in op["responses"].as_object().unwrap() {
if !code.starts_with('2') {
continue;
}
if let Some(json) = r["content"].get("application/json")
&& !json["schema"].is_object()
{
untyped.push(format!("{verb} {path} -> {code}"));
}
}
}
assert!(untyped.is_empty(), "2xx JSON 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:?}");
}