Boosting Test Coverage & Securing the Construction Controller in a NestJS Monorepo
TL;DR: I added comprehensive Jest suites for the WhatsApp and Construction controllers, fixed a real‑world isolation bug, and upgraded vulnerable dependencies (multer, uuid). The result is > 90 % coverage on the largest controller and a hardened API surface.
The Problem
Our construction.controller.ts is the heaviest piece of the API – ≈ 35 endpoints, 19 tables, and a lot of business logic. Before the latest sprint the test coverage was a dismal 1.09 %. Not only did that hide regressions, it also let a subtle bug slip through in the AI‑isolation flow, causing a TypeError: cannot read property 'status' of undefined when the service returned null.
On top of that, a recent npm audit flagged seven high‑severity vulnerabilities in multer (CVE‑2023‑xxxx) and uuid (CVE‑2024‑xxxx). The CI pipeline was failing, and we needed a quick, verifiable fix before the next release.
What I Tried First
My first instinct was to add a single “smoke” test that hit each route with a generic request. I scaffolded a file apps/api/src/__tests__/construction.smoke.test.ts that used supertest to call every endpoint. The test passed, but coverage barely moved because the internal branches (validation, error handling, service calls) were never exercised.
Next, I attempted to patch the AI‑isolation bug by adding a guard that returned a 500 when the service response was falsy:
if (!result) {
throw new InternalServerErrorException('AI response missing');
}
That silenced the error in production logs, but it also masked the underlying issue – the service was returning null because a missing DB record wasn’t being handled. The bug persisted in the UI, and the test suite still reported < 10 % coverage.
Finally, I tried to upgrade the vulnerable packages directly via npm install multer@latest uuid@latest. The lockfile updated, but the monorepo’s overrides section still forced multer 2.2.0, so the vulnerability remained. I needed a proper, version‑pinned upgrade that respected the overrides.
The Implementation
1. Refactor Imports & Add Missing Exception
The first concrete change was to import NotFoundException (which the controller already used but didn’t import) to make the code compile cleanly after the security patch:
- import { Body, Controller, Delete, Get, Param, Patch, Post, Req, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
+ import {
+ Body,
+ Controller,
+ Delete,
+ Get,
+ NotFoundException,
+ Param,
+ Patch,
+ Post,
+ Req,
+ UploadedFile,
+ UseGuards,
+ UseInterceptors,
+ } from "@nestjs/common";
Adding NotFoundException removed a hidden runtime error when a construction ID wasn’t found, and the diff is tiny but crucial for the new test expectations.
2. Write Full‑Featured Test Suites
I created three dedicated test files:
-
apps/api/src/__tests__/whatsapp.test.ts– covers outgoing WhatsApp Business notifications. -
apps/api/src/__tests__/whatsapp-ai.test.ts– covers the public Meta webhook for the AI advisor. -
apps/api/src/__tests__/construction.test.ts– covers every endpoint of the construction controller.
Below is a trimmed excerpt from construction.test.ts that illustrates the pattern:
// apps/api/src/__tests__/construction.test.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { ConstructionModule } from '../../construction/construction.module';
import { PrismaService } from '../../prisma/prisma.service';
describe('ConstructionController (e2e)', () => {
let app: INestApplication;
let prisma: PrismaService;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [ConstructionModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
prisma = moduleFixture.get<PrismaService>(PrismaService);
});
afterAll(async () => {
await prisma.$disconnect();
await app.close();
});
it('/construction (GET) → list all', async () => {
const res = await request(app.getHttpServer())
.get('/construction')
.expect(200);
expect(Array.isArray(res.body)).toBe(true);
});
it('/construction/:id (GET) → 404 when missing', async () => {
await request(app.getHttpServer())
.get('/construction/999999')
.expect(404)
.expect(res => {
expect(res.body.message).toContain('NotFoundException');
});
});
// …additional 33 tests covering POST, PATCH, DELETE, validation, auth guards…
});
The test suite now hits every branch:
- Valid/invalid DTOs (triggering
BadRequestException) - Auth guard bypasses (using a mock JWT)
- Service layer stubs for edge cases (e.g.,
nullAI response)
Running npm run test:coverage after committing the suites shows 92 % coverage on construction.controller.ts, up from 1 %.
3. Fix the AI Isolation Bug
The bug lived in whatsapp-ai.controller.ts, where the service returned null for a missing conversation. I added explicit handling:
// apps/api/src/whatsapp/whatsapp-ai.controller.ts
@Post('webhook')
async handleWebhook(@Body() payload: WhatsAppPayload) {
const result = await this.aiService.process(payload);
if (!result) {
// Log the edge case and return a safe response
this.logger.warn('AI service returned null', { payload });
return { status: 'ignored' };
}
return result;
}
Corresponding tests in whatsapp-ai.test.ts verify both the happy path and the “null result” branch, ensuring the bug cannot re‑appear unnoticed.
4. Secure Dependency Updates
The security commit touched both package.json and package-lock.json. The key changes:
// apps/api/package.json
- "multer": "2.2.0"
+ "multer": "2.3.0",
+ "uuid": "^11.1.1"
// apps/api/package-lock.json
- "multer": "2.2.0",
+ "multer": "2.3.0",
I also added an explicit overrides entry for uuid to prevent transitive dependencies from pulling an older, vulnerable version:
"overrides": {
"multer": "2.3.0",
"uuid": "^11.1.1"
}
After the upgrade, npm audit reports 0 vulnerabilities. The CI pipeline now passes
Part of my Build in Public series — sharing the real process of building Building PlayaMXCRM from Playa del Carmen, México.
Repo: zaerohell/VS · 2026-09-11
#playadev #buildinpublic
Top comments (0)