session-manager-helpers.test.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { describe, expect, test, vi } from "vitest";
  2. const loggerWarnMock = vi.fn();
  3. const PARSE_HEADER_RECORD_WARN_MESSAGE = "SessionManager: Failed to parse header record JSON";
  4. function getParseHeaderRecordWarnCalls(): unknown[][] {
  5. return loggerWarnMock.mock.calls.filter((call) => call[0] === PARSE_HEADER_RECORD_WARN_MESSAGE);
  6. }
  7. vi.mock("server-only", () => ({}));
  8. vi.mock("@/lib/logger", () => ({
  9. logger: {
  10. warn: loggerWarnMock,
  11. trace: vi.fn(),
  12. info: vi.fn(),
  13. error: vi.fn(),
  14. },
  15. }));
  16. const sanitizeHeadersMock = vi.fn();
  17. vi.mock("@/app/v1/_lib/proxy/errors", () => ({
  18. sanitizeHeaders: sanitizeHeadersMock,
  19. }));
  20. async function loadHelpers() {
  21. const mod = await import("@/lib/session-manager");
  22. return {
  23. headersToSanitizedObject: mod.headersToSanitizedObject,
  24. parseHeaderRecord: mod.parseHeaderRecord,
  25. extractClientSessionId: mod.SessionManager.extractClientSessionId,
  26. };
  27. }
  28. describe("SessionManager 辅助函数", () => {
  29. test("parseHeaderRecord:有效 JSON 对象应解析为记录", async () => {
  30. vi.clearAllMocks();
  31. const { parseHeaderRecord } = await loadHelpers();
  32. expect(parseHeaderRecord('{"a":"1","b":"2"}')).toEqual({ a: "1", b: "2" });
  33. expect(getParseHeaderRecordWarnCalls()).toHaveLength(0);
  34. });
  35. test("parseHeaderRecord:空对象应返回空记录", async () => {
  36. vi.clearAllMocks();
  37. const { parseHeaderRecord } = await loadHelpers();
  38. expect(parseHeaderRecord("{}")).toEqual({});
  39. expect(getParseHeaderRecordWarnCalls()).toHaveLength(0);
  40. });
  41. test("parseHeaderRecord:只保留字符串值", async () => {
  42. vi.clearAllMocks();
  43. const { parseHeaderRecord } = await loadHelpers();
  44. expect(parseHeaderRecord('{"a":"1","b":2,"c":true,"d":null,"e":{},"f":[]}')).toEqual({
  45. a: "1",
  46. });
  47. expect(getParseHeaderRecordWarnCalls()).toHaveLength(0);
  48. });
  49. test("parseHeaderRecord:无效 JSON 应返回 null 并记录 warn", async () => {
  50. vi.clearAllMocks();
  51. const { parseHeaderRecord } = await loadHelpers();
  52. expect(parseHeaderRecord("{bad json")).toBe(null);
  53. const calls = getParseHeaderRecordWarnCalls();
  54. expect(calls).toHaveLength(1);
  55. const [message, meta] = calls[0] ?? [];
  56. expect(message).toBe("SessionManager: Failed to parse header record JSON");
  57. expect(meta).toEqual(expect.objectContaining({ error: expect.anything() }));
  58. });
  59. test("parseHeaderRecord:JSON 数组/null/原始值应返回 null", async () => {
  60. vi.clearAllMocks();
  61. const { parseHeaderRecord } = await loadHelpers();
  62. expect(parseHeaderRecord('["a"]')).toBe(null);
  63. expect(parseHeaderRecord("null")).toBe(null);
  64. expect(parseHeaderRecord("1")).toBe(null);
  65. expect(getParseHeaderRecordWarnCalls()).toHaveLength(0);
  66. });
  67. test("headersToSanitizedObject:单个 header 应正确转换", async () => {
  68. vi.clearAllMocks();
  69. const { headersToSanitizedObject } = await loadHelpers();
  70. const headers = new Headers({ "x-test": "1" });
  71. sanitizeHeadersMock.mockReturnValueOnce("x-test: 1");
  72. expect(headersToSanitizedObject(headers)).toEqual({ "x-test": "1" });
  73. expect(sanitizeHeadersMock).toHaveBeenCalledWith(headers);
  74. });
  75. test("headersToSanitizedObject:多个 header 应正确转换", async () => {
  76. vi.clearAllMocks();
  77. const { headersToSanitizedObject } = await loadHelpers();
  78. const headers = new Headers({ a: "1", b: "2" });
  79. sanitizeHeadersMock.mockReturnValueOnce("a: 1\nb: 2");
  80. expect(headersToSanitizedObject(headers)).toEqual({ a: "1", b: "2" });
  81. expect(sanitizeHeadersMock).toHaveBeenCalledWith(headers);
  82. });
  83. test("headersToSanitizedObject:空 Headers 应返回空对象", async () => {
  84. vi.clearAllMocks();
  85. const { headersToSanitizedObject } = await loadHelpers();
  86. const headers = new Headers();
  87. sanitizeHeadersMock.mockReturnValueOnce("(empty)");
  88. expect(headersToSanitizedObject(headers)).toEqual({});
  89. expect(sanitizeHeadersMock).toHaveBeenCalledWith(headers);
  90. });
  91. test("headersToSanitizedObject:值包含冒号时应保留完整值", async () => {
  92. vi.clearAllMocks();
  93. const { headersToSanitizedObject } = await loadHelpers();
  94. const headers = new Headers({ "x-test": "a:b:c" });
  95. sanitizeHeadersMock.mockReturnValueOnce("x-test: a:b:c");
  96. expect(headersToSanitizedObject(headers)).toEqual({ "x-test": "a:b:c" });
  97. expect(sanitizeHeadersMock).toHaveBeenCalledWith(headers);
  98. });
  99. test("extractClientSessionId:应兼容旧格式 metadata.user_id", async () => {
  100. vi.clearAllMocks();
  101. const { extractClientSessionId } = await loadHelpers();
  102. expect(
  103. extractClientSessionId({
  104. metadata: {
  105. user_id:
  106. "user_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_account__session_sess_legacy_123",
  107. },
  108. })
  109. ).toBe("sess_legacy_123");
  110. });
  111. test("extractClientSessionId:应兼容 JSON 字符串 metadata.user_id", async () => {
  112. vi.clearAllMocks();
  113. const { extractClientSessionId } = await loadHelpers();
  114. expect(
  115. extractClientSessionId({
  116. metadata: {
  117. user_id: JSON.stringify({
  118. device_id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  119. account_uuid: "",
  120. session_id: "sess_json_123",
  121. }),
  122. },
  123. })
  124. ).toBe("sess_json_123");
  125. });
  126. test("extractClientSessionId:无效 user_id 时应回退到 metadata.session_id", async () => {
  127. vi.clearAllMocks();
  128. const { extractClientSessionId } = await loadHelpers();
  129. expect(
  130. extractClientSessionId({
  131. metadata: {
  132. user_id: "invalid_user_id",
  133. session_id: "sess_fallback_123",
  134. },
  135. })
  136. ).toBe("sess_fallback_123");
  137. });
  138. });