import type { TableMetadata } from "@tailor-platform/app-shell"; import { describe, it, expect } from "vitest"; import { normalizeCollectionQuery } from "./normalize-collection-query"; // Minimal metadata fixture: `createdAt` is a true datetime (gets normalized), // `orderDate` is a Date scalar (must be left alone), `docNumber` is a string. const metadata: TableMetadata = { name: "purchaseOrder", pluralForm: "purchaseOrders", fields: [ { name: "createdAt", type: "datetime", required: true }, { name: "orderDate", type: "date", required: false }, { name: "docNumber", type: "string", required: false }, ], }; describe("normalizeCollectionQuery", () => { it("returns undefined query untouched", () => { expect(normalizeCollectionQuery(undefined, metadata)).toBeUndefined(); }); it("expands `eq` on a datetime field to a local-day gte/lt range", () => { const result = normalizeCollectionQuery({ createdAt: { eq: "2026-06-03" } }, metadata); const ops = result?.createdAt as Record; // `eq` is replaced by a range — no bare `eq` survives. expect(ops.eq).toBeUndefined(); expect(typeof ops.gte).toBe("string"); expect(typeof ops.lt).toBe("string"); // Lower bound is local midnight of the selected day (timezone-agnostic // check: whatever the runner's TZ, local midnight has 0 h/m/s/ms). const lower = new Date(ops.gte); expect(lower.getHours()).toBe(0); expect(lower.getMinutes()).toBe(0); expect(lower.getSeconds()).toBe(0); expect(lower.getMilliseconds()).toBe(0); // Upper bound is the next local midnight — exactly one day later, and // exclusive (`lt`), so the whole selected day is covered. const upper = new Date(ops.lt); expect(upper.getHours()).toBe(0); expect(upper.getDate()).toBe(lower.getDate() + 1); }); it("expands lower-bound operators to start-of-day UTC", () => { const result = normalizeCollectionQuery({ createdAt: { gte: "2026-06-03" } }, metadata); expect(result?.createdAt).toEqual({ gte: "2026-06-03T00:00:00.000Z" }); const gt = normalizeCollectionQuery({ createdAt: { gt: "2026-06-03" } }, metadata); expect(gt?.createdAt).toEqual({ gt: "2026-06-03T00:00:00.000Z" }); }); it("expands upper-bound operators to end-of-day UTC so the day is inclusive", () => { const lt = normalizeCollectionQuery({ createdAt: { lt: "2026-06-03" } }, metadata); expect(lt?.createdAt).toEqual({ lt: "2026-06-03T23:59:59.999Z" }); const lte = normalizeCollectionQuery({ createdAt: { lte: "2026-06-03" } }, metadata); expect(lte?.createdAt).toEqual({ lte: "2026-06-03T23:59:59.999Z" }); }); it("leaves non-datetime fields untouched", () => { // `orderDate` is a Date scalar (not datetime); `docNumber` is a string. // Both must pass through verbatim so Date filters aren't corrupted. const result = normalizeCollectionQuery( { orderDate: { eq: "2026-06-03" }, docNumber: { eq: "PO-0001" }, }, metadata, ); expect(result).toEqual({ orderDate: { eq: "2026-06-03" }, docNumber: { eq: "PO-0001" }, }); }); it("leaves already-normalized datetime values untouched", () => { const result = normalizeCollectionQuery( { createdAt: { gte: "2026-06-03T08:30:00.000Z" } }, metadata, ); expect(result?.createdAt).toEqual({ gte: "2026-06-03T08:30:00.000Z" }); }); it("normalizes each element of array-valued operators", () => { const result = normalizeCollectionQuery( { createdAt: { in: ["2026-06-03", "2026-06-04"] } }, metadata, ); expect(result?.createdAt).toEqual({ in: ["2026-06-03T00:00:00.000Z", "2026-06-04T00:00:00.000Z"], }); }); });