← back to Designer Wallcoverings
block sample prices from roll imports
3c6d03f68b92fc4a7130eef904673f313f999fcd · 2026-08-27 17:05:45 -0700 · Steve
Files touched
M DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.jsM DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
Diff
commit 3c6d03f68b92fc4a7130eef904673f313f999fcd
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 27 17:05:45 2026 -0700
block sample prices from roll imports
---
.../scripts/import-queue-runner.js | 24 ++++++++++++++++++++--
.../ImportNewSkufromURL/test/cost-guard.test.js | 22 ++++++++++++++------
2 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
index 68a12e4c..ca24bbc2 100644
--- a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
+++ b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
@@ -50,6 +50,24 @@ function mergeSidecar(preview, sc) {
// sidecar), build the create-product ProductDTO straight from the sidecar we
// already have. Re-scrape is the only broken link; everything else is present.
const crypto = require('crypto');
+function normalizeConfirmedRollPrice(value) {
+ let amount;
+ if (typeof value === 'number') {
+ amount = value;
+ } else if (typeof value === 'string' && value.trim()) {
+ const normalized = value.trim().replace(/^\$\s*/, '').replace(/,/g, '');
+ if (!/^\d+(?:\.\d+)?$/.test(normalized)) return null;
+ amount = Number(normalized);
+ } else {
+ return null;
+ }
+
+ // $4.25 is the memo-sample price. A missing or sample-sized amount must
+ // never become a roll/product price.
+ if (!Number.isFinite(amount) || amount <= 5) return null;
+ return amount.toFixed(2);
+}
+
function buildPreviewFromSidecar(vendor, sku, url, sc) {
if (!sc) return null;
const isUrl = (u) => typeof u === 'string' && /^https?:\/\//.test(u);
@@ -60,6 +78,8 @@ function buildPreviewFromSidecar(vendor, sku, url, sc) {
if (sc.color && !String(title).toLowerCase().includes(String(sc.color).toLowerCase())) title += ' ' + sc.color;
if (!/\|/.test(title)) title += ' | ' + brand;
const s = sc.specs || {};
+ const price = normalizeConfirmedRollPrice(s.price);
+ if (!price) return null; // no confirmed roll price → not import-ready
const specs = { sku, brand,
collection: sc.collection || s.collection,
product_type: 'Wallcovering',
@@ -75,7 +95,7 @@ function buildPreviewFromSidecar(vendor, sku, url, sc) {
title, bodyHtml: sc.description || '',
tags: [brand].concat(sc.color ? [sc.color] : []),
images, specs,
- price: (s.price != null && String(s.price)) || '4.25',
+ price,
vendor: brand,
};
}
@@ -187,7 +207,7 @@ function applyBrandOverride(p, brand) {
// invariants without standing up the :9830 app or running the live drain. The
// drain only runs when this file is invoked directly as the main module.
module.exports = {
- buildPreviewFromSidecar, mergeSidecar, applyBrandOverride,
+ buildPreviewFromSidecar, normalizeConfirmedRollPrice, mergeSidecar, applyBrandOverride,
normalizeTitle, scrubStr, SCRUB_RES, TITLE_KEEP,
};
diff --git a/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js b/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
index 6b1af678..1a2b8c47 100644
--- a/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
+++ b/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
@@ -69,12 +69,22 @@ const URL_C = 'https://vendor.example.com/products/tek-wall';
test('I2: sample-only SKU never imports with the $4.25 memo-sample as the product price', () => {
const pv = runner.buildPreviewFromSidecar('phillip-jeffries', 'PJ-1001', URL_A, sidecarSampleOnly);
- // A sample-only SKU has no real roll price. The correct behavior is to refuse
- // to build an importable preview (return null) — NOT to stamp on $4.25.
- if (pv === null) return; // correct: refused to fabricate a price
- const price = parseFloat(pv.price);
- assert.notEqual(price, SAMPLE_PRICE,
- `runner emitted the memo-SAMPLE price ($4.25) as the product price for a sample-only SKU (price=${pv.price})`);
+ assert.equal(pv, null, 'sample-only SKU must not produce an importable preview');
+});
+
+test('I1: zero, malformed, and explicit sample prices are rejected', () => {
+ for (const price of [0, 'not-a-price', SAMPLE_PRICE]) {
+ const pv = runner.buildPreviewFromSidecar('thibaut', 'TH-BAD', URL_B, {
+ ...sidecarCostedRoll,
+ specs: { ...sidecarCostedRoll.specs, price },
+ });
+ assert.equal(pv, null, `invalid roll price ${JSON.stringify(price)} must be rejected`);
+ }
+});
+
+test('I1: confirmed numeric and currency-formatted roll prices are normalized', () => {
+ assert.equal(runner.normalizeConfirmedRollPrice(168), '168.00');
+ assert.equal(runner.normalizeConfirmedRollPrice('$1,234.50'), '1234.50');
});
test('I1: no preview ever carries a roll price <= $5 (placeholder/sample leak)', () => {
← 59499ae1 snapshot before fixing roll price validation
·
back to Designer Wallcoverings
·
improve Shopify purchase and collection UX a6461a60 →