#![cfg(feature = "openapi")]
use fandhe_backend_core::{Handler, Server, handle_connection};
use fandhe_backend_http::request::RequestHead;
use fandhe_backend_http::response::Response;
use fandhe_backend_plugin_openapi::{OPENAPI_JSON, OPENAPI_YAML};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
struct NotCalledHandler;
impl Handler for NotCalledHandler {
fn handle(&self, _head: &RequestHead, _body: &[u8]) -> fandhe_backend_routes::HandlerFuture {
panic!("plugin::try_intercept が Some を返したのに既定 Handler が呼ばれた");
}
}
struct FixedOkHandler;
impl Handler for FixedOkHandler {
fn handle(&self, _head: &RequestHead, _body: &[u8]) -> fandhe_backend_routes::HandlerFuture {
Box::pin(std::future::ready(Response::new(200, b"ok".to_vec())))
}
}
async fn roundtrip(server: &Server, request: &[u8]) -> Vec<u8> {
let (mut client, server_stream) = tokio::io::duplex(1 << 20);
client.write_all(request).await.unwrap();
client.shutdown().await.unwrap();
handle_connection(server, server_stream).await;
let mut out = Vec::new();
client.read_to_end(&mut out).await.unwrap();
out
}
#[tokio::test]
async fn registered_openapi_serves_embedded_json_and_bypasses_default_handler() {
let server = Server::new().handler(NotCalledHandler).openapi();
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/json\r\n"));
assert!(response.contains(&format!("Content-Length: {}\r\n", OPENAPI_JSON.len())));
assert!(response.ends_with(OPENAPI_JSON));
}
#[tokio::test]
async fn registered_openapi_serves_embedded_yaml_and_bypasses_default_handler() {
let server = Server::new().handler(NotCalledHandler).openapi();
let request = b"GET /openapi.yaml HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/yaml\r\n"));
assert!(response.contains(&format!("Content-Length: {}\r\n", OPENAPI_YAML.len())));
assert!(response.ends_with(OPENAPI_YAML));
}
#[tokio::test]
async fn unregistered_openapi_yaml_falls_through_to_404() {
let server = Server::new();
let request = b"GET /openapi.yaml HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn wrong_method_openapi_yaml_falls_through_to_404() {
let server = Server::new().openapi();
let request = b"POST /openapi.yaml HTTP/1.1\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn unregistered_openapi_falls_through_to_404() {
let server = Server::new();
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn wrong_method_falls_through_to_404() {
let server = Server::new().openapi();
let request = b"POST /openapi.json HTTP/1.1\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn unrelated_path_falls_through_to_default_handler() {
let server = Server::new().handler(FixedOkHandler).openapi();
let response = roundtrip(&server, b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n").await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with("ok"));
}
const CUSTOM_JSON: &str = r#"{"openapi":"3.0.0","info":{"title":"custom","version":"1"}}"#;
const CUSTOM_YAML: &str = "openapi: 3.0.0\ninfo:\n title: custom\n";
#[tokio::test]
async fn openapi_with_serves_registered_json_and_bypasses_default_handler() {
use fandhe_backend_plugin_openapi::OpenApiDoc;
let doc = OpenApiDoc::from_json(CUSTOM_JSON).expect("妥当な JSON");
let server = Server::new().handler(NotCalledHandler).openapi_with(doc);
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/json\r\n"));
assert!(response.contains(&format!("Content-Length: {}\r\n", CUSTOM_JSON.len())));
assert!(response.ends_with(CUSTOM_JSON));
}
#[tokio::test]
async fn openapi_with_serves_registered_yaml_when_provided() {
use fandhe_backend_plugin_openapi::OpenApiDoc;
let doc = OpenApiDoc::from_json(CUSTOM_JSON)
.expect("妥当な JSON")
.with_yaml(CUSTOM_YAML)
.expect("妥当な yaml バイト列");
let server = Server::new().openapi_with(doc);
let request = b"GET /openapi.yaml HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/yaml\r\n"));
assert!(response.contains(&format!("Content-Length: {}\r\n", CUSTOM_YAML.len())));
assert!(response.ends_with(CUSTOM_YAML));
}
#[tokio::test]
async fn openapi_with_yaml_falls_through_to_404_when_not_registered() {
use fandhe_backend_plugin_openapi::OpenApiDoc;
let doc = OpenApiDoc::from_json(CUSTOM_JSON).expect("妥当な JSON");
let server = Server::new().openapi_with(doc);
let request = b"GET /openapi.yaml HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 404 Not Found\r\n"));
}
#[tokio::test]
async fn openapi_with_takes_precedence_over_earlier_openapi_call() {
use fandhe_backend_plugin_openapi::OpenApiDoc;
let doc = OpenApiDoc::from_json(CUSTOM_JSON).expect("妥当な JSON");
let server = Server::new().openapi().openapi_with(doc);
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with(CUSTOM_JSON));
assert!(!response.contains(OPENAPI_JSON));
}
#[tokio::test]
async fn openapi_takes_precedence_over_earlier_openapi_with_call() {
use fandhe_backend_plugin_openapi::OpenApiDoc;
let doc = OpenApiDoc::from_json(CUSTOM_JSON).expect("妥当な JSON");
let server = Server::new().openapi_with(doc).openapi();
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with(OPENAPI_JSON));
}
#[tokio::test]
async fn config_built_via_core_reexport_serves_custom_json() {
let doc = fandhe_backend_core::plugin_openapi::OpenApiDoc::from_json(CUSTOM_JSON)
.expect("妥当な JSON");
let server = Server::new().handler(NotCalledHandler).openapi_with(doc);
let request = b"GET /openapi.json HTTP/1.1\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
let response = String::from_utf8_lossy(&response);
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with(CUSTOM_JSON));
}