diff --git a/.github/workflows/node.js-tests-upcoming.yml b/.github/workflows/node.js-tests-upcoming.yml index e411e5483da..f02fd055bd0 100644 --- a/.github/workflows/node.js-tests-upcoming.yml +++ b/.github/workflows/node.js-tests-upcoming.yml @@ -89,6 +89,13 @@ jobs: echo 'SHOW_NEW_CURRICULUM=true' >> .env cat .env + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.9.0 + with: + mongodb-version: 6.0 + mongodb-replica-set: test-rs + mongodb-port: 27017 + - name: Install Dependencies run: | pnpm install diff --git a/.github/workflows/node.js-tests.yml b/.github/workflows/node.js-tests.yml index 6b83b056bdc..4cac9071e6b 100644 --- a/.github/workflows/node.js-tests.yml +++ b/.github/workflows/node.js-tests.yml @@ -100,6 +100,13 @@ jobs: cp sample.env .env cat .env + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.9.0 + with: + mongodb-version: 6.0 + mongodb-replica-set: test-rs + mongodb-port: 27017 + - name: Install Dependencies run: | echo pnpm version $(pnpm -v) @@ -141,6 +148,13 @@ jobs: echo 'SHOW_UPCOMING_CHANGES=true' >> .env cat .env + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.9.0 + with: + mongodb-version: 6.0 + mongodb-replica-set: test-rs + mongodb-port: 27017 + - name: Install Dependencies run: | echo pnpm version $(pnpm -v) @@ -182,6 +196,13 @@ jobs: cp sample.env .env cat .env + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.9.0 + with: + mongodb-version: 6.0 + mongodb-replica-set: test-rs + mongodb-port: 27017 + - name: Install Dependencies env: CURRICULUM_LOCALE: ${{ matrix.locale }} diff --git a/api/index.ts b/api/app.ts similarity index 66% rename from api/index.ts rename to api/app.ts index e88c04a721b..ca9c93d2deb 100644 --- a/api/index.ts +++ b/api/app.ts @@ -1,5 +1,12 @@ import fastifyAuth0 from 'fastify-auth0-verify'; -import Fastify from 'fastify'; +import Fastify, { + FastifyBaseLogger, + FastifyHttpOptions, + FastifyInstance, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault +} from 'fastify'; import middie from '@fastify/middie'; import fastifySession from '@fastify/session'; import fastifyCookie from '@fastify/cookie'; @@ -18,38 +25,26 @@ import { AUTH0_AUDIENCE, AUTH0_DOMAIN, NODE_ENV, - PORT, MONGOHQ_URL, SESSION_SECRET } from './utils/env'; -const envToLogger = { - development: { - transport: { - target: 'pino-pretty', - options: { - translateTime: 'HH:MM:ss Z', - ignore: 'pid,hostname' - } - }, - level: 'debug' - }, - // TODO: is this the right level for production or should we use 'error'? - production: { level: 'fatal' }, - test: false -}; +export type FastifyInstanceWithTypeProvider = FastifyInstance< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + FastifyBaseLogger, + TypeBoxTypeProvider +>; -const fastify = Fastify({ - logger: envToLogger[NODE_ENV] -}).withTypeProvider(); +export const build = async ( + options: FastifyHttpOptions = {} +): Promise => { + const fastify = Fastify(options).withTypeProvider(); -export type FastifyInstanceWithTypeProvider = typeof fastify; - -fastify.get('/', async (_request, _reply) => { - return { hello: 'world' }; -}); - -const start = async () => { + fastify.get('/', async (_request, _reply) => { + return { hello: 'world' }; + }); // NOTE: Awaited to ensure `.use` is registered on `fastify` await fastify.register(middie); await fastify.register(fastifyCookie); @@ -83,15 +78,5 @@ const start = async () => { void fastify.register(testRoutes); void fastify.register(auth0Routes, { prefix: '/auth0' }); void fastify.register(testValidatedRoutes); - - try { - const port = Number(PORT); - fastify.log.info(`Starting server on port ${port}`); - await fastify.listen({ port }); - } catch (err) { - fastify.log.error(err); - process.exit(1); - } + return fastify; }; - -void start(); diff --git a/api/index.test.ts b/api/index.test.ts deleted file mode 100644 index 4489867cc6b..00000000000 --- a/api/index.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import request, { Response } from 'supertest'; -import { API_LOCATION as api } from './utils/env'; - -describe('GET /', () => { - let res: undefined | Response; - - beforeAll(async () => { - res = await request(api).get('/'); - }); - - test('have a 200 response', () => { - expect(res?.statusCode).toBe(200); - }); - - test('return { "hello": "world"}', () => { - expect(res?.body).toEqual({ hello: 'world' }); - }); -}); diff --git a/api/package.json b/api/package.json index d7c025c5266..d9aa3dfe87d 100644 --- a/api/package.json +++ b/api/package.json @@ -49,9 +49,9 @@ }, "scripts": { "build": "tsc", - "develop": "nodemon index.ts", - "start": "NODE_ENV=production node index.ts", - "test": "node --test -r ts-node/register **/*.test.ts", + "develop": "nodemon server.ts", + "start": "NODE_ENV=production node server.js", + "test": "jest --force-exit", "prisma": "MONGOHQ_URL=mongodb://localhost:27017/freecodecamp?directConnection=true prisma", "postinstall": "prisma generate" }, diff --git a/api/routes/validation-test.ts b/api/routes/validation-test.ts index 1726f9500c5..b7a7c417306 100644 --- a/api/routes/validation-test.ts +++ b/api/routes/validation-test.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox'; -import type { FastifyInstanceWithTypeProvider } from '..'; +import type { FastifyInstanceWithTypeProvider } from '../app'; import { responseSchema, subSchema } from '../schemas/example'; export const testValidatedRoutes = ( diff --git a/api/server.test.ts b/api/server.test.ts new file mode 100644 index 00000000000..a192e83b77a --- /dev/null +++ b/api/server.test.ts @@ -0,0 +1,28 @@ +import request, { Response } from 'supertest'; + +import { build } from './app'; + +describe('GET /', () => { + let res: undefined | Response; + let fastify: undefined | Awaited>; + + beforeAll(async () => { + fastify = await build(); + await fastify.ready(); + }, 20000); + + afterAll(async () => { + // Due to a prisma bug, this is not enough, we need to --force-exit jest: + // https://github.com/prisma/prisma/issues/18146 + await fastify?.close(); + }); + + test('have a 200 response', async () => { + res = await request(fastify?.server).get('/'); + expect(res?.statusCode).toBe(200); + }); + + test('return { "hello": "world"}', () => { + expect(res?.body).toEqual({ hello: 'world' }); + }); +}); diff --git a/api/server.ts b/api/server.ts new file mode 100644 index 00000000000..2956b0f9b52 --- /dev/null +++ b/api/server.ts @@ -0,0 +1,33 @@ +import { build } from './app'; + +import { NODE_ENV, PORT } from './utils/env'; + +const envToLogger = { + development: { + transport: { + target: 'pino-pretty', + options: { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname' + } + }, + level: 'debug' + }, + // TODO: is this the right level for production or should we use 'error'? + production: { level: 'fatal' }, + test: undefined +}; + +const start = async () => { + const fastify = await build({ logger: envToLogger[NODE_ENV] }); + try { + const port = Number(PORT); + fastify.log.info(`Starting server on port ${port}`); + await fastify.listen({ port }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +void start(); diff --git a/api/utils/env.ts b/api/utils/env.ts index eaab4b851ce..8c0867485ea 100644 --- a/api/utils/env.ts +++ b/api/utils/env.ts @@ -31,9 +31,14 @@ assert.ok(process.env.AUTH0_AUDIENCE); assert.ok(process.env.API_LOCATION); assert.ok(process.env.SESSION_SECRET); -if (process.env.NODE_ENV !== 'development') { +if (process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test') { assert.ok(process.env.PORT); assert.ok(process.env.MONGOHQ_URL); + assert.notEqual( + process.env.SESSION_SECRET, + 'a_thirty_two_plus_character_session_secret', + 'The session secret should be changed from the default value.' + ); } export const MONGOHQ_URL = diff --git a/package.json b/package.json index a125ab84c39..86715122a20 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "storybook": "cd ./tools/ui-components && pnpm run storybook", "test": "run-s create:* build:curriculum build-workers test:*", "test:source": "jest", + "test:api": "cd api && jest --force-exit", "test:curriculum": "cd ./curriculum && pnpm test", - "test-api": "cd api && jest", "test-curriculum-full-output": "cd ./curriculum && pnpm run test:full-output", "test-client": "jest client", "test-config": "jest config", diff --git a/sample.env b/sample.env index 658b3345974..3b15fdcabf8 100644 --- a/sample.env +++ b/sample.env @@ -13,7 +13,7 @@ AUTH0_DOMAIN=example.auth0.com AUTH0_AUDIENCE=sample_audience # Session, Cookie and JWT encryption strings -SESSION_SECRET=a_session_secret +SESSION_SECRET=a_thirty_two_plus_character_session_secret COOKIE_SECRET=a_cookie_secret JWT_SECRET=a_jwt_secret