Files
pleno-vue/tests/unit/pos-order-item-product-reconciliation.spec.js
T
Jeppe B a0c11e4bb7 Fix POS release-blocking customer rules and completion (#208)
Resolve direct-order product reconciliation context, preserve valid selections when restricted products are activated, remove the redundant certificate request, and add focused regression coverage.
2026-07-21 15:42:50 +02:00

167 lines
4.9 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const { canonicalProductLookup } = vi.hoisted(() => ({
canonicalProductLookup: vi.fn(),
}));
vi.mock("@/components/numberplatescanners/Scans.vue", () => ({
getScansDepartmentPagination: vi.fn(),
}));
vi.mock("@/components/request/HandleGlobalError.vue", () => ({
clearErrors: vi.fn(),
parseError: vi.fn(),
}));
vi.mock("@/components/shop/CustomerNotes.vue", () => ({
createNote: vi.fn(),
deleteNote: vi.fn(),
getNotes: vi.fn(),
}));
vi.mock("@/components/shop/OrdersItems.vue", () => ({
createOrderItem: vi.fn(),
getOrderItems: vi.fn(),
removeOrderItem: vi.fn(),
}));
vi.mock("@/components/shop/CustomerAttributes.vue", () => ({
getAttributes: vi.fn(),
}));
vi.mock("@/components/session/token/SessionUser.vue", () => ({
SessionUser: {
functions: {},
objects: {
products: {
get: {
single: canonicalProductLookup,
},
},
},
},
}));
vi.mock("@/components/session/authenticatedRequest.vue", () => ({
authenticatedRequest: vi.fn(),
}));
vi.mock("@/components/displays/department/pos/utils/washCertificate.js", () => ({
doesOrderContainWashCertificateProduct: vi.fn(() => false),
}));
vi.mock("@/features/customer/customerProductRules.js", () => ({
getCustomerProductRestriction: vi.fn(),
getProductCategoryRestrictionForCustomer: vi.fn(),
hasValidCustomerProductRestrictionContract: vi.fn(() => true),
isProductCategoryRestrictedForCustomer: vi.fn(() => false),
}));
vi.mock("sweetalert2", () => ({
default: { fire: vi.fn() },
}));
import { customer_id, department_id, reconcileOrderItemProducts } from "@/components/shop/POSDepartmentProcess.vue";
const inconsistentItem = (overrides = {}) => ({
id: 9101,
order_id: 54518,
product_id: 63,
product: {
id: 53,
name: "Stale embedded product",
embedded_only: "preserve me",
},
quantity: 2,
price: 777,
notes: "Order-item note",
...overrides,
});
describe("reconcileOrderItemProducts", () => {
beforeEach(() => {
canonicalProductLookup.mockReset();
canonicalProductLookup.mockResolvedValue({
id: 63,
name: "Canonical product",
price: 399,
canonical_only: true,
});
department_id.value = 12;
customer_id.value = 12345679;
});
it("uses explicit order context instead of stale global POS context", async () => {
await reconcileOrderItemProducts([inconsistentItem()], {
departmentId: 44,
customerId: 87654321,
});
expect(canonicalProductLookup).toHaveBeenCalledOnce();
expect(canonicalProductLookup).toHaveBeenCalledWith(63, {
department_id: 44,
customer_id: 87654321,
category_id: null,
final_price: true,
});
});
it("merges canonical product data without losing order-item or embedded product fields", async () => {
const [result] = await reconcileOrderItemProducts([inconsistentItem()]);
expect(result).toMatchObject({
id: 9101,
order_id: 54518,
product_id: 63,
quantity: 2,
price: 777,
notes: "Order-item note",
product: {
id: 63,
name: "Canonical product",
price: 399,
embedded_only: "preserve me",
canonical_only: true,
},
});
});
it("fetches a missing canonical product only once for duplicate product IDs", async () => {
const results = await reconcileOrderItemProducts([
inconsistentItem({ id: 9101 }),
inconsistentItem({ id: 9102, product: { id: 54, name: "Another stale product" } }),
]);
expect(canonicalProductLookup).toHaveBeenCalledOnce();
expect(results.map((item) => item.product.id)).toEqual([63, 63]);
expect(results.map((item) => item.product.name)).toEqual(["Canonical product", "Canonical product"]);
});
it("does not reuse a mismatched embedded product as canonical data for another item", async () => {
const [result] = await reconcileOrderItemProducts([
inconsistentItem({ id: 9101 }),
inconsistentItem({
id: 9102,
product_id: 64,
product: { id: 63, name: "Stale product from another mismatched item" },
}),
]);
expect(canonicalProductLookup).toHaveBeenCalledWith(63, expect.any(Object));
expect(result.product.name).toBe("Canonical product");
});
it("does not fall back to stale global context when explicit order context is empty", async () => {
await reconcileOrderItemProducts([inconsistentItem()], {
departmentId: null,
customerId: null,
});
expect(canonicalProductLookup).toHaveBeenCalledWith(63, {
department_id: null,
customer_id: null,
category_id: null,
final_price: true,
});
});
it.each([undefined, null, {}, "not-an-array"])(
"normalizes malformed item input %# to an empty list",
async (items) => {
await expect(reconcileOrderItemProducts(items)).resolves.toEqual([]);
expect(canonicalProductLookup).not.toHaveBeenCalled();
}
);
});