From d6afcb6f65a95acfbc1c7b4432fd07c8347303b3 Mon Sep 17 00:00:00 2001 From: Jon Snyder Date: Fri, 5 May 2023 08:20:17 -0600 Subject: [PATCH 1/4] WIP add assurance session to functional tests for decisioning.propositionFetch event --- package-lock.json | 83 +++++++++++++++---- package.json | 3 +- .../helpers/assurance/createAssuranceApi.js | 11 +++ .../helpers/assurance/getCredentials.js | 36 ++++++++ .../helpers/assurance/getToken.js.js | 59 +++++++++++++ .../functional/specs/Personalization/C????.js | 67 +++++++++++++++ 6 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 test/functional/helpers/assurance/createAssuranceApi.js create mode 100644 test/functional/helpers/assurance/getCredentials.js create mode 100644 test/functional/helpers/assurance/getToken.js.js create mode 100644 test/functional/specs/Personalization/C????.js diff --git a/package-lock.json b/package-lock.json index 5b805940f..3ecf3628a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "eslint-plugin-import": "^2.16.0", "eslint-plugin-prettier": "^3.0.1", "eslint-plugin-testcafe": "^0.2.1", + "form-data": "^4.0.0", "glob": "^7.1.3", "husky": "^6.0.0", "jasmine-core": "^3.4.0", @@ -54,7 +55,7 @@ "karma-sauce-launcher": "^4.3.6", "karma-spec-reporter": "0.0.32", "lint-staged": "^10.5.4", - "node-fetch": "^2.6.7", + "node-fetch": "^2.6.9", "prettier": "^1.16.4", "promise-polyfill": "^8.1.0", "read-cache": "^1.0.0", @@ -7139,17 +7140,17 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fp-ts": { @@ -10196,9 +10197,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -11655,6 +11656,20 @@ "node": ">= 6" } }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/request/node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -14560,6 +14575,20 @@ "wd": "lib/bin.js" } }, + "node_modules/wd/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/wd/node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -20721,13 +20750,13 @@ "dev": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -23107,9 +23136,9 @@ } }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -24217,6 +24246,17 @@ "uuid": "^3.3.2" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -26559,6 +26599,17 @@ "vargs": "^0.1.0" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", diff --git a/package.json b/package.json index 144fdef95..487783ef6 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "eslint-plugin-import": "^2.16.0", "eslint-plugin-prettier": "^3.0.1", "eslint-plugin-testcafe": "^0.2.1", + "form-data": "^4.0.0", "glob": "^7.1.3", "husky": "^6.0.0", "jasmine-core": "^3.4.0", @@ -101,7 +102,7 @@ "karma-sauce-launcher": "^4.3.6", "karma-spec-reporter": "0.0.32", "lint-staged": "^10.5.4", - "node-fetch": "^2.6.7", + "node-fetch": "^2.6.9", "prettier": "^1.16.4", "promise-polyfill": "^8.1.0", "read-cache": "^1.0.0", diff --git a/test/functional/helpers/assurance/createAssuranceApi.js b/test/functional/helpers/assurance/createAssuranceApi.js new file mode 100644 index 000000000..84fc433cc --- /dev/null +++ b/test/functional/helpers/assurance/createAssuranceApi.js @@ -0,0 +1,11 @@ +import fetch from "node-fetch"; + +export default ({ clientId, orgId, accessToken }) => { + + return { + createSession: async (sessionName) => { + }, + fetchEvents: async (sessionId) => { + } + }; +} diff --git a/test/functional/helpers/assurance/getCredentials.js b/test/functional/helpers/assurance/getCredentials.js new file mode 100644 index 000000000..baba9de64 --- /dev/null +++ b/test/functional/helpers/assurance/getCredentials.js @@ -0,0 +1,36 @@ +import chalk from "chalk"; + +export const IMS_URL = process.env["IMS_URL"] || "https://ims-na1.adobelogin.com"; +// default to Unified JS QE Only org +export const ASSURANCE_ORG_ID = process.env["ASSURANCE_ORG_ID"] || "5BFE274A5F6980A50A495C08@AdobeOrg"; +// default to "Alloy end to end testing" project +export const ASSURANCE_CLIENT_ID = process.env["ASSURANCE_CLIENT_ID"] || "0c1c7478c4994c69866b64c8341578ed"; +// Use the Adobe developer console to get the client secret for the client ID above. +export const ASSURANCE_CLIENT_SECRET = process.env["ASSURANCE_CLIENT_SECRET"]; +// Use the Adobe developer console to generate a JWT token for the client ID above. +export const ASSURANCE_JWT_TOKEN = process.env["ASSURANCE_JWT_TOKEN"]; + +if (!ASSURANCE_CLIENT_SECRET) { + console.error( + chalk.redBright( + `Failed to read client secret. Please ensure the ASSURANCE_CLIENT_SECRET environment variable is set.` + ) + ); +} + +if (!ASSURANCE_JWT_TOKEN) { + console.error( + chalk.redBright( + `Failed to read JWT token. Please ensure the ASSURANCE_JWT_TOKEN environment variable is set.` + ) + ); +} + +export default ({ + imsUrl: IMS_URL, + clientId: ASSURANCE_CLIENT_ID, + clientSecret: ASSURANCE_CLIENT_SECRET, + jwtToken: ASSURANCE_JWT_TOKEN +}); + + diff --git a/test/functional/helpers/assurance/getToken.js.js b/test/functional/helpers/assurance/getToken.js.js new file mode 100644 index 000000000..525909af7 --- /dev/null +++ b/test/functional/helpers/assurance/getToken.js.js @@ -0,0 +1,59 @@ +import fetch from "node-fetch"; +import FormData from "form-data"; + +const throwRequestFailedError = details => { + const error = new Error( + `Request failed while swapping the jwt token. ${details}` + ); + error.code = REQUEST_FAILED; + throw error; +}; + +const throwUnexpectedResponseError = details => { + const error = new Error( + `Unexpected response received while swapping the jwt token. ${details}` + ); + error.code = UNEXPECTED_RESPONSE_BODY; + throw error; +}; + +export default ({ imsUrl, clientId, clientSecret, jwtToken }) => { + + const form = new FormData(); + form.append('client_id', clientId); + form.append('client_secret', clientSecret); + form.append('jwt_token', jwtToken); + + const postOptions = { + method: 'POST', + body: form, + headers: form.getHeaders() + }; + + return fetch(`${imsUrl}/ims/exchange/jwt/`, postOptions) + .catch(e => throwRequestFailedError(e.message)) + .then(res => { + return res.json().then(data => { + return { + ok: res.ok, + json: data + }; + }); + }) + .then(({ ok, json }) => { + const { access_token, error, error_description } = json; + if (ok && access_token) { + return json; + } + + if (error && error_description) { + const swapError = new Error(error_description); + swapError.code = error; + throw swapError; + } else { + throwUnexpectedResponseError( + `The response body is as follows: ${JSON.stringify(json)}` + ); + } + }); +} diff --git a/test/functional/specs/Personalization/C????.js b/test/functional/specs/Personalization/C????.js new file mode 100644 index 000000000..b0f778d5e --- /dev/null +++ b/test/functional/specs/Personalization/C????.js @@ -0,0 +1,67 @@ +import { t, ClientFunction } from "testcafe"; +import createNetworkLogger from "../../helpers/networkLogger"; +import { responseStatus } from "../../helpers/assertions/index"; +import createFixture from "../../helpers/createFixture"; +import { + compose, + orgMainConfigMain, + debugEnabled +} from "../../helpers/constants/configParts"; +import getResponseBody from "../../helpers/networkLogger/getResponseBody"; +import createResponse from "../../helpers/createResponse"; +import createAlloyProxy from "../../helpers/createAlloyProxy"; + +const networkLogger = createNetworkLogger(); +const config = compose(orgMainConfigMain, debugEnabled); +const PAGE_WIDE_SCOPE = "__view__"; +createFixture({ + title: "C???? hide and show containers work" +}); + +test.meta({ + ID: "C????", + SEVERITY: "P0", + TEST_RUN: "Regression" +}); + +test("Test C???? hide and show containers work with prehiding style", async () => { + const alloy = createAlloyProxy(); + await alloy.configure(config); + const eventResult = await alloy.sendEvent({ renderDecisions: true }); + + await responseStatus(networkLogger.edgeEndpointLogs.requests, 200); + + await t.expect(networkLogger.edgeEndpointLogs.requests.length).eql(2); + + const sendEventRequest = networkLogger.edgeEndpointLogs.requests[0]; + const requestBody = JSON.parse(sendEventRequest.request.body); + await t + .expect(requestBody.events[0].query.personalization.decisionScopes) + .eql([PAGE_WIDE_SCOPE]); + + const personalizationSchemas = + requestBody.events[0].query.personalization.schemas; + + const result = [ + "https://ns.adobe.com/personalization/default-content-item", + "https://ns.adobe.com/personalization/dom-action", + "https://ns.adobe.com/personalization/html-content-item", + "https://ns.adobe.com/personalization/json-content-item", + "https://ns.adobe.com/personalization/redirect-item" + ].every(schema => personalizationSchemas.includes(schema)); + + await t.expect(result).eql(true); + + const response = JSON.parse( + getResponseBody(networkLogger.edgeEndpointLogs.requests[0]) + ); + const personalizationPayload = createResponse({ + content: response + }).getPayloadsByType("personalization:decisions"); + + await t.expect(personalizationPayload[0].scope).eql(PAGE_WIDE_SCOPE); + await t.expect(getDecisionContent()).eql("Here is an awesome target offer!"); + + await t.expect(eventResult.decisions).eql([]); + await t.expect(eventResult.propositions[0].renderAttempted).eql(true); +}); From 02169496e7cc9daa13d7c66bc9d85b81c11889e6 Mon Sep 17 00:00:00 2001 From: Jon Snyder Date: Fri, 28 Jul 2023 16:38:24 -0600 Subject: [PATCH 2/4] Add assurance example test --- package-lock.json | 193 ++++++++++++++++++ package.json | 2 + .../helpers/assurance/AssuranceRequestHook.js | 88 ++++++++ .../helpers/assurance/createAssuranceApi.js | 96 ++++++++- .../helpers/assurance/getAuthHeaders.js | 28 +++ .../helpers/assurance/getCredentials.js | 62 ++++-- .../helpers/assurance/getToken.js.js | 59 ------ test/functional/helpers/assurance/index.js | 34 +++ .../configParts/configOverridesAlt.js | 11 + .../configParts/configOverridesMain.js | 11 + .../functional/specs/Personalization/C????.js | 74 ++++--- 11 files changed, 544 insertions(+), 114 deletions(-) create mode 100644 test/functional/helpers/assurance/AssuranceRequestHook.js create mode 100644 test/functional/helpers/assurance/getAuthHeaders.js delete mode 100644 test/functional/helpers/assurance/getToken.js.js create mode 100644 test/functional/helpers/assurance/index.js diff --git a/package-lock.json b/package-lock.json index 3351e2b92..2db72999c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@adobe/alloy": "^2.17.0", + "@adobe/jwt-auth": "^2.0.0", "@babel/cli": "^7.12.8", "@babel/core": "^7.2.2", "@babel/plugin-proposal-object-rest-spread": "^7.3.2", @@ -80,6 +81,7 @@ "testcafe-reporter-saucelabs": "^1.0.1", "url-exists-nodejs": "^0.1.0", "url-parse": "^1.4.7", + "whoami": "^0.0.3", "yargs": "^16.2.0" } }, @@ -97,6 +99,34 @@ "uuid": "^3.3.2" } }, + "node_modules/@adobe/jwt-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@adobe/jwt-auth/-/jwt-auth-2.0.0.tgz", + "integrity": "sha512-8cCcykAU9srM0dK8L1UvLMVzxg7NoqU56pMHkDX1l6e7FM2R+FnnQiPs4MriqxFHKmNqcAxxjo2ky8zrBfBntQ==", + "dev": true, + "dependencies": { + "form-data": "^3.0.0", + "jsonwebtoken": "^9.0.0", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": "^14.18 || ^16.13 || >=18" + } + }, + "node_modules/@adobe/jwt-auth/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@adobe/reactor-load-script": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@adobe/reactor-load-script/-/reactor-load-script-1.1.1.tgz", @@ -5434,6 +5464,12 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -7401,6 +7437,15 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/edge-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", @@ -12509,6 +12554,22 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -12524,6 +12585,27 @@ "node": ">=0.6.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/karma": { "version": "6.3.16", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", @@ -16017,6 +16099,18 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha512-Ny0KN4dyT8ZSCE0frtcbAJGoM/HTArpyPkeli1/00aYfm0sbD/Gk/4x7N2DP9QKGpBsiQH7n6rpm1L79RtviEQ==", + "dev": true, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -18736,6 +18830,18 @@ "which": "bin/which" } }, + "node_modules/whoami": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/whoami/-/whoami-0.0.3.tgz", + "integrity": "sha512-ZXwrGI7u/TYWv53uSAbSpqGviNAy4SNiew6Z/VEDGtrEOh67vyCX80/4oS3hN+Pscu2mY4n+M4ygc3pt8qce3g==", + "dev": true, + "dependencies": { + "shelljs": "^0.3.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -19046,6 +19152,30 @@ "uuid": "^3.3.2" } }, + "@adobe/jwt-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@adobe/jwt-auth/-/jwt-auth-2.0.0.tgz", + "integrity": "sha512-8cCcykAU9srM0dK8L1UvLMVzxg7NoqU56pMHkDX1l6e7FM2R+FnnQiPs4MriqxFHKmNqcAxxjo2ky8zrBfBntQ==", + "dev": true, + "requires": { + "form-data": "^3.0.0", + "jsonwebtoken": "^9.0.0", + "node-fetch": "^2.6.1" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@adobe/reactor-load-script": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@adobe/reactor-load-script/-/reactor-load-script-1.1.1.tgz", @@ -23161,6 +23291,12 @@ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -24738,6 +24874,15 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "edge-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", @@ -28628,6 +28773,18 @@ "universalify": "^2.0.0" } }, + "jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -28640,6 +28797,27 @@ "verror": "1.10.0" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "karma": { "version": "6.3.16", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", @@ -31354,6 +31532,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha512-Ny0KN4dyT8ZSCE0frtcbAJGoM/HTArpyPkeli1/00aYfm0sbD/Gk/4x7N2DP9QKGpBsiQH7n6rpm1L79RtviEQ==", + "dev": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -33522,6 +33706,15 @@ } } }, + "whoami": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/whoami/-/whoami-0.0.3.tgz", + "integrity": "sha512-ZXwrGI7u/TYWv53uSAbSpqGviNAy4SNiew6Z/VEDGtrEOh67vyCX80/4oS3hN+Pscu2mY4n+M4ygc3pt8qce3g==", + "dev": true, + "requires": { + "shelljs": "^0.3.0" + } + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 3703376eb..13c1fc9ae 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ }, "devDependencies": { "@adobe/alloy": "^2.17.0", + "@adobe/jwt-auth": "^2.0.0", "@babel/cli": "^7.12.8", "@babel/core": "^7.2.2", "@babel/plugin-proposal-object-rest-spread": "^7.3.2", @@ -129,6 +130,7 @@ "testcafe-reporter-saucelabs": "^1.0.1", "url-exists-nodejs": "^0.1.0", "url-parse": "^1.4.7", + "whoami": "^0.0.3", "yargs": "^16.2.0" } } diff --git a/test/functional/helpers/assurance/AssuranceRequestHook.js b/test/functional/helpers/assurance/AssuranceRequestHook.js new file mode 100644 index 000000000..bece977ed --- /dev/null +++ b/test/functional/helpers/assurance/AssuranceRequestHook.js @@ -0,0 +1,88 @@ +/* +Copyright 2023 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 { RequestHook, t } from "testcafe"; + +const ASSERTION_DELAY = 200; +const ASSERTION_TIMEOUT = 5000; +const delay = () => { + return new Promise(resolve => setTimeout(resolve, ASSERTION_DELAY)); +}; + +const createRequest = (requestLogs, fetchMore) => { + return { + find: async predicate => { + let i = 0; + for (i = 0; i < requestLogs.length; i += 1) { + if (predicate(requestLogs[i])) { + return requestLogs[i]; + } + } + const expirationTime = new Date().getTime() + ASSERTION_TIMEOUT; + while (new Date().getTime() < expirationTime) { + // eslint-disable-next-line no-await-in-loop + await fetchMore(); + for (; i < requestLogs.length; i += 1) { + if (predicate(requestLogs[i])) { + return requestLogs[i]; + } + } + // eslint-disable-next-line no-await-in-loop + await delay(); + } + t.expect().ok( + "Assurance logs did not contain an event matching the predicate after 5 seconds." + ); + return undefined; + }, + debug() { + // eslint-disable-next-line no-console + console.log(JSON.stringify(requestLogs, null, 2)); + } + }; +}; + +export default class AssuranceRequestHook extends RequestHook { + constructor(assuranceToken, events) { + super(); + this.assuranceToken = assuranceToken; + this.events = events; + this.eventsByRequestId = {}; + this.requests = []; + } + + onRequest(e) { + const url = new URL(e.requestOptions.url); + const requestId = url.searchParams.get("requestId"); + if (requestId) { + e.requestOptions.headers[ + "X-Adobe-AEP-Validation-Token" + ] = this.assuranceToken; + const requestLogs = []; + this.eventsByRequestId[requestId] = requestLogs; + this.requests.push(createRequest(requestLogs, this.fetchMore.bind(this))); + } + } + + // eslint-disable-next-line class-methods-use-this + onResponse() {} + + async fetchMore() { + // eslint-disable-next-line no-await-in-loop + while (await this.events.advance()) { + const event = this.events.current(); + const { payload: { attributes: { requestId } = {} } = {} } = event; + if (requestId && this.eventsByRequestId[requestId]) { + this.eventsByRequestId[requestId].push(event); + } + } + } +} diff --git a/test/functional/helpers/assurance/createAssuranceApi.js b/test/functional/helpers/assurance/createAssuranceApi.js index 84fc433cc..df3ba180d 100644 --- a/test/functional/helpers/assurance/createAssuranceApi.js +++ b/test/functional/helpers/assurance/createAssuranceApi.js @@ -1,11 +1,99 @@ +/* +Copyright 2023 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 fetch from "node-fetch"; -export default ({ clientId, orgId, accessToken }) => { +const CREATE_SESSION_QUERY = ` + mutation createSession($session: SessionInput!) { + createSession(session: $session) { + orgId + uuid + name + link + token + } + }`; + +const FETCH_EVENTS_QUERY = ` + query eventsQuery($sessionUuid: UUID!, $cursor: EventCursor) { + events(sessionUuid:$sessionUuid,first:1000,after:$cursor){ + uuid + eventNumber + clientId + timestamp + vendor + type + payload + annotations { + uuid + type + payload + } + } + }`; + +export default authHeaders => { + const makeGraphRequest = async (query, variables) => { + const response = await fetch("https://graffias.adobe.io/graffias/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + ...authHeaders + }, + body: JSON.stringify({ query, variables }) + }); + const json = await response.json(); + return json.data; + }; return { - createSession: async (sessionName) => { + createSession: async sessionName => { + const response = await makeGraphRequest(CREATE_SESSION_QUERY, { + session: { + name: sessionName, + link: "testUrl://default" + } + }); + const { + createSession: { uuid } + } = response; + return uuid; }, - fetchEvents: async (sessionId) => { + fetchEvents: sessionUuid => { + let cursor = null; + let events = []; + let i = 0; + return { + current: () => { + return events[i]; + }, + advance: async () => { + if (i >= events.length) { + if (events.length > 0) { + const { eventNumber, timestamp } = events[events.length - 1]; + cursor = { eventNumber, timestamp, sessionUuid }; + } + const response = await makeGraphRequest(FETCH_EVENTS_QUERY, { + sessionUuid, + cursor + }); + events = response.events; + i = 0; + return events.length > 0; + } + i += 1; + + return i < events.length; + } + }; } }; -} +}; diff --git a/test/functional/helpers/assurance/getAuthHeaders.js b/test/functional/helpers/assurance/getAuthHeaders.js new file mode 100644 index 000000000..69383a4c1 --- /dev/null +++ b/test/functional/helpers/assurance/getAuthHeaders.js @@ -0,0 +1,28 @@ +/* +Copyright 2023 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. +*/ +const auth = require("@adobe/jwt-auth"); + +/** + * Retrieves an access token for the Adobe I/O integration used for + * running end-to-end tests. + * @returns {Promise} + */ +module.exports = async credentials => { + const result = await auth(credentials); + const { access_token: accessToken } = result; + const { orgId, clientId } = credentials; + return { + "x-gw-ims-org-id": orgId, + "x-api-key": clientId, + Authorization: `Bearer ${accessToken}` + }; +}; diff --git a/test/functional/helpers/assurance/getCredentials.js b/test/functional/helpers/assurance/getCredentials.js index baba9de64..6e9ca9122 100644 --- a/test/functional/helpers/assurance/getCredentials.js +++ b/test/functional/helpers/assurance/getCredentials.js @@ -1,16 +1,37 @@ +/* +Copyright 2023 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 chalk from "chalk"; +import fs from "fs"; -export const IMS_URL = process.env["IMS_URL"] || "https://ims-na1.adobelogin.com"; +export const IMS_URL = process.env.IMS_URL || "https://ims-na1.adobelogin.com"; // default to Unified JS QE Only org -export const ASSURANCE_ORG_ID = process.env["ASSURANCE_ORG_ID"] || "5BFE274A5F6980A50A495C08@AdobeOrg"; +export const ASSURANCE_ORG_ID = + process.env.ASSURANCE_ORG_ID || "5BFE274A5F6980A50A495C08@AdobeOrg"; // default to "Alloy end to end testing" project -export const ASSURANCE_CLIENT_ID = process.env["ASSURANCE_CLIENT_ID"] || "0c1c7478c4994c69866b64c8341578ed"; +export const ASSURANCE_CLIENT_ID = + process.env.ASSURANCE_CLIENT_ID || "0c1c7478c4994c69866b64c8341578ed"; +export const ASSURANCE_TECHNICAL_ACCOUNT_ID = + process.env.ASSURANCE_TECHNICAL_ACCOUNT_ID || + "52202EB9602F004D0A495F8C@techacct.adobe.com"; // Use the Adobe developer console to get the client secret for the client ID above. -export const ASSURANCE_CLIENT_SECRET = process.env["ASSURANCE_CLIENT_SECRET"]; -// Use the Adobe developer console to generate a JWT token for the client ID above. -export const ASSURANCE_JWT_TOKEN = process.env["ASSURANCE_JWT_TOKEN"]; +export const ASSURANCE_CLIENT_SECRET = process.env.ASSURANCE_CLIENT_SECRET; +// Use the Adobe developer console to generate a private key +export const ASSURANCE_PRIVATE_KEY_FILE = + process.env.ASSURANCE_PRIVATE_KEY_FILE; +export const ASSURANCE_PRIVATE_KEY_CONTENTS = + process.env.ASSURANCE_PRIVATE_KEY_CONTENTS; if (!ASSURANCE_CLIENT_SECRET) { + // eslint-disable-next-line no-console console.error( chalk.redBright( `Failed to read client secret. Please ensure the ASSURANCE_CLIENT_SECRET environment variable is set.` @@ -18,19 +39,34 @@ if (!ASSURANCE_CLIENT_SECRET) { ); } -if (!ASSURANCE_JWT_TOKEN) { +if (!ASSURANCE_PRIVATE_KEY_FILE && !ASSURANCE_PRIVATE_KEY_CONTENTS) { + // eslint-disable-next-line no-console + console.error( + chalk.redBright( + `Failed to read private key. Please ensure the ASSURANCE_PRIVATE_KEY_FILE or ASSURANCE_PRIVATE_KEY_CONTENTS environment variable is set.` + ) + ); +} + +const privateKey = + ASSURANCE_PRIVATE_KEY_CONTENTS || + fs.readFileSync(ASSURANCE_PRIVATE_KEY_FILE, "utf8"); + +if (!privateKey) { + // eslint-disable-next-line no-console console.error( chalk.redBright( - `Failed to read JWT token. Please ensure the ASSURANCE_JWT_TOKEN environment variable is set.` + `Failed to read private key. Please ensure ASSURANCE_PRIVATE_KEY_FILE exists and is readable.` ) ); } -export default ({ - imsUrl: IMS_URL, +export default () => ({ clientId: ASSURANCE_CLIENT_ID, + technicalAccountId: ASSURANCE_TECHNICAL_ACCOUNT_ID, + orgId: ASSURANCE_ORG_ID, clientSecret: ASSURANCE_CLIENT_SECRET, - jwtToken: ASSURANCE_JWT_TOKEN + privateKey, + metaScopes: ["https://ims-na1.adobelogin.com/s/assurance_automation"], + ims: IMS_URL }); - - diff --git a/test/functional/helpers/assurance/getToken.js.js b/test/functional/helpers/assurance/getToken.js.js deleted file mode 100644 index 525909af7..000000000 --- a/test/functional/helpers/assurance/getToken.js.js +++ /dev/null @@ -1,59 +0,0 @@ -import fetch from "node-fetch"; -import FormData from "form-data"; - -const throwRequestFailedError = details => { - const error = new Error( - `Request failed while swapping the jwt token. ${details}` - ); - error.code = REQUEST_FAILED; - throw error; -}; - -const throwUnexpectedResponseError = details => { - const error = new Error( - `Unexpected response received while swapping the jwt token. ${details}` - ); - error.code = UNEXPECTED_RESPONSE_BODY; - throw error; -}; - -export default ({ imsUrl, clientId, clientSecret, jwtToken }) => { - - const form = new FormData(); - form.append('client_id', clientId); - form.append('client_secret', clientSecret); - form.append('jwt_token', jwtToken); - - const postOptions = { - method: 'POST', - body: form, - headers: form.getHeaders() - }; - - return fetch(`${imsUrl}/ims/exchange/jwt/`, postOptions) - .catch(e => throwRequestFailedError(e.message)) - .then(res => { - return res.json().then(data => { - return { - ok: res.ok, - json: data - }; - }); - }) - .then(({ ok, json }) => { - const { access_token, error, error_description } = json; - if (ok && access_token) { - return json; - } - - if (error && error_description) { - const swapError = new Error(error_description); - swapError.code = error; - throw swapError; - } else { - throwUnexpectedResponseError( - `The response body is as follows: ${JSON.stringify(json)}` - ); - } - }); -} diff --git a/test/functional/helpers/assurance/index.js b/test/functional/helpers/assurance/index.js new file mode 100644 index 000000000..fd6967e95 --- /dev/null +++ b/test/functional/helpers/assurance/index.js @@ -0,0 +1,34 @@ +/* +Copyright 2023 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 createAssuranceApi from "./createAssuranceApi"; +import getAuthHeaders from "./getAuthHeaders"; +import getCredentials from "./getCredentials"; +import AssuranceRequestHook from "./AssuranceRequestHook"; + +let sessionUuid; +let events; + +export const createAssuranceRequestHook = async () => { + if (!sessionUuid) { + const credentials = getCredentials(); + const authHeaders = await getAuthHeaders(credentials); + const api = createAssuranceApi(authHeaders); + const sessionName = `Tests - ${process.env.USER} - ${Math.random() + .toString(36) + .slice(2)}`; + // eslint-disable-next-line no-console + console.log(`Creating assurance session: ${sessionName}`); + sessionUuid = await api.createSession(sessionName); + events = api.fetchEvents(sessionUuid); + } + return new AssuranceRequestHook(sessionUuid, events); +}; diff --git a/test/functional/helpers/constants/configParts/configOverridesAlt.js b/test/functional/helpers/constants/configParts/configOverridesAlt.js index 478d1a469..4df0f5a92 100644 --- a/test/functional/helpers/constants/configParts/configOverridesAlt.js +++ b/test/functional/helpers/constants/configParts/configOverridesAlt.js @@ -1,3 +1,14 @@ +/* +Copyright 2023 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 default { com_adobe_experience_platform: { datasets: { diff --git a/test/functional/helpers/constants/configParts/configOverridesMain.js b/test/functional/helpers/constants/configParts/configOverridesMain.js index 03ec868ec..2c2e987bd 100644 --- a/test/functional/helpers/constants/configParts/configOverridesMain.js +++ b/test/functional/helpers/constants/configParts/configOverridesMain.js @@ -1,3 +1,14 @@ +/* +Copyright 2023 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 default { com_adobe_experience_platform: { datasets: { diff --git a/test/functional/specs/Personalization/C????.js b/test/functional/specs/Personalization/C????.js index b0f778d5e..795f10059 100644 --- a/test/functional/specs/Personalization/C????.js +++ b/test/functional/specs/Personalization/C????.js @@ -1,4 +1,15 @@ -import { t, ClientFunction } from "testcafe"; +/* +Copyright 2023 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 { t } from "testcafe"; import createNetworkLogger from "../../helpers/networkLogger"; import { responseStatus } from "../../helpers/assertions/index"; import createFixture from "../../helpers/createFixture"; @@ -7,15 +18,15 @@ import { orgMainConfigMain, debugEnabled } from "../../helpers/constants/configParts"; -import getResponseBody from "../../helpers/networkLogger/getResponseBody"; -import createResponse from "../../helpers/createResponse"; import createAlloyProxy from "../../helpers/createAlloyProxy"; +import { createAssuranceRequestHook } from "../../helpers/assurance"; const networkLogger = createNetworkLogger(); const config = compose(orgMainConfigMain, debugEnabled); -const PAGE_WIDE_SCOPE = "__view__"; + createFixture({ - title: "C???? hide and show containers work" + title: "C???? assurance example", + requestHooks: [networkLogger.edgeEndpointLogs] }); test.meta({ @@ -24,44 +35,31 @@ test.meta({ TEST_RUN: "Regression" }); -test("Test C???? hide and show containers work with prehiding style", async () => { +test("Test C???? assurance example", async () => { + const assuranceRequests = await createAssuranceRequestHook(); + await t.addRequestHooks(assuranceRequests); + const alloy = createAlloyProxy(); await alloy.configure(config); - const eventResult = await alloy.sendEvent({ renderDecisions: true }); + await alloy.sendEvent({ + renderDecisions: true, + decisionScopes: ["alloy-test-scope-1"] + }); await responseStatus(networkLogger.edgeEndpointLogs.requests, 200); - await t.expect(networkLogger.edgeEndpointLogs.requests.length).eql(2); - - const sendEventRequest = networkLogger.edgeEndpointLogs.requests[0]; - const requestBody = JSON.parse(sendEventRequest.request.body); + const mappingLog = await assuranceRequests.requests[0].find(log => { + const { vendor, payload: { name } = {} } = log; + return vendor === "com.adobe.analytics" && name === "analytics.mapping"; + }); await t - .expect(requestBody.events[0].query.personalization.decisionScopes) - .eql([PAGE_WIDE_SCOPE]); - - const personalizationSchemas = - requestBody.events[0].query.personalization.schemas; - - const result = [ - "https://ns.adobe.com/personalization/default-content-item", - "https://ns.adobe.com/personalization/dom-action", - "https://ns.adobe.com/personalization/html-content-item", - "https://ns.adobe.com/personalization/json-content-item", - "https://ns.adobe.com/personalization/redirect-item" - ].every(schema => personalizationSchemas.includes(schema)); - - await t.expect(result).eql(true); - - const response = JSON.parse( - getResponseBody(networkLogger.edgeEndpointLogs.requests[0]) - ); - const personalizationPayload = createResponse({ - content: response - }).getPayloadsByType("personalization:decisions"); - - await t.expect(personalizationPayload[0].scope).eql(PAGE_WIDE_SCOPE); - await t.expect(getDecisionContent()).eql("Here is an awesome target offer!"); + .expect( + mappingLog.payload.context.mappedQueryParams.unifiedjsqeonlylatest.g + ) + .eql("https://alloyio.com/functional-test/testPage.html"); - await t.expect(eventResult.decisions).eql([]); - await t.expect(eventResult.propositions[0].renderAttempted).eql(true); + // you can run this to see all the logs instead of the find above + // await new Promise(resolve => setTimeout(resolve, 5000)); + // await assuranceRequests.fetchMore(); + // assuranceRequests.requests[0].debug(); }); From 1a7aeaa0e440012ad82cc37e2cb767fb57eacd89 Mon Sep 17 00:00:00 2001 From: Jon Snyder Date: Fri, 28 Jul 2023 16:44:13 -0600 Subject: [PATCH 3/4] Remove unused dependency --- package-lock.json | 40 ---------------------------------------- package.json | 1 - 2 files changed, 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2db72999c..c77c0aa7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,6 @@ "testcafe-reporter-saucelabs": "^1.0.1", "url-exists-nodejs": "^0.1.0", "url-parse": "^1.4.7", - "whoami": "^0.0.3", "yargs": "^16.2.0" } }, @@ -16099,18 +16098,6 @@ "node": ">=8" } }, - "node_modules/shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha512-Ny0KN4dyT8ZSCE0frtcbAJGoM/HTArpyPkeli1/00aYfm0sbD/Gk/4x7N2DP9QKGpBsiQH7n6rpm1L79RtviEQ==", - "dev": true, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -18830,18 +18817,6 @@ "which": "bin/which" } }, - "node_modules/whoami": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/whoami/-/whoami-0.0.3.tgz", - "integrity": "sha512-ZXwrGI7u/TYWv53uSAbSpqGviNAy4SNiew6Z/VEDGtrEOh67vyCX80/4oS3hN+Pscu2mY4n+M4ygc3pt8qce3g==", - "dev": true, - "dependencies": { - "shelljs": "^0.3.0" - }, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -31532,12 +31507,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha512-Ny0KN4dyT8ZSCE0frtcbAJGoM/HTArpyPkeli1/00aYfm0sbD/Gk/4x7N2DP9QKGpBsiQH7n6rpm1L79RtviEQ==", - "dev": true - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -33706,15 +33675,6 @@ } } }, - "whoami": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/whoami/-/whoami-0.0.3.tgz", - "integrity": "sha512-ZXwrGI7u/TYWv53uSAbSpqGviNAy4SNiew6Z/VEDGtrEOh67vyCX80/4oS3hN+Pscu2mY4n+M4ygc3pt8qce3g==", - "dev": true, - "requires": { - "shelljs": "^0.3.0" - } - }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 13c1fc9ae..17b949bda 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,6 @@ "testcafe-reporter-saucelabs": "^1.0.1", "url-exists-nodejs": "^0.1.0", "url-parse": "^1.4.7", - "whoami": "^0.0.3", "yargs": "^16.2.0" } } From c1a4ad3a7d145504d42a850a95d3ebbe518d634c Mon Sep 17 00:00:00 2001 From: Jon Snyder Date: Wed, 7 Feb 2024 13:14:31 -0700 Subject: [PATCH 4/4] Add two mapping tests --- .../helpers/assurance/AssuranceRequestHook.js | 23 ++- .../helpers/assurance/createAssuranceApi.js | 24 +-- .../specs/Data Collector/C15958190.js | 140 ++++++++++++++++++ .../C????.js => Data Collector/C15958191.js} | 39 ++--- 4 files changed, 192 insertions(+), 34 deletions(-) create mode 100644 test/functional/specs/Data Collector/C15958190.js rename test/functional/specs/{Personalization/C????.js => Data Collector/C15958191.js} (67%) diff --git a/test/functional/helpers/assurance/AssuranceRequestHook.js b/test/functional/helpers/assurance/AssuranceRequestHook.js index bece977ed..4174c214b 100644 --- a/test/functional/helpers/assurance/AssuranceRequestHook.js +++ b/test/functional/helpers/assurance/AssuranceRequestHook.js @@ -12,7 +12,7 @@ governing permissions and limitations under the License. import { RequestHook, t } from "testcafe"; const ASSERTION_DELAY = 200; -const ASSERTION_TIMEOUT = 5000; +const ASSERTION_TIMEOUT = 10000; const delay = () => { return new Promise(resolve => setTimeout(resolve, ASSERTION_DELAY)); }; @@ -38,9 +38,11 @@ const createRequest = (requestLogs, fetchMore) => { // eslint-disable-next-line no-await-in-loop await delay(); } - t.expect().ok( - "Assurance logs did not contain an event matching the predicate after 5 seconds." - ); + await t + .expect() + .ok( + "Assurance logs did not contain an event matching the predicate after 10 seconds." + ); return undefined; }, debug() { @@ -79,9 +81,16 @@ export default class AssuranceRequestHook extends RequestHook { // eslint-disable-next-line no-await-in-loop while (await this.events.advance()) { const event = this.events.current(); - const { payload: { attributes: { requestId } = {} } = {} } = event; - if (requestId && this.eventsByRequestId[requestId]) { - this.eventsByRequestId[requestId].push(event); + const { + payload: { + attributes: { requestId } = {}, + header: { xactionId } = {} + } = {} + } = event; + + const id = requestId || xactionId; + if (id && this.eventsByRequestId[id]) { + this.eventsByRequestId[id].push(event); } } } diff --git a/test/functional/helpers/assurance/createAssuranceApi.js b/test/functional/helpers/assurance/createAssuranceApi.js index df3ba180d..3c7b5e03d 100644 --- a/test/functional/helpers/assurance/createAssuranceApi.js +++ b/test/functional/helpers/assurance/createAssuranceApi.js @@ -23,8 +23,8 @@ const CREATE_SESSION_QUERY = ` }`; const FETCH_EVENTS_QUERY = ` - query eventsQuery($sessionUuid: UUID!, $cursor: EventCursor) { - events(sessionUuid:$sessionUuid,first:1000,after:$cursor){ + query eventsQuery($sessionUuid: UUID!) { + events(sessionUuid:$sessionUuid,first:100) { uuid eventNumber clientId @@ -51,6 +51,9 @@ export default authHeaders => { body: JSON.stringify({ query, variables }) }); const json = await response.json(); + if (json.errors) { + throw new Error(json.errors.message); + } return json.data; }; @@ -68,7 +71,7 @@ export default authHeaders => { return uuid; }, fetchEvents: sessionUuid => { - let cursor = null; + const seenUuids = new Set(); let events = []; let i = 0; return { @@ -77,15 +80,16 @@ export default authHeaders => { }, advance: async () => { if (i >= events.length) { - if (events.length > 0) { - const { eventNumber, timestamp } = events[events.length - 1]; - cursor = { eventNumber, timestamp, sessionUuid }; - } const response = await makeGraphRequest(FETCH_EVENTS_QUERY, { - sessionUuid, - cursor + sessionUuid + }); + events = response.events.filter(e => { + if (seenUuids.has(e.uuid)) { + return false; + } + seenUuids.add(e.uuid); + return true; }); - events = response.events; i = 0; return events.length > 0; } diff --git a/test/functional/specs/Data Collector/C15958190.js b/test/functional/specs/Data Collector/C15958190.js new file mode 100644 index 000000000..4a38f33b0 --- /dev/null +++ b/test/functional/specs/Data Collector/C15958190.js @@ -0,0 +1,140 @@ +/* +Copyright 2023 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 { t } from "testcafe"; +import createNetworkLogger from "../../helpers/networkLogger"; +import { responseStatus } from "../../helpers/assertions/index"; +import createFixture from "../../helpers/createFixture"; +import { + compose, + orgMainConfigMain, + debugEnabled +} from "../../helpers/constants/configParts"; +import createAlloyProxy from "../../helpers/createAlloyProxy"; +import { createAssuranceRequestHook } from "../../helpers/assurance"; + +const networkLogger = createNetworkLogger(); +const config = compose(orgMainConfigMain, debugEnabled); + +createFixture({ + title: "C15958190 Analytics maps long names from data", + requestHooks: [networkLogger.edgeEndpointLogs] +}); + +test.meta({ + ID: "C15958190", + SEVERITY: "P0", + TEST_RUN: "Regression" +}); + +test("Test C15958190 Analytics maps long names from data", async () => { + const assuranceRequests = await createAssuranceRequestHook(); + await t.addRequestHooks(assuranceRequests); + + const alloy = createAlloyProxy(); + await alloy.configure(config); + await alloy.sendEvent({ + xdm: {}, + data: { + __adobe: { + analytics: { + pageName: "C15958190-pagename", + pageURL: "C15958190-pageurl", + referrer: "C15958190-referrer", + contextData: { + "contextData-key": "C15958190-contextData-value" + }, + currencyCode: "C15958190-currencyCode", + purchaseID: "C15958190-purchaseID", + channel: "C15958190-channel", + server: "C15958190-server", + pageType: "C15958190-pageType", + transactionID: "C15958190-transactionID", + campaign: "C15958190-campaign", + zip: "C15958190-zip", + events: "C15958190-events", + events2: "C15958190-events2", + products: "C15958190-products", + linkURL: "C15958190-linkURL", + linkName: "C15958190-linkName", + linkType: "e", + eVar1: "C15958190-eVar1", + prop1: "C15958190-prop1", + list1: "C15958190-list1", + pe: "C15958190-pe", + pev1: "C15958190-pev1", + pev2: "C15958190-pev2", + pev3: "C15958190-pev3", + latitude: "C15958190-latitude", + longitude: "C15958190-longitude", + resolution: "C15958190-resolution", + colorDepth: "C15958190-colorDepth", + javascriptVersion: "C15958190-javascriptVersion", + javaEnabled: "C15958190-javaEnabled", + cookiesEnabled: "C15958190-cookiesEnabled", + browserWidth: "C15958190-browserWidth", + browserHeight: "C15958190-browserHeight", + connectionType: "C15958190-connectionType" + } + } + } + }); + + await responseStatus(networkLogger.edgeEndpointLogs.requests, 200); + + const mappingLog = await assuranceRequests.requests[0].find(log => { + const { vendor, payload: { name } = {} } = log; + return vendor === "com.adobe.analytics" && name === "analytics.mapping"; + }); + + const mappings = + mappingLog.payload.context.mappedQueryParams.unifiedjsqeonlylatest; + console.log(JSON.stringify(mappings, null, 2)); + const expectedMappings = { + gn: "C15958190-pageName", + g: "C15958190-pageURL", + r: "C15958190-referrer", + "c.contextData-key": "C15958190-contextData-value", + cc: "C15958190-currencyCode", + purchaseID: "C15958190-purchaseID", + ch: "C15958190-channel", + server: "C15958190-server", + gt: "C15958190-pageType", + xact: "C15958190-transactionID", + v0: "C15958190-campaign", + zip: "C15958190-zip", + events: "C15958190-events,C15958190-events2", + products: "C15958190-products", + pev1: "C15958190-linkURL", + pev2: "C15958190-linkName", + pe: "lnk_e", + v1: "C15958190-eVar1", + c1: "C15958190-prop1", + list1: "C15958190-list1", + pev3: "C15958190-pev3", + lat: "C15958190-latitude", + lon: "C15958190-longitude", + s: "C15958190-resolution", + c: "C15958190-colorDepth", + j: "C15958190-javascriptVersion", + v: "C15958190-javaEnabled", + k: "C15958190-cookiesEnabled", + bw: "C15958190-browserWidth", + bh: "C15958190-browserHeight", + ct: "C15958190-connectionType" + }; + + // eslint-disable-next-line no-restricted-syntax + for (const key of Object.keys(expectedMappings)) { + // eslint-disable-next-line no-await-in-loop + await t.expect(mappings[key]).eql(expectedMappings[key]); + } +}); diff --git a/test/functional/specs/Personalization/C????.js b/test/functional/specs/Data Collector/C15958191.js similarity index 67% rename from test/functional/specs/Personalization/C????.js rename to test/functional/specs/Data Collector/C15958191.js index 795f10059..66be8529c 100644 --- a/test/functional/specs/Personalization/C????.js +++ b/test/functional/specs/Data Collector/C15958191.js @@ -25,41 +25,46 @@ const networkLogger = createNetworkLogger(); const config = compose(orgMainConfigMain, debugEnabled); createFixture({ - title: "C???? assurance example", + title: "C15958191 Data prep maps products to XDM", requestHooks: [networkLogger.edgeEndpointLogs] }); test.meta({ - ID: "C????", + ID: "C15958191", SEVERITY: "P0", TEST_RUN: "Regression" }); -test("Test C???? assurance example", async () => { +test("Test C15958191 Data prep maps products to XDM", async () => { const assuranceRequests = await createAssuranceRequestHook(); await t.addRequestHooks(assuranceRequests); const alloy = createAlloyProxy(); await alloy.configure(config); await alloy.sendEvent({ - renderDecisions: true, - decisionScopes: ["alloy-test-scope-1"] + xdm: {}, + data: { + __adobe: { + analytics: { + eVar1: "eVar1Value", + prop1: "prop1Value", + products: "" + } + } + } }); await responseStatus(networkLogger.edgeEndpointLogs.requests, 200); - const mappingLog = await assuranceRequests.requests[0].find(log => { - const { vendor, payload: { name } = {} } = log; - return vendor === "com.adobe.analytics" && name === "analytics.mapping"; + const pipelineLog = await assuranceRequests.requests[0].find(log => { + const { vendor, payload: { header: { msgType } = {} } = {} } = log; + return ( + vendor === "com.adobe.streaming.validation" && + msgType === "xdmEntityCreate" + ); }); - await t - .expect( - mappingLog.payload.context.mappedQueryParams.unifiedjsqeonlylatest.g - ) - .eql("https://alloyio.com/functional-test/testPage.html"); + const mappedXdm = pipelineLog.payload.body.xdmEntity; + console.log(JSON.stringify(mappedXdm, null, 2)); - // you can run this to see all the logs instead of the find above - // await new Promise(resolve => setTimeout(resolve, 5000)); - // await assuranceRequests.fetchMore(); - // assuranceRequests.requests[0].debug(); + // TODO add assertions here that the mapping happened correctly. });