|
|
|
@@ -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<string, Record<string, unknown>> = {};
|
|
|
|
|
|
|
|
|
|
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<string, unknown>) => {
|
|
|
|
|
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<string, unknown>).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, unknown>) && 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<string, unknown>) => 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,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|