在 NestJS 单体中补充 Jest 测试套件修复 WhatsApp 与建筑控制器隔离 bug,同时升级 multer/uuid 高危漏洞依赖,覆盖率超 90%。
TL;DR:为 WhatsApp 和 Construction 控制器添加了全面的 Jest 测试套件,修复了一个真实场景中的隔离 bug,并升级了存在漏洞的依赖(multer、uuid)。最终效果是最大控制器的覆盖率超过 90%,API 对外接口也得到了加固。
我们的 construction.controller.ts 是 API 中最重的部分——约 35 个端点、19 张表,还有大量业务逻辑。在最近一个 sprint 之前,测试覆盖率只有惨淡的 1.09%。这不仅掩盖了回归问题,还让一个隐蔽的 AI 隔离流程 bug 趁机溜了进去——当服务返回 null 时,引发了 TypeError: cannot read property 'status' of undefined。
此外,最近的 npm audit 标记出了 multer(CVE-2023-xxxx)和 uuid(CVE-2024-xxxx)中的七个高危漏洞。CI 流水线正在失败,我们需要在下一次发布前快速、可靠地修复。
我的第一反应是添加一个单一的"冒烟"测试,用通用请求访问每条路由。我用 supertest 搭建了一个文件 apps/api/src/__tests__/construction.smoke.test.ts 来调用每个端点。测试通过了,但覆盖率几乎没有移动,因为内部分支(验证、错误处理、服务调用)从未被执行到。
接下来,我尝试通过添加一个守卫来解决 AI 隔离 bug——当服务响应为假时抛出 500:
if (!result) {
throw new InternalServerErrorException('AI response missing');
}
这让生产日志里的错误静默了,但也掩盖了底层问题——服务返回 null 是因为缺失的 DB 记录没有被处理。Bug 在 UI 中依然存在,测试套件报告的覆盖率仍然低于 10%。
最后,我尝试直接通过 npm install multer@latest uuid@latest 升级有漏洞的包。lockfile 更新了,但 monorepo 的 overrides 部分仍然强制使用 multer 2.2.0,所以漏洞依然存在。我需要一个尊重 overrides 的正确版本固定升级。
第一个具体改动是导入 NotFoundException(控制器已经在使用但没有导入),以使安全补丁后的代码能够顺利编译:
- 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";
添加 NotFoundException 消除了找不到 construction ID 时的隐藏运行时错误,diff 很小但对新测试的预期至关重要。
我创建了三个专用测试文件:
apps/api/src/__tests__/whatsapp.test.ts – 覆盖外发 WhatsApp Business 通知apps/api/src/__tests__/whatsapp-ai.test.ts – 覆盖 AI 顾问的公开 Meta webhookapps/api/src/__tests__/construction.test.ts – 覆盖 construction 控制器的每个端点下面是 construction.test.ts 的一个精简片段,说明了模式:
// 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…
});
测试套件现在覆盖了每个分支:
提交测试套件后运行 npm run test:coverage,显示 construction.controller.ts 的覆盖率达到 92%,从之前的 1% 提升上来。
Bug 藏在 whatsapp-ai.controller.ts 中,服务为缺失的对话返回了 null。我添加了显式处理:
// 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;
}
whatsapp-ai.test.ts 中对应的测试验证了正常路径和"null 结果"分支,确保 bug 不会再次悄无声息地出现。
安全提交同时修改了 package.json 和 package-lock.json。关键改动:
// 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",
我还为 uuid 添加了显式的 overrides 条目,防止传递依赖拉取更旧的、有漏洞的版本:
"overrides": {
"multer": "2.3.0",
"uuid": "^11.1.1"
}
升级后,npm audit 报告 0 个漏洞。CI 流水线现在通过了。
这是 Build in Public 系列的一部分——分享在墨西哥卡波圣卢卡斯从零构建 Building PlayaMX CRM 的真实过程。
Repo: zaerohell/VS · 2026-09-11
#playadev #buildinpublic