# Backend Test Coverage

## Test Runner

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

```bash
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

```typescript
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

```typescript
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

```typescript
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/timezone.test.ts` | campus-local date (Phoenix no-DST + DST) + `isValidIanaTimezone` | ~3 |
| `shared/constants/zone-checkin.test.ts` | zone colors + `isZoneCheckinColor` | ~2 |
| `shared/constants/week.test.ts` | Sunday-start week normalization (`toWeekStartIso`) + invalid-date rejection | ~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` | Backend import-boundary validation: layer assignment, API/BLL/DAL/shared direction, exact exception allowlists | ~8 |

## Testing Patterns

### Pure Function Tests

Test pure functions directly without mocking:

```typescript
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:

```typescript
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:

```typescript
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
