From f35ff3a1a95bd47e8154e7ed49c36506cbc8d005 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Tue, 10 Oct 2023 16:52:12 +0300 Subject: [PATCH] Augment supertest .expect(status) to log the response body when failing Change-type: patch Change-type: patch --- test/test-lib/init-tests.ts | 7 +++++- test/test-lib/supertest.ts | 38 +++++++++++++++++++++++++++++++- typings/supertest-extension.d.ts | 10 +++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 typings/supertest-extension.d.ts diff --git a/test/test-lib/init-tests.ts b/test/test-lib/init-tests.ts index 2a6221aa3..b538e1d20 100644 --- a/test/test-lib/init-tests.ts +++ b/test/test-lib/init-tests.ts @@ -1,6 +1,10 @@ import jsonwebtoken from 'jsonwebtoken'; import * as fixtures from './fixtures'; -import { supertest, UserObjectParam } from './supertest'; +import { + supertest, + augmentStatusAssertionError, + UserObjectParam, +} from './supertest'; import { version } from './versions'; import { getContractRepos, @@ -8,6 +12,7 @@ import { } from '../../src/features/contracts'; export const preInit = async () => { + augmentStatusAssertionError(); await import('./aws-mock'); await import('./contracts-mock'); diff --git a/test/test-lib/supertest.ts b/test/test-lib/supertest.ts index 32a7031d9..821c17174 100644 --- a/test/test-lib/supertest.ts +++ b/test/test-lib/supertest.ts @@ -1,13 +1,49 @@ import { app } from '../../init'; import $supertest from 'supertest'; import { User } from '../../src/infra/auth/jwt-passport'; +import { ThisShouldNeverHappenError } from '../../src/infra/error-handling'; export type UserObjectParam = Partial; +export const augmentStatusAssertionError = () => { + const originalExpect: $supertest.Test['expect'] = + $supertest.Test.prototype.expect; + /** + * This enhances `.expect(statusCode, ...)` to also log the response body when + * the statusCode is different than expected, to make the original error more useful. + */ + $supertest.Test.prototype.expect = function (this: $supertest.Test, ...args) { + const [expectedStatus] = args; + let supertestFluentChain = this; + if (typeof expectedStatus === 'number') { + // TODO: Switch `.bind()` to `.call()` once TS is able to pick the correct overload. + supertestFluentChain = originalExpect.bind(supertestFluentChain)( + (res) => { + const error = this._assertStatus(expectedStatus, res); + if (error) { + error.message += `, with response body:\n${JSON.stringify( + res.body, + null, + 2, + )}`; + throw error; + } + }, + ); + } + return originalExpect.apply(supertestFluentChain, args); + } satisfies typeof originalExpect; +}; + export const supertest = function (user?: string | UserObjectParam) { // Can be an object with `token`, a JWT string or an API key string let token = user; - if (typeof user === 'object' && user.token) { + if (user != null && typeof user === 'object') { + if (user.token == null) { + throw ThisShouldNeverHappenError( + 'Heads-up: You provided an object as a parameter to supertest that does not include a token, making requests that require authentication to always return 401!!!', + ); + } token = user.token; } // We have to cast `as any` because the types are poorly maintained diff --git a/typings/supertest-extension.d.ts b/typings/supertest-extension.d.ts new file mode 100644 index 000000000..6c32298bc --- /dev/null +++ b/typings/supertest-extension.d.ts @@ -0,0 +1,10 @@ +import 'supertest'; + +// Augment supertest +declare module 'supertest' { + interface Test { + _assertStatus(status: number, res: Response): Error | undefined; + } + + function Test(app: any, method: string, path: string): Test; +}