use futures::TryStreamExt as _; use juniper::{ http::tests::{run_http_test_suite, HttpIntegration, TestResponse}, tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, EmptySubscription, RootNode, }; use juniper_warp::{make_graphql_filter, make_graphql_filter_sync}; use warp::{ body, filters::{path, BoxedFilter}, http, reply, Filter as _, }; struct TestWarpIntegration { filter: BoxedFilter<(reply::Response,)>, } impl TestWarpIntegration { fn new(is_sync: bool) -> Self { let schema = RootNode::new( Query, EmptyMutation::::new(), EmptySubscription::::new(), ); let db = warp::any().map(Database::new); Self { filter: path::end() .and(if is_sync { make_graphql_filter_sync(schema, db).boxed() } else { make_graphql_filter(schema, db).boxed() }) .boxed(), } } fn make_request(&self, req: warp::test::RequestBuilder) -> TestResponse { let rt = tokio::runtime::Runtime::new() .unwrap_or_else(|e| panic!("failed to create `tokio::Runtime`: {e}")); rt.block_on(async move { into_test_response(req.filter(&self.filter).await.unwrap_or_else(|rejection| { let code = if rejection.is_not_found() { http::StatusCode::NOT_FOUND } else if let Some(body::BodyDeserializeError { .. }) = rejection.find() { http::StatusCode::BAD_REQUEST } else { http::StatusCode::INTERNAL_SERVER_ERROR }; http::Response::builder() .status(code) .header("content-type", "application/json") .body(Vec::new().into()) .unwrap() })) .await }) } } impl HttpIntegration for TestWarpIntegration { fn get(&self, url: &str) -> TestResponse { use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use url::Url; /// https://url.spec.whatwg.org/#query-state const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); let url = Url::parse(&format!("http://localhost:3000{url}")).expect("url to parse"); let url: String = utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET) .collect::>() .join(""); self.make_request( warp::test::request() .method("GET") .path(&format!("/?{url}")), ) } fn post_json(&self, url: &str, body: &str) -> TestResponse { self.make_request( warp::test::request() .method("POST") .header("content-type", "application/json; charset=utf-8") .path(url) .body(body), ) } fn post_graphql(&self, url: &str, body: &str) -> TestResponse { self.make_request( warp::test::request() .method("POST") .header("content-type", "application/graphql; charset=utf-8") .path(url) .body(body), ) } } async fn into_test_response(resp: reply::Response) -> TestResponse { let (parts, body) = resp.into_parts(); let status_code = parts.status.as_u16().into(); let content_type = parts .headers .get("content-type") .map(|header| { header .to_str() .unwrap_or_else(|e| panic!("not UTF-8 header: {e}")) .to_owned() }) .unwrap_or_default(); let body = String::from_utf8( body.map_ok(|bytes| bytes.to_vec()) .try_concat() .await .unwrap(), ) .unwrap_or_else(|e| panic!("not UTF-8 body: {e}")); TestResponse { status_code, content_type, body: Some(body), } } #[test] fn test_warp_integration() { run_http_test_suite(&TestWarpIntegration::new(false)); } #[test] fn test_sync_warp_integration() { run_http_test_suite(&TestWarpIntegration::new(true)); }