Skip to content

Commit 4cdc188

Browse files
committed
Add Apollo Server end-to-end tests
1 parent 4329760 commit 4cdc188

File tree

11 files changed

+559
-9
lines changed

11 files changed

+559
-9
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: defer-router-tests
2+
3+
on:
4+
schedule:
5+
- cron: '0 3 * * *'
6+
env:
7+
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
8+
9+
jobs:
10+
defer-with-router-tests:
11+
runs-on: ubuntu-latest
12+
if: github.repository == 'apollographql/apollo-kotlin'
13+
steps:
14+
- name: Checkout project
15+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
16+
17+
- name: Install and run graph
18+
working-directory: tests/defer/apollo-server/
19+
run: |
20+
npm install --legacy-peer-deps
21+
npx patch-package
22+
APOLLO_PORT=4000 npm start &
23+
24+
- name: Setup Java
25+
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 #v4.2.1
26+
with:
27+
distribution: 'temurin'
28+
java-version: 17
29+
30+
- name: Setup Gradle
31+
uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda #v3.4.2
32+
33+
- name: Run Apollo Kotlin @defer tests
34+
env:
35+
DEFER_WITH_APOLLO_SERVER_TESTS: true
36+
run: |
37+
./gradlew --no-daemon --console plain -p tests :defer:allTests

libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/internal/DeferredJsonMerger.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,29 @@ class DeferredJsonMerger {
5252
}
5353

5454
fun merge(payload: JsonMap): JsonMap {
55+
val completed = payload["completed"] as? List<JsonMap>
5556
if (merged.isEmpty()) {
5657
// Initial payload, no merging needed (strip some fields that should not appear in the final result)
5758
_merged += payload - "hasNext" - "pending"
5859
handlePending(payload)
59-
handleCompleted(payload)
60+
handleCompleted(completed)
6061
return merged
6162
}
6263
handlePending(payload)
6364

6465
val incrementalList = payload["incremental"] as? List<JsonMap>
65-
if (incrementalList == null) {
66-
isEmptyPayload = true
67-
} else {
68-
isEmptyPayload = false
66+
if (incrementalList != null) {
6967
for (incrementalItem in incrementalList) {
7068
mergeIncrementalData(incrementalItem)
7169
// Merge errors (if any) of the incremental item
7270
(incrementalItem["errors"] as? List<JsonMap>)?.let { getOrPutMergedErrors() += it }
7371
}
7472
}
73+
isEmptyPayload = completed == null && incrementalList == null
7574

7675
hasNext = payload["hasNext"] as Boolean? ?: false
7776

78-
handleCompleted(payload)
77+
handleCompleted(completed)
7978

8079
(payload["extensions"] as? JsonMap)?.let { getOrPutExtensions() += it }
8180

@@ -98,8 +97,7 @@ class DeferredJsonMerger {
9897
}
9998
}
10099

101-
private fun handleCompleted(payload: JsonMap) {
102-
val completed = payload["completed"] as? List<JsonMap>
100+
private fun handleCompleted(completed: List<JsonMap>?) {
103101
if (completed != null) {
104102
for (completedItem in completed) {
105103
// Merge errors (if any) of the completed item

tests/defer/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,16 @@ To run them locally:
1616
subgraph: `(cd tests/defer/router/subgraphs/computers && npm install && APOLLO_PORT=4001 npm start)&`
1717
2. Run the router: `path/to/router --supergraph tests/defer/router/simple-supergraph.graphqls &`
1818
3. Run the tests: `DEFER_WITH_ROUTER_TESTS=true ./gradlew -p tests :defer:allTests`
19+
20+
## End-to-end tests with Apollo Server
21+
22+
The tests in `DeferWithApolloServerTest` are not run by default (they are excluded in the gradle conf) because they
23+
expect an instance of [Apollo Server](https://www.apollographql.com/docs/apollo-server) running locally.
24+
25+
They are enabled only when running from the specific `defer-with-apollo-server-tests` CI workflow.
26+
27+
To run them locally:
28+
29+
1. Install and run the
30+
subgraph: `(cd tests/defer/apollo-server && npm install --legacy-peer-deps && npx patch-package && APOLLO_PORT=4000 npm start)&`
31+
2. Run the tests: `DEFER_WITH_APOLLO_SERVER_TESTS=true ./gradlew -p tests :defer:allTests`

tests/defer/apollo-server/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Test server using Apollo Server, for `@defer` tests
2+
3+
- This uses graphql-js `17.0.0-alpha.7`, which implements the latest draft of the `@defer` incremental format (as of 2024-12-16).
4+
- Apollo Server `4.11.2` needs a patch (in `patches`) to surface this format in the responses.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
type Query {
2+
computers: [Computer!]!
3+
computer(id: ID!): Computer
4+
}
5+
6+
type Mutation {
7+
computers: [Computer!]!
8+
}
9+
10+
type Computer {
11+
id: ID!
12+
cpu: String!
13+
year: Int!
14+
screen: Screen!
15+
errorField: String
16+
nonNullErrorField: String!
17+
}
18+
19+
type Screen {
20+
resolution: String!
21+
isColor: Boolean!
22+
}
23+
24+
directive @defer(
25+
if: Boolean! = true
26+
label: String
27+
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {ApolloServer} from '@apollo/server';
2+
import {startStandaloneServer} from '@apollo/server/standalone';
3+
import {readFileSync} from 'fs';
4+
5+
const port = process.env.APOLLO_PORT || 4000;
6+
7+
const computers = [
8+
{id: 'Computer1', cpu: "386", year: 1993, screen: {resolution: "640x480", isColor: false}},
9+
{id: 'Computer2', cpu: "486", year: 1996, screen: {resolution: "800x600", isColor: true}},
10+
]
11+
12+
const typeDefs = readFileSync('./computers.graphqls', {encoding: 'utf-8'});
13+
const resolvers = {
14+
Query: {
15+
computers: (_, args, context) => {
16+
return computers;
17+
},
18+
computer: (_, args, context) => {
19+
return computers.find(p => p.id === args.id);
20+
}
21+
},
22+
Mutation: {
23+
computers: (_, args, context) => {
24+
return computers;
25+
}
26+
},
27+
Computer: {
28+
errorField: (_, args, context) => {
29+
throw new Error("Error field");
30+
},
31+
nonNullErrorField: (_, args, context) => {
32+
return null;
33+
}
34+
}
35+
}
36+
const server = new ApolloServer({typeDefs, resolvers});
37+
const {url} = await startStandaloneServer(server, {
38+
listen: {port: port},
39+
});
40+
console.log(`🚀 Computers subgraph ready at ${url}`);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"type": "module",
3+
"name": "subgraph-computers",
4+
"version": "1.1.0",
5+
"description": "",
6+
"main": "computers.js",
7+
"scripts": {
8+
"start": "node computers.js"
9+
},
10+
"dependencies": {
11+
"@apollo/server": "4.11.2",
12+
"graphql": "17.0.0-alpha.7",
13+
"patch-package": "^8.0.0"
14+
},
15+
"keywords": [],
16+
"author": "",
17+
"license": "MIT"
18+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
diff --git a/node_modules/@apollo/server/dist/esm/runHttpQuery.js b/node_modules/@apollo/server/dist/esm/runHttpQuery.js
2+
index 96ef0ab..0d341fa 100644
3+
--- a/node_modules/@apollo/server/dist/esm/runHttpQuery.js
4+
+++ b/node_modules/@apollo/server/dist/esm/runHttpQuery.js
5+
@@ -187,6 +187,7 @@ function orderExecutionResultFields(result) {
6+
}
7+
function orderInitialIncrementalExecutionResultFields(result) {
8+
return {
9+
+ ...result,
10+
hasNext: result.hasNext,
11+
errors: result.errors,
12+
data: result.data,
13+
@@ -196,6 +197,7 @@ function orderInitialIncrementalExecutionResultFields(result) {
14+
}
15+
function orderSubsequentIncrementalExecutionResultFields(result) {
16+
return {
17+
+ ...result,
18+
hasNext: result.hasNext,
19+
incremental: orderIncrementalResultFields(result.incremental),
20+
extensions: result.extensions,
21+
@@ -203,6 +205,7 @@ function orderSubsequentIncrementalExecutionResultFields(result) {
22+
}
23+
function orderIncrementalResultFields(incremental) {
24+
return incremental?.map((i) => ({
25+
+ ...i,
26+
hasNext: i.hasNext,
27+
errors: i.errors,
28+
path: i.path,

tests/defer/build.gradle.kts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,12 @@ tasks.withType(AbstractTestTask::class.java) {
7474
} else {
7575
filter.setExcludePatterns("test.DeferWithRouterTest")
7676
}
77-
}
7877

78+
// Run the defer with Apollo Server tests only from a specific CI job
79+
val runDeferWithApolloServerTests = System.getenv("DEFER_WITH_APOLLO_SERVER_TESTS").toBoolean()
80+
if (runDeferWithApolloServerTests) {
81+
filter.setIncludePatterns("test.DeferWithApolloServerTest")
82+
} else {
83+
filter.setExcludePatterns("test.DeferWithApolloServerTest")
84+
}
85+
}

tests/defer/src/commonMain/graphql/base/operation.graphql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ query CanDeferAFragmentThatIsAlsoNotDeferredDeferredFragmentIsFirstQuery {
109109
}
110110
}
111111

112+
query DeferFragmentThatIsAlsoNotDeferredIsSkipped1Query {
113+
computer(id: "Computer1") {
114+
screen {
115+
...ScreenFields @defer
116+
...ScreenFields
117+
}
118+
}
119+
}
120+
112121
query CanDeferAFragmentThatIsAlsoNotDeferredNotDeferredFragmentIsFirstQuery {
113122
computer(id: "Computer1") {
114123
screen {
@@ -118,6 +127,15 @@ query CanDeferAFragmentThatIsAlsoNotDeferredNotDeferredFragmentIsFirstQuery {
118127
}
119128
}
120129

130+
query DeferFragmentThatIsAlsoNotDeferredIsSkipped2Query {
131+
computer(id: "Computer1") {
132+
screen {
133+
...ScreenFields
134+
...ScreenFields @defer
135+
}
136+
}
137+
}
138+
121139
query HandlesErrorsThrownInDeferredFragmentsQuery {
122140
computer(id: "Computer1") {
123141
id

0 commit comments

Comments
 (0)