refactor: single refusal shape with openapi contract test

This commit is contained in:
2026-10-25 21:55:38 +01:00
parent 816d39ffd6
commit c8d9138349
25 changed files with 764 additions and 273 deletions

View File

@@ -393,3 +393,139 @@ async fn following_collection_returns_500_on_federation_error() {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
// --- One refusal shape ---
async fn refusal_body(resp: axum::response::Response) -> serde_json::Value {
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
serde_json::from_slice(&bytes).expect("refusal body is JSON")
}
#[tokio::test]
async fn a_missing_resource_is_refused_with_not_found_code() {
let state =
crate::extractors::tests::test_app_state_with_person_query(Arc::new(PersonQueryStub));
let app = Router::new()
.route(
"/api/v1/people/{id}",
get(crate::api::search::get_person_handler),
)
.with_state(state);
let resp = app
.oneshot(
Request::builder()
.uri(format!("/api/v1/people/{}", Uuid::new_v4()))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let body = refusal_body(resp).await;
assert_eq!(body["code"], "NOT_FOUND");
assert!(body["error"].is_string());
}
#[tokio::test]
async fn a_missing_credential_is_refused_with_unauthorized_code() {
let app = Router::new()
.route(
"/api/v1/watchlist",
get(crate::api::watchlist::get_watchlist_handler),
)
.with_state(test_app_state());
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/watchlist")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
assert_eq!(refusal_body(resp).await["code"], "UNAUTHORIZED");
}
#[tokio::test]
async fn a_malformed_path_parameter_is_refused_with_invalid_input_code() {
let state =
crate::extractors::tests::test_app_state_with_person_query(Arc::new(PersonQueryStub));
let app = Router::new()
.route(
"/api/v1/people/{id}",
get(crate::api::search::get_person_handler),
)
.with_state(state);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/people/not-a-uuid")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
assert_eq!(refusal_body(resp).await["code"], "INVALID_INPUT");
}
#[tokio::test]
async fn a_malformed_json_body_is_refused_with_invalid_input_code() {
let app = Router::new()
.route(
"/api/v1/auth/login",
axum::routing::post(crate::api::auth::login),
)
.with_state(test_app_state());
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/auth/login")
.header("content-type", "application/json")
.body(Body::from("{not json"))
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let body = refusal_body(resp).await;
assert_eq!(body["code"], "INVALID_INPUT");
assert!(body["error"].is_string());
}
#[tokio::test]
async fn a_json_body_of_the_wrong_shape_is_refused_as_unprocessable() {
let app = Router::new()
.route(
"/api/v1/auth/login",
axum::routing::post(crate::api::auth::login),
)
.with_state(test_app_state());
let resp = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/auth/login")
.header("content-type", "application/json")
.body(Body::from("{}"))
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNPROCESSABLE_ENTITY);
assert_eq!(refusal_body(resp).await["code"], "INVALID_INPUT");
}