From 266614672b7d3afa73fbb87333e783548f1482cc Mon Sep 17 00:00:00 2001 From: Serban Stancu Date: Wed, 9 Oct 2024 17:38:09 -0600 Subject: [PATCH] Add test for conflict resolution testing. --- .../Personalization/conflictResolutions.js | 251 ++++++++++++++++++ .../Personalization/conflictResolutions.js | 127 +++++++++ 2 files changed, 378 insertions(+) create mode 100644 test/functional/fixtures/Personalization/conflictResolutions.js create mode 100644 test/functional/specs/Personalization/conflictResolutions.js diff --git a/test/functional/fixtures/Personalization/conflictResolutions.js b/test/functional/fixtures/Personalization/conflictResolutions.js new file mode 100644 index 000000000..88a493ca0 --- /dev/null +++ b/test/functional/fixtures/Personalization/conflictResolutions.js @@ -0,0 +1,251 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +export const inAppMessagesPropositions = { + requestId: "09ead9d7-b91f-41d6-9bb5-e7a5396315d6", + handle: [ + { + payload: [ + { + id: "03560676528201411421379778603411811904", + namespace: { + code: "ECID", + }, + }, + ], + type: "identity:result", + }, + { + payload: [ + { + id: "id2", + scope: "web://www.test.com/", + scopeDetails: { + rank: 2, + decisionProvider: "AJO", + correlationID: "3a034993-3bdb-4d8d-870f-0817e8949ebd-0", + characteristics: { + eventToken: + "eyJtZXNzYWdlRXhlY3V0aW9uIjp7Im1lc3NhZ2VFeGVjdXRpb25JRCI6IlVFOkluYm91bmQiLCJtZXNzYWdlSUQiOiIxYmQ4NWMwMC1iNGM5LTQ0NjQtYjIyMC1hYzY5ZTY5NGI1M2QiLCJtZXNzYWdlUHVibGljYXRpb25JRCI6IjNhMDM0OTkzLTNiZGItNGQ4ZC04NzBmLTA4MTdlODk0OWViZCIsIm1lc3NhZ2VUeXBlIjoibWFya2V0aW5nIiwiY2FtcGFpZ25JRCI6IjY4ODg5NDdjLTAyZDQtNDFhNi05YzZjLTE1YTU4ZTVlNzcyYyIsImNhbXBhaWduVmVyc2lvbklEIjoiNDU1NzE2MmQtNzgyMi00Zjg4LTkyZWItMmMxZmViZTFhMmViIiwiY2FtcGFpZ25BY3Rpb25JRCI6ImNhMzJlZjZmLWY3NTItNDA2Ny04YmFlLTE1NGY2MDAyN2E3ZSJ9LCJtZXNzYWdlUHJvZmlsZSI6eyJtZXNzYWdlUHJvZmlsZUlEIjoiYjBlM2M0YjctMWE3YS00NTljLWE2YzUtNTFlNDU0ZDVjYmRiIiwiY2hhbm5lbCI6eyJfaWQiOiJodHRwczovL25zLmFkb2JlLmNvbS94ZG0vY2hhbm5lbHMvaW5BcHAiLCJfdHlwZSI6Imh0dHBzOi8vbnMuYWRvYmUuY29tL3hkbS9jaGFubmVsLXR5cGVzL2luQXBwIn19fQ==", + }, + activity: { + id: "6888947c-02d4-41a6-9c6c-15a58e5e772c#ca32ef6f-f752-4067-8bae-154f60027a7e", + matchedSurfaces: ["web://surface1/"], + }, + }, + items: [ + { + id: "e1090531-caee-4749-a738-680f393cf4a9", + schema: "https://ns.adobe.com/personalization/ruleset-item", + data: { + version: 1, + rules: [ + { + condition: { + definition: { + key: "~type", + matcher: "eq", + values: ["com.adobe.eventType.rulesEngine"], + }, + type: "matcher", + }, + consequences: [ + { + id: "f03980c9-5344-4e32-84c7-d74ffebf06d6", + type: "schema", + detail: { + id: "f03980c9-5344-4e32-84c7-d74ffebf06d6", + schema: + "https://ns.adobe.com/personalization/message/in-app", + data: { + content: + "Proposition 2", + contentType: "text/html", + remoteAssets: [ + "https://cdn.experience.adobe.net/solutions/cjm-inapp-editor/assets/InAppBlockImageDefault.aa849e19.svg", + ], + webParameters: { + "alloy-content-iframe": { + style: { + border: "none", + height: "100%", + width: "100%", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "#alloy-messaging-container", + }, + }, + "alloy-messaging-container": { + style: { + backgroundColor: "#000000", + border: "none", + borderRadius: "15px", + height: "100vh", + overflow: "hidden", + position: "fixed", + width: "100%", + left: "50%", + transform: + "translateX(-50%) translateY(-50%)", + top: "50%", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "body", + }, + }, + "alloy-overlay-container": { + style: { + position: "fixed", + top: "0", + left: "0", + width: "100%", + height: "100%", + background: "transparent", + opacity: 0.2, + backgroundColor: "#000000", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "body", + }, + }, + }, + publishedDate: 1727568749, + }, + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + id: "id1", + scope: "web://www.test.com/", + scopeDetails: { + rank: 1, + decisionProvider: "AJO", + correlationID: "d4f5d677-1123-4605-ad9e-86fead923220-0", + characteristics: { + eventToken: + "eyJtZXNzYWdlRXhlY3V0aW9uIjp7Im1lc3NhZ2VFeGVjdXRpb25JRCI6IlVFOkluYm91bmQiLCJtZXNzYWdlSUQiOiJiNmY1ODVmZS1mNWE1LTQ0NWUtYjU0OC0yOThlMmQ1MDFkZTYiLCJtZXNzYWdlUHVibGljYXRpb25JRCI6ImQ0ZjVkNjc3LTExMjMtNDYwNS1hZDllLTg2ZmVhZDkyMzIyMCIsIm1lc3NhZ2VUeXBlIjoibWFya2V0aW5nIiwiY2FtcGFpZ25JRCI6ImI3NWNkNDAyLTIwNzMtNDc5OC1hMDJjLTc0YmQyMDg0MmQyNyIsImNhbXBhaWduVmVyc2lvbklEIjoiMmExMDJjZDMtZmM0NC00ZDI0LWEzMWMtNzZhZTBmY2FjMWJhIiwiY2FtcGFpZ25BY3Rpb25JRCI6ImNhMzJlZjZmLWY3NTItNDA2Ny04YmFlLTE1NGY2MDAyN2E3ZSJ9LCJtZXNzYWdlUHJvZmlsZSI6eyJtZXNzYWdlUHJvZmlsZUlEIjoiNzEyZjYwN2UtNjNmNS00NGExLTg3ZDYtM2Y1NWQzYjVmNGM2IiwiY2hhbm5lbCI6eyJfaWQiOiJodHRwczovL25zLmFkb2JlLmNvbS94ZG0vY2hhbm5lbHMvaW5BcHAiLCJfdHlwZSI6Imh0dHBzOi8vbnMuYWRvYmUuY29tL3hkbS9jaGFubmVsLXR5cGVzL2luQXBwIn19fQ==", + }, + activity: { + id: "b75cd402-2073-4798-a02c-74bd20842d27#ca32ef6f-f752-4067-8bae-154f60027a7e", + matchedSurfaces: ["web://surface1/"], + }, + }, + items: [ + { + id: "471af8f8-7806-47ce-885e-45fffe14108a", + schema: "https://ns.adobe.com/personalization/ruleset-item", + data: { + version: 1, + rules: [ + { + condition: { + definition: { + key: "~type", + matcher: "eq", + values: ["com.adobe.eventType.rulesEngine"], + }, + type: "matcher", + }, + consequences: [ + { + id: "613e9843-7291-4505-9cca-3f76caff6bec", + type: "schema", + detail: { + id: "613e9843-7291-4505-9cca-3f76caff6bec", + schema: + "https://ns.adobe.com/personalization/message/in-app", + data: { + content: + "Proposition 1", + contentType: "text/html", + remoteAssets: [ + "https://cdn.experience.adobe.net/solutions/cjm-inapp-editor/assets/InAppBlockImageDefault.aa849e19.svg", + ], + webParameters: { + "alloy-content-iframe": { + style: { + border: "none", + height: "100%", + width: "100%", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "#alloy-messaging-container", + }, + }, + "alloy-messaging-container": { + style: { + backgroundColor: "#000000", + border: "none", + borderRadius: "15px", + height: "100vh", + overflow: "hidden", + position: "fixed", + width: "100%", + left: "50%", + transform: + "translateX(-50%) translateY(-50%)", + top: "50%", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "body", + }, + }, + "alloy-overlay-container": { + style: { + position: "fixed", + top: "0", + left: "0", + width: "100%", + height: "100%", + background: "transparent", + opacity: 0.2, + backgroundColor: "#000000", + }, + params: { + enabled: true, + insertionMethod: "appendChild", + parentElement: "body", + }, + }, + }, + publishedDate: 1727568995, + }, + }, + }, + ], + }, + ], + }, + }, + ], + }, + ], + type: "personalization:decisions", + eventIndex: 0, + }, + ], +}; diff --git a/test/functional/specs/Personalization/conflictResolutions.js b/test/functional/specs/Personalization/conflictResolutions.js new file mode 100644 index 000000000..d45b19d0e --- /dev/null +++ b/test/functional/specs/Personalization/conflictResolutions.js @@ -0,0 +1,127 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import { ClientFunction, RequestMock, t } from "testcafe"; +import createNetworkLogger from "../../helpers/networkLogger/index.js"; +import createFixture from "../../helpers/createFixture/index.js"; +import { inAppMessagesPropositions } from "../../fixtures/Personalization/conflictResolutions.js"; +import createAlloyProxy from "../../helpers/createAlloyProxy.js"; +import getBaseConfig from "../../helpers/getBaseConfig.js"; +import { + compose, + debugEnabled, +} from "../../helpers/constants/configParts/index.js"; + +const networkLogger = createNetworkLogger(); + +const organizationId = "5BFE274A5F6980A50A495C08@AdobeOrg"; +const dataStreamId = "ae47e1ea-7625-49b9-b69f-8ad372e46344"; + +const orgMainConfigMain = getBaseConfig(organizationId, dataStreamId); +const config = compose(orgMainConfigMain, debugEnabled); + +const firstInteractCallReturnsInAppMessagesMock = () => { + let i = 0; + return RequestMock() + .onRequestTo((request) => { + if (request.url.includes("/interact")) { + i += 1; + } + + const r = request.url.includes("/interact") && i === 2; + return r; + }) + .respond(JSON.stringify(inAppMessagesPropositions), 200, { + "access-control-allow-origin": "https://alloyio.com", + "access-control-allow-credentials": true, + "content-type": " application/json;charset=utf-8", + }); +}; + +createFixture({ + title: + "Conflict resoulution: show one in app message propositions sorted by rank", + requestHooks: [ + firstInteractCallReturnsInAppMessagesMock(), + networkLogger.edgeEndpointLogs, + ], +}); + +test("propositions are sorted ascending by rank", async () => { + const alloy = createAlloyProxy(); + await alloy.configure(config); + + // Sending an event for getting an Identity cookie. + await alloy.sendEvent(); + + await alloy.sendEvent({ + renderDecisions: true, + }); + + const result = await alloy.evaluateRulesets({ + renderDecisions: true, + }); + + // When we receive multiple In-App messages propositions, DecisioningEngine will sort them ascending by rank. + // Lower rank means higher priority. We need to diplay just one proposition (with the rank 1). There can be + // multiple propositions with the same rank but with different surfaces. + // The mock returns the propositions in descending order by rank. + // For the displayed propositions an event with the type decisioning.propositionDisplay is sent back to the edge. + // For the suppressed propositions an event with the type decisioning.propositionSuppressDisplay is sent back to the edge. + await t.expect(result.propositions[0].scopeDetails.rank).eql(1); + await t.expect(result.propositions[1].scopeDetails.rank).eql(2); + + const edgeRequest = networkLogger.edgeEndpointLogs.requests; + + const propositionDisplayEvent = edgeRequest[edgeRequest.length - 2]; + const propositionDisplayEventBody = JSON.parse( + propositionDisplayEvent.request.body, + ); + + await t + .expect(propositionDisplayEventBody.events[0].xdm.eventType) + .eql("decisioning.propositionDisplay"); + await t + .expect( + // eslint-disable-next-line no-underscore-dangle + propositionDisplayEventBody.events[0].xdm._experience.decisioning + .propositions[0].id, + ) + .eql("id1"); + + const propositionSuppressDisplayEvent = edgeRequest[edgeRequest.length - 1]; + const propositionSuppressDisplayEventBody = JSON.parse( + propositionSuppressDisplayEvent.request.body, + ); + + await t + .expect(propositionSuppressDisplayEventBody.events[0].xdm.eventType) + .eql("decisioning.propositionSuppressDisplay"); + await t + .expect( + // eslint-disable-next-line no-underscore-dangle + propositionSuppressDisplayEventBody.events[0].xdm._experience.decisioning + .propositions[0].id, + ) + .eql("id2"); + + // Check there is only one iframe in the DOM. + const iframesContent = await ClientFunction(() => { + const iframes = document.querySelectorAll("#alloy-content-iframe"); + const r = []; + iframes.forEach((i) => { + r.push(i.contentDocument.body.textContent); + }); + return r; + })(); + await t.expect(iframesContent.length).eql(1); + await t.expect(iframesContent[0]).eql("Proposition 1"); +});