test(server): stop racing supertest's own listener in the scope e2e

Three cases fired their reads through `Promise.all`. The Nest app under
test is never told to listen, so supertest binds an ephemeral port on the
first request; four of those at once race four `listen(0)` calls on the
same server and the losers come back as ECONNRESET. It held here and
failed on a loaded runner. The reads run one after the other now, which
is all they ever needed.
This commit is contained in:
Maurice
2026-09-12 23:55:22 +02:00
parent 105bba2b95
commit 8a28d4dd4a
+30 -16
View File
@@ -226,6 +226,20 @@ describe('Public API v1 e2e (real guard + real SQL)', () => {
return token ? req.set('Authorization', `Bearer ${token}`) : req; return token ? req.set('Authorization', `Bearer ${token}`) : req;
}; };
/**
* Several reads in a row, one after the other.
*
* Not `Promise.all`: the Nest app is never told to listen, so supertest binds
* an ephemeral port itself on the first request. Fired in parallel, four
* requests race four `listen(0)` calls on the same server and the losers come
* back as ECONNRESET — reliably on a loaded CI runner, almost never here.
*/
const getEach = async (...calls: Array<[string, string?]>) => {
const out = [];
for (const [path, token] of calls) out.push(await get(path, token));
return out;
};
describe('authentication', () => { describe('authentication', () => {
it('401s without a token', async () => { it('401s without a token', async () => {
const res = await get('/api/v1/trips'); const res = await get('/api/v1/trips');
@@ -448,12 +462,12 @@ describe('Public API v1 e2e (real guard + real SQL)', () => {
}); });
it('PUBAPI-SCOPE-002: and still reads every section, which is the whole promise', async () => { it('PUBAPI-SCOPE-002: and still reads every section, which is the whole promise', async () => {
const [trips, bucket, stats, trip] = await Promise.all([ const [trips, bucket, stats, trip] = await getEach(
get('/api/v1/trips', ADA_TOKEN), ['/api/v1/trips', ADA_TOKEN],
get('/api/v1/bucket-list', ADA_TOKEN), ['/api/v1/bucket-list', ADA_TOKEN],
get('/api/v1/stats', ADA_TOKEN), ['/api/v1/stats', ADA_TOKEN],
get('/api/v1/trips/1', ADA_TOKEN), ['/api/v1/trips/1', ADA_TOKEN],
]); );
expect([trips.status, bucket.status, stats.status, trip.status]).toEqual([200, 200, 200, 200]); expect([trips.status, bucket.status, stats.status, trip.status]).toEqual([200, 200, 200, 200]);
expect(stats.body).toMatchObject({ total_trips: 2, total_countries: 2 }); expect(stats.body).toMatchObject({ total_trips: 2, total_countries: 2 });
// The full payload, not a narrowed one: every section is there. // The full payload, not a narrowed one: every section is there.
@@ -498,12 +512,12 @@ describe('Public API v1 e2e (real guard + real SQL)', () => {
* 401 and does nothing at all for a 403. * 401 and does nothing at all for a 403.
*/ */
it('PUBAPI-SCOPE-006: the refusal is a 403 and leaves both 401 bodies exactly as they were', async () => { it('PUBAPI-SCOPE-006: the refusal is a 403 and leaves both 401 bodies exactly as they were', async () => {
const [none, unknown, wrongKind, noneOnStats] = await Promise.all([ const [none, unknown, wrongKind, noneOnStats] = await getEach(
get('/api/v1/trips'), ['/api/v1/trips'],
get('/api/v1/trips', 'trek_' + 'z'.repeat(48)), ['/api/v1/trips', 'trek_' + 'z'.repeat(48)],
get('/api/v1/trips', MCP_TOKEN), ['/api/v1/trips', MCP_TOKEN],
get('/api/v1/stats'), ['/api/v1/stats'],
]); );
expect(none.status).toBe(401); expect(none.status).toBe(401);
expect(none.body).toEqual({ error: 'API token required', code: 'API_TOKEN_REQUIRED' }); expect(none.body).toEqual({ error: 'API token required', code: 'API_TOKEN_REQUIRED' });
expect(unknown.status).toBe(401); expect(unknown.status).toBe(401);
@@ -561,10 +575,10 @@ describe('Public API v1 e2e (real guard + real SQL)', () => {
// widen because its list became unreadable — a hand-edited row, or a scope // widen because its list became unreadable — a hand-edited row, or a scope
// constant a later version renamed away. Failing closed turns that into a // constant a later version renamed away. Failing closed turns that into a
// support ticket instead of a key quietly reading every trip. // support ticket instead of a key quietly reading every trip.
const [bucket, trip] = await Promise.all([ const [bucket, trip] = await getEach(
get('/api/v1/bucket-list', BROKEN_SCOPES_TOKEN), ['/api/v1/bucket-list', BROKEN_SCOPES_TOKEN],
get('/api/v1/trips/1', BROKEN_SCOPES_TOKEN), ['/api/v1/trips/1', BROKEN_SCOPES_TOKEN],
]); );
expect(bucket.status).toBe(403); expect(bucket.status).toBe(403);
expect(bucket.body).toMatchObject({ code: 'API_SCOPE_FORBIDDEN' }); expect(bucket.body).toMatchObject({ code: 'API_SCOPE_FORBIDDEN' });
expect(trip.status).toBe(403); expect(trip.status).toBe(403);