diff --git a/integrations/actix-web/tests/graphql.rs b/integrations/actix-web/tests/graphql.rs new file mode 100644 index 00000000..dc75676a --- /dev/null +++ b/integrations/actix-web/tests/graphql.rs @@ -0,0 +1,137 @@ +mod test_utils; +use actix_web::{guard, test, web, App}; +use async_graphql::*; +use serde_json::json; +use test_utils::*; + +#[actix_rt::test] +async fn test_playground() { + let srv = test::start(|| { + App::new().service( + web::resource("/") + .guard(guard::Get()) + .to(test_utils::gql_playgound), + ) + }); + let mut response = srv.get("/").send().await.unwrap(); + assert!(response.status().is_success()); + let body = response.body().await.unwrap(); + assert!(std::str::from_utf8(&body).unwrap().contains("graphql")); +} + +#[actix_rt::test] +async fn test_add() { + let srv = test::start(|| { + App::new() + .data(Schema::new(AddQueryRoot, EmptyMutation, EmptySubscription)) + .service( + web::resource("/") + .guard(guard::Post()) + .to(gql_handle_schema::), + ) + }); + let mut response = srv + .post("/") + .send_body(r#"{"query":"{ add(a: 10, b: 20) }"}"#) + .await + .unwrap(); + assert!(response.status().is_success()); + let body = response.body().await.unwrap(); + assert_eq!(body, json!({"data": {"add": 30}}).to_string()); +} + +#[actix_rt::test] +async fn test_hello() { + let srv = test::start(|| { + App::new() + .data(Schema::new( + HelloQueryRoot, + EmptyMutation, + EmptySubscription, + )) + .service( + web::resource("/") + .guard(guard::Post()) + .to(gql_handle_schema::), + ) + }); + + let mut response = srv + .post("/") + .send_body(r#"{"query":"{ hello }"}"#) + .await + .unwrap(); + assert!(response.status().is_success()); + let body = response.body().await.unwrap(); + assert_eq!( + body, + json!({"data": {"hello": "Hello, world!"}}).to_string() + ); +} + +#[actix_rt::test] +async fn test_hello_header() { + let srv = test::start(|| { + App::new() + .data(Schema::new( + HelloQueryRoot, + EmptyMutation, + EmptySubscription, + )) + .service( + web::resource("/") + .guard(guard::Post()) + .to(gql_handle_schema_with_header::), + ) + }); + + let mut response = srv + .post("/") + .header("Name", "Foo") + .send_body(r#"{"query":"{ hello }"}"#) + .await + .unwrap(); + assert!(response.status().is_success()); + let body = response.body().await.unwrap(); + assert_eq!(body, json!({"data": {"hello": "Hello, Foo!"}}).to_string()); +} + +#[actix_rt::test] +async fn test_count() { + let srv = test::start(|| { + App::new() + .data( + Schema::build(CountQueryRoot, CountMutation, EmptySubscription) + .data(Count::default()) + .finish(), + ) + .service( + web::resource("/") + .guard(guard::Post()) + .to(gql_handle_schema::), + ) + }); + count_action_helper(0, r#"{"query":"{ count }"}"#, &srv).await; + count_action_helper(10, r#"{"query":"mutation{ addCount(count: 10) }"}"#, &srv).await; + count_action_helper( + 8, + r#"{"query":"mutation{ subtractCount(count: 2) }"}"#, + &srv, + ) + .await; + count_action_helper( + 6, + r#"{"query":"mutation{ subtractCount(count: 2) }"}"#, + &srv, + ) + .await; +} + +async fn count_action_helper(expected: i32, body: &'static str, srv: &test::TestServer) { + let mut response = srv.post("/").send_body(body).await.unwrap(); + assert!(response.status().is_success()); + let body = response.body().await.unwrap(); + assert!(std::str::from_utf8(&body) + .unwrap() + .contains(&expected.to_string())); +} diff --git a/integrations/actix-web/tests/test_utils.rs b/integrations/actix-web/tests/test_utils.rs new file mode 100644 index 00000000..f4023dcb --- /dev/null +++ b/integrations/actix-web/tests/test_utils.rs @@ -0,0 +1,91 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use async_graphql::http::{playground_source, GraphQLPlaygroundConfig}; +use async_graphql::{ + Context, EmptyMutation, EmptySubscription, Object, ObjectType, Schema, SubscriptionType, +}; +use async_graphql_actix_web::{Request, Response}; +use futures::lock::Mutex; + +pub async fn gql_playgound() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(playground_source(GraphQLPlaygroundConfig::new("/"))) +} + +pub(crate) struct AddQueryRoot; + +#[Object] +impl AddQueryRoot { + /// Returns the sum of a and b + async fn add(&self, a: i32, b: i32) -> i32 { + a + b + } +} + +struct Hello(String); + +pub(crate) struct HelloQueryRoot; + +#[Object] +impl HelloQueryRoot { + /// Returns hello + async fn hello<'a>(&self, ctx: &'a Context<'_>) -> String { + let name = ctx.data_opt::().map(|hello| hello.0.as_str()); + format!("Hello, {}!", name.unwrap_or("world")) + } +} + +pub type Count = Mutex; + +pub(crate) struct CountQueryRoot; + +#[Object] +impl CountQueryRoot { + async fn count<'a>(&self, ctx: &'a Context<'_>) -> i32 { + ctx.data_unchecked::().lock().await.clone() + } +} + +pub(crate) struct CountMutation; + +#[Object] +impl CountMutation { + async fn add_count<'a>(&self, ctx: &'a Context<'_>, count: i32) -> i32 { + let mut guard_count = ctx.data_unchecked::().lock().await; + *guard_count += count; + guard_count.clone() + } + + async fn subtract_count<'a>(&self, ctx: &'a Context<'_>, count: i32) -> i32 { + let mut guard_count = ctx.data_unchecked::().lock().await; + *guard_count -= count; + guard_count.clone() + } +} + +pub async fn gql_handle_schema< + Q: Send + Sync + ObjectType + 'static, + M: Send + Sync + ObjectType + 'static, + S: Send + Sync + SubscriptionType + 'static, +>( + schema: web::Data>, + req: Request, +) -> Response { + schema.execute(req.into_inner()).await.into() +} + +pub async fn gql_handle_schema_with_header( + schema: actix_web::web::Data>, + req: HttpRequest, + gql_request: Request, +) -> Response { + let name = req + .headers() + .get("Name") + .and_then(|value| value.to_str().map(|s| Hello(s.to_string())).ok()); + let mut request = gql_request.into_inner(); + if let Some(name) = name { + request = request.data(name); + } + schema.execute(request).await.into() +}