# Testing Guide

Author: Nguyen Thi Nguyet Que Created on: May 2, 2026

## Policy

Test any behavior-changing code: new features, bug fixes, business logic updates.

Skip tests for: typo fixes, log message changes, swagger docs, renames, pure refactors.

---

## Test levels

| Level       | What to test                             | Location                                  |
| ----------- | ---------------------------------------- | ----------------------------------------- |
| Unit        | Services, utilities, validators          | `tests/unit/`                             |
| Integration | Routes, controllers, DB flows end-to-end | `tests/integration/`                      |
| Critical    | Auth, permissions, file upload security  | `tests/unit/services/auth*`, `virusScan*` |

**Rules:**

- Always unit test: Services with business logic, utility functions, validators
- Always integration test: Auth flows, file upload, permission checks
- Skip unit testing controllers — they thin-wrap services; use integration tests for routes instead

---

## Commands

```bash
pnpm test:unit        # Unit tests only
pnpm test:integration # Integration tests only
pnpm test:coverage    # Full suite + coverage report
pnpm test:critical    # Critical gate — auth + security (< 30s)
pnpm test:watch       # Watch mode during development
```

---

## Coverage thresholds

| Scope                          | Threshold |
| ------------------------------ | --------- |
| Global                         | ≥ 80%     |
| Critical modules (auth, files) | ≥ 90%     |

Prefer **meaningful assertions** over hitting coverage numbers. Avoid `expect(true).toBe(true)` —
branch coverage and edge cases matter more than line %.

---

## Pre-push enforcement

```
git commit  →  lint + typecheck (fast, < 5s)
git push    →  pnpm test:critical (auth + security only, < 30s)
CI pipeline →  full suite + coverage thresholds
```

`git push` is blocked only if critical tests fail. Full coverage runs in CI, not locally on every
push.

---

## Writing tests

### Unit test example (service)

```typescript
// tests/unit/services/productService.test.ts
import { ProductService } from '#services/productService';

describe('ProductService', () => {
	it('should throw NOT_FOUND when product does not exist', async () => {
		await expect(ProductService.getById('non-existent-id')).rejects.toMatchObject({
			code: 'NOT_FOUND',
		});
	});
});
```

### Integration test example (route)

```typescript
// tests/integration/product.test.ts
import request from 'supertest';
import app from '#/app';

describe('GET /api/v1/product', () => {
	it('returns 401 without auth token', async () => {
		const res = await request(app).get('/api/v1/product');
		expect(res.status).toBe(401);
	});
});
```

---

See `tests/README.md` for the full testing guide including factory helpers and mock setup.
