361 lines
12 KiB
TypeScript
361 lines
12 KiB
TypeScript
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
import { buildMockCatalogItem, buildMockConstants } from "../setup";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Stable mock factory
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function createStablePrismaMock(
|
|
overrides: Record<string, Record<string, any>> = {}
|
|
) {
|
|
return new Proxy(
|
|
{},
|
|
{
|
|
get(_target, model: string) {
|
|
if (model === "$connect" || model === "$disconnect")
|
|
return mock(() => Promise.resolve());
|
|
if (overrides[model]) return overrides[model];
|
|
return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("procurement manager", () => {
|
|
beforeEach(() => {
|
|
mock.restore();
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// fetchItem
|
|
// -------------------------------------------------------------------
|
|
describe("fetchItem()", () => {
|
|
test("returns CatalogItemController by internal ID", async () => {
|
|
const mockData = { ...buildMockCatalogItem(), linkedItems: [] };
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findFirst: mock(() => Promise.resolve(mockData)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.fetchItem("cat-1");
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBe("cat-1");
|
|
});
|
|
|
|
test("looks up by cwCatalogId for numeric identifiers", async () => {
|
|
const mockData = { ...buildMockCatalogItem(), linkedItems: [] };
|
|
const findFirst = mock(() => Promise.resolve(mockData));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: { findFirst },
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.fetchItem(500);
|
|
const where = findFirst.mock.calls[0]?.[0]?.where;
|
|
expect(where).toHaveProperty("cwCatalogId", 500);
|
|
});
|
|
|
|
test("throws 404 when not found", async () => {
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
try {
|
|
await procurement.fetchItem("nonexistent");
|
|
expect(true).toBe(false);
|
|
} catch (e: any) {
|
|
expect(e.name).toBe("CatalogItemNotFound");
|
|
expect(e.status).toBe(404);
|
|
}
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// fetchPages
|
|
// -------------------------------------------------------------------
|
|
describe("fetchPages()", () => {
|
|
test("returns paginated catalog items", async () => {
|
|
const items = [
|
|
{ ...buildMockCatalogItem(), linkedItems: [] },
|
|
{ ...buildMockCatalogItem({ id: "cat-2" }), linkedItems: [] },
|
|
];
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany: mock(() => Promise.resolve(items)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.fetchPages(1, 10);
|
|
expect(result).toBeArrayOfSize(2);
|
|
});
|
|
|
|
test("clamps page to minimum 1", async () => {
|
|
const findMany = mock(() => Promise.resolve([]));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany,
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.fetchPages(0, 10);
|
|
const opts = findMany.mock.calls[0]?.[0];
|
|
expect(opts.skip).toBe(0); // (max(0,1)-1) * 10 = 0
|
|
});
|
|
|
|
test("falls back to safe pagination when page/rpp are invalid", async () => {
|
|
const findMany = mock(() => Promise.resolve([]));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany,
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.fetchPages(Number.NaN, Number.NaN);
|
|
const opts = findMany.mock.calls[0]?.[0];
|
|
expect(opts.skip).toBe(0);
|
|
expect(opts.take).toBe(30);
|
|
});
|
|
|
|
test("uses relational manufacturer filter when manufacturer option is provided", async () => {
|
|
const findMany = mock(() => Promise.resolve([]));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany,
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.fetchPages(1, 20, { manufacturer: "Ubiquiti" });
|
|
|
|
const where = findMany.mock.calls[0]?.[0]?.where;
|
|
expect(JSON.stringify(where)).toContain(
|
|
'"manufacturer":{"is":{"name":{"contains":"Ubiquiti"'
|
|
);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// search
|
|
// -------------------------------------------------------------------
|
|
describe("search()", () => {
|
|
test("returns matching catalog items", async () => {
|
|
const items = [{ ...buildMockCatalogItem(), linkedItems: [] }];
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany: mock(() => Promise.resolve(items)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.search("switch", 1, 10);
|
|
expect(result).toBeArrayOfSize(1);
|
|
});
|
|
|
|
test("ignores invalid numeric filters instead of passing NaN to Prisma", async () => {
|
|
const findMany = mock(() => Promise.resolve([]));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany,
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.search("switch", Number.NaN, Number.NaN, {
|
|
minPrice: Number.NaN,
|
|
maxPrice: Number.NaN,
|
|
});
|
|
|
|
const where = findMany.mock.calls[0]?.[0]?.where;
|
|
expect(where).toHaveProperty("OR");
|
|
expect(JSON.stringify(where)).not.toContain("NaN");
|
|
});
|
|
|
|
test("uses relational manufacturer search clause", async () => {
|
|
const findMany = mock(() => Promise.resolve([]));
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany,
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
await procurement.search("wir", 1, 20);
|
|
|
|
const where = findMany.mock.calls[0]?.[0]?.where;
|
|
expect(JSON.stringify(where)).toContain(
|
|
'"manufacturer":{"is":{"name":{"contains":"wir"'
|
|
);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// count
|
|
// -------------------------------------------------------------------
|
|
describe("count()", () => {
|
|
test("returns total count", async () => {
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
count: mock(() => Promise.resolve(50)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.count();
|
|
expect(result).toBe(50);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// countSearch
|
|
// -------------------------------------------------------------------
|
|
describe("countSearch()", () => {
|
|
test("returns count of matching items", async () => {
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
count: mock(() => Promise.resolve(12)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.countSearch("switch");
|
|
expect(result).toBe(12);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// fetchDistinctValues
|
|
// -------------------------------------------------------------------
|
|
describe("fetchDistinctValues()", () => {
|
|
test("returns sorted distinct category values", async () => {
|
|
const items = [{ category: "Technology" }, { category: "Accessories" }];
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany: mock(() => Promise.resolve(items)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.fetchDistinctValues("category");
|
|
expect(result).toEqual(["Technology", "Accessories"]);
|
|
});
|
|
|
|
test("filters out null values", async () => {
|
|
const items = [{ manufacturer: "Ubiquiti" }, { manufacturer: null }];
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findMany: mock(() => Promise.resolve(items)),
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
const result = await procurement.fetchDistinctValues("manufacturer");
|
|
expect(result).toEqual(["Ubiquiti"]);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// fetchLaborCatalogItems
|
|
// -------------------------------------------------------------------
|
|
describe("fetchLaborCatalogItems()", () => {
|
|
test("throws when labor items not found", async () => {
|
|
mock.module("../../src/constants", () =>
|
|
buildMockConstants({
|
|
prisma: createStablePrismaMock({
|
|
catalogItem: {
|
|
findFirst: mock(() => Promise.resolve(null)),
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
|
|
const { procurement } = await import("../../src/managers/procurement");
|
|
try {
|
|
await procurement.fetchLaborCatalogItems();
|
|
expect(true).toBe(false);
|
|
} catch (e: any) {
|
|
expect(e.name).toBe("LaborCatalogProductsNotFound");
|
|
expect(e.status).toBe(500);
|
|
}
|
|
});
|
|
});
|
|
});
|