bundle-offers-app/engine/tests/differential.test.js
metatroncubeswdev 365e45406a Bundle Offers Shopify app: discount function, admin, storefront, deploy stack
Rust discount function (one automatic discount evaluating all offers in priority
order, parity with the Odoo engine), React Router admin with Polaris, theme app
extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and
a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:35:58 -04:00

50 lines
2.5 KiB
JavaScript

// 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);
}
});