84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
/**
|
|
* Unit tests for searchRows — covers the SQL fallback contract: tenant
|
|
* + entity scoping, value-text predicate, limit, and total accounting.
|
|
*/
|
|
|
|
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("~/utils/db.server", () => ({
|
|
db: {
|
|
row: {
|
|
findMany: vi.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { db } from "~/utils/db.server";
|
|
import { searchRows } from "./rowSearch.server";
|
|
|
|
const dbMock = db as unknown as {
|
|
row: {
|
|
findMany: ReturnType<typeof vi.fn>;
|
|
};
|
|
};
|
|
|
|
const originalMeili = process.env.MEILI_URL;
|
|
const originalBackend = process.env.SEARCH_BACKEND;
|
|
|
|
afterAll(() => {
|
|
if (originalMeili === undefined) delete process.env.MEILI_URL;
|
|
else process.env.MEILI_URL = originalMeili;
|
|
if (originalBackend === undefined) delete process.env.SEARCH_BACKEND;
|
|
else process.env.SEARCH_BACKEND = originalBackend;
|
|
});
|
|
|
|
describe("searchRows (sql fallback)", () => {
|
|
beforeEach(() => {
|
|
delete process.env.MEILI_URL;
|
|
delete process.env.SEARCH_BACKEND;
|
|
dbMock.row.findMany.mockReset();
|
|
});
|
|
|
|
it("scopes by tenantId + entityId and maps results", async () => {
|
|
dbMock.row.findMany.mockResolvedValueOnce([
|
|
{ id: "row-1", folio: 12, entity: { name: "deal" } },
|
|
{ id: "row-2", folio: 7, entity: { name: "deal" } },
|
|
]);
|
|
|
|
const result = await searchRows({ tenantId: "tenant-1", entityId: "entity-1", query: "acme" });
|
|
|
|
expect(dbMock.row.findMany).toHaveBeenCalledTimes(1);
|
|
const where = dbMock.row.findMany.mock.calls[0][0].where as Record<string, unknown>;
|
|
expect(where.tenantId).toBe("tenant-1");
|
|
expect(where.entityId).toBe("entity-1");
|
|
expect(where.deletedAt).toBeNull();
|
|
expect(where.values).toBeDefined();
|
|
|
|
expect(result.fellBack).toBe(true);
|
|
expect(result.hits).toEqual([
|
|
{ id: "row-1", folio: 12, entityName: "deal" },
|
|
{ id: "row-2", folio: 7, entityName: "deal" },
|
|
]);
|
|
expect(result.total).toBe(2);
|
|
});
|
|
|
|
it("omits value-text predicate when the query is empty", async () => {
|
|
dbMock.row.findMany.mockResolvedValueOnce([]);
|
|
await searchRows({ tenantId: "tenant-1", entityId: "entity-1", query: "" });
|
|
const where = dbMock.row.findMany.mock.calls[0][0].where as Record<string, unknown>;
|
|
expect(where.values).toBeUndefined();
|
|
});
|
|
|
|
it("honours the limit option", async () => {
|
|
dbMock.row.findMany.mockResolvedValueOnce([]);
|
|
await searchRows({ tenantId: "tenant-1", entityId: "entity-1", query: "x", limit: 25 });
|
|
expect(dbMock.row.findMany.mock.calls[0][0].take).toBe(25);
|
|
});
|
|
|
|
it("defaults folio to 0 when missing on the row", async () => {
|
|
dbMock.row.findMany.mockResolvedValueOnce([{ id: "row-x", folio: null, entity: null }]);
|
|
const result = await searchRows({ tenantId: "tenant-1", entityId: "entity-1", query: "x" });
|
|
expect(result.hits[0]).toEqual({ id: "row-x", folio: 0, entityName: "" });
|
|
});
|
|
});
|