// server/test/middleware.csrf.test.js // 测试 server/src/middleware/csrf.js 的所有分支 import { describe, it, expect, vi } from 'vitest'; import { requireCsrf } from '../src/middleware/csrf.js'; function mockRes() { return { statusCode: 200, body: null, status(c) { this.statusCode = c; return this; }, json(b) { this.body = b; return this; }, }; } describe('middleware/requireCsrf', () => { it('GET 请求直接放行', () => { const req = { method: 'GET', session: { csrfToken: 'abc' } }; const res = mockRes(); const next = vi.fn(); requireCsrf(req, res, next); expect(next).toHaveBeenCalledOnce(); expect(res.statusCode).toBe(200); }); it('HEAD 请求直接放行', () => { const req = { method: 'HEAD', session: { csrfToken: 'abc' } }; const next = vi.fn(); requireCsrf(req, mockRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('OPTIONS 请求直接放行', () => { const req = { method: 'OPTIONS', session: { csrfToken: 'abc' } }; const next = vi.fn(); requireCsrf(req, mockRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('POST 没有 session.csrfToken → 403', () => { const req = { method: 'POST', body: { csrf_token: 'abc' } }; const res = mockRes(); const next = vi.fn(); requireCsrf(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.statusCode).toBe(403); expect(res.body.error.code).toBe('CSRF'); }); it('POST session 没 csrfToken 但用户提交了 → 403', () => { const req = { method: 'POST', body: { csrf_token: 'abc' }, session: {} }; const res = mockRes(); requireCsrf(req, res, vi.fn()); expect(res.statusCode).toBe(403); }); it('POST 错误 token → 403', () => { const req = { method: 'POST', body: { csrf_token: 'wrong' }, session: { csrfToken: 'right' }, }; const res = mockRes(); requireCsrf(req, res, vi.fn()); expect(res.statusCode).toBe(403); expect(res.body.error.message).toMatch(/校验失败/); }); it('POST 正确 token(body)→ 放行', () => { const req = { method: 'POST', body: { csrf_token: 'good' }, session: { csrfToken: 'good' }, }; const next = vi.fn(); requireCsrf(req, mockRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('POST 正确 token(header)→ 放行', () => { const req = { method: 'POST', body: {}, headers: { 'x-csrf-token': 'good' }, session: { csrfToken: 'good' }, }; const next = vi.fn(); requireCsrf(req, mockRes(), next); expect(next).toHaveBeenCalledOnce(); }); it('PUT 也需校验', () => { const req = { method: 'PUT', body: {}, headers: {}, session: { csrfToken: 'x' } }; const res = mockRes(); requireCsrf(req, res, vi.fn()); expect(res.statusCode).toBe(403); }); it('DELETE 也需校验', () => { const req = { method: 'DELETE', body: {}, headers: {}, session: { csrfToken: 'x' } }; const res = mockRes(); requireCsrf(req, res, vi.fn()); expect(res.statusCode).toBe(403); }); it('长度为 0 的 token(防御性)→ 抛错或 403', () => { const req = { method: 'POST', body: { csrf_token: '' }, session: { csrfToken: 'good' }, }; const res = mockRes(); // 防御:空 token 走 timingSafeEqual 会抛错,被 require() 内部 try/catch 吞掉 // 期望:不调用 next try { requireCsrf(req, res, vi.fn()); expect(res.statusCode).toBe(403); } catch { // 也接受抛错(更安全的行为) expect(true).toBe(true); } }); });