From 46b910ee408d8c32ff289ba72ee4acc2ad956e77 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Thu, 24 Apr 2025 19:03:49 +0200 Subject: [PATCH] dev(api): add build options to test env (#59957) --- api/jest.utils.ts | 8 +++-- api/package.json | 1 + api/src/app.ts | 68 +++++++++++++++++++++++++++++++++++++++++- api/src/server.ts | 75 ++--------------------------------------------- 4 files changed, 77 insertions(+), 75 deletions(-) diff --git a/api/jest.utils.ts b/api/jest.utils.ts index bacbccf46fc..1b47b0f04c2 100644 --- a/api/jest.utils.ts +++ b/api/jest.utils.ts @@ -1,6 +1,6 @@ import request from 'supertest'; -import { build } from './src/app'; +import { build, buildOptions } from './src/app'; import { createUserInput } from './src/utils/create-user'; import { examJson } from './__mocks__/exam'; import { CSRF_COOKIE, CSRF_HEADER } from './src/plugins/csrf'; @@ -158,7 +158,11 @@ const indexData: IndexData[] = [ export function setupServer(): void { let fastify: FastifyTestInstance; beforeAll(async () => { - fastify = await build(); + if (process.env.FCC_ENABLE_TEST_LOGGING !== 'true') { + // @ts-expect-error Disable logging by unsetting logger + buildOptions.logger = undefined; + } + fastify = await build(buildOptions); await fastify.ready(); // Prisma does not support TTL indexes in the schema yet, so, to avoid diff --git a/api/package.json b/api/package.json index b7011341a4e..e8c4232e667 100644 --- a/api/package.json +++ b/api/package.json @@ -70,6 +70,7 @@ "develop": "tsx watch --clear-screen=false src/server.ts", "start": "FREECODECAMP_NODE_ENV=production node dist/server.js", "test": "jest --force-exit", + "test-with-logging": "FCC_ENABLE_TEST_LOGGING=true pnpm run test", "prisma": "dotenv -e ../.env prisma", "postinstall": "prisma generate", "generate-exams": "tsx tools/exam-environment/generate/index.ts", diff --git a/api/src/app.ts b/api/src/app.ts index 9c98251a8b5..43177afe14a 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,3 +1,5 @@ +import { randomBytes } from 'crypto'; +import { isEmpty } from 'lodash'; import fastifyAccepts from '@fastify/accepts'; import fastifySwagger from '@fastify/swagger'; import fastifySwaggerUI from '@fastify/swagger-ui'; @@ -9,6 +11,7 @@ import Fastify, { FastifyBaseLogger, FastifyHttpOptions, FastifyInstance, + FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault @@ -42,7 +45,9 @@ import { FCC_ENABLE_EXAM_ENVIRONMENT, FCC_ENABLE_SENTRY_ROUTES, GROWTHBOOK_FASTIFY_API_HOST, - GROWTHBOOK_FASTIFY_CLIENT_KEY + GROWTHBOOK_FASTIFY_CLIENT_KEY, + FREECODECAMP_NODE_ENV, + FCC_API_LOG_LEVEL } from './utils/env'; import { isObjectID } from './utils/validation'; import { @@ -78,6 +83,67 @@ ajv.addFormat('objectid', { validate: (str: string) => isObjectID(str) }); +const requestSerializer = (req: FastifyRequest) => { + const method = req.method || 'METHOD not found'; + const url = req.url || 'URL not found'; + const headers = req.headers || 'HEADERS not found'; + const xForwardedFor = Array.isArray(req.headers['x-forwarded-for']) + ? req.headers['x-forwarded-for'][0] + : req.headers['x-forwarded-for']; + const ip = + xForwardedFor || req.headers['x-real-ip'] || req.ip || 'IP not found'; + const query = isEmpty(req.query) ? 'QUERY not found' : req.query; + const hostname = req.hostname || 'HOSTNAME not found'; + const remotePort = req.socket.remotePort || 'REMOTE_PORT not found'; + + return { + REQ_ID: req.id, + METHOD: method, + URL: url, + IP: ip, + HOSTNAME: hostname, + REMOTE_PORT: remotePort, + QUERY: query, + HEADERS: headers + }; +}; + +const envToLogger = { + development: { + transport: { + target: 'pino-pretty', + options: { + singleLine: true, + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname' + } + }, + level: FCC_API_LOG_LEVEL || 'info', + serializers: { + req: (req: FastifyRequest) => { + return { + method: req.method, + url: req.url + }; + } + } + // No need to redact in development + }, + production: { + level: FCC_API_LOG_LEVEL || 'info', + serializers: { + req: requestSerializer + }, + redact: ['req.HEADERS.cookie'] + } +}; + +export const buildOptions = { + logger: envToLogger[FREECODECAMP_NODE_ENV] ?? true, + genReqId: () => randomBytes(8).toString('hex'), + disableRequestLogging: true +}; + /** * Top-level wrapper to instantiate the API server. This is where all middleware and * routes should be mounted. diff --git a/api/src/server.ts b/api/src/server.ts index 3ea66d1c628..84b106c4373 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -1,79 +1,10 @@ import './instrument'; -import { randomBytes } from 'crypto'; - -import { FastifyRequest } from 'fastify'; -import { isEmpty } from 'lodash'; - -import { build } from './app'; -import { - FREECODECAMP_NODE_ENV, - FCC_API_LOG_LEVEL, - HOST, - PORT -} from './utils/env'; - -const requestSerializer = (req: FastifyRequest) => { - const method = req.method || 'METHOD not found'; - const url = req.url || 'URL not found'; - const headers = req.headers || 'HEADERS not found'; - const xForwardedFor = Array.isArray(req.headers['x-forwarded-for']) - ? req.headers['x-forwarded-for'][0] - : req.headers['x-forwarded-for']; - const ip = - xForwardedFor || req.headers['x-real-ip'] || req.ip || 'IP not found'; - const query = isEmpty(req.query) ? 'QUERY not found' : req.query; - const hostname = req.hostname || 'HOSTNAME not found'; - const remotePort = req.socket.remotePort || 'REMOTE_PORT not found'; - - return { - REQ_ID: req.id, - METHOD: method, - URL: url, - IP: ip, - HOSTNAME: hostname, - REMOTE_PORT: remotePort, - QUERY: query, - HEADERS: headers - }; -}; - -const envToLogger = { - development: { - transport: { - target: 'pino-pretty', - options: { - singleLine: true, - translateTime: 'HH:MM:ss Z', - ignore: 'pid,hostname' - } - }, - level: FCC_API_LOG_LEVEL || 'info', - serializers: { - req: (req: FastifyRequest) => { - return { - method: req.method, - url: req.url - }; - } - } - // No need to redact in development - }, - production: { - level: FCC_API_LOG_LEVEL || 'info', - serializers: { - req: requestSerializer - }, - redact: ['req.HEADERS.cookie'] - } -}; +import { build, buildOptions } from './app'; +import { HOST, PORT } from './utils/env'; const start = async () => { - const fastify = await build({ - logger: envToLogger[FREECODECAMP_NODE_ENV] ?? true, - genReqId: () => randomBytes(8).toString('hex'), - disableRequestLogging: true - }); + const fastify = await build(buildOptions); try { const port = Number(PORT); fastify.log.info(`Starting server on port ${port}`);