[object Object]

← back to Designer Wallcoverings

TK-11786: free-samples anchored title match + block-nonpurchasable Fentucci vendor fallback

8196c654c63abce8775ffbff39d388beb77a96f7 · 2026-09-22 13:35:25 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3ChReG53fwpNgUESv4SY7

Files touched

Diff

commit 8196c654c63abce8775ffbff39d388beb77a96f7
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Sep 22 13:35:25 2026 -0700

    TK-11786: free-samples anchored title match + block-nonpurchasable Fentucci vendor fallback
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01G3ChReG53fwpNgUESv4SY7
---
 .../block-nonpurchasable-checkout/src/run.js       | 10 +++++--
 .../block-nonpurchasable-checkout/src/run.test.js  | 34 ++++++++++++++++++++--
 .../extensions/free-samples-discount/src/run.js    |  7 ++++-
 .../free-samples-discount/src/run.test.js          | 16 ++++++++++
 4 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.js b/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.js
index b0b9f2c5..73b0e803 100644
--- a/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.js
+++ b/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.js
@@ -47,9 +47,15 @@ export function run(input) {
 
     const hasTags = variant.product?.hasTags ?? [];
     const present = hasTags.find((t) => t && t.hasTag);
-    if (!present) return; // purchasable — no non-purchasable tag present
+    // Vendor fallback (mirrors isPriceSuppressed() in scripts/lib/inventory-stamp-guard.mjs):
+    // Fentucci Naturals ships quote-only with NO quote-only tag, so tag-matching alone misses
+    // its ~500 active products. Treat the whole vendor as quote-only (samples still exempt above).
+    const vendorSuppressed =
+      String(variant.product?.vendor || "").trim().toLowerCase() === "fentucci naturals";
+    if (!present && !vendorSuppressed) return; // purchasable — no non-purchasable tag or vendor
 
-    const discontinued = DISCONTINUED_RE.test(present.tag || "");
+    // `present` may be null when only the vendor fallback fired → not discontinued, quote message.
+    const discontinued = present ? DISCONTINUED_RE.test(present.tag || "") : false;
     errors.push({
       message: discontinued
         ? "This item has been discontinued and can no longer be ordered. Please remove it to continue."
diff --git a/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.test.js b/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.test.js
index 2a2c5482..d1a25ddb 100644
--- a/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.test.js
+++ b/shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.test.js
@@ -3,8 +3,9 @@ import assert from "node:assert";
 import { run } from "./run.js";
 
 // Helper: build a ProductVariant cart line. `tags` = array of tags that ARE present.
-function line(index, { title = "Roll", tags = [], sample = false, priceZero = false, typename = "ProductVariant" } = {}) {
-  const ALL = ["quote-only", "Quote Only", "quote_only", "Quote-Only", "Discontinued", "Discontinued-Review", "YB-Discontinued-2026-04"];
+function line(index, { title = "Roll", tags = [], sample = false, priceZero = false, typename = "ProductVariant", vendor = "Acme Wallcoverings" } = {}) {
+  // Mirrors the run.graphql hasTags list (keep in sync).
+  const ALL = ["quote-only", "Quote Only", "quote only", "quote_only", "Quote-Only", "quotes", "needs-price", "Needs-Price", "needs price", "contact-for-price", "contact for price", "Discontinued", "Discontinued-Review", "YB-Discontinued-2026-04"];
   return {
     id: `gid://shopify/CartLine/${index}`,
     merchandise: {
@@ -15,6 +16,7 @@ function line(index, { title = "Roll", tags = [], sample = false, priceZero = fa
       product: {
         handle: `p-${index}`,
         title: `Product ${index}`,
+        vendor,
         hasTags: ALL.map((t) => ({ tag: t, hasTag: tags.includes(t) })),
       },
     },
@@ -70,4 +72,32 @@ describe("block-nonpurchasable-checkout", () => {
     assert.equal(errs.length, 2);
     assert.deepEqual(errs.map((e) => e.target).sort(), ["$.cart.lines[1]", "$.cart.lines[3]"]);
   });
+
+  // --- coverage-gap regression tests (2026-09-22) ---
+  it("BLOCKS the broadened quote-suppressed tags (quotes / Needs-Price / needs price / contact-for-price)", () => {
+    for (const t of ["quotes", "Needs-Price", "needs price", "contact-for-price"]) {
+      const errs = errorsFor([line(0, { tags: [t] })]);
+      assert.equal(errs.length, 1, `expected block for tag ${t}`);
+      assert.match(errs[0].message, /quote only/i);
+    }
+  });
+
+  it("BLOCKS a Fentucci Naturals line with NO tag (vendor fallback)", () => {
+    const errs = errorsFor([line(0, { tags: [], vendor: "Fentucci Naturals" })]);
+    assert.equal(errs.length, 1);
+    assert.match(errs[0].message, /quote only/i);
+    assert.equal(errs[0].target, "$.cart.lines[0]");
+  });
+
+  it("vendor fallback is case/whitespace-insensitive", () => {
+    assert.equal(errorsFor([line(0, { vendor: "  FENTUCCI NATURALS " })]).length, 1);
+  });
+
+  it("ALLOWS a Fentucci Naturals SAMPLE line (samples exempt even for a suppressed vendor)", () => {
+    assert.equal(errorsFor([line(0, { sample: true, vendor: "Fentucci Naturals" })]).length, 0);
+  });
+
+  it("ALLOWS a normal vendor with no non-purchasable tag", () => {
+    assert.equal(errorsFor([line(0, { tags: [], vendor: "Thibaut" })]).length, 0);
+  });
 });
diff --git a/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.js b/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.js
index f551d0de..4b69b454 100644
--- a/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.js
+++ b/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.js
@@ -141,7 +141,12 @@ export function run(input) {
 function isSampleLine(line) {
   const m = line?.merchandise;
   if (!m || m.__typename !== "ProductVariant") return false;
-  const titleIsSample = typeof m.title === "string" && /\bsample\b/i.test(m.title);
+  // ANCHORED match (not substring): a variant is title-sample ONLY if its title is exactly
+  // "Sample" (the importer's canonical sample title, per CLAUDE.md). A substring /\bsample\b/i
+  // would zero out any nonzero-priced variant whose title merely CONTAINS "sample" (e.g. a
+  // "Sample Pack" option) — 100% off, unlimited for trade. SKU (-Sample) + custom.is_sample
+  // still catch real samples, so anchoring loses no true sample.
+  const titleIsSample = typeof m.title === "string" && /^\s*sample\s*$/i.test(m.title);
   const skuIsSample = typeof m.sku === "string" && /-sample$/i.test(m.sku.trim());
   const metaIsSample =
     (m.product?.isSampleMeta?.value || "").toString().trim().toLowerCase() === "true";
diff --git a/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.test.js b/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.test.js
index 6fbcd9a1..7281bed0 100644
--- a/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.test.js
+++ b/shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.test.js
@@ -131,3 +131,19 @@ test("sample SKU suffix is recognized when title differs", () => {
   const r = run(cart(retail(), [line(1, 1, v)]));
   assert.equal(r.operations[0].productDiscountsAdd.candidates.length, 1);
 });
+
+// Regression: a nonzero-priced line whose title merely CONTAINS the word "Sample"
+// (but is not exactly "Sample", has no -Sample SKU, and no is_sample metafield) must
+// NOT be treated as a free sample. Guards the anchored title match in isSampleLine —
+// a substring /\bsample\b/i would zero this $89 line 100%/unlimited for a trade account.
+test("nonzero-priced line whose title only CONTAINS 'Sample' is NOT discounted", () => {
+  const fauxSample = {
+    __typename: "ProductVariant",
+    id: "v-kit",
+    title: "Sample Pack",
+    sku: "DWXX-1000-KIT",
+    product: { isSampleMeta: null },
+  };
+  const r = run(cart(trade, [line(1, 1, fauxSample, "89.00")]));
+  assert.deepEqual(r.operations, [], "a full-price 'Sample Pack' must not be a free sample");
+});

← e11fa699 TK-11786: cadence setInventory2026 retries index-lag SKUs th  ·  back to Designer Wallcoverings  ·  TK-11857 R1/R4: price-integrity gate fails closed on non-boo 76f11f71 →