Appearance
Backend Testing
The backend uses Vitest as its test runner with Supertest for HTTP assertions. Tests run against a real PostgreSQL database (not mocked).
STATUS: BUILT
25 test files covering all 20 API modules, plus test helpers for authentication and common operations.
WARNING
Tests require a running PostgreSQL instance. Make sure your DATABASE_URL in server/.env points to a valid database before running tests.
Running Tests
From the server/ directory:
bash
# Run all tests once
npm test
# Run tests in watch mode
npm run test:watch
# Run tests and save a markdown report
npm run test:reportTest Infrastructure
Setup (src/__tests__/setup.ts)
The test setup creates a shared Express app instance used across all test files:
typescript
import { createApp } from '../app.js'
import type { Express } from 'express'
process.env.NODE_ENV = 'test'
let app: Express
beforeAll(() => {
app = createApp()
})
export function getApp() {
return app
}Helpers (src/__tests__/helpers.ts)
The helpers module provides authenticated request utilities and test user creation:
User creation:
| Helper | Description |
|---|---|
registerTestUser(app, overrides?) | Register a user and return {userId, accessToken, refreshToken} |
getUserAuth(app) | Get or create a regular user token (cached) |
getAdminAuth(app) | Get admin token (registers user, then mints elevated JWT) |
getVendorAuth(app) | Get vendor token |
getTeacherAuth(app) | Get teacher token |
getSchoolAdminAuth(app) | Get school admin token (registers user, then mints elevated JWT) |
Tokens are cached across tests for performance. Admin and school admin tokens are minted directly with jwt.sign() since registration blocks privileged roles by design.
Authenticated requests:
| Helper | Description |
|---|---|
authGet(app, path, token) | GET with Authorization: Bearer header |
authPost(app, path, token, body?) | POST with auth |
authPut(app, path, token, body?) | PUT with auth |
authDelete(app, path, token) | DELETE with auth |
anonGet(app, path) | Unauthenticated GET |
anonPost(app, path, body?) | Unauthenticated POST |
All helpers prepend the API base path (/api/portal).
Writing a Test
Example test for a protected endpoint:
typescript
import { describe, it, expect } from 'vitest'
import { getApp } from './setup.js'
import { getUserAuth, authGet, anonGet } from './helpers.js'
describe('Challenges', () => {
it('GET /v2/challenges returns paginated list', async () => {
const app = getApp()
const res = await anonGet(app, '/v2/challenges?page=1&limit=5')
expect(res.status).toBe(200)
expect(res.body.success).toBe(true)
expect(res.body.data).toBeInstanceOf(Array)
expect(res.body.pagination).toBeDefined()
expect(res.body.pagination.limit).toBe(5)
})
it('POST /v2/challenges requires auth', async () => {
const app = getApp()
const res = await request(app)
.post('/api/portal/v2/challenges')
.send({ title: 'Test Challenge' })
expect(res.status).toBe(401)
})
it('POST /v2/challenges creates a challenge', async () => {
const app = getApp()
const { token } = await getUserAuth(app)
const res = await authPost(app, '/v2/challenges', token, {
title: 'Rock Climbing 101',
difficulty: 'beginner',
fulfillmentType: 'hosted',
})
expect(res.status).toBe(201)
expect(res.body.data.title).toBe('Rock Climbing 101')
})
})Test File Catalog
| File | Module | Endpoints Covered |
|---|---|---|
health.test.ts | System | Health check |
auth.test.ts | Auth | Register, login, refresh, logout, email verify |
users.test.ts | Users | Profile, settings, deletion, data export |
challenges.test.ts | Challenges | List, detail, create, update, discussion |
trackRecords.test.ts | Track Records | Entries, media, submit, verify, vote, comment |
gamification.test.ts | Gamification | Badges, leaderboard, XP stats |
communities.test.ts | Communities | CRUD, join/leave, feed, goals |
school.test.ts | Schools | Classes, roster, teachers, assignments |
admin.test.ts | Admin | User mgmt, approval queue, analytics |
explore.test.ts | Explore | Browse, search, saved, Dealer's Choice |
learningPaths.test.ts | Learning Paths | List, detail, enroll |
vendor.test.ts | Vendor | Profile, challenges, analytics |
gifts.test.ts | Gifts | Send, accept, decline, cancel |
invitations.test.ts | Invitations | Create, respond, cancel |
notifications.test.ts | Notifications | List, read, preferences |
portfolios.test.ts | Portfolios | CRUD |
reflections.test.ts | Reflections | Prompts, responses, analytics |
payments.test.ts | Payments | Methods, checkout, orders, refunds |
events.test.ts | Events | List, register, cancel |
missingRoutes.test.ts | Coverage | Verifies all planned routes exist |
helpers.ts | -- | Test utilities (not a test file) |
reporter.ts | -- | Custom markdown reporter |
setup.ts | -- | Test environment setup |
Test Conventions
- Each test file maps to one API module
- Tests use
describeblocks named after the module - Each
itblock tests one endpoint or behavior - Auth tests use
getUserAuth()/getAdminAuth()for tokens - Anonymous endpoint tests use
anonGet()/anonPost() - Tests assert both status codes and response body structure
See Also
- Backend Quick Start -- running the server
- Backend Architecture -- middleware and service patterns
- API Endpoints -- what to test against
- Testing -- frontend testing strategy