Files
pleno-vue/tests/unit/customer-product-rules.spec.js
T
2026-07-16 12:11:06 +02:00

156 lines
5.6 KiB
JavaScript

import { describe, expect, it } from "vitest";
import {
getCustomerProductRestriction,
getProductCategoryRestrictionForCustomer,
hasValidCustomerProductRestrictionContract,
isProductRestrictedForCustomer,
normalizeCustomerAttributeProductRestriction,
} from "@/features/customer/customerProductRules.js";
import { getCustomerRuleProductImpact } from "@/features/customer/customerRuleProductImpact.js";
const attribute = (name, productIds, collections = []) => ({
attribute: name,
product_restriction: {
version: 3,
disabled_product_ids: productIds,
collections,
},
});
describe("customer product rules", () => {
it("matches only configured product ids and never infers from category or name", () => {
const attributes = [attribute("restrictTankCleaning", [52])];
expect(isProductRestrictedForCustomer({ id: "52", category: 4, name: "Ordinary wash" }, attributes)).toBe(true);
expect(isProductRestrictedForCustomer({ id: 53, category: 5, name: "Tank cleaning" }, attributes)).toBe(false);
expect(isProductRestrictedForCustomer({ id: 54, name: "Tankrens premium" }, attributes)).toBe(false);
});
it("keeps a deterministic primary rule and returns every matching rule", () => {
const attributes = [attribute("restrictInteriorCleaning", [63]), attribute("restrictAdditionalServices", [63])];
expect(getCustomerProductRestriction({ id: 63 }, attributes)).toMatchObject({
restricted: true,
rule: "restrictAdditionalServices",
rules: ["restrictAdditionalServices", "restrictInteriorCleaning"],
messageKey: "pos.restrictions.addons_not_allowed",
});
});
it("returns the collections that contain a blocked product", () => {
const attributes = [
attribute(
"restrictSpotFree",
[],
[
{ id: 7, name: "Rinses", product_ids: [23, "24"] },
{ id: 8, name: "Legacy", products: [{ id: 23 }] },
]
),
];
const restriction = getCustomerProductRestriction({ product_id: "23" }, attributes);
expect(restriction.collections).toEqual([
{ id: 7, name: "Rinses", product_ids: [23, 24], attribute: "restrictSpotFree" },
{ id: 8, name: "Legacy", product_ids: [23], attribute: "restrictSpotFree" },
]);
});
it("treats workflow rules, legacy string rows, and malformed ids as unrestricted", () => {
expect(getCustomerProductRestriction({ id: 23 }, ["restrictSpotFree"])).toEqual({
restricted: false,
rule: null,
rules: [],
collections: [],
messageKey: null,
});
expect(isProductRestrictedForCustomer({ id: 23 }, [attribute("requiresReferenceNumber", [23])])).toBe(false);
expect(isProductRestrictedForCustomer({ id: "not-an-id" }, [attribute("restrictSpotFree", [23])])).toBe(false);
expect(isProductRestrictedForCustomer({ id: "23-trailing" }, [attribute("restrictSpotFree", [23])])).toBe(false);
});
it("validates the structured exact-product contract for every active product-impact rule", () => {
expect(
hasValidCustomerProductRestrictionContract([
attribute("restrictSpotFree", [23], [{ id: 7, name: "Rinses", product_ids: [23] }]),
{ attribute: "requiresReferenceNumber", product_restriction: null },
])
).toBe(true);
expect(hasValidCustomerProductRestrictionContract([{ attribute: "restrictSpotFree" }])).toBe(false);
expect(
hasValidCustomerProductRestrictionContract([
{
attribute: "restrictSpotFree",
product_restriction: { version: 1, collections: [], disabled_product_ids: null },
},
])
).toBe(false);
});
it("normalizes top-level and nested restriction response variants", () => {
expect(
normalizeCustomerAttributeProductRestriction({
disabled_product_ids: ["23", 23, 0],
collections: [{ id: 2, products: [{ product_id: 24 }] }],
})
).toEqual({
version: 0,
collections: [{ id: 2, name: "", product_ids: [24] }],
disabled_product_ids: [23, 24],
});
});
it("keeps category helpers as an exact-id compatibility seam", () => {
const attributes = [attribute("onlyTankCleaning", [10])];
expect(getProductCategoryRestrictionForCustomer(4, attributes).restricted).toBe(false);
expect(getProductCategoryRestrictionForCustomer(4, attributes, { productId: 10 }).restricted).toBe(true);
});
it("describes exact primary and addon impact from the configured set", () => {
const products = [
{
id: 10,
name: "Truck wash",
addons: [
{ id: 900, option_id: 63, name: "Interior", product: { id: 63, name: "Interior" } },
{ id: 901, option_id: 64, name: "Dolly", product: { id: 64, name: "Dolly" } },
],
},
{ id: 53, name: "Primary product" },
];
const impact = getCustomerRuleProductImpact(
"restrictInteriorCleaning",
products,
attribute("restrictInteriorCleaning", [53, 63])
);
expect(impact.blocked.primaryProducts.map((product) => product.id)).toEqual([53]);
expect(impact.blocked.relatedAddons.map((product) => product.id)).toEqual([63]);
expect(impact.available.primaryProducts).toEqual([]);
});
it("does not give workflow-only rules product impact", () => {
expect(
getCustomerRuleProductImpact(
"requiresReferenceNumber",
[{ id: 23, name: "Rinse" }],
attribute("requiresReferenceNumber", [23])
)
).toEqual({
hasProductImpact: false,
blocked: {
primaryProducts: [],
relatedAddons: [],
standaloneAdditionalServices: [],
},
available: {
primaryProducts: [],
relatedAddons: [],
standaloneAdditionalServices: [],
},
});
});
});