From c2cb818f8766cbaf7a8bd8c5aafd3cced2dfd18f Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Tue, 9 May 2023 07:45:54 +0200 Subject: [PATCH] chore(api): add test utilities (#50289) --- api/jest.utils.ts | 66 +++++++++++++++++++++++++++++++++++++++--- api/src/server.test.ts | 24 ++++++++++----- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/api/jest.utils.ts b/api/jest.utils.ts index d50d12104d8..04369cdc7b8 100644 --- a/api/jest.utils.ts +++ b/api/jest.utils.ts @@ -7,10 +7,68 @@ declare global { var fastifyTestInstance: Awaited> | undefined; } -export function superGet(endpoint: string): request.Test { - return request(fastifyTestInstance?.server) - .get(endpoint) - .set('Origin', 'https://www.freecodecamp.org'); +type Options = { + sendCSRFToken: boolean; +}; + +// TODO: remove this function and use superRequest instead +export function superPut( + resource: string, + setCookies: string[], + opts?: Options +): request.Test { + return superRequest( + resource, + { + method: 'PUT', + setCookies + }, + opts + ); +} + +/* eslint-disable @typescript-eslint/naming-convention */ +const requests = { + GET: (resource: string) => request(fastifyTestInstance?.server).get(resource), + POST: (resource: string) => + request(fastifyTestInstance?.server).post(resource), + PUT: (resource: string) => request(fastifyTestInstance?.server).put(resource) +}; +/* eslint-enable @typescript-eslint/naming-convention */ + +export const getCsrfToken = (setCookies: string[]): string | undefined => { + const csrfSetCookie = setCookies.find(str => str.includes('csrf_token')); + const [csrfCookie] = csrfSetCookie?.split(';') ?? []; + const [_key, csrfToken] = csrfCookie?.split('=') ?? []; + + return csrfToken; +}; + +export function superRequest( + resource: string, + config: { + method: 'GET' | 'POST' | 'PUT'; + setCookies?: string[]; + }, + options?: Options +): request.Test { + const { method, setCookies } = config; + const { sendCSRFToken = true } = options ?? {}; + + const req = requests[method](resource).set( + 'Origin', + 'https://www.freecodecamp.org' + ); + + if (setCookies) { + void req.set('Cookie', setCookies); + } + + const csrfToken = (setCookies && getCsrfToken(setCookies)) ?? ''; + if (sendCSRFToken) { + void req.set('CSRF-Token', csrfToken); + } + return req; } export function setupServer(): void { diff --git a/api/src/server.test.ts b/api/src/server.test.ts index 913a4a05ea6..f7b4db237a5 100644 --- a/api/src/server.test.ts +++ b/api/src/server.test.ts @@ -1,4 +1,4 @@ -import { setupServer, superGet } from '../jest.utils'; +import { setupServer, superRequest } from '../jest.utils'; import { HOME_LOCATION } from './utils/env'; jest.mock('./utils/env', () => { @@ -14,17 +14,17 @@ describe('production', () => { setupServer(); describe('GET /', () => { test('have a 200 response', async () => { - const res = await superGet('/'); + const res = await superRequest('/', { method: 'GET' }); expect(res.statusCode).toBe(200); }); test('return { "hello": "world"}', async () => { - const res = await superGet('/'); + const res = await superRequest('/', { method: 'GET' }); expect(res.body).toEqual({ hello: 'world' }); }); test('should have OWASP recommended headers', async () => { - const res = await superGet('/'); + const res = await superRequest('/', { method: 'GET' }); expect(res.headers).toMatchObject({ 'cache-control': 'no-store', 'content-security-policy': "frame-ancestors 'none'", @@ -45,7 +45,10 @@ describe('production', () => { ])( 'should have Access-Control-Allow-Origin header for %s', async origin => { - const res = await superGet('/').set('origin', origin); + const res = await superRequest('/', { method: 'GET' }).set( + 'origin', + origin + ); expect(res.headers).toMatchObject({ 'access-control-allow-origin': origin }); @@ -53,14 +56,17 @@ describe('production', () => { ); test('should have HOME_LOCATION Access-Control-Allow-Origin header for other origins', async () => { - const res = await superGet('/').set('origin', 'https://www.google.com'); + const res = await superRequest('/', { method: 'GET' }).set( + 'origin', + 'https://www.google.com' + ); expect(res.headers).toMatchObject({ 'access-control-allow-origin': HOME_LOCATION }); }); test('should have Access-Control-Allow-(Headers+Credentials) headers', async () => { - const res = await superGet('/'); + const res = await superRequest('/', { method: 'GET' }); expect(res.headers).toMatchObject({ 'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept', @@ -71,7 +77,9 @@ describe('production', () => { describe('GET /documentation', () => { test('should have OWASP recommended headers, except content-type', async () => { - const res = await superGet('/documentation/static/index.html'); + const res = await superRequest('/documentation/static/index.html', { + method: 'GET' + }); expect(res.headers).toMatchObject({ 'cache-control': 'no-store', 'content-security-policy': "frame-ancestors 'none'",