Files
i/server/test/integration.middleware.test.js
T
wsh5485 65b0bb04f8 feat: import CarLog v2.8 code + dev plan
把 CarLog v2.8 全套源码 + 配置导入到 i 仓库作为 baseline:
- server/src/ (13 个路由 + middleware + services + config)
- server/migrations/ (0001~0018 共 18 个迁移 + mysql)
- server/test/ (12 文件 101 测试)
- client/src/ (20 个 view + components + stores + api + composables)
- client/public/ + client/scripts/
- 全部配置文件 (.editorconfig, .eslintrc.json, .prettierrc.json, vitest.config.js, lighthouserc.json, .pa11yci.json, package.json, carlog-init.sql)
- .husky/pre-commit (git hooks)
- docs/install/ (宝塔部署文档)

不含:
- node_modules/ (本地 npm install)
- .env (敏感, 走 .env.example)
- *.zip / *.log / *.sqlite / .DS_Store

新增文档 docs/DEV-PLAN.md:
- Phase 1: 平台基座 (019 migration + 3 个 platform 路由 + 3 个 view)
- Phase 2: CarLog 子系统化 (后端 routes/ → subsystems/carlog/ + 前端 views/ → views/subsystems/carlog/ + 元数据驱动菜单)
- Phase 3: 验证 (测试 + E2E + DB 完整性)
- 交付清单 + commit 模板 + 给 Mavis review 的材料

后续 Trae 实施, 提交后我 code review + 跑测试。
2026-06-20 22:30:19 +08:00

99 lines
3.7 KiB
JavaScript
Raw 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);
});
});