Files
CarLog/server/test/integration.middleware.test.js
wsh5485 fe17886ac4 feat: 洗车管理系统 v2.8 — 个人 detailer 单用户全栈应用
- 车辆 / 洗车 / 加油 / 充电 / 保养 / 保险 完整 CRUD + 软删
- AI 截图识别(5 类型 OCR schema):OpenAI 兼容 + MiniMax M3
- 化学品 / Grocy 实例对接 + 库存镜像同步
- 仪表盘:30 天频次 + 健康度 + 同比环比 + 油价趋势 + 年均养护
- 月度报表:Excel 6 sheet + PDF
- PWA:manifest / SW / 离线缓存 / iOS 引导
- 安全:bcrypt + CSRF + 登录锁定(IP/用户/全局三级)+ 401 自动跳登录 + 表单草稿
- 高 ROI 8 功能:里程/提醒/成本/搜索/标签/通知/同比/成就
- 3 个新 migration(0016/0017/0018)+ 18 个迁移全幂等
- 101/101 测试通过(含 ipRateLimit / CSRF / retry / stats / tags / notifications)
- 部署:宝塔面板文档 + PM2 + Nginx
2026-06-20 21:11:54 +08:00

99 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// server/test/integration.middleware.test.js
// 用 supertest 串联多个中间件,验证真实 Express 流程
// 选最小依赖:手动构造 app(不依赖 initDb / 真实路由)
import { describe, it, expect } from 'vitest';
import express from 'express';
import session from 'express-session';
import request from 'supertest';
import { requireAuth } from '../src/middleware/auth.js';
import { requireCsrf } from '../src/middleware/csrf.js';
function buildApp() {
const app = express();
app.use(express.json());
app.use(
session({
name: 'TEST_SID',
secret: 'test-secret',
resave: false,
saveUninitialized: false,
})
);
// 公共路由:登出
app.post('/api/test/logout', requireCsrf, (req, res) => {
req.session.destroy(() => res.json({ ok: true }));
});
// 公共路由:CSRF
app.get('/api/test/csrf', (req, res) => {
if (!req.session.csrfToken) {
req.session.csrfToken = 'test-token-' + Math.random().toString(36).slice(2);
}
res.json({ ok: true, data: { csrf_token: req.session.csrfToken } });
});
// 受保护路由
app.get('/api/test/protected', requireAuth, (req, res) => res.json({ ok: true }));
app.post('/api/test/protected', requireAuth, requireCsrf, (req, res) => res.json({ ok: true }));
// 普通路由(非 /api/
app.get('/settings', requireAuth, (req, res) => res.json({ ok: true }));
return app;
}
describe('集成:中间件链路', () => {
it('GET /api/test/protected 未登录 → 401 JSON', async () => {
const app = buildApp();
const r = await request(app).get('/api/test/protected');
expect(r.status).toBe(401);
expect(r.body.error.code).toBe('UNAUTHORIZED');
});
it('GET /settings 未登录 → 302 redirect', async () => {
const app = buildApp();
const r = await request(app).get('/settings');
expect(r.status).toBe(302);
expect(r.headers.location).toMatch(/\/login\?return_to=/);
});
it('完整流程:拿 csrf → 登录模拟 → 访问受保护', async () => {
const app = buildApp();
const agent = request.agent(app);
// 1. 拿 csrfGET 通过,但 express-session 需要先有 session
// 这里用 agent 自动管理 cookie
const csrfRes = await agent.get('/api/test/csrf');
expect(csrfRes.status).toBe(200);
const token = csrfRes.body.data.csrf_token;
// 2. 手动模拟"已登录":直接 post 到受保护路由但先注入 userId
// 此处改用 post 触发 CSRF 校验,要求带正确 token
// (受保护路由会先 401 因为没 userId
const r401 = await agent.post('/api/test/protected').send({ csrf_token: token });
expect(r401.status).toBe(401);
// 3. 验证 logout 流程:先用 csrf 路由拿到 tokencookie 已通过 agent 维持)
// 然后 POST logout
const logoutRes = await agent
.post('/api/test/logout')
.send({ csrf_token: token });
expect(logoutRes.status).toBe(200);
expect(logoutRes.body.ok).toBe(true);
});
it('POST /api/test/logout 缺 CSRF → 403', async () => {
const app = buildApp();
const r = await request(app).post('/api/test/logout').send({});
expect(r.status).toBe(403);
expect(r.body.error.code).toBe('CSRF');
});
it('POST /api/test/logout 错 CSRF → 403', async () => {
const app = buildApp();
const r = await request(app).post('/api/test/logout').send({ csrf_token: 'fake' });
expect(r.status).toBe(403);
});
});