← 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
M shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.jsM shopify/staged/free-samples-function/extensions/block-nonpurchasable-checkout/src/run.test.jsM shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.jsM shopify/staged/free-samples-function/extensions/free-samples-discount/src/run.test.js
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 →