Skip to content

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:report

Test 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:

HelperDescription
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:

HelperDescription
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

FileModuleEndpoints Covered
health.test.tsSystemHealth check
auth.test.tsAuthRegister, login, refresh, logout, email verify
users.test.tsUsersProfile, settings, deletion, data export
challenges.test.tsChallengesList, detail, create, update, discussion
trackRecords.test.tsTrack RecordsEntries, media, submit, verify, vote, comment
gamification.test.tsGamificationBadges, leaderboard, XP stats
communities.test.tsCommunitiesCRUD, join/leave, feed, goals
school.test.tsSchoolsClasses, roster, teachers, assignments
admin.test.tsAdminUser mgmt, approval queue, analytics
explore.test.tsExploreBrowse, search, saved, Dealer's Choice
learningPaths.test.tsLearning PathsList, detail, enroll
vendor.test.tsVendorProfile, challenges, analytics
gifts.test.tsGiftsSend, accept, decline, cancel
invitations.test.tsInvitationsCreate, respond, cancel
notifications.test.tsNotificationsList, read, preferences
portfolios.test.tsPortfoliosCRUD
reflections.test.tsReflectionsPrompts, responses, analytics
payments.test.tsPaymentsMethods, checkout, orders, refunds
events.test.tsEventsList, register, cancel
missingRoutes.test.tsCoverageVerifies 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 describe blocks named after the module
  • Each it block 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

DoCurious Platform Documentation