From f6a99eb4af5128f040f9e641ddf7e783ba3e73cc Mon Sep 17 00:00:00 2001 From: Vlad Saitov Date: Thu, 20 Aug 2026 19:11:16 +0500 Subject: [PATCH 1/4] [NO-REF] - update PIA module with test parameters --- spec/src/modules/pia.js | 71 +++++++++++++++++++++++++++++++++++++++++ src/modules/pia.js | 48 +++++++++++++++++++++++++++- src/types/pia.d.ts | 16 +++++++--- 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/spec/src/modules/pia.js b/spec/src/modules/pia.js index 99d4f125..67bce624 100644 --- a/spec/src/modules/pia.js +++ b/spec/src/modules/pia.js @@ -145,6 +145,48 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass features as query parameters when provided', () => { + const features = { pia_v2: true }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { features }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('features'); + }); + }); + + it('Should pass feature_variants as query parameters when provided', () => { + const featureVariants = { pia_v2: 'control' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { featureVariants }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('feature_variants'); + }); + }); + + it('Should pass pre_filter_expression as a query parameter when provided', () => { + const preFilterExpression = { brand: 'apple' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { preFilterExpression }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('pre_filter_expression'); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true, @@ -283,6 +325,35 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass features as query parameters when provided', function () { + this.timeout(10000); + const features = { pia_v2: true }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { features }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('features'); + }); + }); + + it('Should pass guard as a query parameter when provided', function () { + this.timeout(10000); + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { guard: true }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('guard').to.equal('true'); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true, diff --git a/src/modules/pia.js b/src/modules/pia.js index f48a69e4..6aa0d8a0 100644 --- a/src/modules/pia.js +++ b/src/modules/pia.js @@ -9,6 +9,7 @@ function createPiaUrl(itemId, parameters, options, questionPath) { clientId, sessionId, segments, + testCells, userId, version, agentServiceUrl, @@ -33,13 +34,29 @@ function createPiaUrl(itemId, parameters, options, questionPath) { queryParams.us = segments; } + // Pull test cells from options + if (testCells) { + Object.keys(testCells).forEach((testCellKey) => { + queryParams[`ef-${testCellKey}`] = testCells[testCellKey]; + }); + } + // Pull user id from options and ensure string if (userId) { queryParams.ui = String(userId); } if (parameters) { - const { threadId, variationId, numResults } = parameters; + const { + threadId, + variationId, + numResults, + features, + featureVariants, + preFilterExpression, + guard, + fmtOptions, + } = parameters; if (threadId) { queryParams.thread_id = threadId; @@ -52,6 +69,26 @@ function createPiaUrl(itemId, parameters, options, questionPath) { if (!helpers.isNil(numResults)) { queryParams.num_results = numResults; } + + if (features) { + queryParams.features = features; + } + + if (featureVariants) { + queryParams.feature_variants = featureVariants; + } + + if (preFilterExpression) { + queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); + } + + if (!helpers.isNil(guard)) { + queryParams.guard = guard; + } + + if (fmtOptions) { + queryParams.fmt_options = fmtOptions; + } } queryParams._dt = Date.now(); @@ -85,6 +122,9 @@ class Pia { * @param {string} [parameters.threadId] - Thread ID for conversation context (UUID) * @param {string} [parameters.variationId] - Variation ID of the item * @param {number} [parameters.numResults] - Number of suggested questions to return + * @param {object} [parameters.features] - Feature toggles for A/B testing + * @param {object} [parameters.featureVariants] - Feature variant overrides + * @param {object} [parameters.preFilterExpression] - Faceting expression to scope results * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {Promise} @@ -92,6 +132,7 @@ class Pia { * constructorio.agent.pia.getSuggestedQuestions('item-123', { * variationId: 'variation-456', * numResults: 3, + * features: { my_feature: true }, * }); */ getSuggestedQuestions(itemId, parameters, networkParameters = {}) { @@ -136,6 +177,11 @@ class Pia { * @param {object} [parameters] - Additional parameters to refine result set * @param {string} [parameters.threadId] - Thread ID for conversation context (UUID) * @param {string} [parameters.variationId] - Variation ID of the item + * @param {object} [parameters.features] - Feature toggles for A/B testing + * @param {object} [parameters.featureVariants] - Feature variant overrides + * @param {object} [parameters.preFilterExpression] - Faceting expression to scope results + * @param {boolean} [parameters.guard] - Enable or disable moderation check + * @param {object} [parameters.fmtOptions] - Response format options * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {Promise} diff --git a/src/types/pia.d.ts b/src/types/pia.d.ts index b7f676d4..1488608c 100644 --- a/src/types/pia.d.ts +++ b/src/types/pia.d.ts @@ -1,5 +1,7 @@ import { ConstructorClientOptions, + FilterExpression, + FmtOptions, NetworkParameters, Item, } from '.'; @@ -10,15 +12,21 @@ export interface PiaQuestion { value: string; } -export interface PiaSuggestedQuestionsParameters { +export interface PiaBaseParameters { threadId?: string; variationId?: string; + features?: Record; + featureVariants?: Record; + preFilterExpression?: FilterExpression; +} + +export interface PiaSuggestedQuestionsParameters extends PiaBaseParameters { numResults?: number; } -export interface PiaAnswerResultsParameters { - threadId?: string; - variationId?: string; +export interface PiaAnswerResultsParameters extends PiaBaseParameters { + guard?: boolean; + fmtOptions?: FmtOptions; } export interface PiaSuggestedQuestionsResponse { From d7ec164d15d44b863bf4a60e39f4b575e14193cb Mon Sep 17 00:00:00 2001 From: Vlad Saitov Date: Thu, 20 Aug 2026 20:12:49 +0500 Subject: [PATCH 2/4] Remove testCeels, add qsParam --- src/modules/pia.js | 13 +++++-------- src/types/pia.d.ts | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/modules/pia.js b/src/modules/pia.js index 6aa0d8a0..596fc29c 100644 --- a/src/modules/pia.js +++ b/src/modules/pia.js @@ -9,7 +9,6 @@ function createPiaUrl(itemId, parameters, options, questionPath) { clientId, sessionId, segments, - testCells, userId, version, agentServiceUrl, @@ -34,13 +33,6 @@ function createPiaUrl(itemId, parameters, options, questionPath) { queryParams.us = segments; } - // Pull test cells from options - if (testCells) { - Object.keys(testCells).forEach((testCellKey) => { - queryParams[`ef-${testCellKey}`] = testCells[testCellKey]; - }); - } - // Pull user id from options and ensure string if (userId) { queryParams.ui = String(userId); @@ -54,6 +46,7 @@ function createPiaUrl(itemId, parameters, options, questionPath) { features, featureVariants, preFilterExpression, + qsParam, guard, fmtOptions, } = parameters; @@ -82,6 +75,10 @@ function createPiaUrl(itemId, parameters, options, questionPath) { queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); } + if (qsParam) { + queryParams.qs = JSON.stringify(qsParam); + } + if (!helpers.isNil(guard)) { queryParams.guard = guard; } diff --git a/src/types/pia.d.ts b/src/types/pia.d.ts index 1488608c..3e69f692 100644 --- a/src/types/pia.d.ts +++ b/src/types/pia.d.ts @@ -18,6 +18,7 @@ export interface PiaBaseParameters { features?: Record; featureVariants?: Record; preFilterExpression?: FilterExpression; + qsParam?: Record; } export interface PiaSuggestedQuestionsParameters extends PiaBaseParameters { From e6ee23ce5fbe7babdef10eea7fd3c5fa4b2ee9be Mon Sep 17 00:00:00 2001 From: Vlad Saitov Date: Thu, 20 Aug 2026 20:26:20 +0500 Subject: [PATCH 3/4] Fix tests --- spec/src/modules/pia.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/src/modules/pia.js b/spec/src/modules/pia.js index 67bce624..60a74d01 100644 --- a/spec/src/modules/pia.js +++ b/spec/src/modules/pia.js @@ -155,7 +155,7 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { return pia.getSuggestedQuestions(validItemId, { features }).then(() => { const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); - expect(requestedUrlParams).to.have.property('features'); + expect(requestedUrlParams).to.have.property('features').to.deep.equal({ pia_v2: 'true' }); }); }); @@ -169,7 +169,7 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { return pia.getSuggestedQuestions(validItemId, { featureVariants }).then(() => { const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); - expect(requestedUrlParams).to.have.property('feature_variants'); + expect(requestedUrlParams).to.have.property('feature_variants').to.deep.equal({ pia_v2: 'control' }); }); }); @@ -183,7 +183,7 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { return pia.getSuggestedQuestions(validItemId, { preFilterExpression }).then(() => { const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); - expect(requestedUrlParams).to.have.property('pre_filter_expression'); + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); }); }); @@ -336,7 +336,7 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { return pia.getAnswerResults(validItemId, validQuestion, { features }).then(() => { const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); - expect(requestedUrlParams).to.have.property('features'); + expect(requestedUrlParams).to.have.property('features').to.deep.equal({ pia_v2: 'true' }); }); }); From 3314c57cda34fb2581f86114ce07d2300ffc4afb Mon Sep 17 00:00:00 2001 From: Vlad Saitov Date: Thu, 20 Aug 2026 20:47:37 +0500 Subject: [PATCH 4/4] Add missing tests --- spec/src/modules/pia.js | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/spec/src/modules/pia.js b/spec/src/modules/pia.js index 60a74d01..779420e2 100644 --- a/spec/src/modules/pia.js +++ b/spec/src/modules/pia.js @@ -187,6 +187,20 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass qs as a query parameter when qsParam is provided', () => { + const qsParam = { new_qs_param: 'test_value' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { qsParam }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true, @@ -340,6 +354,36 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass feature_variants as query parameters when provided', function () { + this.timeout(10000); + const featureVariants = { pia_v2: 'control' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { featureVariants }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('feature_variants').to.deep.equal({ pia_v2: 'control' }); + }); + }); + + it('Should pass pre_filter_expression as a query parameter when provided', function () { + this.timeout(10000); + const preFilterExpression = { brand: 'apple' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { preFilterExpression }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); + }); + }); + it('Should pass guard as a query parameter when provided', function () { this.timeout(10000); const { agent: { pia } } = new ConstructorIO({ @@ -354,6 +398,36 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass fmt_options as query parameters when provided', function () { + this.timeout(10000); + const fmtOptions = { groups_max_depth: 2 }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { fmtOptions }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('fmt_options').to.deep.equal({ groups_max_depth: '2' }); + }); + }); + + it('Should pass qs as a query parameter when qsParam is provided', function () { + this.timeout(10000); + const qsParam = { new_qs_param: 'test_value' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { qsParam }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true,