test(context): getOrderItemContext unit tests (null, quote, stock low/out, invoiced)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
ced25be47a
commit
351c1b2b96
1 changed files with 148 additions and 0 deletions
148
src/lib/server/order-item-context-service.test.ts
Normal file
148
src/lib/server/order-item-context-service.test.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { getOrderItemContext } from "@/lib/server/order-item-context-service";
|
||||
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
|
||||
|
||||
vi.mock("@/lib/server/pocketbase-admin", () => ({
|
||||
getAdminPocketBase: vi.fn(),
|
||||
}));
|
||||
|
||||
interface CollectionMock {
|
||||
getOne: ReturnType<typeof vi.fn>;
|
||||
getList: ReturnType<typeof vi.fn>;
|
||||
}
|
||||
|
||||
function makePb(collections: Record<string, CollectionMock>) {
|
||||
return {
|
||||
collection: (name: string) => {
|
||||
const c = collections[name];
|
||||
if (!c) throw new Error(`unexpected collection: ${name}`);
|
||||
return c;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const ORD = "order_items";
|
||||
const Q = "quote_items";
|
||||
const STK = "stock_items";
|
||||
const SUP = "suppliers";
|
||||
|
||||
describe("getOrderItemContext", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns all-null context when the target has no SKU and no description", async () => {
|
||||
const pb = makePb({
|
||||
[ORD]: { getOne: vi.fn().mockResolvedValue({ id: "x", sku: "", description: "" }), getList: vi.fn() },
|
||||
});
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(pb as unknown as Awaited<ReturnType<typeof getAdminPocketBase>>);
|
||||
|
||||
const ctx = await getOrderItemContext("x");
|
||||
expect(ctx.last_quote).toBeNull();
|
||||
expect(ctx.last_order).toBeNull();
|
||||
expect(ctx.last_invoiced).toBeNull();
|
||||
expect(ctx.stock).toBeNull();
|
||||
expect(ctx.suppliers).toEqual([]);
|
||||
expect(ctx.cheapest).toBeNull();
|
||||
expect(ctx.match).toEqual({ sku: "", match_kind: "none", match_confidence: 0 });
|
||||
});
|
||||
|
||||
it("populates last_quote from the SKU-based quote_items join", async () => {
|
||||
const target = { id: "x", sku: "DRL-6MM", description: "Drill 6mm", quote_item: "qi0" };
|
||||
const quoteList = {
|
||||
items: [{
|
||||
id: "qi1",
|
||||
quote: "q1",
|
||||
cost_price: 100,
|
||||
selling_price: 150,
|
||||
created: "2026-08-01T10:00:00Z",
|
||||
expand: { quote: { quote_number: "Q-26080101", status: "approved", customer_name: "Acme", company: "Acme Ltd" } },
|
||||
}],
|
||||
};
|
||||
const emptyList = { items: [] };
|
||||
const supplierList = { items: [{ id: "sup1", suppliername: "Fastener Co" }] };
|
||||
|
||||
const pb = makePb({
|
||||
[ORD]: { getOne: vi.fn().mockResolvedValue(target), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[Q]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(quoteList) },
|
||||
[STK]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[SUP]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(supplierList) },
|
||||
});
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(pb as unknown as Awaited<ReturnType<typeof getAdminPocketBase>>);
|
||||
|
||||
const ctx = await getOrderItemContext("x");
|
||||
expect(ctx.match).toEqual({ sku: "DRL-6MM", match_kind: "sku_exact", match_confidence: 1 });
|
||||
expect(ctx.last_quote?.quote_number).toBe("Q-26080101");
|
||||
expect(ctx.last_quote?.cost_price).toBe(100);
|
||||
expect(ctx.last_quote?.selling_price).toBe(150);
|
||||
expect(ctx.last_quote?.markup_percent).toBe(50);
|
||||
});
|
||||
|
||||
it("marks stock status as 'low' when current_qty <= min_qty and > 0", async () => {
|
||||
const target = { id: "x", sku: "DRL-6MM", description: "Drill", quote_item: "" };
|
||||
const emptyList = { items: [] };
|
||||
const supplierList = { items: [] };
|
||||
const stockList = {
|
||||
items: [{ id: "s1", name: "Drill 6mm", sku: "DRL-6MM", current_qty: 5, min_qty: 10, selling_price: 140, markup: 40 }],
|
||||
};
|
||||
const pb = makePb({
|
||||
[ORD]: { getOne: vi.fn().mockResolvedValue(target), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[Q]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[STK]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(stockList) },
|
||||
[SUP]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(supplierList) },
|
||||
});
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(pb as unknown as Awaited<ReturnType<typeof getAdminPocketBase>>);
|
||||
|
||||
const ctx = await getOrderItemContext("x");
|
||||
expect(ctx.stock?.status).toBe("low");
|
||||
});
|
||||
|
||||
it("marks stock status as 'out' when current_qty <= 0", async () => {
|
||||
const target = { id: "x", sku: "DRL-6MM", description: "Drill", quote_item: "" };
|
||||
const emptyList = { items: [] };
|
||||
const supplierList = { items: [] };
|
||||
const stockList = {
|
||||
items: [{ id: "s1", name: "Drill 6mm", sku: "DRL-6MM", current_qty: 0, min_qty: 5, selling_price: 140, markup: 40 }],
|
||||
};
|
||||
const pb = makePb({
|
||||
[ORD]: { getOne: vi.fn().mockResolvedValue(target), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[Q]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[STK]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(stockList) },
|
||||
[SUP]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(supplierList) },
|
||||
});
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(pb as unknown as Awaited<ReturnType<typeof getAdminPocketBase>>);
|
||||
|
||||
const ctx = await getOrderItemContext("x");
|
||||
expect(ctx.stock?.status).toBe("out");
|
||||
});
|
||||
|
||||
it("populates last_invoiced when an invoiced row exists", async () => {
|
||||
const target = { id: "x", sku: "DRL-6MM", description: "Drill", quote_item: "" };
|
||||
const emptyList = { items: [] };
|
||||
const supplierList = { items: [] };
|
||||
const invoicedList = {
|
||||
items: [{
|
||||
id: "oi2",
|
||||
order: "o1",
|
||||
supplier_invoice_number: "INV-99",
|
||||
customer_invoice_number: "C-INV-7",
|
||||
invoiced_date: "2026-08-02T10:00:00Z",
|
||||
cost_price: 105,
|
||||
quantity: 10,
|
||||
qty_invoiced: 10,
|
||||
expand: { order: { order_number: "ORD-26080201" } },
|
||||
}],
|
||||
};
|
||||
const pb = makePb({
|
||||
[ORD]: { getOne: vi.fn().mockResolvedValue(target), getList: vi.fn((_n: string, _p: unknown, opts: { sort?: string }) => opts?.sort === "-invoiced_date" ? invoicedList : emptyList) },
|
||||
[Q]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[STK]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(emptyList) },
|
||||
[SUP]: { getOne: vi.fn(), getList: vi.fn().mockResolvedValue(supplierList) },
|
||||
});
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(pb as unknown as Awaited<ReturnType<typeof getAdminPocketBase>>);
|
||||
|
||||
const ctx = await getOrderItemContext("x");
|
||||
expect(ctx.last_invoiced?.supplier_invoice_number).toBe("INV-99");
|
||||
expect(ctx.last_invoiced?.cost_price).toBe(105);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue