65b0bb04f8
把 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 + 跑测试。
99 lines
3.7 KiB
JavaScript
99 lines
3.7 KiB
JavaScript
// 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. 拿 csrf(GET 通过,但 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 路由拿到 token(cookie 已通过 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);
|
||
});
|
||
});
|