40227-vm/backend/docs/test-coverage.md
2026-06-12 06:55:35 +02:00

5.6 KiB

Backend Test Coverage

Test Runner

The backend uses Node.js built-in test runner (node:test) with tsx for TypeScript execution.

npm run test    # Run all tests
npm run verify  # Typecheck + lint + tests

Test Structure

Tests are colocated with source files using the .test.ts suffix:

src/
├── services/
│   ├── auth.ts
│   ├── auth.test.ts          # Auth service tests
│   └── shared/
│       ├── crud-service.ts
│       ├── crud-service.test.ts
│       └── role-policy.test.ts
├── api/controllers/
│   ├── auth.controller.ts
│   └── auth.controller.test.ts
├── middlewares/
│   ├── error-handler.ts
│   └── error-handler.test.ts
├── db/api/shared/
│   ├── repository.ts
│   └── repository.test.ts
└── test-utils/
    └── index.ts              # Shared test utilities

Test Utilities

Located in src/test-utils/index.ts:

Test Data Builders

import { createTestUser, createGlobalAccessUser } from '@/test-utils';

// Create a standard test user
const user = createTestUser();

// Create user with global access
const admin = createGlobalAccessUser();

// Override specific properties
const customUser = createTestUser({
  organizationId: 'custom-org',
  app_role: { name: 'director', globalAccess: false },
});

Mock DB API Factory

import { createMockDbApi } from '@/test-utils';

// Create a mock with default behavior
const mockDbApi = createMockDbApi();

// Customize responses
const mockDbApi = createMockDbApi({
  findBy: async (where) => where.id === 'exists' ? { id: 'exists' } : null,
});

// Check calls
expect(mockDbApi.calls.create.length).toBe(1);
mockDbApi.reset(); // Clear call history

Mock Request/Response

import { createMockRequest } from '@/test-utils';

const req = createMockRequest({
  body: { email: 'test@example.com' },
  currentUser: createTestUser(),
});

Current Coverage

Services

File Description Tests
services/auth.test.ts Auth helpers and service methods ~40
services/shared/crud-service.test.ts CRUD factory ~20
services/shared/role-policy.test.ts Role constraints ~10
services/shared/audio-access.test.ts Audio-library visibility/management rules ~12
services/refresh-token-maintenance.test.ts Refresh-token retention cutoff + cleanup orchestration (mocked DB API) ~4

Domain constants / pure rules

File Description Tests
shared/constants/audio-files.test.ts file/url/recipe kinds + isAudioFileKind ~2
shared/constants/policy-documents.test.ts category validation + version-bump re-acknowledgment rule ~several
shared/constants/users.test.ts honorific name-prefix formatting (formatPersonName) ~several

Controllers

File Description Tests
api/controllers/auth.controller.test.ts Auth endpoints ~20
api/controllers/campus_attendance.controller.test.ts Attendance endpoints ~10

Infrastructure

File Description Tests
middlewares/error-handler.test.ts Error normalization ~10
db/api/shared/repository.test.ts Repository base ~10
shared/architecture/import-boundaries.test.ts Architecture validation ~5

Testing Patterns

Pure Function Tests

Test pure functions directly without mocking:

import { test } from 'node:test';
import assert from 'node:assert/strict';

test('formats user display name', () => {
  const result = formatDisplayName('John', 'Doe');
  assert.equal(result, 'John Doe');
});

Service Tests with Mocked DB APIs

Mock the data layer to test service logic:

import { test, describe, mock, beforeEach } from 'node:test';
import assert from 'node:assert/strict';

describe('UserService', () => {
  let mockDbApi: ReturnType<typeof createMockDbApi>;

  beforeEach(() => {
    mockDbApi = createMockDbApi();
  });

  test('creates user with hashed password', async () => {
    mockDbApi.create.mock.mockImplementation(async (data) => ({
      id: 'new-user',
      ...data,
    }));

    await createUser({ email: 'new@example.com', password: 'secret' });

    assert.equal(mockDbApi.calls.create.length, 1);
    const [data] = mockDbApi.calls.create[0];
    assert.notEqual(data.password, 'secret'); // Should be hashed
  });
});

Controller Tests

Mock services and test request/response handling:

import { test, describe, mock } from 'node:test';
import assert from 'node:assert/strict';

describe('auth controller', () => {
  test('returns user profile on successful signin', async () => {
    const req = createMockRequest({
      body: { email: 'test@example.com', password: 'password' },
    });
    const res = createMockResponse();

    await signinHandler(req, res, mockAuthService);

    assert.equal(res.statusCode, 200);
    assert.equal(res.body.email, 'test@example.com');
  });
});

Adding New Tests

  1. Create a .test.ts file next to the source file
  2. Import from node:test and node:assert/strict
  3. Use @/test-utils for common setup
  4. Follow the describe/test structure
  5. Run npm run test to verify

Best Practices

  • Test behavior, not implementation details
  • Use descriptive test names that explain the expected behavior
  • Keep tests focused - one assertion per test when possible
  • Mock at the boundary (DB APIs, external services)
  • Use beforeEach to reset mocks between tests
  • Prefer assert.deepEqual for objects, assert.equal for primitives