session-origin-chain.test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { beforeEach, describe, expect, test, vi } from "vitest";
  2. import type { ProviderChainItem } from "@/types/message";
  3. const getSessionMock = vi.fn();
  4. const findSessionOriginChainMock = vi.fn();
  5. const findKeyListMock = vi.fn();
  6. const dbSelectMock = vi.fn();
  7. const dbFromMock = vi.fn();
  8. const dbWhereMock = vi.fn();
  9. const dbLimitMock = vi.fn();
  10. vi.mock("@/lib/auth", () => ({
  11. getSession: getSessionMock,
  12. }));
  13. vi.mock("@/repository/message", () => ({
  14. findSessionOriginChain: findSessionOriginChainMock,
  15. }));
  16. vi.mock("@/repository/key", () => ({
  17. findKeyList: findKeyListMock,
  18. }));
  19. vi.mock("@/drizzle/db", () => ({
  20. db: {
  21. select: dbSelectMock,
  22. },
  23. }));
  24. describe("getSessionOriginChain", () => {
  25. beforeEach(() => {
  26. vi.clearAllMocks();
  27. dbSelectMock.mockReturnValue({ from: dbFromMock });
  28. dbFromMock.mockReturnValue({ where: dbWhereMock });
  29. dbWhereMock.mockReturnValue({ limit: dbLimitMock });
  30. dbLimitMock.mockResolvedValue([{ id: 1 }]);
  31. findKeyListMock.mockResolvedValue([{ key: "user-key-1" }]);
  32. });
  33. test("admin happy path: returns provider chain", async () => {
  34. getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } });
  35. const chain: ProviderChainItem[] = [
  36. {
  37. id: 11,
  38. name: "provider-a",
  39. reason: "initial_selection",
  40. },
  41. ];
  42. findSessionOriginChainMock.mockResolvedValue(chain);
  43. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  44. const result = await getSessionOriginChain("sess-admin");
  45. expect(result).toEqual({ ok: true, data: chain });
  46. expect(findSessionOriginChainMock).toHaveBeenCalledWith("sess-admin");
  47. expect(findKeyListMock).not.toHaveBeenCalled();
  48. expect(dbSelectMock).not.toHaveBeenCalled();
  49. });
  50. test("non-admin happy path: returns provider chain after ownership check", async () => {
  51. getSessionMock.mockResolvedValue({ user: { id: 2, role: "user" } });
  52. const chain: ProviderChainItem[] = [
  53. {
  54. id: 22,
  55. name: "provider-b",
  56. reason: "session_reuse",
  57. },
  58. ];
  59. findSessionOriginChainMock.mockResolvedValue(chain);
  60. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  61. const result = await getSessionOriginChain("sess-user");
  62. expect(result).toEqual({ ok: true, data: chain });
  63. expect(findKeyListMock).toHaveBeenCalledWith(2);
  64. expect(dbSelectMock).toHaveBeenCalledTimes(1);
  65. expect(findSessionOriginChainMock).toHaveBeenCalledWith("sess-user");
  66. });
  67. test("unauthenticated: returns not logged in", async () => {
  68. getSessionMock.mockResolvedValue(null);
  69. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  70. const result = await getSessionOriginChain("sess-no-auth");
  71. expect(result).toEqual({ ok: false, error: "未登录" });
  72. expect(findSessionOriginChainMock).not.toHaveBeenCalled();
  73. expect(findKeyListMock).not.toHaveBeenCalled();
  74. expect(dbSelectMock).not.toHaveBeenCalled();
  75. });
  76. test("non-admin without access: returns unauthorized error", async () => {
  77. getSessionMock.mockResolvedValue({ user: { id: 3, role: "user" } });
  78. findKeyListMock.mockResolvedValue([{ key: "user-key-3" }]);
  79. dbLimitMock.mockResolvedValue([]);
  80. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  81. const result = await getSessionOriginChain("sess-other-user");
  82. expect(result).toEqual({ ok: false, error: "无权访问该 Session" });
  83. expect(findSessionOriginChainMock).not.toHaveBeenCalled();
  84. });
  85. test("exception path: returns error on unexpected throw", async () => {
  86. getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } });
  87. findSessionOriginChainMock.mockRejectedValue(new Error("db error"));
  88. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  89. const result = await getSessionOriginChain("sess-throws");
  90. expect(result).toEqual({ ok: false, error: "获取会话来源链失败" });
  91. });
  92. test("not found: returns ok with null data", async () => {
  93. getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } });
  94. findSessionOriginChainMock.mockResolvedValue(null);
  95. const { getSessionOriginChain } = await import("@/actions/session-origin-chain");
  96. const result = await getSessionOriginChain("sess-not-found");
  97. expect(result).toEqual({ ok: true, data: null });
  98. expect(findSessionOriginChainMock).toHaveBeenCalledWith("sess-not-found");
  99. expect(findKeyListMock).not.toHaveBeenCalled();
  100. expect(dbSelectMock).not.toHaveBeenCalled();
  101. });
  102. });