Skip to content

Latest commit

 

History

History
186 lines (142 loc) · 5.85 KB

File metadata and controls

186 lines (142 loc) · 5.85 KB

Testing guide

How to run and write tests for OpenJustice.

Overview

OpenJustice has two test suites:

  • Server (Jest): 122 unit tests covering auth, encryption, permissions, audit, cases, persons, and evidence
  • Client (Vitest): 23 unit tests covering auth context, offline storage, and background sync

Tests are colocated with source files in the server (*.spec.ts) and grouped under lib/__tests__/ in the client.

Running tests

Server

cd server

npm test                # run all tests
npm run test:watch      # watch mode
npm run test:cov        # with coverage report
npm run test:e2e        # e2e tests (requires running database)

Client

cd client

npm test                # run all tests (vitest run)
npm run test:watch      # watch mode (vitest)
npm run test:coverage   # with coverage

Server test setup

The server uses Jest 30 with ts-jest. Configuration is in package.json:

{
  "jest": {
    "moduleFileExtensions": ["js", "json", "ts"],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": { "^.+\\.(t|j)s$": "ts-jest" },
    "collectCoverageFrom": ["**/*.(t|j)s"],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

Test files

File What it tests
common/utils/encryption.util.spec.ts AES-256-GCM encrypt/decrypt, PII helpers, key validation
common/utils/hash.util.spec.ts Argon2id PIN hashing and verification
common/utils/crypto.util.spec.ts SHA-256, custody signatures, audit entry hashes
common/guards/permissions.guard.spec.ts Scope hierarchy, SuperAdmin bypass, buildScopeFilter
modules/auth/auth.service.spec.ts Login, PIN expiry, account lockout, token refresh, PIN change/reset
modules/audit/audit.service.spec.ts Hash-chain creation, integrity verification, tamper detection
modules/cases/cases.service.spec.ts Case CRUD, status transitions, person roles, notes
modules/persons/persons.service.spec.ts Person CRUD, NIN uniqueness, wanted status, risk levels, aliases
modules/evidence/evidence.service.spec.ts Evidence CRUD, status transitions, custody events, sealing

Writing a server test

Tests use NestJS Test.createTestingModule with mocked dependencies. Here is the pattern used throughout:

import { Test } from '@nestjs/testing';
import { CasesService } from './cases.service';
import { CasesRepository } from './cases.repository';
import { PrismaService } from '../../common/database/prisma.service';

describe('CasesService', () => {
  let service: CasesService;
  let casesRepository: Record<string, jest.Mock>;
  let prisma: Record<string, any>;

  beforeEach(async () => {
    casesRepository = {
      findById: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      // ... mock each repository method used
    };
    prisma = {
      auditLog: { create: jest.fn().mockResolvedValue({}) },
    };

    const module = await Test.createTestingModule({
      providers: [
        CasesService,
        { provide: CasesRepository, useValue: casesRepository },
        { provide: PrismaService, useValue: prisma },
      ],
    }).compile();

    service = module.get(CasesService);
  });

  it('throws NotFoundException when case not found', async () => {
    casesRepository.findById.mockResolvedValue(null);
    await expect(service.findById('missing', 'off-1'))
      .rejects.toThrow(NotFoundException);
  });
});

Key points:

  • Mock repositories and PrismaService -- never hit a real database in unit tests
  • Mock the AuditService (or prisma.auditLog) so audit writes don't interfere
  • Use jest.fn() for each method, set return values with .mockResolvedValue()
  • Test both success paths and error/validation paths

Client test setup

The client uses Vitest with jsdom environment and React Testing Library. Configuration is in vitest.config.ts:

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
  },
  resolve: {
    alias: { '@': path.resolve(__dirname, '.') },
  },
});

Test files

File What it tests
lib/__tests__/auth-context.test.tsx Permission checking, officer data transformation, session refresh
lib/__tests__/offline-storage.test.ts IndexedDB operations, sync queue, entity caching, store name mapping
lib/__tests__/sync-service.test.ts Queue processing, retry with backoff, max retries, offline guard

Writing a client test

Client tests mock browser APIs (fetch, IndexedDB, navigator) and external modules:

import { describe, it, expect, vi } from 'vitest';
import { renderHook } from '@testing-library/react';

vi.mock('../api-client', () => ({
  default: { post: vi.fn() },
  setTokens: vi.fn(),
  clearTokens: vi.fn(),
}));

const mockFetch = vi.fn();
global.fetch = mockFetch;

it('returns false for hasPermission when user is null', async () => {
  mockFetch.mockResolvedValue({ ok: false });
  const { result } = renderHook(() => useAuth(), { wrapper });
  await vi.waitFor(() => expect(result.current.loading).toBe(false));
  expect(result.current.hasPermission('cases', 'read')).toBe(false);
});

CI integration

Both test suites run in GitHub Actions CI:

  • Server CI (.github/workflows/ci.yml): runs npm test -- --coverage after lint and build
  • Client CI (.github/workflows/ci.yml): runs npm test before build

Adding new tests

When adding a new server module, create a colocated *.spec.ts file. When adding new client logic under lib/, add a test file in lib/__tests__/.

Coverage targets are not formally enforced yet, but critical modules (auth, encryption, permissions, audit) should maintain test coverage.