// The optimised engine must give exactly the same applications as the reference port (which passes the // Odoo vectors) on random carts and offers, including ties, overlaps, zero-discount uses and max uses. import { test, expect } from 'vitest'; import * as fast from '../engine.js'; import * as ref from './reference/bundle-engine.reference.js'; function rng(seed) { let s = seed >>> 0; return () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 2 ** 32; }; } function randomCase(r) { const pick = (n) => Math.floor(r() * n); const nLines = 1 + pick(8); const lines = Array.from({ length: nLines }, (_, i) => ({ id: `l${i}`, variantId: `v${pick(6)}`, productId: `p${pick(4)}`, collectionIds: [`c${pick(3)}`, ...(r() < 0.3 ? [`c${pick(3)}`] : [])], unitPrice: [1, 2, 2, 5, 7.5, 10, 10, 19.99][pick(8)], quantity: 1 + pick(4), })); const scope = () => ({ variantIds: new Set(r() < 0.3 ? [`v${pick(6)}`] : []), productIds: new Set(r() < 0.4 ? [`p${pick(4)}`] : []), collectionIds: new Set(r() < 0.7 ? [`c${pick(3)}`] : []) }); const dts = ['percent', 'amount', 'fixed_price']; const offers = Array.from({ length: 1 + pick(4) }, (_, i) => { const type = ['fixed', 'mix', 'bxgy'][pick(3)]; const o = { id: `o${i}`, type, maxUses: pick(3), discountType: dts[pick(3)], value: [0, 1, 5, 10, 20, 50, 100][pick(7)] }; if (type === 'fixed') o.lines = Array.from({ length: 1 + pick(3) }, () => (r() < 0.5 ? { variantId: `v${pick(6)}`, quantity: 1 + pick(2) } : { productId: `p${pick(4)}`, quantity: 1 + pick(2) })); if (type === 'mix') { o.qty = 1 + pick(4); o.scope = scope(); } if (type === 'bxgy') { o.buyQty = 1 + pick(3); o.getQty = 1 + pick(2); o.getPercent = [0, 50, 100][pick(3)]; o.buyScope = scope(); o.getScope = scope(); } return o; }); return { lines, offers }; } const summary = (apps) => apps.map((a) => ({ o: a.offerId, d: a.discount, u: a.units.map((u) => `${u.lineId}:${u.price}`) })); test('optimised engine == reference engine on 5000 random cases', () => { const r = rng(20261013); for (let i = 0; i < 5000; i += 1) { const { lines, offers } = randomCase(r); const a = fast.applyOffers(fast.toUnits(lines), offers); const b = ref.applyOffers(ref.toUnits(lines), offers); expect(summary(a), `case ${i}`).toEqual(summary(b)); const ta = [...fast.allocateByLine(a)].map(([k, v]) => [k, v.toFixed(9)]); const tb = [...ref.allocateByLine(b)].map(([k, v]) => [k, v.toFixed(9)]); expect(ta, `allocation case ${i}`).toEqual(tb); } });