| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- import { describe, expect, test, vi } from "vitest";
- function createThenableQuery<T>(result: T) {
- const query: any = Promise.resolve(result);
- query.from = vi.fn(() => query);
- query.limit = vi.fn(() => query);
- query.set = vi.fn(() => query);
- query.where = vi.fn(() => query);
- query.returning = vi.fn(() => query);
- query.values = vi.fn(() => query);
- query.onConflictDoNothing = vi.fn(() => query);
- return query;
- }
- function createRejectedThenableQuery(error: unknown) {
- const query: any = {};
- query.from = vi.fn(() => query);
- query.limit = vi.fn(() => Promise.reject(error));
- query.set = vi.fn(() => query);
- query.where = vi.fn(() => query);
- query.returning = vi.fn(() => Promise.reject(error));
- query.values = vi.fn(() => query);
- query.onConflictDoNothing = vi.fn(() => Promise.reject(error));
- return query;
- }
- describe("SystemSettings:数据库缺列时的保存兜底", () => {
- test("updateSystemSettings 遇到 42703(列缺失)应返回可行动的错误信息", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectQuery = createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- createdAt: now,
- updatedAt: now,
- },
- ]);
- const selectMock = vi.fn(() => selectQuery);
- const updateQuery = createThenableQuery([] as unknown[]);
- updateQuery.returning = vi.fn(() => Promise.reject({ code: "42703" }));
- const updateMock = vi.fn(() => updateQuery);
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: updateMock,
- insert: vi.fn(() => createThenableQuery([])),
- // 给 tests/setup.ts 的 afterAll 清理逻辑一个可用的 execute
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { updateSystemSettings } = await import("@/repository/system-config");
- await expect(updateSystemSettings({ siteTitle: "AutoBits Claude Code Hub" })).rejects.toThrow(
- "system_settings 表列缺失"
- );
- vi.useRealTimers();
- });
- test("updateSystemSettings 遇到 42P01(表不存在)应提示先执行迁移", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectQuery = createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- createdAt: now,
- updatedAt: now,
- },
- ]);
- const selectMock = vi.fn(() => selectQuery);
- const updateQuery = createThenableQuery([] as unknown[]);
- updateQuery.returning = vi.fn(() => Promise.reject({ code: "42P01" }));
- const updateMock = vi.fn(() => updateQuery);
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: updateMock,
- insert: vi.fn(() => createThenableQuery([])),
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { updateSystemSettings } = await import("@/repository/system-config");
- await expect(updateSystemSettings({ siteTitle: "AutoBits Claude Code Hub" })).rejects.toThrow(
- "系统设置数据表不存在"
- );
- vi.useRealTimers();
- });
- test("getSystemSettings 在仅缺 codex_priority_billing_source 列时应保留已有设置", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectMock = vi
- .fn()
- .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" }))
- .mockReturnValueOnce(
- createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- timezone: "Asia/Shanghai",
- enableAutoCleanup: true,
- cleanupRetentionDays: 90,
- cleanupSchedule: "0 3 * * *",
- cleanupBatchSize: 5000,
- enableClientVersionCheck: true,
- verboseProviderError: true,
- enableHttp2: true,
- interceptAnthropicWarmupRequests: true,
- enableThinkingSignatureRectifier: false,
- enableThinkingBudgetRectifier: false,
- enableBillingHeaderRectifier: false,
- enableResponseInputRectifier: false,
- enableCodexSessionIdCompletion: false,
- enableClaudeMetadataUserIdInjection: false,
- enableResponseFixer: false,
- responseFixerConfig: {
- fixTruncatedJson: false,
- fixSseFormat: false,
- fixEncoding: false,
- maxJsonDepth: 50,
- maxFixSize: 2048,
- },
- quotaDbRefreshIntervalSeconds: 30,
- quotaLeasePercent5h: "0.10",
- quotaLeasePercentDaily: "0.11",
- quotaLeasePercentWeekly: "0.12",
- quotaLeasePercentMonthly: "0.13",
- quotaLeaseCapUsd: "1.50",
- createdAt: now,
- updatedAt: now,
- },
- ])
- );
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: vi.fn(() => createThenableQuery([])),
- insert: vi.fn(() => createThenableQuery([])),
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { getSystemSettings } = await import("@/repository/system-config");
- const result = await getSystemSettings();
- expect(result.codexPriorityBillingSource).toBe("requested");
- expect(result.enableHttp2).toBe(true);
- expect(result.interceptAnthropicWarmupRequests).toBe(true);
- expect(result.verboseProviderError).toBe(true);
- expect(result.quotaLeasePercentDaily).toBe(0.11);
- vi.useRealTimers();
- });
- test("getSystemSettings 在缺少新列且无记录时应使用降级插入初始化", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectMock = vi
- .fn()
- .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" }))
- .mockReturnValueOnce(createThenableQuery([]))
- .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" }))
- .mockReturnValueOnce(
- createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- createdAt: now,
- updatedAt: now,
- },
- ])
- );
- const rejectedInsertQuery = createThenableQuery([] as unknown[]);
- rejectedInsertQuery.onConflictDoNothing = vi.fn(() => Promise.reject({ code: "42703" }));
- const insertMock = vi
- .fn()
- .mockReturnValueOnce(rejectedInsertQuery)
- .mockReturnValueOnce(createThenableQuery([]));
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: vi.fn(() => createThenableQuery([])),
- insert: insertMock,
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { getSystemSettings } = await import("@/repository/system-config");
- const result = await getSystemSettings();
- expect(result.siteTitle).toBe("Claude Code Hub");
- expect(result.codexPriorityBillingSource).toBe("requested");
- expect(insertMock).toHaveBeenCalledTimes(2);
- vi.useRealTimers();
- });
- test("updateSystemSettings 在仅缺新列时应降级保存其他字段", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectMock = vi
- .fn()
- .mockReturnValueOnce(createRejectedThenableQuery({ code: "42703" }))
- .mockReturnValueOnce(
- createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- createdAt: now,
- updatedAt: now,
- },
- ])
- );
- const rejectedUpdateQuery = createThenableQuery([] as unknown[]);
- rejectedUpdateQuery.returning = vi.fn(() => Promise.reject({ code: "42703" }));
- const downgradedUpdateQuery = createThenableQuery([
- {
- id: 1,
- siteTitle: "Updated Title",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- createdAt: now,
- updatedAt: now,
- },
- ]);
- const updateMock = vi
- .fn()
- .mockReturnValueOnce(rejectedUpdateQuery)
- .mockReturnValueOnce(downgradedUpdateQuery);
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: updateMock,
- insert: vi.fn(() => createThenableQuery([])),
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { updateSystemSettings } = await import("@/repository/system-config");
- const result = await updateSystemSettings({
- siteTitle: "Updated Title",
- codexPriorityBillingSource: "actual",
- });
- expect(result.siteTitle).toBe("Updated Title");
- expect(result.codexPriorityBillingSource).toBe("requested");
- expect(updateMock).toHaveBeenCalledTimes(2);
- vi.useRealTimers();
- });
- test("updateSystemSettings 在仅缺 enable_high_concurrency_mode 列时,仍应保留 codexPriorityBillingSource 更新", async () => {
- vi.resetModules();
- const now = new Date("2026-01-04T00:00:00.000Z");
- vi.useFakeTimers();
- vi.setSystemTime(now);
- const selectMock = vi.fn().mockReturnValue(
- createThenableQuery([
- {
- id: 1,
- siteTitle: "Claude Code Hub",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- codexPriorityBillingSource: "requested",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- createdAt: now,
- updatedAt: now,
- },
- ])
- );
- const rejectedUpdateQuery = createThenableQuery([] as unknown[]);
- rejectedUpdateQuery.returning = vi.fn(() => Promise.reject({ code: "42703" }));
- const downgradedUpdateQuery = createThenableQuery([
- {
- id: 1,
- siteTitle: "Updated Title",
- allowGlobalUsageView: false,
- currencyDisplay: "USD",
- billingModelSource: "original",
- codexPriorityBillingSource: "actual",
- enableAutoCleanup: false,
- cleanupRetentionDays: 30,
- cleanupSchedule: "0 2 * * *",
- cleanupBatchSize: 10000,
- enableClientVersionCheck: false,
- verboseProviderError: false,
- enableHttp2: false,
- interceptAnthropicWarmupRequests: false,
- createdAt: now,
- updatedAt: now,
- },
- ]);
- const updateMock = vi
- .fn()
- .mockReturnValueOnce(rejectedUpdateQuery)
- .mockReturnValueOnce(downgradedUpdateQuery);
- vi.doMock("@/drizzle/db", () => ({
- db: {
- select: selectMock,
- update: updateMock,
- insert: vi.fn(() => createThenableQuery([])),
- execute: vi.fn(async () => ({ count: 0 })),
- },
- }));
- const { updateSystemSettings } = await import("@/repository/system-config");
- const result = await updateSystemSettings({
- siteTitle: "Updated Title",
- codexPriorityBillingSource: "actual",
- enableHighConcurrencyMode: true,
- });
- expect(result.siteTitle).toBe("Updated Title");
- expect(result.codexPriorityBillingSource).toBe("actual");
- expect(result.enableHighConcurrencyMode).toBe(false);
- expect(updateMock).toHaveBeenCalledTimes(2);
- vi.useRealTimers();
- });
- });
|