error-rules-default-codex-responses.test.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { describe, expect, test, vi } from "vitest";
  2. // 该测试通过 mock 仓储层验证默认规则内容,不需要真实 DB/Redis。
  3. // 禁用 tests/setup.ts 中基于 DSN/Redis 的默认同步与清理协调,避免无关依赖引入。
  4. process.env.DSN = "";
  5. process.env.AUTO_CLEANUP_TEST_DATA = "false";
  6. const capturedInsertedRules: any[] = [];
  7. vi.mock("drizzle-orm", async (importOriginal) => {
  8. const actual = await importOriginal<typeof import("drizzle-orm")>();
  9. return {
  10. ...actual,
  11. // 仅用于构造查询条件参数,单测不关心其实现细节
  12. desc: vi.fn((...args: unknown[]) => ({ args, op: "desc" })),
  13. eq: vi.fn((...args: unknown[]) => ({ args, op: "eq" })),
  14. inArray: vi.fn((...args: unknown[]) => ({ args, op: "inArray" })),
  15. };
  16. });
  17. vi.mock("@/drizzle/schema", () => ({
  18. // 仅需提供被 syncDefaultErrorRules 用到的字段占位符
  19. errorRules: {
  20. id: "error_rules.id",
  21. pattern: "error_rules.pattern",
  22. isDefault: "error_rules.is_default",
  23. },
  24. }));
  25. vi.mock("@/drizzle/db", () => ({
  26. db: {
  27. transaction: vi.fn(async (fn: (tx: any) => Promise<void>) => {
  28. const tx = {
  29. query: {
  30. errorRules: {
  31. findMany: vi.fn(async () => []),
  32. },
  33. },
  34. delete: vi.fn(() => ({
  35. where: vi.fn(async () => []),
  36. })),
  37. insert: vi.fn(() => ({
  38. values: (rule: any) => {
  39. capturedInsertedRules.push(rule);
  40. return {
  41. onConflictDoNothing: () => ({
  42. returning: vi.fn(async () => [{ id: 1 }]),
  43. }),
  44. };
  45. },
  46. })),
  47. update: vi.fn(() => ({
  48. set: vi.fn(() => ({
  49. where: vi.fn(async () => []),
  50. })),
  51. })),
  52. };
  53. await fn(tx);
  54. }),
  55. },
  56. }));
  57. vi.mock("@/lib/emit-event", () => ({
  58. emitErrorRulesUpdated: vi.fn(async () => {}),
  59. }));
  60. vi.mock("@/lib/logger", () => ({
  61. logger: {
  62. info: vi.fn(),
  63. warn: vi.fn(),
  64. error: vi.fn(),
  65. trace: vi.fn(),
  66. },
  67. }));
  68. describe("syncDefaultErrorRules - OpenAI Responses API error rules", () => {
  69. test("should include store=false rule with OpenAI error format", async () => {
  70. capturedInsertedRules.length = 0;
  71. vi.resetModules();
  72. const { syncDefaultErrorRules } = await import("@/repository/error-rules");
  73. await syncDefaultErrorRules();
  74. const rule = capturedInsertedRules.find((r) => r.pattern === "Items are not persisted when");
  75. expect(rule).toBeTruthy();
  76. expect(rule.matchType).toBe("contains");
  77. expect(rule.category).toBe("store_error");
  78. expect(rule.priority).toBe(73);
  79. // OpenAI error format (no top-level type field)
  80. expect(rule.overrideResponse?.error?.type).toBe("invalid_request_error");
  81. expect(rule.overrideResponse?.error?.message).toContain("store=false");
  82. expect(rule.overrideResponse?.error?.message).toContain("store=true");
  83. expect(rule.overrideResponse?.error?.message).toContain("rs_xxx");
  84. });
  85. test("should include input-must-be-a-list rule with OpenAI error format", async () => {
  86. capturedInsertedRules.length = 0;
  87. vi.resetModules();
  88. const { syncDefaultErrorRules } = await import("@/repository/error-rules");
  89. await syncDefaultErrorRules();
  90. const rule = capturedInsertedRules.find((r) => r.pattern === "Input must be a list");
  91. expect(rule).toBeTruthy();
  92. expect(rule.matchType).toBe("contains");
  93. expect(rule.category).toBe("parameter_error");
  94. expect(rule.priority).toBe(74);
  95. // OpenAI error format with param field
  96. expect(rule.overrideResponse?.error?.type).toBe("invalid_request_error");
  97. expect(rule.overrideResponse?.error?.param).toBe("input");
  98. expect(rule.overrideResponse?.error?.message).toContain("input");
  99. });
  100. });