diff --git a/node/clients/intsch/index.ts b/node/clients/intsch/index.ts index b23fa0ea..39ef13a8 100644 --- a/node/clients/intsch/index.ts +++ b/node/clients/intsch/index.ts @@ -30,6 +30,7 @@ import { parseState } from '../../utils/searchState' import { filterUndefinedNonNull, filterByAllowedIntelligentSearchQueryKeys, + shouldInjectDPPreview, } from './utils' export class Intsch extends JanusClient implements IIntelligentSearchClient { @@ -171,6 +172,10 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient { const requestPath = `/api/intelligent-search/v1/product-search/${path}` + const dpPreview = shouldInjectDPPreview(this.context.production) + ? 'true' + : undefined + const merged = filterUndefinedNonNull({ sc: params.salesChannel ? params.salesChannel : segmentParams?.sc, regionId: params.regionId ?? segmentParams?.regionId, @@ -208,6 +213,7 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient { : undefined, simulationBehavior: params.simulationBehavior ?? undefined, variant: params.variant, + dpPreview, ...parseState(searchState), }) @@ -253,6 +259,10 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient { const facetsPath = `/api/intelligent-search/v1/facets/${path}` + const dpPreview = shouldInjectDPPreview(this.context.production) + ? 'true' + : undefined + const merged = filterUndefinedNonNull({ sc: segmentParams?.sc, regionId: params.regionId ?? segmentParams?.regionId, @@ -272,6 +282,7 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient { locale: this.locale ?? segmentParams?.locale, bgy_leap: leap ? 'true' : undefined, variant: params.variant, + dpPreview, ...parseState(searchState), } as Record) diff --git a/node/clients/intsch/utils.test.ts b/node/clients/intsch/utils.test.ts new file mode 100644 index 00000000..17cd69a4 --- /dev/null +++ b/node/clients/intsch/utils.test.ts @@ -0,0 +1,57 @@ +import { + filterByAllowedIntelligentSearchQueryKeys, + filterUndefinedNonNull, + shouldInjectDPPreview, +} from './utils' + +describe('filterUndefinedNonNull', () => { + it('drops undefined and null values', () => { + expect( + filterUndefinedNonNull({ a: 1, b: undefined, c: null, d: 'x' }) + ).toEqual({ a: 1, d: 'x' }) + }) + + it('keeps falsy-but-meaningful values (false, "", 0)', () => { + expect(filterUndefinedNonNull({ a: false, b: '', c: 0 })).toEqual({ + a: false, + b: '', + c: 0, + }) + }) +}) + +describe('filterByAllowedIntelligentSearchQueryKeys', () => { + it('keeps known IS API query keys and drops unknown ones', () => { + const result = filterByAllowedIntelligentSearchQueryKeys({ + query: 'shoes', + foo: 'bar', + regionId: 'r1', + }) + expect(result).toEqual({ query: 'shoes', regionId: 'r1' }) + }) + + it('accepts the DPT-67 dpPreview key on the allowlist', () => { + const result = filterByAllowedIntelligentSearchQueryKeys({ + dpPreview: 'true', + ignored: 'value', + }) + expect(result).toEqual({ dpPreview: 'true' }) + }) +}) + +/** + * DPT-67: production-driven QA mode. The storefront emits `dpPreview=true` on + * non-production workspaces so the IS API activates the Delivery Promises code + * path without the store having to flip `deliveryPromisesEnabled`. Production + * traffic must never carry the param — including named workspaces promoted to + * production, not just `master`. + */ +describe('shouldInjectDPPreview', () => { + it('returns true for a non-production (QA/dev) workspace', () => { + expect(shouldInjectDPPreview(false)).toBe(true) + }) + + it('returns false for a production workspace', () => { + expect(shouldInjectDPPreview(true)).toBe(false) + }) +}) diff --git a/node/clients/intsch/utils.ts b/node/clients/intsch/utils.ts index 075b353c..07fe1f91 100644 --- a/node/clients/intsch/utils.ts +++ b/node/clients/intsch/utils.ts @@ -90,8 +90,20 @@ export const INTELLIGENT_SEARCH_PRODUCT_QUERY_KEYS = new Set([ 'priceTables', 'simulationBehavior', 'variant', + 'dpPreview', ]) +/** + * Whether the IS API call should carry `dpPreview=true`. Non-production + * workspaces are treated as QA and get DP activated end-to-end without the + * store flipping `deliveryPromisesEnabled`. Production workspaces never carry + * the param — including named workspaces promoted to production, not just + * `master`. + */ +export function shouldInjectDPPreview(production: boolean): boolean { + return !production +} + export function filterByAllowedIntelligentSearchQueryKeys( obj: Record, allowedKeys: Set = INTELLIGENT_SEARCH_PRODUCT_QUERY_KEYS