diff --git a/apps/api/src/__tests__/petProfileSummary.test.ts b/apps/api/src/__tests__/petProfileSummary.test.ts index 38b138c..f7e5686 100644 --- a/apps/api/src/__tests__/petProfileSummary.test.ts +++ b/apps/api/src/__tests__/petProfileSummary.test.ts @@ -178,6 +178,9 @@ vi.mock("../db/index.js", () => { const staff = new Proxy({ _name: "staff" }, { get: (t, p) => p === "_name" ? "staff" : {} }); const services = new Proxy({ _name: "services" }, { get: (t, p) => p === "_name" ? "services" : {} }); + // Tracks { [tableName]: { [alias]: SQLExpression } } for the current select() call + let selectedColumns: Record> = {}; + function makeChainable(rows: unknown[]) { const arr = rows as unknown[]; return new Proxy(arr, { @@ -188,25 +191,67 @@ vi.mock("../db/index.js", () => { if (prop === Symbol.iterator) { return function* () { for (const v of target) yield v; }; } + if (prop === Symbol.asyncIterator) { + return async function* () { for (const v of target) yield v; }; + } // @ts-expect-error proxy return target[prop]; }, }); } + // sql mock: returns an object with .as() so drizzle's select() can alias it + function sqlMock(_strings: TemplateStringsArray, ..._params: unknown[]) { + const queryString = _strings[0]; + const asFn = (alias: string) => ({ + sql: { queryChunks: [_strings[0]] }, + fieldAlias: alias, + getSQL() { return this.sql; }, + }); + return { queryChunks: [queryString], as: asFn }; + } + return { getDb: () => ({ - select: () => ({ - from: (table: unknown) => { - const name = (table as { _name?: string })._name; - if (name === "pets") return makeChainable(mock.pets); - if (name === "appointments") return makeChainable(mock.appointments); - if (name === "groomingVisitLogs") return makeChainable(mock.groomingLogs); - if (name === "staff") return makeChainable(mock.staffMembers); - if (name === "services") return makeChainable(mock.services); - return makeChainable([]); - }, - }), + select: (cols?: Record) => { + selectedColumns = {}; + if (cols) { + // Inspect cols to find sql-aliased expressions and their aliases + for (const [alias, expr] of Object.entries(cols)) { + if (expr && typeof expr === "object" && "as" in expr && typeof (expr as Record).as === "function") { + const aliased = (expr as { as: (a: string) => { fieldAlias: string; sql: unknown } }).as(alias); + // Detect count(*) queries + if (typeof aliased.sql === "object" && aliased.sql !== null && "queryChunks" in (aliased.sql as Record) && String((aliased.sql as { queryChunks?: unknown[] }).queryChunks).includes("count")) { + // Store count query intent — we'll resolve it in from() + if (!selectedColumns["appointments"]) selectedColumns["appointments"] = {}; + selectedColumns["appointments"][alias] = { _isCountQuery: true }; + } + } + } + } + return { + from: (table: unknown) => { + const name = (table as { _name?: string })._name; + const tableCols = selectedColumns[name] || {}; + // If this table has a count query, return computed count result + const countQueryEntry = Object.entries(tableCols).find(([, v]) => + typeof v === "object" && v !== null && "_isCountQuery" in v + ); + if (countQueryEntry) { + const [countAlias] = countQueryEntry; + const count = (name === "appointments" ? mock.appointments : []) + .filter((row: Record) => row.status === "completed").length; + return makeChainable([{ [countAlias]: count }]); + } + if (name === "pets") return makeChainable(mock.pets); + if (name === "appointments") return makeChainable(mock.appointments); + if (name === "groomingVisitLogs") return makeChainable(mock.groomingLogs); + if (name === "staff") return makeChainable(mock.staffMembers); + if (name === "services") return makeChainable(mock.services); + return makeChainable([]); + }, + }; + }, insert: () => ({ values: () => ({ returning: () => [{}] }) }), update: () => ({ set: () => ({ where: () => ({ returning: () => [{}] }) }) }), delete: () => ({ where: () => ({ returning: () => [{}] }) }), @@ -222,7 +267,7 @@ vi.mock("../db/index.js", () => { exists: vi.fn(() => true), gte: vi.fn((a: unknown, b: unknown) => ({ col: a, val: b })), or: vi.fn((a: unknown, b: unknown) => [a, b]), - sql: vi.fn((str: string) => str), + sql: sqlMock, }; });