Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lerna-debug.log*
# Tests
/coverage
/.nyc_output
junit.xml

# IDEs and editors
/.idea
Expand Down
87 changes: 87 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Testing Guide

## Overview
Jest with `@nestjs/testing` is used for unit and lightweight integration tests. Tests focus on isolating controllers, services, DTO validation, and module wiring without hitting external systems such as databases or the filesystem.

## Running Tests
- `pnpm test` — run all specs once
- `pnpm test:watch` — watch mode during development
- `pnpm test:cov` — generate coverage report
- `pnpm test:debug` — start Jest in debug/inspect mode

## Test Structure
- Tests live alongside source files and are named `*.spec.ts`.
- Use `describe` blocks per class or function and keep expectations close to the behavior under test.
- Mock framework dependencies (e.g., `DbService`, `SqlLoaderService`) to avoid side effects.

## Writing Tests for SFDC Endpoints
- **Controllers:** mock services, assert route handlers pass through parameters and propagate errors.
- **Services:** mock `DbService` and `SqlLoaderService`; verify parameter ordering and validation paths.
- **DTOs:** leverage `class-validator` with `validate()` to assert decorator behavior and data transforms.
- **Modules:** use `Test.createTestingModule` to ensure providers/controllers are registered and injectable.

## Mock Data
Reusable fixtures live under `src/reports/sfdc/test-helpers`. Share mock responses and query DTOs across controller, service, and DTO specs to keep assertions consistent.

## Best Practices
- Reset mocks in `beforeEach` to avoid cross-test leakage.
- Cover happy paths and error cases, especially validation failures.
- Prefer deterministic data; avoid randomness and timers.
- Keep assertions focused—each test should validate one behavior.

## Example Patterns
```ts
const mockDb = { query: jest.fn() };
const moduleRef = await Test.createTestingModule({
providers: [{ provide: DbService, useValue: mockDb }, SfdcReportsService],
}).compile();
```

```ts
const dto = plainToInstance(ChallengesReportQueryDto, input);
const errors = await validate(dto);
expect(errors).toHaveLength(0);
```

## CI/CD Integration
- Ensure `pnpm test` and `pnpm test:cov` run cleanly in CI; failing tests should block deployments.
- Coverage output is written to `coverage/` and should be ignored from commits by default.

## Coverage for SFDC Endpoints

All SFDC report endpoints (`/challenges`, `/payments`, `/taas/*`, `/ba-fees`) should have:
- **Controller tests**: Route handling, parameter transforms, error propagation
- **Service tests**: SQL loading, parameter ordering (verify all query params), filter logic (include/exclude via `multiValueArrayFilter`), empty results, error handling
- **DTO tests**: All validators (`@IsString`, `@IsDateString`, `@IsNumber`, etc.), transforms (`transformArray`, `transformToNumber`), optional fields, invalid input rejection

Example service test for parameter ordering:
```ts
it('passes all filters in correct order', async () => {
await service.getPaymentsReport({
billingAccountIds: ['12345'],
challengeIds: ['uuid1'],
handles: ['user1'],
challengeName: 'Task',
startDate: '2023-01-01',
endDate: '2023-12-31',
minPaymentAmount: 100,
maxPaymentAmount: 1000,
challengeStatus: ['COMPLETED']
});

expect(mockDbService.query).toHaveBeenCalledWith(mockSql, [
['12345'], // include billing accounts
undefined, // exclude billing accounts
['uuid1'], // challenge IDs
['user1'], // handles
'Task', // challenge name
'2023-01-01', // start date
'2023-12-31', // end date
100, // min amount
1000, // max amount
['COMPLETED'] // challenge status
]);
});
```

Refer to `sfdc-reports.controller.spec.ts`, `sfdc-reports.service.spec.ts`, and `sfdc-reports.dto.spec.ts` for complete patterns.
20 changes: 20 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: 'src',
testRegex: '.*\\.spec\\.ts$',
moduleFileExtensions: ['js', 'json', 'ts'],
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/$1',
},
collectCoverageFrom: [
'**/*.{service,controller,dto}.ts',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Consider adding more specific patterns to collectCoverageFrom to ensure that only the intended files are included in the coverage report. This can help avoid unintentional inclusion of files that match the current pattern but are not meant to be covered.

'!**/*.spec.ts',
'!**/interfaces/**',
'!**/node_modules/**',
],
coverageDirectory: '../coverage',
};
41 changes: 24 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,37 @@
"start:prod": "node dist/main.js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"lint": "eslint \"src/**/*.ts\" --fix"
"lint": "eslint \"src/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
},
"dependencies": {
"@nestjs/cli": "^10.3.0",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^11.2.0",
"@prisma/client": "^5.17.0",
"@types/express": "^4.17.21",
"@nestjs/cli": "^11.0.12",
"@nestjs/common": "^11.1.9",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.9",
"@nestjs/platform-express": "^11.1.9",
"@nestjs/swagger": "^11.2.3",
"@prisma/client": "^7.0.1",
"@types/express": "^5.0.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"date-fns": "^3.6.0",
"class-validator": "^0.14.3",
"date-fns": "^4.1.0",
"i18n-iso-countries": "^3.7.1",
"json-stringify-safe": "^5.0.1",
"pg": "^8.11.5",
"pg": "^8.16.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4"
"rxjs": "^7.8.2",
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#security",
"@nestjs/schematics": "^11.0.9",
"@types/jest": "^29.5.8",
"@nestjs/testing": "^11.1.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/node": "^20.11.24",
"@types/pg": "^8.15.5",
"@typescript-eslint/eslint-plugin": "^7.13.0",
Expand All @@ -43,8 +48,10 @@
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.1.3",
"globals": "^16.3.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"prisma": "^5.17.0",
"prisma": "^7.0.1",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"typescript-eslint": "^8.38.0"
Expand Down
Loading
Loading