Skip to content

Commit

Permalink
Add first backend tests (#128)
Browse files Browse the repository at this point in the history
* Set up vitest, Node 18, first tests

* Deterministic test data, clear DB before each test

* More tests for GET requests

* Run tests in Actions workflow

* Remove --ignore-scripts to ensure bcrypt builds

* Fix test runs with fresh database

* setup pnpm correctly and bump it

---------

Co-authored-by: Kalle Ahlström <[email protected]>
  • Loading branch information
PurkkaKoodari and kahlstrm authored May 14, 2024
1 parent cc310ba commit bb14cb8
Show file tree
Hide file tree
Showing 24 changed files with 2,147 additions and 996 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# This file contains example configuration for Ilmomasiina.
# Copy it to .env, read thoroughly and update to match your environment.


# Server listening address (defaults to localhost; set to 0.0.0.0 by Dockerfiles)
#HOST=0.0.0.0
# Server port
Expand Down
55 changes: 55 additions & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This file contains the bare minimum settings to test Ilmomasiina.
# Database settings are not set here, as your setup for that may vary.


# Database settings

# Choose mysql or postgres
#DB_DIALECT=<mysql|postgres>
#DB_HOST=localhost
#DB_PORT=<3306|5432>
#DB_USER=ilmo_user
#DB_PASSWORD=password
#DB_DATABASE=ilmomasiina
#DB_SSL=false


# Authentication secrets

# Set both of these to different secure random strings.
# You can generate one with the command:
# openssl rand -hex 32
NEW_EDIT_TOKEN_SECRET=insecure_but_that_is_ok_in_testing_1
FEATHERS_AUTH_SECRET=insecure_but_that_is_ok_in_testing_2


# Mail settings

# Mail sender
MAIL_FROM=<[email protected]>


# URL settings

# Canonical base URL for the app. Used by the backend.
# Include $PATH_PREFIX, but NOT a final "/".
# e.g. "http://example.com" or "http://example.com/ilmo"
BASE_URL=http://localhost:3000


# Branding settings

# Website strings (requires website rebuild)
BRANDING_HEADER_TITLE_TEXT=Ilmomasiina
BRANDING_FOOTER_GDPR_TEXT=Tietosuoja
BRANDING_FOOTER_GDPR_LINK=http://example.com/privacy
BRANDING_FOOTER_HOME_TEXT=Example.com
BRANDING_FOOTER_HOME_LINK=http://example.com
[email protected]

# Email strings
BRANDING_MAIL_FOOTER_TEXT=Ilmomasiina test email footer
BRANDING_MAIL_FOOTER_LINK=https://example.com

# iCalendar exported calendar name
BRANDING_ICAL_CALENDAR_NAME=Ilmomasiina
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
"**/dist/**",
"**/build/**",
".eslintrc.js",
"jest.config.js",
"*.scss",
"*.json"
],
Expand Down
108 changes: 108 additions & 0 deletions .github/workflows/lint-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Lint & test

on:
push:
branches: [dev]
pull_request:
branches: [dev]
types: [synchronize, opened, reopened, ready_for_review]
workflow_call:

jobs:
lint:
name: ESLint
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:

- name: Check out the repo
uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run ESLint
run: npm run lint

typecheck:
name: Type checking
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:

- name: Check out the repo
uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run type checking
run: npm run typecheck

test:
name: Tests
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5

services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
# https://docs.github.com/en/actions/using-containerized-services/creating-postgresql-service-containers
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:

- name: Check out the repo
uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run tests
run: |
cp .env.test.example .env.test
npm run test
# Non-database test variables are kept in a separate file
env:
DB_DIALECT: postgres
DB_HOST: localhost
DB_USER: postgres
DB_PASSWORD: testpass
DB_DATABASE: testdb
THIS_IS_A_TEST_DB_AND_CAN_BE_WIPED: 1
38 changes: 0 additions & 38 deletions .github/workflows/lint.yml

This file was deleted.

6 changes: 2 additions & 4 deletions .github/workflows/npm-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v4

- uses: pnpm/action-setup@v3
with:
version: 8
- uses: pnpm/action-setup@v4

- name: Setup Node.js for NPM
uses: actions/setup-node@v4
Expand All @@ -26,7 +24,7 @@ jobs:
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
run: pnpm install --frozen-lockfile

- name: Build packages
run: |
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16
18
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Build stage:
FROM node:16-alpine as builder
FROM node:18-alpine as builder

RUN apk add --no-cache brotli

# Build-time env variables
ARG SENTRY_DSN
ARG PATH_PREFIX
Expand All @@ -18,7 +20,7 @@ COPY packages /opt/ilmomasiina/packages
WORKDIR /opt/ilmomasiina

# Install dependencies (we're running as root, so the postinstall script doesn't run automatically)
RUN npm install -g pnpm@8 && pnpm install --frozen-lockfile
RUN corepack enable && pnpm install --frozen-lockfile

# Default to production (after pnpm install, so we get our types etc.)
ENV NODE_ENV=production
Expand All @@ -31,7 +33,7 @@ RUN find packages/ilmomasiina-frontend/build -type f\
-regex ".*\.\(js\|json\|html\|map\|css\|svg\|ico\|txt\)" -exec gzip -k "{}" \; -exec brotli "{}" \;

# Main stage:
FROM node:16-alpine
FROM node:18-alpine

# Accept VERSION at build time, pass to backend server
ARG VERSION
Expand All @@ -49,7 +51,7 @@ COPY packages /opt/ilmomasiina/packages
WORKDIR /opt/ilmomasiina

# Install dependencies for backend only
RUN npm install -g pnpm@8 && pnpm install --frozen-lockfile --prod --filter @tietokilta/ilmomasiina-backend --filter @tietokilta/ilmomasiina-models
RUN corepack enable && pnpm install --frozen-lockfile --prod --filter @tietokilta/ilmomasiina-backend --filter @tietokilta/ilmomasiina-models

# Copy compiled ilmomasiina-models from build stage
COPY --from=builder /opt/ilmomasiina/packages/ilmomasiina-models/dist /opt/ilmomasiina/packages/ilmomasiina-models/dist
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Node modules will be installed at the build phase and included in the container image.
# Sources and other files are intended to be provided using bind mounts.

FROM node:16-alpine
FROM node:18-alpine

WORKDIR /opt/ilmomasiina

Expand Down
15 changes: 15 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,18 @@ pre-configured PostgreSQL server, so an external database server is not required
Due to how the dev Docker is set up, you will still need to rebuild the development image if you change the
dependencies, package.json or ESLint configs. You'll also need Node.js and pnpm installed locally to do that.
### Test database setup
To run tests, you'll likely want another test database so test data doesn't clutter your manual development database.
1. Follow the same steps as in [the usual database setup](#database-setup), but name the database something different. This example uses `ilmo_test`.
2. Create a `.env.test` file at the root of this repository. Assuming your test database runs on the same MySQL/Postgres server, just put this in:
```shell
DB_DATABASE=ilmo_test
THIS_IS_A_TEST_DB_AND_CAN_BE_WIPED=1
```
- The latter line is required to avoid accidental loss of data, because the test suite truncates all database
tables whenever you run `npm test`. **Make absolutely sure you're not using an important database for testing.**
You most likely want to move your regular database configs to `.env.development` or `.env.production`, and copy
the necessary bits over to `.env.test`.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"private": true,
"license": "MIT",
"engines": {
"node": "^16",
"node": "^18",
"npm": "^7"
},
"packageManager": "[email protected]",
"scripts": {
"bootstrap": "pnpm install",
"clean": "pnpm run -r clean",
Expand Down Expand Up @@ -37,7 +38,6 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"pnpm": "^8.15.3",
"typescript": "~5.2.2"
},
"browserslist": {
Expand Down
7 changes: 5 additions & 2 deletions packages/ilmomasiina-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build": "tsc --build tsconfig.build.json",
"clean": "rimraf dist",
"typecheck": "tsc --build tsconfig.build.json",
"test": "vitest",
"filldb": "ts-node -r tsconfig-paths/register --project tsconfig.json test/fillDatabase.ts"
},
"betterScripts": {
Expand Down Expand Up @@ -75,14 +76,16 @@
"@types/email-templates": "^10.0.4",
"@types/http-errors": "^2.0.4",
"@types/lodash": "^4.14.202",
"@types/node": "^16.18.83",
"@types/node": "^18",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.14",
"@types/nodemailer-mailgun-transport": "^1.4.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0",
"typescript": "~5.2.2"
"typescript": "~5.2.2",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^1.3.1"
}
}
23 changes: 13 additions & 10 deletions packages/ilmomasiina-backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ export default async function initApp(): Promise<FastifyInstance> {

const server = fastify({
trustProxy: config.isAzure || config.trustProxy, // Get IPs from X-Forwarded-For
logger: true, // Enable logger
logger: config.nodeEnv !== 'test', // Enable logger when not testing
});
server.setValidatorCompiler(({ httpPart, schema }) => (
httpPart === 'body' ? bodyCompiler.compile(schema) : defaultCompiler.compile(schema)
));

// Enable admin registration if no users are present
// Enable admin registration if no users are present.
// The "cached" flag is present to prevent an unnecessary DB check on every /api/events call.
server.decorate('initialSetupDone', await isInitialSetupDone());

// Register fastify-sensible (https://github.com/fastify/fastify-sensible)
Expand Down Expand Up @@ -125,17 +126,19 @@ export default async function initApp(): Promise<FastifyInstance> {
adminSession: new AdminAuthSession(config.feathersAuthSecret),
});

// Every minute, remove signups that haven't been confirmed fast enough
cron.schedule('* * * * *', deleteUnconfirmedSignups);
if (config.nodeEnv !== 'test') {
// Every minute, remove signups that haven't been confirmed fast enough
cron.schedule('* * * * *', deleteUnconfirmedSignups);

// Daily at 8am, anonymize old signups
cron.schedule('0 8 * * *', anonymizeOldSignups);
// Daily at 8am, anonymize old signups
cron.schedule('0 8 * * *', anonymizeOldSignups);

// Daily at 8am, delete deleted items from the database
cron.schedule('0 8 * * *', removeDeletedData);
// Daily at 8am, delete deleted items from the database
cron.schedule('0 8 * * *', removeDeletedData);

// Daily at 8am, delete old audit logs
cron.schedule('0 8 * * *', deleteOldAuditLogs);
// Daily at 8am, delete old audit logs
cron.schedule('0 8 * * *', deleteOldAuditLogs);
}

return server;
}
Expand Down
Loading

0 comments on commit bb14cb8

Please sign in to comment.