Skip to content

[BE-31] Backend Unit & Integration Test Suite: Auth, Shipments & Guards #1017

Description

@mftee

Overview

The backend has zero automated tests. Any refactor or new feature can silently break existing behavior. This issue establishes the NestJS testing foundation and writes unit + integration tests for the most critical modules: Auth, Shipments, and Guards. The companion backend test issue for remaining modules is [BE-32].

Technical Details

1. Testing Framework Setup

NestJS ships with Jest built in. Confirm the following are in backend/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"
}

Add a test:cov script that runs Jest with --coverage.

2. Auth Service Unit Tests (backend/src/auth/auth.service.spec.ts)

Mock UsersService, JwtService, ConfigService, and MailService. Write tests for:

describe('AuthService') {
  it('register() creates a user, hashes password, sends verification email')
  it('register() throws ConflictException if email already exists')
  it('validateUser() returns user for correct credentials')
  it('validateUser() returns null for wrong password')
  it('validateUser() returns null for non-existent email')
  it('login() returns access_token and refresh_token')
  it('refreshToken() returns new tokens for a valid refresh token')
  it('refreshToken() throws UnauthorizedException for invalid token')
  it('forgotPassword() queues a reset email without revealing user existence')
  it('resetPassword() updates password and invalidates the reset token')
  it('resetPassword() throws BadRequestException for expired token')
}

3. Shipments Service Unit Tests (backend/src/shipments/shipments.service.spec.ts)

Mock the Shipment repository. Write tests for:

describe('ShipmentsService') {
  it('create() sets trackingNumber, status=pending, and assigns the shipperId')
  it('findAll() returns only the requesting user\'s shipments')
  it('findOne() throws NotFoundException for a shipment that does not exist')
  it('findOne() throws ForbiddenException when accessed by a non-party user')
  it('updateStatus() transitions pending → accepted correctly')
  it('updateStatus() throws BadRequestException for invalid transition (e.g. pending → completed)')
  it('updateStatus() throws ForbiddenException when a carrier tries to accept their own bid')
}

4. JWT Auth Guard Integration Test (backend/src/auth/guards/jwt-auth.guard.spec.ts)

Use the NestJS Test.createTestingModule() to spin up a real guard instance:

it('passes for a valid JWT in the Authorization header')
it('passes for a valid JWT in an httpOnly cookie')
it('throws UnauthorizedException for an expired JWT')
it('throws UnauthorizedException for a missing JWT')
it('skips auth for routes decorated with @Public()')
it('throws ForbiddenException for a suspended user')

5. E2E Test Setup (backend/test/app.e2e-spec.ts)

Set up a NestJS e2e test that starts the full app against a test PostgreSQL database (use an in-memory SQLite or a Docker Compose test service defined in docker-compose.test.yml):

  • POST /api/v1/auth/registerPOST /api/v1/auth/loginGET /api/v1/auth/me full flow
  • POST /api/v1/shipmentsGET /api/v1/shipments/:id → check status = pending

Acceptance Criteria

  • npm run test runs without errors and all tests pass
  • npm run test:cov reports ≥ 70% line coverage for auth.service.ts
  • npm run test:cov reports ≥ 60% line coverage for shipments.service.ts
  • All auth guard tests pass (including @Public() bypass and suspended user check)
  • E2E test for register → login → me flow passes
  • E2E test for creating and reading a shipment passes
  • Tests use mocks where appropriate — no real DB calls in unit tests
  • Jest config is committed to package.json or jest.config.ts

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions