#![allow(missing_docs)] #![cfg(feature = "axum-compat")]
#![allow(clippy::unwrap_used)] #![allow(clippy::indexing_slicing)]
use axum::{
http::{HeaderValue, StatusCode},
response::IntoResponse,
};
use fraiseql_error::{ErrorResponse, FileError, FraiseQLError};
#[test]
fn parse_error_returns_400() {
let err = FraiseQLError::parse("bad query");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn validation_error_returns_400() {
let err = FraiseQLError::validation("missing required field");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn authentication_error_returns_401() {
let err = FraiseQLError::auth_error("bad token");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[test]
fn auth_subsystem_error_returns_401() {
let inner = FraiseQLError::auth_error("subsystem rejected token");
let err = FraiseQLError::Auth(Box::new(inner));
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[test]
fn authorization_error_returns_403() {
let err = FraiseQLError::unauthorized("not allowed");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[test]
fn not_found_returns_404() {
let err = FraiseQLError::not_found("User", "42");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test]
fn rate_limited_returns_429() {
let err = FraiseQLError::rate_limited_with_retry(60);
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);
assert_eq!(resp.headers().get("Retry-After").unwrap(), &HeaderValue::from_static("60"));
}
#[test]
fn service_unavailable_returns_503() {
let err = FraiseQLError::ServiceUnavailable {
message: "maintenance".into(),
retry_after: Some(120),
};
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(resp.headers().get("Retry-After").unwrap(), &HeaderValue::from_static("120"));
}
#[test]
fn service_unavailable_without_retry_after_omits_header() {
let err = FraiseQLError::ServiceUnavailable {
message: "down".into(),
retry_after: None,
};
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
assert!(resp.headers().get("Retry-After").is_none());
}
#[test]
fn database_error_returns_500() {
let err = FraiseQLError::database("connection refused");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn internal_error_returns_500() {
let err = FraiseQLError::internal("oops");
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn observer_error_returns_500() {
let inner = FraiseQLError::internal("observer fault");
let err = FraiseQLError::Observer(Box::new(inner));
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn webhook_error_returns_400() {
let inner = FraiseQLError::validation("bad signature");
let err = FraiseQLError::Webhook(Box::new(inner));
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn file_too_large_returns_400() {
let err: FraiseQLError = FileError::TooLarge {
size: 100,
max: 50,
}
.into();
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn file_invalid_type_returns_400() {
let err: FraiseQLError = FileError::InvalidType {
got: "exe".into(),
allowed: vec!["png".into()],
}
.into();
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn file_not_found_returns_404() {
let err: FraiseQLError = FileError::NotFound { id: "abc".into() }.into();
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test]
fn file_quota_exceeded_returns_400() {
let err: FraiseQLError = FileError::QuotaExceeded.into();
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn file_virus_detected_returns_400() {
let err: FraiseQLError = FileError::VirusDetected {
details: "eicar".into(),
}
.into();
let resp = err.into_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn error_response_serialization_basic() {
let resp = ErrorResponse::new("test_error", "Something failed", "test_code");
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["error"], "test_error");
assert_eq!(json["error_description"], "Something failed");
assert_eq!(json["error_code"], "test_code");
assert_eq!(json["error_uri"], "https://docs.fraiseql.dev/errors#test_code");
assert!(json.get("details").is_none());
assert!(json.get("retry_after").is_none());
}
#[test]
fn error_response_with_details() {
let resp = ErrorResponse::new("err", "desc", "code")
.with_details(serde_json::json!({"field": "name"}));
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["details"]["field"], "name");
}
#[test]
fn error_response_with_retry_after() {
let resp = ErrorResponse::new("err", "desc", "code").with_retry_after(30);
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["retry_after"], 30);
}
use std::error::Error as _;
#[test]
fn auth_variant_preserves_source_chain() {
let inner: Box<dyn std::error::Error + Send + Sync> =
Box::new(std::io::Error::other("token-store unreachable"));
let inner_display = inner.to_string();
let err = FraiseQLError::Auth(inner);
let source = err.source().expect("FraiseQLError::Auth must expose its boxed source");
assert_eq!(
source.to_string(),
inner_display,
"Error::source() should yield the inner subsystem error verbatim",
);
}
#[test]
fn webhook_variant_preserves_source_chain() {
let inner: Box<dyn std::error::Error + Send + Sync> =
Box::new(std::io::Error::other("webhook delivery refused"));
let inner_display = inner.to_string();
let err = FraiseQLError::Webhook(inner);
let source = err.source().expect("FraiseQLError::Webhook must expose its boxed source");
assert_eq!(source.to_string(), inner_display);
}
#[test]
fn observer_variant_preserves_source_chain() {
let inner: Box<dyn std::error::Error + Send + Sync> =
Box::new(std::io::Error::other("observer dispatch failed"));
let inner_display = inner.to_string();
let err = FraiseQLError::Observer(inner);
let source = err.source().expect("FraiseQLError::Observer must expose its boxed source");
assert_eq!(source.to_string(), inner_display);
}