| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import { describe, expect, test, vi } from "vitest";
- import "@/lib/auth-session-storage.node";
- /**
- * 回归用例:/api/actions adapter 鉴权通过后,action 内部调用 getSession() 仍应拿到会话
- *
- * 背景:
- * - adapter 层使用 hono 读取 Cookie/Authorization 并 validateKey
- * - action 层传统依赖 next/headers 读取请求上下文
- * - 某些运行时下 action 读取不到上下文,导致返回 ok 但 data 为空
- *
- * 期望:
- * - adapter 在调用 action 时注入 session(AsyncLocalStorage)
- * - action 内 getSession() 优先读取注入会话,不触发 next/headers
- */
- vi.mock("next/headers", () => ({
- cookies: () => {
- throw new Error("不应在该用例中调用 next/headers.cookies()");
- },
- headers: () => ({
- get: () => null,
- }),
- }));
- describe("Action Adapter:会话透传", () => {
- test("requiresAuth=true:action 内 getSession() 应返回注入的 session", async () => {
- vi.resetModules();
- const mockSession = {
- user: {
- id: 123,
- name: "u1",
- description: "",
- role: "user" as const,
- rpm: null,
- dailyQuota: null,
- providerGroup: null,
- tags: [],
- createdAt: new Date(),
- updatedAt: new Date(),
- deletedAt: undefined,
- dailyResetMode: "fixed" as const,
- dailyResetTime: "00:00",
- isEnabled: true,
- expiresAt: null,
- allowedClients: [],
- allowedModels: [],
- },
- key: {
- id: 1,
- userId: 123,
- name: "k1",
- key: "token-1",
- isEnabled: true,
- expiresAt: undefined,
- canLoginWebUi: false,
- limit5hUsd: null,
- limitDailyUsd: null,
- dailyResetMode: "fixed" as const,
- dailyResetTime: "00:00",
- limitWeeklyUsd: null,
- limitMonthlyUsd: null,
- limitTotalUsd: null,
- limitConcurrentSessions: 0,
- providerGroup: null,
- cacheTtlPreference: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- deletedAt: undefined,
- },
- };
- vi.doMock("@/lib/auth", async (importActual) => {
- const actual = (await importActual()) as typeof import("@/lib/auth");
- return {
- ...actual,
- validateKey: vi.fn(async () => mockSession),
- validateAuthToken: vi.fn(async () => mockSession),
- };
- });
- const { createActionRoute } = await import("@/lib/api/action-adapter-openapi");
- const { getSession, validateAuthToken } = await import("@/lib/auth");
- const action = vi.fn(async () => {
- const session = await getSession();
- // 显式降权校验:当 key 为只读(canLoginWebUi=false)时,strict session 应返回 null
- const strictSession = await getSession({ allowReadOnlyAccess: false });
- return {
- ok: true,
- data: { userId: session?.user.id ?? null, strictUserId: strictSession?.user.id ?? null },
- };
- });
- const { handler } = createActionRoute("users", "getUsers", action as any, {
- requiresAuth: true,
- allowReadOnlyAccess: true,
- });
- const response = (await handler({
- req: {
- raw: new Request("http://localhost/api/actions/users/getUsers", {
- headers: new Headers(),
- }),
- json: async () => ({}),
- header: (name: string) => {
- if (name.toLowerCase() === "authorization") return "Bearer token-1";
- return undefined;
- },
- },
- json: (payload: unknown, status = 200) =>
- new Response(JSON.stringify(payload), {
- status,
- headers: { "content-type": "application/json" },
- }),
- } as any)) as Response;
- expect(validateAuthToken).toHaveBeenCalledTimes(1);
- expect(action).toHaveBeenCalledTimes(1);
- expect(response.status).toBe(200);
- await expect(response.json()).resolves.toEqual({
- ok: true,
- data: { userId: 123, strictUserId: null },
- });
- });
- });
|