| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- /**
- * Timezone Resolver Tests (Task 2)
- *
- * TDD tests for the system timezone resolver:
- * - Fallback chain: DB timezone -> env TZ -> UTC
- * - Validation of resolved timezone
- * - Integration with cached system settings
- */
- import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
- // Mock the system settings cache
- vi.mock("@/lib/config/system-settings-cache", () => ({
- getCachedSystemSettings: vi.fn(),
- }));
- // Mock env config
- vi.mock("@/lib/config/env.schema", () => ({
- getEnvConfig: vi.fn(),
- isDevelopment: vi.fn(() => false),
- }));
- // Mock logger
- vi.mock("@/lib/logger", () => ({
- logger: {
- debug: vi.fn(),
- warn: vi.fn(),
- info: vi.fn(),
- error: vi.fn(),
- },
- }));
- import { getCachedSystemSettings } from "@/lib/config/system-settings-cache";
- import { getEnvConfig } from "@/lib/config/env.schema";
- import type { SystemSettings } from "@/types/system-config";
- const getCachedSystemSettingsMock = vi.mocked(getCachedSystemSettings);
- const getEnvConfigMock = vi.mocked(getEnvConfig);
- function createSettings(overrides: Partial<SystemSettings> = {}): SystemSettings {
- return {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- timezone: null,
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- enableThinkingSignatureRectifier: true,
- enableCodexSessionIdCompletion: true,
- enableResponseFixer: true,
- responseFixerConfig: {
- fixTruncatedJson: true,
- fixSseFormat: true,
- fixEncoding: true,
- maxJsonDepth: 200,
- maxFixSize: 1024 * 1024,
- },
- quotaDbRefreshIntervalSeconds: 10,
- quotaLeasePercent5h: 0.05,
- quotaLeasePercentDaily: 0.05,
- quotaLeasePercentWeekly: 0.05,
- quotaLeasePercentMonthly: 0.05,
- quotaLeaseCapUsd: null,
- createdAt: new Date("2026-01-01T00:00:00.000Z"),
- updatedAt: new Date("2026-01-01T00:00:00.000Z"),
- ...overrides,
- };
- }
- function mockEnvConfig(tz = "Asia/Shanghai") {
- getEnvConfigMock.mockReturnValue({
- NODE_ENV: "test",
- TZ: tz,
- PORT: 23000,
- AUTO_MIGRATE: true,
- ENABLE_RATE_LIMIT: true,
- ENABLE_SECURE_COOKIES: true,
- SESSION_TTL: 300,
- STORE_SESSION_MESSAGES: false,
- DEBUG_MODE: false,
- LOG_LEVEL: "info",
- ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS: false,
- ENABLE_PROVIDER_CACHE: true,
- MAX_RETRY_ATTEMPTS_DEFAULT: 2,
- FETCH_BODY_TIMEOUT: 600000,
- FETCH_HEADERS_TIMEOUT: 600000,
- FETCH_CONNECT_TIMEOUT: 30000,
- REDIS_TLS_REJECT_UNAUTHORIZED: true,
- } as ReturnType<typeof getEnvConfig>);
- }
- beforeEach(() => {
- vi.clearAllMocks();
- });
- describe("resolveSystemTimezone", () => {
- it("should return DB timezone when set and valid", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockResolvedValue(createSettings({ timezone: "America/New_York" }));
- mockEnvConfig("Asia/Shanghai");
- const result = await resolveSystemTimezone();
- expect(result).toBe("America/New_York");
- });
- it("should fallback to env TZ when DB timezone is null", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockResolvedValue(createSettings({ timezone: null }));
- mockEnvConfig("Europe/London");
- const result = await resolveSystemTimezone();
- expect(result).toBe("Europe/London");
- });
- it("should fallback to env TZ when DB timezone is invalid", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockResolvedValue(
- createSettings({ timezone: "Invalid/Timezone_Zone" })
- );
- mockEnvConfig("Asia/Tokyo");
- const result = await resolveSystemTimezone();
- expect(result).toBe("Asia/Tokyo");
- });
- it("should fallback to UTC when both DB timezone and env TZ are invalid", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockResolvedValue(createSettings({ timezone: "Invalid/Zone" }));
- // Empty string TZ won't pass isValidIANATimezone
- mockEnvConfig("");
- const result = await resolveSystemTimezone();
- expect(result).toBe("UTC");
- });
- it("should fallback to UTC when getCachedSystemSettings throws", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockRejectedValue(new Error("DB connection failed"));
- mockEnvConfig("Asia/Shanghai");
- const result = await resolveSystemTimezone();
- // Should still try env TZ fallback
- expect(result).toBe("Asia/Shanghai");
- });
- it("should fallback to UTC when getCachedSystemSettings throws and env TZ is empty", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockRejectedValue(new Error("DB connection failed"));
- mockEnvConfig("");
- const result = await resolveSystemTimezone();
- expect(result).toBe("UTC");
- });
- it("should handle empty string DB timezone as null", async () => {
- const { resolveSystemTimezone } = await import("@/lib/utils/timezone");
- getCachedSystemSettingsMock.mockResolvedValue(
- createSettings({ timezone: "" as unknown as null })
- );
- mockEnvConfig("Europe/Paris");
- const result = await resolveSystemTimezone();
- expect(result).toBe("Europe/Paris");
- });
- });
|