Skip to content

Commit

Permalink
test(3010): add integration tests for dedupe (#3145)
Browse files Browse the repository at this point in the history
Co-authored-by: laststylebender <[email protected]>
  • Loading branch information
dekkku and laststylebender14 authored Nov 27, 2024
1 parent 47d6b0c commit d053a88
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 24 deletions.
5 changes: 5 additions & 0 deletions tests/core/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ impl HttpIO for Http {
self.spec_path
))?;

if let Some(delay) = execution_mock.mock.delay {
// add delay to the request if there's a delay in the mock.
let _ = tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await;
}

execution_mock.actual_hits.fetch_add(1, Ordering::Relaxed);

// Clone the response from the mock to avoid borrowing issues.
Expand Down
8 changes: 8 additions & 0 deletions tests/core/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ mod default {
1
}

pub fn concurrency() -> usize {
1
}

pub fn assert_hits() -> bool {
true
}
Expand All @@ -42,6 +46,8 @@ pub struct Mock {
pub assert_hits: bool,
#[serde(default = "default::expected_hits")]
pub expected_hits: usize,
#[serde(default)]
pub delay: Option<u64>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
Expand All @@ -57,6 +63,8 @@ pub struct APIRequest {
pub test_traces: bool,
#[serde(default)]
pub test_metrics: bool,
#[serde(default = "default::concurrency")]
pub concurrency: usize,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
Expand Down
40 changes: 40 additions & 0 deletions tests/core/snapshots/test-dedupe.md_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
source: tests/core/spec.rs
expression: response
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {
"posts": [
{
"id": 1,
"userId": 1,
"user": {
"id": 1,
"name": "user-1"
},
"duplicateUser": {
"id": 1,
"name": "user-1"
}
},
{
"id": 2,
"userId": 2,
"user": {
"id": 2,
"name": "user-2"
},
"duplicateUser": {
"id": 2,
"name": "user-2"
}
}
]
}
}
}
24 changes: 24 additions & 0 deletions tests/core/snapshots/test-dedupe.md_client.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
source: tests/core/spec.rs
expression: formatted
---
type Post {
body: String
id: Int
title: String
user: User
userId: Int!
}

type Query {
posts: [Post]
}

type User {
id: Int
name: String
}

schema {
query: Query
}
30 changes: 30 additions & 0 deletions tests/core/snapshots/test-dedupe.md_merged.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: tests/core/spec.rs
expression: formatter
---
schema @server(port: 8000) @upstream(batch: {delay: 1, headers: []}) {
query: Query
}

type Post {
body: String
id: Int
title: String
user: User
@http(
url: "http://jsonplaceholder.typicode.com/users"
batchKey: ["id"]
query: [{key: "id", value: "{{.value.userId}}"}]
dedupe: true
)
userId: Int!
}

type Query {
posts: [Post] @http(url: "http://jsonplaceholder.typicode.com/posts?id=1", dedupe: true)
}

type User {
id: Int
name: String
}
82 changes: 58 additions & 24 deletions tests/core/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{fs, panic};
use anyhow::Context;
use colored::Colorize;
use futures_util::future::join_all;
use http::Request;
use http::{Request, Response};
use hyper::Body;
use serde::{Deserialize, Serialize};
use tailcall::core::app_context::AppContext;
Expand Down Expand Up @@ -282,29 +282,63 @@ async fn run_test(
app_ctx: Arc<AppContext>,
request: &APIRequest,
) -> anyhow::Result<http::Response<Body>> {
let body = request
.body
.as_ref()
.map(|body| Body::from(body.to_bytes()))
.unwrap_or_default();

let method = request.method.clone();
let headers = request.headers.clone();
let url = request.url.clone();
let req = headers
let request_count = request.concurrency;

let futures = (0..request_count).map(|_| {
let app_ctx = app_ctx.clone();
let body = request
.body
.as_ref()
.map(|body| Body::from(body.to_bytes()))
.unwrap_or_default();

let method = request.method.clone();
let headers = request.headers.clone();
let url = request.url.clone();

tokio::spawn(async move {
let req = headers
.into_iter()
.fold(
Request::builder()
.method(method.to_hyper())
.uri(url.as_str()),
|acc, (key, value)| acc.header(key, value),
)
.body(body)?;

if app_ctx.blueprint.server.enable_batch_requests {
handle_request::<GraphQLBatchRequest>(req, app_ctx).await
} else {
handle_request::<GraphQLRequest>(req, app_ctx).await
}
})
});

let responses = join_all(futures).await;

// Unwrap the Result from join_all and the individual task results
let responses = responses
.into_iter()
.fold(
Request::builder()
.method(method.to_hyper())
.uri(url.as_str()),
|acc, (key, value)| acc.header(key, value),
)
.body(body)?;

// TODO: reuse logic from server.rs to select the correct handler
if app_ctx.blueprint.server.enable_batch_requests {
handle_request::<GraphQLBatchRequest>(req, app_ctx).await
} else {
handle_request::<GraphQLRequest>(req, app_ctx).await
.map(|res| res.map_err(anyhow::Error::from).and_then(|inner| inner))
.collect::<Result<Vec<_>, _>>()?;

let mut base_response = None;

// ensure all the received responses are the same.
for response in responses {
let (head, body) = response.into_parts();
let body = hyper::body::to_bytes(body).await?;

if let Some((_, base_body)) = &base_response {
if *base_body != body {
return Err(anyhow::anyhow!("Responses are not the same."));
}
} else {
base_response = Some((head, body));
}
}

let (head, body) = base_response.ok_or_else(|| anyhow::anyhow!("No Response received."))?;
Ok(Response::from_parts(head, Body::from(body)))
}
65 changes: 65 additions & 0 deletions tests/execution/test-dedupe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# testing dedupe functionality

```graphql @config
schema @server(port: 8000) @upstream(batch: {delay: 1}) {
query: Query
}

type Query {
posts: [Post] @http(url: "http://jsonplaceholder.typicode.com/posts?id=1", dedupe: true)
}

type Post {
id: Int
title: String
body: String
userId: Int!
user: User
@http(
url: "http://jsonplaceholder.typicode.com/users"
query: [{key: "id", value: "{{.value.userId}}"}]
batchKey: ["id"]
dedupe: true
)
}

type User {
id: Int
name: String
}
```

```yml @mock
- request:
method: GET
url: http://jsonplaceholder.typicode.com/posts?id=1
expectedHits: 1
delay: 10
response:
status: 200
body:
- id: 1
userId: 1
- id: 2
userId: 2
- request:
method: GET
url: http://jsonplaceholder.typicode.com/users?id=1&id=2
expectedHits: 1
delay: 10
response:
status: 200
body:
- id: 1
name: user-1
- id: 2
name: user-2
```

```yml @test
- method: POST
url: http://localhost:8080/graphql
concurrency: 10
body:
query: query { posts { id, userId user { id name } duplicateUser:user { id name } } }
```

1 comment on commit d053a88

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running 30s test @ http://localhost:8000/graphql

4 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 4.08ms 2.09ms 37.14ms 82.56%
Req/Sec 6.34k 0.94k 10.37k 93.42%

756881 requests in 30.02s, 3.79GB read

Requests/sec: 25210.01

Transfer/sec: 129.39MB

Please sign in to comment.