system-config-update-missing-columns.test.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { describe, expect, test, vi } from "vitest";
  2. function createThenableQuery<T>(result: T) {
  3. const query: any = Promise.resolve(result);
  4. query.from = vi.fn(() => query);
  5. query.limit = vi.fn(() => query);
  6. query.set = vi.fn(() => query);
  7. query.where = vi.fn(() => query);
  8. query.returning = vi.fn(() => query);
  9. query.values = vi.fn(() => query);
  10. query.onConflictDoNothing = vi.fn(() => query);
  11. return query;
  12. }
  13. describe("SystemSettings:数据库缺列时的保存兜底", () => {
  14. test("updateSystemSettings 遇到 42703(列缺失)应返回可行动的错误信息", async () => {
  15. vi.resetModules();
  16. const now = new Date("2026-01-04T00:00:00.000Z");
  17. vi.useFakeTimers();
  18. vi.setSystemTime(now);
  19. const selectQuery = createThenableQuery([
  20. {
  21. id: 1,
  22. siteTitle: "Claude Code Hub",
  23. allowGlobalUsageView: false,
  24. currencyDisplay: "USD",
  25. billingModelSource: "original",
  26. enableAutoCleanup: false,
  27. cleanupRetentionDays: 30,
  28. cleanupSchedule: "0 2 * * *",
  29. cleanupBatchSize: 10000,
  30. enableClientVersionCheck: false,
  31. verboseProviderError: false,
  32. enableHttp2: false,
  33. interceptAnthropicWarmupRequests: false,
  34. createdAt: now,
  35. updatedAt: now,
  36. },
  37. ]);
  38. const selectMock = vi.fn(() => selectQuery);
  39. const updateQuery = createThenableQuery([] as unknown[]);
  40. updateQuery.returning = vi.fn(() => Promise.reject({ code: "42703" }));
  41. const updateMock = vi.fn(() => updateQuery);
  42. vi.doMock("@/drizzle/db", () => ({
  43. db: {
  44. select: selectMock,
  45. update: updateMock,
  46. insert: vi.fn(() => createThenableQuery([])),
  47. // 给 tests/setup.ts 的 afterAll 清理逻辑一个可用的 execute
  48. execute: vi.fn(async () => ({ count: 0 })),
  49. },
  50. }));
  51. const { updateSystemSettings } = await import("@/repository/system-config");
  52. await expect(updateSystemSettings({ siteTitle: "AutoBits Claude Code Hub" })).rejects.toThrow(
  53. "system_settings 表列缺失"
  54. );
  55. vi.useRealTimers();
  56. });
  57. test("updateSystemSettings 遇到 42P01(表不存在)应提示先执行迁移", async () => {
  58. vi.resetModules();
  59. const now = new Date("2026-01-04T00:00:00.000Z");
  60. vi.useFakeTimers();
  61. vi.setSystemTime(now);
  62. const selectQuery = createThenableQuery([
  63. {
  64. id: 1,
  65. siteTitle: "Claude Code Hub",
  66. allowGlobalUsageView: false,
  67. currencyDisplay: "USD",
  68. billingModelSource: "original",
  69. createdAt: now,
  70. updatedAt: now,
  71. },
  72. ]);
  73. const selectMock = vi.fn(() => selectQuery);
  74. const updateQuery = createThenableQuery([] as unknown[]);
  75. updateQuery.returning = vi.fn(() => Promise.reject({ code: "42P01" }));
  76. const updateMock = vi.fn(() => updateQuery);
  77. vi.doMock("@/drizzle/db", () => ({
  78. db: {
  79. select: selectMock,
  80. update: updateMock,
  81. insert: vi.fn(() => createThenableQuery([])),
  82. execute: vi.fn(async () => ({ count: 0 })),
  83. },
  84. }));
  85. const { updateSystemSettings } = await import("@/repository/system-config");
  86. await expect(updateSystemSettings({ siteTitle: "AutoBits Claude Code Hub" })).rejects.toThrow(
  87. "系统设置数据表不存在"
  88. );
  89. vi.useRealTimers();
  90. });
  91. });