route-auth-gate.test.js 4.74 KB
/**
 * route-auth-gate.test.js —— 38 号文档 Phase 1 的守卫测试
 *
 * 只验证一件事:"这个 router 的 requireAuth/requireAdmin/requireAuthOrInternal 是不是真的
 * 挂在最前面",不发真实 HTTP 请求、不碰 DB —— 结构检查:`router.stack` 里能不能找到那个
 * 中间件函数本身(同一个函数引用,不是同名重写),且它排在所有具体路由(`layer.route` 有值
 * 的层)前面。真正打 HTTP 会 401 由各业务测试文件顺带覆盖(jwt-auth.js 的
 * FORCE_REAL_AUTH 默认在测试环境放行,这里显式打开验证真实拦截效果)。
 *
 * 这条测试存在的理由:67 处业务路由测试全靠 supertest 直打路由,从没设过登录 cookie——
 * 如果只跑那些测试,`router.use(requireAuth)` 被误删也不会被任何测试发现(默认放行,
 * 删不删都是 200)。这条测试专门盯"锁挂了没有"这一件事,跟业务逻辑测试互不重叠。
 */
import { describe, it, expect, afterAll } from 'vitest';

process.env.NODE_ENV = 'test';
process.env.FORCE_REAL_AUTH = '1'; // 关掉 jwt-auth.js 的测试环境默认放行

const {
  requireAuth,
  requireAdmin,
  requireAuthOrInternal,
} = require('../../middleware/jwt-auth');

/** router.stack 里有没有这个中间件函数本身,且排在第一条具体路由之前 */
function gateIndex(router, middlewareFn) {
  const stack = router.stack || [];
  return stack.findIndex((layer) => layer.handle === middlewareFn);
}
function firstRouteIndex(router) {
  const stack = router.stack || [];
  return stack.findIndex((layer) => !!layer.route);
}
function expectGated(router, middlewareFn, label) {
  const gateAt = gateIndex(router, middlewareFn);
  const routeAt = firstRouteIndex(router);
  expect(gateAt, `${label}: 没有挂上预期的鉴权中间件`).toBeGreaterThanOrEqual(0);
  expect(gateAt, `${label}: 鉴权中间件排在了具体路由后面,前面那些路由没被拦到`)
    .toBeLessThan(routeAt === -1 ? Infinity : routeAt);
}

describe('route-auth-gate(38 号文档 Phase 1)', () => {
  afterAll(() => {
    delete process.env.FORCE_REAL_AUTH;
  });

  it('company.js 挂了 requireAuthOrInternal(39 号文档:goals/tasks 系列端点要给 agent 用 curl 直接调,比如 ceo 的心跳监督循环,不是普通 requireAuth)', () => {
    expectGated(require('../../routes/company.js'), requireAuthOrInternal, 'company.js');
  });

  it('tasks.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/tasks.js'), requireAuth, 'tasks.js');
  });

  it('devloop.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/devloop.js'), requireAuth, 'devloop.js');
  });

  it('graph-run.js 两个挂载点都挂了 requireAuthOrInternal(39 号文档:ceo 心跳要能读/停/重试 run,不能只有 company.js 一侧放行 agent 自环调用)', () => {
    const graphRun = require('../../routes/graph-run.js');
    expectGated(graphRun, requireAuthOrInternal, 'graph-run.js router');
    expectGated(graphRun.companyRouter, requireAuthOrInternal, 'graph-run.js companyRouter');
  });

  it('okr.js 挂了 requireAdmin(孤儿页面,前端无入口,收紧到 admin-only,见 38 号文档 Phase 2)', () => {
    expectGated(require('../../routes/okr.js'), requireAdmin, 'okr.js');
  });

  it('arena.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/arena.js'), requireAuth, 'arena.js');
  });

  it('config.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/config.js'), requireAuth, 'config.js');
  });

  it('evolution.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/evolution.js'), requireAuth, 'evolution.js');
  });

  it('whatsapp-auth.js 挂了 requireAuth', () => {
    expectGated(require('../../routes/whatsapp-auth.js'), requireAuth, 'whatsapp-auth.js');
  });

  it('dream.js 挂了 requireAdmin(系统级,只有 admin 能看,不是登录即可看)', () => {
    expectGated(require('../../routes/dream.js'), requireAdmin, 'dream.js');
  });

  it('agent-chat.js 挂了 requireAuthOrInternal(不是普通 requireAuth——三处服务端自环调用要放行)', () => {
    expectGated(require('../../routes/agent-chat.js'), requireAuthOrInternal, 'agent-chat.js');
  });

  it('open-api.js 不该挂 cookie 鉴权(它自己一套 OPEN_API_KEYS 体系,面向外部 Skill 市场调用方)', () => {
    const openApi = require('../../routes/open-api.js');
    expect(gateIndex(openApi, requireAuth)).toBe(-1);
    expect(gateIndex(openApi, requireAuthOrInternal)).toBe(-1);
  });

  it('auth.js 自身不挂全局鉴权(登录/注册端点必须对未登录用户可达)', () => {
    const auth = require('../../routes/auth.js');
    expect(gateIndex(auth, requireAuth)).toBe(-1);
  });
});