| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- import { beforeEach, describe, expect, it, vi } from "vitest";
- import { locales } from "@/i18n/config";
- // Mock dependencies
- const getSessionMock = vi.fn();
- const revalidatePathMock = vi.fn();
- const invalidateSystemSettingsCacheMock = vi.fn();
- const updateSystemSettingsMock = vi.fn();
- const getSystemSettingsMock = vi.fn();
- vi.mock("@/lib/auth", () => ({
- getSession: () => getSessionMock(),
- }));
- vi.mock("next/cache", () => ({
- revalidatePath: (...args: unknown[]) => revalidatePathMock(...args),
- }));
- vi.mock("@/lib/config", () => ({
- invalidateSystemSettingsCache: () => invalidateSystemSettingsCacheMock(),
- }));
- vi.mock("@/lib/logger", () => ({
- logger: {
- trace: vi.fn(),
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- },
- }));
- vi.mock("@/lib/utils/timezone", () => ({
- resolveSystemTimezone: vi.fn(async () => "UTC"),
- isValidIANATimezone: vi.fn(() => true),
- }));
- vi.mock("@/repository/system-config", () => ({
- getSystemSettings: () => getSystemSettingsMock(),
- updateSystemSettings: (...args: unknown[]) => updateSystemSettingsMock(...args),
- }));
- // Import the action after mocks are set up
- import { saveSystemSettings } from "@/actions/system-config";
- describe("saveSystemSettings", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- // Default: admin session
- getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin" } });
- // Default: successful update
- updateSystemSettingsMock.mockResolvedValue({
- id: 1,
- siteTitle: "Test Site",
- allowGlobalUsageView: false,
- currencyDisplay: "CNY",
- billingModelSource: "original",
- timezone: null,
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 3 * * *",
- cleanupBatchSize: 1000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- enableThinkingSignatureRectifier: false,
- enableCodexSessionIdCompletion: false,
- enableResponseFixer: false,
- responseFixerConfig: {
- fixEncoding: false,
- fixStreamingJson: false,
- fixEmptyResponse: false,
- fixContentBlockDelta: false,
- maxRetries: 3,
- timeout: 5000,
- },
- quotaDbRefreshIntervalSeconds: 60,
- quotaLeasePercent5h: 0.05,
- quotaLeasePercentDaily: 0.05,
- quotaLeasePercentWeekly: 0.05,
- quotaLeasePercentMonthly: 0.05,
- quotaLeaseCapUsd: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- });
- });
- it("should return error when user is not admin", async () => {
- getSessionMock.mockResolvedValue({ user: { id: 1, role: "user" } });
- const result = await saveSystemSettings({ siteTitle: "New Title" });
- expect(result.ok).toBe(false);
- expect(result.error).toContain("无权限");
- expect(updateSystemSettingsMock).not.toHaveBeenCalled();
- });
- it("should return error when user is not logged in", async () => {
- getSessionMock.mockResolvedValue(null);
- const result = await saveSystemSettings({ siteTitle: "New Title" });
- expect(result.ok).toBe(false);
- expect(result.error).toContain("无权限");
- expect(updateSystemSettingsMock).not.toHaveBeenCalled();
- });
- it("should call updateSystemSettings with validated data", async () => {
- const result = await saveSystemSettings({
- siteTitle: "New Site Title",
- verboseProviderError: true,
- });
- expect(result.ok).toBe(true);
- expect(updateSystemSettingsMock).toHaveBeenCalledWith(
- expect.objectContaining({
- siteTitle: "New Site Title",
- verboseProviderError: true,
- })
- );
- });
- it("should invalidate system settings cache after successful save", async () => {
- await saveSystemSettings({ siteTitle: "New Title" });
- expect(invalidateSystemSettingsCacheMock).toHaveBeenCalled();
- });
- describe("revalidatePath locale coverage", () => {
- it("should revalidate paths for ALL supported locales", async () => {
- await saveSystemSettings({ siteTitle: "New Title" });
- // Collect all revalidatePath calls
- const calls = revalidatePathMock.mock.calls.map((call) => call[0]);
- // Check that each locale's settings/config path is revalidated
- for (const locale of locales) {
- const expectedSettingsPath = `/${locale}/settings/config`;
- expect(calls).toContain(expectedSettingsPath);
- }
- });
- it("should revalidate dashboard paths for ALL supported locales", async () => {
- await saveSystemSettings({ siteTitle: "New Title" });
- const calls = revalidatePathMock.mock.calls.map((call) => call[0]);
- // Check that each locale's dashboard path is revalidated
- for (const locale of locales) {
- const expectedDashboardPath = `/${locale}/dashboard`;
- expect(calls).toContain(expectedDashboardPath);
- }
- });
- it("should revalidate root layout", async () => {
- await saveSystemSettings({ siteTitle: "New Title" });
- // Check that root layout is revalidated
- expect(revalidatePathMock).toHaveBeenCalledWith("/", "layout");
- });
- it("should call revalidatePath at least 2 * locales.length + 1 times", async () => {
- await saveSystemSettings({ siteTitle: "New Title" });
- // 2 paths per locale (settings/config + dashboard) + 1 for root layout
- const expectedMinCalls = locales.length * 2 + 1;
- expect(revalidatePathMock).toHaveBeenCalledTimes(expectedMinCalls);
- });
- });
- it("should return updated settings on success", async () => {
- const mockUpdated = {
- id: 1,
- siteTitle: "Updated Title",
- allowGlobalUsageView: true,
- currencyDisplay: "USD",
- billingModelSource: "original",
- timezone: "America/New_York",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 3 * * *",
- cleanupBatchSize: 1000,
- enableClientVersionCheck: false,
- verboseProviderError: true,
- enableHttp2: true,
- interceptAnthropicWarmupRequests: false,
- enableThinkingSignatureRectifier: false,
- enableCodexSessionIdCompletion: false,
- enableResponseFixer: false,
- responseFixerConfig: {
- fixEncoding: false,
- fixStreamingJson: false,
- fixEmptyResponse: false,
- fixContentBlockDelta: false,
- maxRetries: 3,
- timeout: 5000,
- },
- quotaDbRefreshIntervalSeconds: 60,
- quotaLeasePercent5h: 0.05,
- quotaLeasePercentDaily: 0.05,
- quotaLeasePercentWeekly: 0.05,
- quotaLeasePercentMonthly: 0.05,
- quotaLeaseCapUsd: null,
- createdAt: new Date(),
- updatedAt: new Date(),
- };
- updateSystemSettingsMock.mockResolvedValue(mockUpdated);
- const result = await saveSystemSettings({
- siteTitle: "Updated Title",
- allowGlobalUsageView: true,
- currencyDisplay: "USD",
- timezone: "America/New_York",
- verboseProviderError: true,
- enableHttp2: true,
- });
- expect(result.ok).toBe(true);
- expect(result.data).toEqual(mockUpdated);
- });
- it("should handle repository errors gracefully", async () => {
- updateSystemSettingsMock.mockRejectedValue(new Error("Database error"));
- const result = await saveSystemSettings({ siteTitle: "New Title" });
- expect(result.ok).toBe(false);
- expect(result.error).toContain("Database error");
- });
- });
|