Skip to content

Commit 42fc65c

Browse files
Test against Node 20, update tests for compatibility (#7636)
Test against Node 20 and update tests as needed. This unblocks our integrations from testing against Node 20 as well since the testsuite is part of this changeset / has Node 20 test failures.
1 parent 9139a28 commit 42fc65c

File tree

6 files changed

+76
-34
lines changed

6 files changed

+76
-34
lines changed

.changeset/four-mangos-hammer.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@apollo/server-integration-testsuite': patch
3+
'@apollo/server': patch
4+
---
5+
6+
Update test suite for compatibility with Node v20

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ jobs:
132132
command: "! git grep FIXM\x45"
133133

134134
workflows:
135-
version: 2
136135
Build:
137136
jobs:
138137
- NodeJS:
@@ -143,6 +142,7 @@ workflows:
143142
- "14"
144143
- "16"
145144
- "18"
145+
- "20"
146146
- "Check for FIXM\x45"
147147
- Prettier
148148
- ESLint

packages/integration-testsuite/src/httpServerTests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ export function defineIntegrationTestSuiteHttpServerTests(
382382
return req.then((res) => {
383383
expect(res.status).toEqual(400);
384384
expect(
385-
['Unexpected token f', 'Bad Request', 'Invalid JSON'].some(
385+
['in JSON at position 1', 'Bad Request', 'Invalid JSON'].some(
386386
(substring) => (res.error as HTTPError).text.includes(substring),
387387
),
388388
).toBe(true);

packages/server/src/__tests__/express4/expressSpecific.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ it('supporting doubly-encoded variables example from migration guide', async ()
193193
query: 'query Hello($s: String!){hello(s: $s)}',
194194
variables: '{malformed JSON}',
195195
})
196-
.expect(400, 'Unexpected token m in JSON at position 1');
196+
.expect(400, /in JSON at position 1/);
197197

198198
await server.stop();
199199
});

packages/server/src/__tests__/plugin/drainHttpServer/stoppable.test.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const a: any = require('awaiting');
3232
const request: any = require('requisition');
3333
import fs from 'fs';
3434
import { Stopper } from '../../../plugin/drainHttpServer/stoppable';
35-
import child from 'child_process';
3635
import path from 'path';
3736
import type { AddressInfo } from 'net';
3837
import { describe, it, expect, afterEach, beforeEach } from '@jest/globals';
@@ -113,7 +112,7 @@ Object.keys(schemes).forEach((schemeName) => {
113112
const err = await a.failure(
114113
request(`${schemeName}://localhost:${p}`).agent(scheme.agent()),
115114
);
116-
expect(err.message).toMatch(/ECONNREFUSED/);
115+
expect(err.code).toMatch(/ECONNREFUSED/);
117116
expect(closed).toBe(1);
118117
});
119118

@@ -135,9 +134,54 @@ Object.keys(schemes).forEach((schemeName) => {
135134
scheme.agent({ keepAlive: true }),
136135
),
137136
);
138-
expect(err.message).toMatch(/ECONNREFUSED/);
139-
expect(closed).toBe(0);
137+
expect(err.code).toMatch(/ECONNREFUSED/);
138+
139+
// Node 19 (http) and 20.4+ (https) more aggressively close idle
140+
// connections. `Stopper` is no longer needed for the purpose of closing
141+
// idle connections in these versions. However, `Stopper` _is_ still
142+
// useful for gracefully finishing in-flight requests within the timeout
143+
// (and aborting requests beyond the timeout).
144+
const isNode20 = !!process.version.match(/^v20\./);
145+
expect(closed).toBe(isNode20 ? 1 : 0);
140146
});
147+
148+
// This test specifically added for Node 20 fails for Node 14. Just going
149+
// to skip it since we're dropping Node 14 soon anyway.
150+
const node14 = !!process.version.match(/^v14\./);
151+
(node14 ? it.skip : it)(
152+
'with unfinished requests',
153+
async () => {
154+
const server = scheme.server(async (_req, res) => {
155+
res.writeHead(200);
156+
res.write('hi'); // note lack of end()!
157+
});
158+
// The server will prevent itself from closing while the connection
159+
// remains open (default no timeout). This will close the connection
160+
// after 100ms so the test can finish.
161+
server.setTimeout(100);
162+
163+
server.listen(0);
164+
const p = port(server);
165+
166+
const response = await request(
167+
`${schemeName}://localhost:${p}`,
168+
).agent(scheme.agent({ keepAlive: true }));
169+
// ensure we got the headers, etc.
170+
expect(response.status).toBe(200);
171+
172+
server.close();
173+
await a.event(server, 'close');
174+
175+
try {
176+
await response.text();
177+
} catch (e: any) {
178+
expect(e.code).toMatch(/ECONNRESET/);
179+
}
180+
// ensure the expectation in the catch block is reached (+ the one above)
181+
expect.assertions(2);
182+
},
183+
35000,
184+
);
141185
});
142186

143187
describe('Stopper', function () {
@@ -159,7 +203,7 @@ Object.keys(schemes).forEach((schemeName) => {
159203
const err = await a.failure(
160204
request(`${schemeName}://localhost:${p}`).agent(scheme.agent()),
161205
);
162-
expect(err.message).toMatch(/ECONNREFUSED/);
206+
expect(err.code).toMatch(/ECONNREFUSED/);
163207

164208
expect(closed).toBe(1);
165209
expect(gracefully).toBe(true);
@@ -185,7 +229,7 @@ Object.keys(schemes).forEach((schemeName) => {
185229
scheme.agent({ keepAlive: true }),
186230
),
187231
);
188-
expect(err.message).toMatch(/ECONNREFUSED/);
232+
expect(err.code).toMatch(/ECONNREFUSED/);
189233

190234
expect(closed).toBe(1);
191235
expect(gracefully).toBe(true);
@@ -301,12 +345,21 @@ Object.keys(schemes).forEach((schemeName) => {
301345

302346
if (schemeName === 'http') {
303347
it('with in-flights finishing before grace period ends', async () => {
304-
const file = path.join(__dirname, 'stoppable', 'server.js');
305-
const server = child.spawn('node', [file]);
306-
const [data] = await a.event(server.stdout, 'data');
307-
const port = +data.toString();
308-
expect(typeof port).toBe('number');
309-
const res = await request(`${schemeName}://localhost:${port}/`).agent(
348+
let stopper: Stopper;
349+
const killServerBarrier = resolvable();
350+
const server = http.createServer(async (_, res) => {
351+
res.writeHead(200);
352+
res.write('hello');
353+
354+
await killServerBarrier;
355+
res.end('world');
356+
await stopper.stop();
357+
});
358+
stopper = new Stopper(server);
359+
server.listen(0);
360+
const p = port(server);
361+
362+
const res = await request(`${schemeName}://localhost:${p}/`).agent(
310363
scheme.agent({ keepAlive: true }),
311364
);
312365
let gotBody = false;
@@ -320,13 +373,13 @@ Object.keys(schemes).forEach((schemeName) => {
320373
expect(gotBody).toBe(false);
321374

322375
// Tell the server that its request should finish.
323-
server.kill('SIGUSR1');
376+
killServerBarrier.resolve();
324377

325378
const body = await bodyPromise;
326379
expect(gotBody).toBe(true);
327380
expect(body).toBe('helloworld');
328381

329-
// Wait for subprocess to go away.
382+
// Wait for server to close.
330383
await a.event(server, 'close');
331384
});
332385
}

packages/server/src/__tests__/plugin/drainHttpServer/stoppable/server.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)