#![cfg(feature = "graphql")]
use async_graphql::Value;
use async_graphql::dynamic::{Field, FieldFuture, Object, Schema, TypeRef};
use fandhe_backend_core::{Handler, Server, handle_connection};
use fandhe_backend_http::request::RequestHead;
use fandhe_backend_http::response::Response;
use fandhe_backend_plugin_graphql::GraphQlConfig;
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())))
}
}
fn demo_schema() -> GraphQlConfig {
let query = Object::new("Query").field(Field::new(
"hello",
TypeRef::named_nn(TypeRef::STRING),
|_ctx| FieldFuture::new(async move { Ok(Some(Value::from("world"))) }),
));
let schema = Schema::build(query.type_name(), None, None)
.register(query)
.finish()
.expect("デモスキーマの構築は静的に妥当なので必ず成功する");
GraphQlConfig::new(schema)
}
async fn roundtrip(server: &Server, request: &[u8]) -> String {
let (mut client, server_stream) = tokio::io::duplex(8192);
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();
String::from_utf8(out).unwrap()
}
#[tokio::test]
async fn registered_schema_executes_query_and_bypasses_default_handler() {
let server = Server::new()
.handler(NotCalledHandler)
.graphql(demo_schema());
let body = br#"{"query":"{ hello }"}"#;
let mut request = format!(
"POST /graphql HTTP/1.1\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
)
.into_bytes();
request.extend_from_slice(body);
let response = roundtrip(&server, &request).await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains("Content-Type: application/json\r\n"));
assert!(response.contains(r#""hello":"world"#));
}
#[tokio::test]
async fn unregistered_schema_falls_through_to_404() {
let server = Server::new();
let request = b"POST /graphql HTTP/1.1\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
let response = roundtrip(&server, request).await;
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).graphql(demo_schema());
let response = roundtrip(&server, b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n").await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.ends_with("ok"));
}
#[tokio::test]
async fn config_built_via_core_reexport_executes_query() {
let query = Object::new("Query").field(Field::new(
"hello",
TypeRef::named_nn(TypeRef::STRING),
|_ctx| FieldFuture::new(async move { Ok(Some(Value::from("world"))) }),
));
let schema = Schema::build(query.type_name(), None, None)
.register(query)
.finish()
.expect("デモスキーマの構築は静的に妥当なので必ず成功する");
let config = fandhe_backend_core::plugin_graphql::GraphQlConfig::new(schema);
let server = Server::new().handler(NotCalledHandler).graphql(config);
let body = br#"{"query":"{ hello }"}"#;
let mut request = format!(
"POST /graphql HTTP/1.1\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
)
.into_bytes();
request.extend_from_slice(body);
let response = roundtrip(&server, &request).await;
assert!(response.starts_with("HTTP/1.1 200 OK\r\n"));
assert!(response.contains(r#""hello":"world"#));
}