system-settings-responses-websocket-toggle.test.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
  2. import type { SystemSettings } from "@/types/system-config";
  3. const getSessionMock = vi.fn();
  4. const updateSystemSettingsMock = vi.fn();
  5. const getSystemSettingsRepoMock = vi.fn();
  6. const revalidatePathMock = vi.fn();
  7. const invalidateSystemSettingsCacheMock = vi.fn();
  8. const loggerMock = {
  9. trace: vi.fn(),
  10. debug: vi.fn(),
  11. info: vi.fn(),
  12. warn: vi.fn(),
  13. error: vi.fn(),
  14. };
  15. vi.mock("server-only", () => ({}));
  16. vi.mock("@/lib/auth", () => ({
  17. getSession: () => getSessionMock(),
  18. }));
  19. vi.mock("next/cache", () => ({
  20. revalidatePath: (...args: unknown[]) => revalidatePathMock(...args),
  21. }));
  22. vi.mock("@/lib/config", () => ({
  23. invalidateSystemSettingsCache: () => invalidateSystemSettingsCacheMock(),
  24. }));
  25. vi.mock("@/lib/logger", () => ({
  26. logger: loggerMock,
  27. }));
  28. vi.mock("@/lib/utils/timezone", () => ({
  29. resolveSystemTimezone: vi.fn(async () => "UTC"),
  30. isValidIANATimezone: vi.fn(() => true),
  31. }));
  32. vi.mock("@/repository/system-config", () => ({
  33. getSystemSettings: () => getSystemSettingsRepoMock(),
  34. updateSystemSettings: (...args: unknown[]) => updateSystemSettingsMock(...args),
  35. }));
  36. function createSettings(overrides: Partial<SystemSettings> = {}): SystemSettings {
  37. const base = {
  38. id: 1,
  39. siteTitle: "Claude Code Hub",
  40. allowGlobalUsageView: false,
  41. currencyDisplay: "USD",
  42. billingModelSource: "original",
  43. timezone: null,
  44. enableAutoCleanup: false,
  45. cleanupRetentionDays: 30,
  46. cleanupSchedule: "0 2 * * *",
  47. cleanupBatchSize: 10000,
  48. enableClientVersionCheck: false,
  49. verboseProviderError: false,
  50. enableHttp2: false,
  51. enableResponsesWebSocket: false,
  52. interceptAnthropicWarmupRequests: false,
  53. enableThinkingSignatureRectifier: true,
  54. enableThinkingBudgetRectifier: true,
  55. enableBillingHeaderRectifier: true,
  56. enableCodexSessionIdCompletion: true,
  57. enableClaudeMetadataUserIdInjection: true,
  58. enableResponseFixer: true,
  59. responseFixerConfig: {
  60. fixTruncatedJson: true,
  61. fixSseFormat: true,
  62. fixEncoding: true,
  63. maxJsonDepth: 200,
  64. maxFixSize: 1024 * 1024,
  65. },
  66. quotaDbRefreshIntervalSeconds: 10,
  67. quotaLeasePercent5h: 0.05,
  68. quotaLeasePercentDaily: 0.05,
  69. quotaLeasePercentWeekly: 0.05,
  70. quotaLeasePercentMonthly: 0.05,
  71. quotaLeaseCapUsd: null,
  72. createdAt: new Date("2026-01-01T00:00:00.000Z"),
  73. updatedAt: new Date("2026-01-01T00:00:00.000Z"),
  74. } satisfies SystemSettings;
  75. return { ...base, ...overrides };
  76. }
  77. describe("system settings responses websocket toggle", () => {
  78. beforeEach(() => {
  79. vi.clearAllMocks();
  80. vi.resetModules();
  81. vi.useFakeTimers();
  82. vi.setSystemTime(new Date("2026-03-08T00:00:00.000Z"));
  83. getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } });
  84. });
  85. afterEach(() => {
  86. vi.useRealTimers();
  87. });
  88. it("keeps enableResponsesWebSocket on the typed SystemSettings surface", () => {
  89. const settings = createSettings({ enableResponsesWebSocket: true });
  90. expect(settings.enableResponsesWebSocket).toBe(true);
  91. });
  92. it("forwards enableResponsesWebSocket through saveSystemSettings", async () => {
  93. updateSystemSettingsMock.mockResolvedValue(createSettings({ enableResponsesWebSocket: true }));
  94. const { saveSystemSettings } = await import("@/actions/system-config");
  95. const result = await saveSystemSettings({ enableResponsesWebSocket: true });
  96. expect(result.ok).toBe(true);
  97. expect(updateSystemSettingsMock).toHaveBeenCalledWith(
  98. expect.objectContaining({ enableResponsesWebSocket: true })
  99. );
  100. expect(invalidateSystemSettingsCacheMock).toHaveBeenCalledTimes(1);
  101. });
  102. it("refreshes the cached helper after invalidation within the same cycle", async () => {
  103. getSystemSettingsRepoMock
  104. .mockResolvedValueOnce(createSettings({ id: 1, enableResponsesWebSocket: false }))
  105. .mockResolvedValueOnce(createSettings({ id: 2, enableResponsesWebSocket: true }));
  106. const cacheModule = await import("@/lib/config/system-settings-cache");
  107. const first = await cacheModule.getCachedSystemSettings();
  108. expect(first.enableResponsesWebSocket).toBe(false);
  109. cacheModule.invalidateSystemSettingsCache();
  110. const second = await cacheModule.getCachedSystemSettings();
  111. expect(second.enableResponsesWebSocket).toBe(true);
  112. expect(await cacheModule.isResponsesWebSocketEnabled()).toBe(true);
  113. expect(getSystemSettingsRepoMock).toHaveBeenCalledTimes(2);
  114. });
  115. });