| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import { beforeEach, describe, expect, it, vi } from "vitest";
- /**
- * Regression test for: operator does not exist: integer = text
- *
- * The CTE `VALUES ($1), ($2)` generated by Drizzle infers columns as text.
- * The LATERAL join then compares integer (table column) to text (CTE column),
- * which PostgreSQL rejects. The fix adds an explicit `::integer` cast.
- */
- function sqlToString(sqlObj: unknown): string {
- const visited = new Set<unknown>();
- const walk = (node: unknown): string => {
- if (!node || visited.has(node)) return "";
- visited.add(node);
- if (typeof node === "string") return node;
- if (typeof node === "number") return String(node);
- if (typeof node === "object") {
- const anyNode = node as Record<string, unknown>;
- if (Array.isArray(anyNode)) {
- return anyNode.map(walk).join("");
- }
- if (anyNode.value !== undefined) {
- if (Array.isArray(anyNode.value)) {
- return (anyNode.value as unknown[]).map(walk).join("");
- }
- return walk(anyNode.value);
- }
- if (anyNode.queryChunks) {
- return walk(anyNode.queryChunks);
- }
- }
- return "";
- };
- return walk(sqlObj);
- }
- let capturedExecuteQuery: unknown = null;
- vi.mock("@/drizzle/db", () => ({
- db: {
- execute: vi.fn((query: unknown) => {
- capturedExecuteQuery = query;
- return Promise.resolve([]);
- }),
- },
- }));
- vi.mock("@/drizzle/schema", () => ({
- providerEndpointProbeLogs: {},
- providerEndpoints: {
- vendorId: "vendorId",
- providerType: "providerType",
- isEnabled: "isEnabled",
- lastProbeOk: "lastProbeOk",
- deletedAt: "deletedAt",
- },
- }));
- vi.mock("@/lib/logger", () => ({
- logger: {
- trace: vi.fn(),
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- },
- }));
- describe("findProviderEndpointProbeLogsBatch - integer cast regression", () => {
- beforeEach(() => {
- capturedExecuteQuery = null;
- });
- it("CTE VALUES must cast endpoint IDs to ::integer to avoid 'integer = text' error", async () => {
- const { findProviderEndpointProbeLogsBatch } = await import(
- "@/repository/provider-endpoints-batch"
- );
- await findProviderEndpointProbeLogsBatch({
- endpointIds: [10, 20, 30],
- limitPerEndpoint: 5,
- });
- expect(capturedExecuteQuery).toBeTruthy();
- const sqlStr = sqlToString(capturedExecuteQuery);
- // The VALUES clause must contain ::integer casts to prevent PG type mismatch
- expect(sqlStr).toContain("::integer");
- });
- it("single endpoint ID also gets ::integer cast", async () => {
- const { findProviderEndpointProbeLogsBatch } = await import(
- "@/repository/provider-endpoints-batch"
- );
- await findProviderEndpointProbeLogsBatch({
- endpointIds: [42],
- limitPerEndpoint: 1,
- });
- expect(capturedExecuteQuery).toBeTruthy();
- const sqlStr = sqlToString(capturedExecuteQuery);
- expect(sqlStr).toContain("::integer");
- });
- it("empty endpointIds should not execute any query", async () => {
- const { findProviderEndpointProbeLogsBatch } = await import(
- "@/repository/provider-endpoints-batch"
- );
- const result = await findProviderEndpointProbeLogsBatch({
- endpointIds: [],
- limitPerEndpoint: 5,
- });
- expect(capturedExecuteQuery).toBeNull();
- expect(result.size).toBe(0);
- });
- });
|