diff --git a/.eslintrc.json b/.eslintrc.json index d2928e50fdc..7258c5c5366 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,9 +9,9 @@ "airbnb-base", "airbnb-typescript/base", "plugin:sonarjs/recommended", - "prettier", "plugin:json/recommended", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "prettier" ], "plugins": ["@typescript-eslint", "unicorn"], "globals": {}, diff --git a/.github/workflows/build-push-docker-image.yml b/.github/workflows/build-push-docker-image.yml index 9f6709a0405..7ddae0a3aed 100644 --- a/.github/workflows/build-push-docker-image.yml +++ b/.github/workflows/build-push-docker-image.yml @@ -155,7 +155,7 @@ jobs: if: ${{ inputs.build_type == 'dt' }} run: | docker buildx imagetools create -t rudderstack/rudder-transformer:latest ${{ inputs.push_tags }}-amd64 ${{ inputs.push_tags }}-arm64 - + - name: Create latest ut multi-arch manifest # To be triggered only for release/hotfix PR merges coming from `prepare-for-prod-ut-deploy.yaml` if: ${{ inputs.build_type == 'ut' }} diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 00000000000..a8ff39eee0d --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,36 @@ +name: Commitlint + +on: [push] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4.0.1 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Print versions + run: | + git --version + node --version + npm --version + npx commitlint --version + + # Run the commitlint action, considering its own dependencies and yours as well 🚀 + # `github.workspace` is the path to your repository. + - uses: wagoid/commitlint-github-action@v5 + env: + NODE_PATH: ${{ github.workspace }}/node_modules + with: + commitDepth: 1 diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml index e41ca0a723a..3d457df9ff8 100644 --- a/.github/workflows/component-test-report.yml +++ b/.github/workflows/component-test-report.yml @@ -43,7 +43,7 @@ jobs: - name: Uplaod Report to S3 run: | aws s3 cp ./test_reports/ s3://test-integrations-dev/integrations-test-reports/rudder-transformer/${{ github.event.number }}/ --recursive - + - name: Add Test Report Link as Comment on PR uses: actions/github-script@v7 with: @@ -75,5 +75,3 @@ jobs: issue_number: prNumber, body: commentBody }); - - \ No newline at end of file diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index 4e8f29cffa7..1bd7e276f43 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -68,7 +68,6 @@ jobs: secrets: DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} - create-pull-request: name: Update Helm Charts For Staging and Create Pull Request runs-on: ubuntu-latest @@ -102,7 +101,7 @@ jobs: cd rudder-devops BRANCH_NAME="shared-transformer-$TAG_NAME" echo $BRANCH_NAME - if [ `git ls-remote --heads origin $BRANCH_NAME 2>/dev/null` ] + if [ -n "$(git ls-remote --heads origin $BRANCH_NAME 2>/dev/null)" ] then echo "Staging deployment branch already exists!" else diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 00000000000..4caef8dd918 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,36 @@ +name: Verify + +on: + pull_request: + +jobs: + formatting-lint: + name: Check for formatting & lint errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + # Make sure the actual branch is checked out when running on pull requests + ref: ${{ github.head_ref }} + + - name: Setup Node + uses: actions/setup-node@v3.7.0 + with: + node-version-file: .nvmrc + cache: 'npm' + + - name: Install Dependencies + run: npm ci + + - name: Run Lint Checks + run: | + npm run lint + + - run: git diff --exit-code + + - name: Error message + if: ${{ failure() }} + run: | + echo 'Eslint check is failing Ensure you have run `npm run lint` and committed the files locally.' diff --git a/.prettierignore b/.prettierignore index 93eb370b0de..99747b29bbb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ test/**/*.js !test/**/data.js src/util/lodash-es-core.js src/util/url-search-params.min.js +dist diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..13c49c08f15 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "prettier.requireConfig": true, + "prettier.configPath": ".prettierrc", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "never" + }, + "eslint.validate": ["javascript", "typescript"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index a043cfb6e3b..c1438510912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.58.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.1...v1.58.0) (2024-03-04) + + +### Features + +* add support for interaction events in sfmc ([#3109](https://github.com/rudderlabs/rudder-transformer/issues/3109)) ([0486049](https://github.com/rudderlabs/rudder-transformer/commit/0486049ba2ad96b50d8f29e96b46b96a8a5c9f76)) +* add support of custom page/screen event name in mixpanel ([#3098](https://github.com/rudderlabs/rudder-transformer/issues/3098)) ([0eb2393](https://github.com/rudderlabs/rudder-transformer/commit/0eb2393939fba2452ef7f07a1d149d87f18290c3)) +* consent mode support for google adwords remarketing list ([#3143](https://github.com/rudderlabs/rudder-transformer/issues/3143)) ([7532c90](https://github.com/rudderlabs/rudder-transformer/commit/7532c90d7e1feac00f12961c56da18757010f44a)) +* **facebook:** update content_type mapping logic for fb pixel and fb conversions ([#3113](https://github.com/rudderlabs/rudder-transformer/issues/3113)) ([aea417c](https://github.com/rudderlabs/rudder-transformer/commit/aea417cd2691547399010c034cadbc5db6b0c6ee)) +* klaviyo profile mapping ([#3105](https://github.com/rudderlabs/rudder-transformer/issues/3105)) ([2761786](https://github.com/rudderlabs/rudder-transformer/commit/2761786ff3fc99ed6d4d3b7a6c2400226b1cfb12)) +* onboard new destination ninetailed ([#3106](https://github.com/rudderlabs/rudder-transformer/issues/3106)) ([0e2588e](https://github.com/rudderlabs/rudder-transformer/commit/0e2588ecd87f3b2c6877a099aa1cbf2d5325966c)) + + +### Bug Fixes + +* add error handling for tiktok ads ([#3144](https://github.com/rudderlabs/rudder-transformer/issues/3144)) ([e93e47f](https://github.com/rudderlabs/rudder-transformer/commit/e93e47f33e098104fb532916932fe38bbfeaa4a1)) +* **algolia:** added check for objectIds or filters to be non empty ([#3126](https://github.com/rudderlabs/rudder-transformer/issues/3126)) ([d619c97](https://github.com/rudderlabs/rudder-transformer/commit/d619c9769cd270cb2d16dad0865683ff4beb2d19)) +* clevertap remove stringification of array object properties ([#3048](https://github.com/rudderlabs/rudder-transformer/issues/3048)) ([69e43b6](https://github.com/rudderlabs/rudder-transformer/commit/69e43b6ffadeaec87b7440da34a341890ceba252)) +* convert to string from null in hs ([#3136](https://github.com/rudderlabs/rudder-transformer/issues/3136)) ([75e9f46](https://github.com/rudderlabs/rudder-transformer/commit/75e9f462b0ff9b9a8abab3c78dc7d147926e9e5e)) +* event fix and added utility ([#3142](https://github.com/rudderlabs/rudder-transformer/issues/3142)) ([9b705b7](https://github.com/rudderlabs/rudder-transformer/commit/9b705b71a9d3a595ea0fbf532602c3941b0a18db)) +* metadata structure correction ([#3119](https://github.com/rudderlabs/rudder-transformer/issues/3119)) ([8351b5c](https://github.com/rudderlabs/rudder-transformer/commit/8351b5cbbf81bbc14b2f884feaae4ad3ca59a39a)) +* one_signal: Encode external_id in endpoint ([#3140](https://github.com/rudderlabs/rudder-transformer/issues/3140)) ([8a20886](https://github.com/rudderlabs/rudder-transformer/commit/8a2088608d6da4b35bbb506db2fc3df1e4d41f3b)) +* rakuten: sync property mapping sourcekeys to rudderstack standard spec ([#3129](https://github.com/rudderlabs/rudder-transformer/issues/3129)) ([2ebff95](https://github.com/rudderlabs/rudder-transformer/commit/2ebff956ff2aa74b008a8de832a31d8774d2d47e)) +* reddit revenue mapping for floating point values ([#3118](https://github.com/rudderlabs/rudder-transformer/issues/3118)) ([41f4078](https://github.com/rudderlabs/rudder-transformer/commit/41f4078011ef54334bb9ecc11a7b2ccc8831a4aa)) +* version deprecation failure false positive ([#3104](https://github.com/rudderlabs/rudder-transformer/issues/3104)) ([657b780](https://github.com/rudderlabs/rudder-transformer/commit/657b7805eb01da25a007d978198d5debf03917fd)) + ### [1.57.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.0...v1.57.1) (2024-03-04) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1cef57af73e..bdd76d916cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,6 @@ See the project's [README](README.md) for further information about working in t - Include instructions on how to test your changes. 3. Your branch may be merged once all configured checks pass, including: - A review from appropriate maintainers -4. Along with the PR in transformer raise a PR against [config-generator][config-generator] with the configurations. ## Committing diff --git a/github-release.config.js b/github-release.config.js new file mode 100644 index 00000000000..df269d8a02b --- /dev/null +++ b/github-release.config.js @@ -0,0 +1,5 @@ +module.exports = { + gitRawCommitsOpts: { + merges: null, + }, +}; diff --git a/package-lock.json b/package-lock.json index 13be0a6ceb5..700207e0210 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "license": "ISC", "dependencies": { "@amplitude/ua-parser-js": "0.7.24", @@ -19,8 +19,9 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", + "@rudderstack/integrations-lib": "^0.2.4", "@rudderstack/workflow-engine": "^0.7.2", + "@shopify/jest-koa-mocks": "^5.1.1", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -91,9 +92,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -106,7 +108,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", @@ -154,7 +156,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3235,7 +3236,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -3250,7 +3250,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -3259,7 +3258,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -3282,7 +3280,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3297,14 +3294,12 @@ "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3334,7 +3329,6 @@ "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", @@ -3348,7 +3342,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "engines": { "node": ">=12.22" }, @@ -3360,8 +3353,7 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==" }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", @@ -4348,7 +4340,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4361,7 +4352,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -4370,7 +4360,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4389,6 +4378,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4468,14 +4469,15 @@ } }, "node_modules/@rudderstack/integrations-lib": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.2.tgz", - "integrity": "sha512-LilQsYcYh/4XXHNmYHM164fCbO5U3uvlw7k1wiCvFOR0MS1RhFXD9sPgCYpri683Jy3gqq1FrKN1EFj7oWAMjw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.2.4.tgz", + "integrity": "sha512-32Zose9aOPNWd4EyUNuS5YY+Vq4LYMuDcabJ+s3t1ZfHHMfISlDNF02b60MWgOrU8PARYC+siDs5wgA6xfZpzQ==", "dependencies": { - "@rudderstack/workflow-engine": "^0.5.7", "axios": "^1.4.0", "axios-mock-adapter": "^1.22.0", "crypto": "^1.0.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "get-value": "^3.0.1", "handlebars": "^4.7.8", "lodash": "^4.17.21", @@ -4487,54 +4489,6 @@ "winston": "^3.11.0" } }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-4.0.0.tgz", - "integrity": "sha512-MHGJyjE7TX9aaqXj7zk2ppnFUOhaDs5sP+HtNS0evOxn72c+5njUmyJmpGd7TfyoDznZlHMmdo/xGUdu2NIjNQ==", - "dependencies": { - "@aws-crypto/util": "^4.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-4.0.0.tgz", - "integrity": "sha512-2EnmPy2gsFZ6m8bwUQN4jq+IyXV3quHAcwPOS6ZA3k+geujiqI8aRokO2kFJe+idJ/P3v4qWI186rVMo0+zLDQ==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/json-template-engine": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.5.5.tgz", - "integrity": "sha512-p3HdTqgZiJjjZmjaHN2paa1e87ifGE5UjkA4zdvge4bBzJbKKMQNWqRg6I96SwoA+hsxNkW/f9R83SPLU9t7LA==" - }, - "node_modules/@rudderstack/integrations-lib/node_modules/@rudderstack/workflow-engine": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.5.8.tgz", - "integrity": "sha512-H1aCowYqTnOoqJtL9cGDhdhoGNl+KzqmVbSjFmE7n75onZaIMs87+HQyW08jYxS9l1Uo4TL8SAvzFICqFqkBbw==", - "dependencies": { - "@aws-crypto/sha256-js": "^4.0.0", - "@rudderstack/json-template-engine": "^0.5.5", - "js-yaml": "^4.1.0", - "jsonata": "^2.0.3", - "lodash": "^4.17.21", - "object-sizeof": "^2.6.3" - } - }, "node_modules/@rudderstack/json-template-engine": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.5.tgz", @@ -4576,6 +4530,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@shopify/jest-koa-mocks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@shopify/jest-koa-mocks/-/jest-koa-mocks-5.1.1.tgz", + "integrity": "sha512-H1dRznXIK03ph1l/VDBQ5ef+A9kkEn3ikNfk70zwm9auW15MfHfY9gekE99VecxUSekws7sbFte0i8ltWCS4/g==", + "dependencies": { + "koa": "^2.13.4", + "node-mocks-http": "^1.11.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -5466,14 +5432,12 @@ "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, "node_modules/@types/keygrip": { "version": "1.0.6", @@ -5555,8 +5519,7 @@ "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==" }, "node_modules/@types/send": { "version": "0.17.4", @@ -5607,7 +5570,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -5641,7 +5603,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5668,7 +5629,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" @@ -5685,7 +5645,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", - "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", @@ -5712,7 +5671,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5725,7 +5683,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", @@ -5752,7 +5709,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -5778,7 +5734,6 @@ "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, "dependencies": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" @@ -5794,8 +5749,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/abbrev": { "version": "1.1.1", @@ -5818,7 +5772,6 @@ "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5830,7 +5783,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -6030,7 +5982,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -6058,7 +6009,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6077,7 +6027,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { "node": ">=8" } @@ -6086,7 +6035,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6105,7 +6053,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6123,7 +6070,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -6141,7 +6087,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", @@ -6205,7 +6150,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6620,7 +6564,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -6827,7 +6770,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -7559,8 +7501,7 @@ "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -8568,7 +8509,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -8741,8 +8681,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -8789,7 +8728,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -9162,7 +9100,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -9174,7 +9111,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9404,7 +9340,6 @@ "version": "1.22.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", @@ -9457,7 +9392,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "has-tostringtag": "^1.0.0", @@ -9471,7 +9405,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" } @@ -9480,7 +9413,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -9564,7 +9496,6 @@ "version": "8.56.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9619,7 +9550,6 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", @@ -9638,7 +9568,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9647,7 +9576,6 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", - "dev": true, "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, @@ -9674,7 +9602,6 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -9685,7 +9612,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9694,7 +9620,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, "dependencies": { "debug": "^3.2.7" }, @@ -9711,7 +9636,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9720,7 +9644,6 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -9751,7 +9674,6 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, "dependencies": { "ms": "^2.1.1" } @@ -9760,7 +9682,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, "dependencies": { "esutils": "^2.0.2" }, @@ -9772,7 +9693,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -9790,6 +9710,36 @@ "node": ">=12.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-sonarjs": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.19.0.tgz", @@ -9941,7 +9891,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9954,7 +9903,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9966,7 +9914,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9982,7 +9929,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -9997,7 +9943,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10013,7 +9958,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -10024,14 +9968,12 @@ "node_modules/eslint/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -10043,7 +9985,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -10059,7 +10000,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10068,7 +10008,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -10076,14 +10015,12 @@ "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10095,7 +10032,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -10125,7 +10061,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10137,7 +10072,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10146,7 +10080,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -10158,7 +10091,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10167,7 +10099,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -10176,7 +10107,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10283,11 +10213,16 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10303,7 +10238,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -10314,14 +10248,12 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-printf": { "version": "1.6.9", @@ -10366,7 +10298,6 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -10404,7 +10335,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -10467,7 +10397,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -10495,7 +10424,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10542,7 +10470,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -10555,8 +10482,7 @@ "node_modules/flatted": { "version": "3.2.9", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/flatten": { "version": "1.0.3", @@ -10593,7 +10519,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -10745,7 +10670,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -10763,7 +10687,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11086,7 +11009,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -11342,7 +11264,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -11432,7 +11353,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -11447,7 +11367,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -11462,7 +11381,6 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -11559,8 +11477,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/growly": { "version": "1.3.0", @@ -11609,7 +11526,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11947,7 +11863,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, "engines": { "node": ">= 4" } @@ -11956,7 +11871,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -11972,7 +11886,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -12000,7 +11913,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -12162,7 +12074,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2", "hasown": "^2.0.0", @@ -12228,7 +12139,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -12247,7 +12157,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -12259,7 +12168,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12312,7 +12220,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12335,7 +12242,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12365,7 +12271,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12421,7 +12326,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12453,7 +12357,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12465,7 +12368,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -12474,7 +12376,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12507,7 +12408,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -12544,7 +12444,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -12584,7 +12483,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12607,7 +12505,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -12622,7 +12519,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -12649,7 +12545,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -12700,7 +12595,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -12743,8 +12637,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -14553,8 +14446,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-diff": { "version": "1.0.6", @@ -14599,8 +14491,7 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -14694,7 +14585,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -14944,7 +14834,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -15294,7 +15183,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -15371,8 +15259,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -16139,6 +16026,14 @@ "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==", "dev": true }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -16149,7 +16044,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -16166,7 +16060,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -16653,14 +16546,12 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/natural-compare-lite": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -16721,6 +16612,47 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, + "node_modules/node-mocks-http": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.14.1.tgz", + "integrity": "sha512-mfXuCGonz0A7uG1FEjnypjm34xegeN5+HI6xeGhYKecfgaZhjsmYoLE9LEFmT+53G1n8IuagPZmVnEL/xNsFaA==", + "dependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.10.6", + "accepts": "^1.3.7", + "content-disposition": "^0.5.3", + "depd": "^1.1.0", + "fresh": "^0.5.2", + "merge-descriptors": "^1.0.1", + "methods": "^1.1.2", + "mime": "^1.3.4", + "parseurl": "^1.3.3", + "range-parser": "^1.2.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/node-mocks-http/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-mocks-http/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/node-notifier": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", @@ -16883,7 +16815,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -16923,7 +16854,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.5", "define-properties": "^1.2.1", @@ -16941,7 +16871,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16955,7 +16884,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16972,7 +16900,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -16984,7 +16911,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -17055,7 +16981,6 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -17215,7 +17140,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -17277,7 +17201,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -17359,7 +17282,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -17403,7 +17325,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -17418,7 +17339,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -17913,7 +17833,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -17928,20 +17847,32 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -18136,7 +18067,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -18173,6 +18103,14 @@ "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", "dev": true }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -18403,7 +18341,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -18604,7 +18541,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -18702,7 +18638,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -18734,7 +18669,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -18751,8 +18685,7 @@ "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/safe-buffer": { "version": "5.2.1", @@ -18786,7 +18719,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -18901,7 +18833,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", @@ -18946,7 +18877,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -18958,7 +18888,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -19010,7 +18939,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -19696,7 +19624,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19713,7 +19640,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19727,7 +19653,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -19816,7 +19741,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -19930,6 +19854,22 @@ "node": ">=10" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -20027,8 +19967,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/through": { "version": "2.3.8", @@ -20093,7 +20032,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -20251,7 +20189,6 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -20263,7 +20200,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -20275,7 +20211,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "engines": { "node": ">=4" } @@ -20297,7 +20232,6 @@ "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, "dependencies": { "tslib": "^1.8.1" }, @@ -20311,14 +20245,12 @@ "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -20339,7 +20271,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "engines": { "node": ">=10" }, @@ -20363,7 +20294,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1", @@ -20377,7 +20307,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20395,7 +20324,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -20414,7 +20342,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "for-each": "^0.3.3", @@ -20434,7 +20361,6 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20481,7 +20407,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -20749,7 +20674,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -20764,7 +20688,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -20786,7 +20709,6 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.4", diff --git a/package.json b/package.json index dccf41a2615..070510029ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-transformer", - "version": "1.57.1", + "version": "1.58.0", "description": "", "homepage": "https://github.com/rudderlabs/rudder-transformer#readme", "bugs": { @@ -19,9 +19,9 @@ "setup": "npm ci", "setup:swagger": "swagger-cli bundle swagger/api.yaml --outfile dist/swagger.json --type json", "format": "prettier --write .", - "lint": "eslint . || exit 0", "lint:fix": "eslint . --fix", "lint:fix:json": "eslint --ext .json --fix .", + "lint": "npm run format && npm run lint:fix", "check:merge": "npm run verify || exit 1; codecov", "start": "cd dist;node ./src/index.js;cd ..", "build:start": "npm run build && npm run start", @@ -49,7 +49,7 @@ "commit-msg": "commitlint --edit", "prepare": "node ./scripts/skipPrepareScript.js || husky install", "release": "npx standard-version", - "release:github": "DEBUG=conventional-github-releaser npx conventional-github-releaser -p angular -v", + "release:github": "DEBUG=conventional-github-releaser npx conventional-github-releaser -p angular --config github-release.config.js", "clean:node": "modclean", "check:lint": "eslint . -f json -o reports/eslint.json || exit 0" }, @@ -64,8 +64,9 @@ "@koa/router": "^12.0.0", "@ndhoule/extend": "^2.0.0", "@pyroscope/nodejs": "^0.2.6", - "@rudderstack/integrations-lib": "^0.2.2", + "@rudderstack/integrations-lib": "^0.2.4", "@rudderstack/workflow-engine": "^0.7.2", + "@shopify/jest-koa-mocks": "^5.1.1", "ajv": "^8.12.0", "ajv-draft-04": "^1.0.0", "ajv-formats": "^2.1.1", @@ -136,9 +137,10 @@ "eslint": "^8.40.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-prettier": "^8.10.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.19.0", "eslint-plugin-unicorn": "^46.0.1", "glob": "^10.3.3", @@ -151,7 +153,7 @@ "madge": "^6.1.0", "mocked-env": "^1.3.5", "node-notifier": "^10.0.1", - "prettier": "^2.8.8", + "prettier": "^3.2.4", "semver": "^7.5.3", "standard-version": "^9.5.0", "supertest": "^6.3.3", diff --git a/src/adapters/network.js b/src/adapters/network.js index b0bd14374eb..0720638d12d 100644 --- a/src/adapters/network.js +++ b/src/adapters/network.js @@ -49,11 +49,15 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { const destType = statTags.destType ? statTags.destType : ''; const feature = statTags.feature ? statTags.feature : ''; const endpointPath = statTags.endpointPath ? statTags.endpointPath : ''; + const requestMethod = statTags.requestMethod ? statTags.requestMethod : ''; + const module = statTags.module ? statTags.module : ''; const statusCode = clientResponse.success ? clientResponse.response.status : ''; stats.timing('outgoing_request_latency', startTime, { feature, destType, endpointPath, + requestMethod, + module, }); stats.counter('outgoing_request_count', 1, { feature, @@ -61,6 +65,8 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => { endpointPath, success: clientResponse.success, statusCode, + requestMethod, + module, }); }; diff --git a/src/adapters/networkHandlerFactory.js b/src/adapters/networkHandlerFactory.js index e8c3748d15a..de80809a041 100644 --- a/src/adapters/networkHandlerFactory.js +++ b/src/adapters/networkHandlerFactory.js @@ -27,8 +27,9 @@ SUPPORTED_VERSIONS.forEach((version) => { // }, // generic: GenericNetworkHandler, // } - handlers[version][dest] = - require(`../${version}/destinations/${dest}/networkHandler`).networkHandler; + handlers[version][dest] = require( + `../${version}/destinations/${dest}/networkHandler`, + ).networkHandler; } catch { // Do nothing as exception indicates // network handler is not defined for that destination diff --git a/src/cdk/v2/destinations/algolia/procWorkflow.yaml b/src/cdk/v2/destinations/algolia/procWorkflow.yaml index b9ce7ef7fd4..f9ac8e3ae6e 100644 --- a/src/cdk/v2/destinations/algolia/procWorkflow.yaml +++ b/src/cdk/v2/destinations/algolia/procWorkflow.yaml @@ -61,7 +61,7 @@ steps: const filters = $.context.payload.filters; const objectIDs = $.context.payload.objectIDs; $.assert(!(filters && objectIDs), "event can't have both objectIds and filters at the same time."); - $.assert(filters || objectIDs, "Either filters or objectIds is required."); + $.assert(filters.length || objectIDs.length, "Either filters or objectIds is required and must be non empty."); - name: validatePayloadForClickEvent condition: $.context.payload.eventType === "click" diff --git a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml index 744c05a9a6d..3292d66c690 100644 --- a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml @@ -50,4 +50,4 @@ steps: const response = $.defaultRequestConfig(); response.body.JSON = payload; response - ) \ No newline at end of file + ) diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml index 378659fa2ac..480bced699b 100644 --- a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml +++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml @@ -54,7 +54,7 @@ steps: $.verifyPayload(newPayload, ^.message); $.removeUndefinedNullValuesAndEmptyObjectArray(newPayload) )[]; - + - name: buildResponse template: | $.context.payloads.( diff --git a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml index 50ac2a81630..1a54e8688ca 100644 --- a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml +++ b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml @@ -5,7 +5,7 @@ bindings: exportAll: true - name: removeUndefinedAndNullValues path: ../../../../v0/util - + steps: - name: validateInput template: | @@ -28,7 +28,7 @@ steps: $.context.payload.uid = .message.userId; $.context.payload.email = .message.context.traits.email; $.context.payload.display_name = .message.context.traits.name; - + - name: trackPayload condition: $.context.messageType == "track" template: | @@ -42,9 +42,9 @@ steps: condition: $.context.messageType == "track" template: | $.assert(.message.event, "event is required for track call") - + - name: mapContextFieldsForTrack - condition: $.context.messageType == "track" + condition: $.context.messageType == "track" template: | $.context.payload.context.browser = { "url": .message.context.page.url, @@ -67,7 +67,7 @@ steps: "region": .message.properties.region, "country": .message.properties.country, }; - + - name: mapIdsForTrack condition: $.context.messageType == "track" template: | @@ -99,6 +99,3 @@ steps: "params": {}, "files": {} }) - - - diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml index fe8697bc31a..a53a0ca8f57 100644 --- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml @@ -13,7 +13,6 @@ bindings: path: ../../../../adapters/network - name: processAxiosResponse path: ../../../../adapters/utils/networkUtils - steps: - name: checkIfProcessed diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml index 341e5552c83..fc5d474d606 100644 --- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/heap/procWorkflow.yaml b/src/cdk/v2/destinations/heap/procWorkflow.yaml index 0191b75d188..8326a61a795 100644 --- a/src/cdk/v2/destinations/heap/procWorkflow.yaml +++ b/src/cdk/v2/destinations/heap/procWorkflow.yaml @@ -58,5 +58,3 @@ steps: "Content-Type": "application/json" }; response - - \ No newline at end of file diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml index 4b2ca1869e0..0a8842d5e77 100644 --- a/src/cdk/v2/destinations/intercom/procWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml @@ -71,7 +71,7 @@ steps: payload; - name: identifyPayloadForLatestVersion - condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" template: | const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForLatestVersion; payload.name = $.getName(.message); @@ -187,7 +187,7 @@ steps: template: | $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies"; - name: prepareFinalPayload - template: + template: | $.context.requestMethod = 'POST'; $.removeUndefinedAndNullValues($.context.payload); diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml index 3ed1046959b..edb7267b84c 100644 --- a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml @@ -30,4 +30,4 @@ steps: )[] - name: finalPayload template: | - [...$.outputs.successfulEvents, ...$.outputs.failedEvents] \ No newline at end of file + [...$.outputs.successfulEvents, ...$.outputs.failedEvents] diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js index 0f18029f198..ba3063c9f9d 100644 --- a/src/cdk/v2/destinations/intercom/utils.js +++ b/src/cdk/v2/destinations/intercom/utils.js @@ -246,11 +246,20 @@ const searchContact = async (message, destination) => { const headers = getHeaders(destination); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${SEARCH_CONTACT_ENDPOINT}`; - const response = await httpPOST(endpoint, data, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + data, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/contacts/search', + requestMethod: 'POST', + module: 'router', + }, + ); const processedUserResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedUserResponse.status)) { return processedUserResponse.response?.data.length > 0 @@ -280,11 +289,20 @@ const createOrUpdateCompany = async (payload, destination) => { const finalPayload = JSON.stringify(removeUndefinedAndNullValues(payload)); const baseEndPoint = getBaseEndpoint(destination); const endpoint = `${baseEndPoint}/${CREATE_OR_UPDATE_COMPANY_ENDPOINT}`; - const response = await httpPOST(endpoint, finalPayload, { - headers, - destType: 'intercom', - feature: 'transformation', - }); + const response = await httpPOST( + endpoint, + finalPayload, + { + headers, + }, + { + destType: 'intercom', + feature: 'transformation', + endpointPath: '/companies', + requestMethod: 'POST', + module: 'router', + }, + ); const processedResponse = processAxiosResponse(response); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/cdk/v2/destinations/kochava/procWorkflow.yaml b/src/cdk/v2/destinations/kochava/procWorkflow.yaml index 557b1a0b630..3e73ee15206 100644 --- a/src/cdk/v2/destinations/kochava/procWorkflow.yaml +++ b/src/cdk/v2/destinations/kochava/procWorkflow.yaml @@ -10,7 +10,6 @@ bindings: - path: ../../bindings/jsontemplate - path: ./config.js - steps: - name: validateInput template: | diff --git a/src/cdk/v2/destinations/lytics/procWorkflow.yaml b/src/cdk/v2/destinations/lytics/procWorkflow.yaml index 1d6177fe09e..26221462213 100644 --- a/src/cdk/v2/destinations/lytics/procWorkflow.yaml +++ b/src/cdk/v2/destinations/lytics/procWorkflow.yaml @@ -45,7 +45,7 @@ steps: $.context.payload._e = .message.event; - name: pageOrScreenPayload condition: $.context.messageType === {{$.EventType.PAGE}} || - $.context.messageType === {{$.EventType.SCREEN}} + $.context.messageType === {{$.EventType.SCREEN}} template: | $.context.payload.event = .message.name - name: cleanPaylod @@ -66,4 +66,3 @@ steps: "Content-Type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/ninetailed/config.js b/src/cdk/v2/destinations/ninetailed/config.js new file mode 100644 index 00000000000..c38496a4155 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/config.js @@ -0,0 +1,31 @@ +const { getMappingConfig } = require('../../../../v0/util'); + +const ConfigCategories = { + GENERAL: { + type: 'general', + name: 'generalPayloadMapping', + }, + CONTEXT: { + type: 'context', + name: 'contextMapping', + }, + TRACK: { + type: 'track', + name: 'trackMapping', + }, + IDENTIFY: { + type: 'identify', + name: 'identifyMapping', + }, + PAGE: { + type: 'page', + name: 'pageMapping', + }, +}; + +// MAX_BATCH_SIZE : // Maximum number of events to send in a single batch +const mappingConfig = getMappingConfig(ConfigCategories, __dirname); +const batchEndpoint = + 'https://experience.ninetailed.co/v2/organizations/{{organisationId}}/environments/{{environment}}/events'; + +module.exports = { ConfigCategories, mappingConfig, batchEndpoint, MAX_BATCH_SIZE: 200 }; diff --git a/src/cdk/v2/destinations/ninetailed/data/contextMapping.json b/src/cdk/v2/destinations/ninetailed/data/contextMapping.json new file mode 100644 index 00000000000..3d6392dd1ec --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/data/contextMapping.json @@ -0,0 +1,43 @@ +[ + { + "sourceKeys": "app.name", + "required": true, + "destKey": "app.name" + }, + { + "sourceKeys": "app.version", + "required": true, + "destKey": "app.version" + }, + { + "sourceKeys": "campaign", + "destKey": "campaign" + }, + { + "sourceKeys": "library.name", + "required": true, + "destKey": "library.name" + }, + { + "sourceKeys": "library.version", + "required": true, + "destKey": "library.version" + }, + { + "sourceKeys": "locale", + "destKey": "locale" + }, + { + "sourceKeys": "page", + "destKey": "page" + }, + { + "sourceKeys": "userAgent", + "destKey": "userAgent" + }, + { + "sourceKeys": "location", + "required": true, + "destKey": "location" + } +] diff --git a/src/cdk/v2/destinations/ninetailed/data/generalPayloadMapping.json b/src/cdk/v2/destinations/ninetailed/data/generalPayloadMapping.json new file mode 100644 index 00000000000..3ab72d1b9f4 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/data/generalPayloadMapping.json @@ -0,0 +1,25 @@ +[ + { + "sourceKeys": "anonymousId", + "required": true, + "destKey": "anonymousId" + }, + { + "sourceKeys": "messageId", + "required": true, + "destKey": "messageId" + }, + { + "sourceKeys": "channel", + "required": true, + "destKey": "channel" + }, + { + "sourceKeys": "type", + "destKey": "type" + }, + { + "sourceKeys": "originalTimestamp", + "destKey": "originalTimestamp" + } +] diff --git a/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json b/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json new file mode 100644 index 00000000000..e8d3f7797d6 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json @@ -0,0 +1,14 @@ +[ + { + "sourceKeys": "traits", + "sourceFromGenericMap": true, + "required": true, + "destKey": "traits" + }, + { + "sourceKeys": "userIdOnly", + "sourceFromGenericMap": true, + "required": true, + "destKey": "userId" + } +] diff --git a/src/cdk/v2/destinations/ninetailed/data/pageMapping.json b/src/cdk/v2/destinations/ninetailed/data/pageMapping.json new file mode 100644 index 00000000000..80ec2f58f12 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/data/pageMapping.json @@ -0,0 +1,7 @@ +[ + { + "sourceKeys": "properties", + "required": true, + "destKey": "properties" + } +] diff --git a/src/cdk/v2/destinations/ninetailed/data/trackMapping.json b/src/cdk/v2/destinations/ninetailed/data/trackMapping.json new file mode 100644 index 00000000000..44af6dd1a34 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/data/trackMapping.json @@ -0,0 +1,12 @@ +[ + { + "sourceKeys": "properties", + "required": true, + "destKey": "properties" + }, + { + "sourceKeys": "event", + "required": true, + "destKey": "event" + } +] diff --git a/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml b/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml new file mode 100644 index 00000000000..6f5056ce100 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml @@ -0,0 +1,33 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + - name: defaultRequestConfig + path: ../../../../v0/util + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + - path: ./utils + +steps: + - name: messageType + template: | + .message.type.toLowerCase(); + - name: validateInput + template: | + let messageType = $.outputs.messageType; + $.assert(messageType, "message Type is not present. Aborting"); + $.assert(messageType in {{$.EventType.([.TRACK,.IDENTIFY,.PAGE])}}, "message type " + messageType + " is not supported"); + $.assertConfig(.destination.Config.organisationId, "Organisation ID is not present. Aborting"); + $.assertConfig(.destination.Config.environment, "Environment is not present. Aborting"); + - name: preparePayload + template: | + const payload = $.constructFullPayload(.message); + $.context.payload = $.removeUndefinedAndNullValues(payload); + + - name: buildResponse + template: | + const response = $.defaultRequestConfig(); + response.body.JSON.events = [$.context.payload]; + response.endpoint = $.getEndpoint(.destination.Config); + response.method = "POST"; + response diff --git a/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml b/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml new file mode 100644 index 00000000000..30dd3fdd95d --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml @@ -0,0 +1,35 @@ +bindings: + - path: ./config + - name: handleRtTfSingleEventError + path: ../../../../v0/util/index + - path: ./utils +steps: + - name: validateInput + template: | + $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array") + + - name: transform + externalWorkflow: + path: ./procWorkflow.yaml + loopOverInput: true + + - name: successfulEvents + template: | + $.outputs.transform#idx.output.({ + "output": .body.JSON.events[0], + "destination": ^[idx].destination, + "metadata": ^[idx].metadata + })[] + - name: failedEvents + template: | + $.outputs.transform#idx.error.( + $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {}) + )[] + - name: batchSuccessfulEvents + description: Batches the successfulEvents + template: | + $.batchResponseBuilder($.outputs.successfulEvents); + + - name: finalPayload + template: | + [...$.outputs.failedEvents, ...$.outputs.batchSuccessfulEvents] diff --git a/src/cdk/v2/destinations/ninetailed/utils.js b/src/cdk/v2/destinations/ninetailed/utils.js new file mode 100644 index 00000000000..b716422a0e8 --- /dev/null +++ b/src/cdk/v2/destinations/ninetailed/utils.js @@ -0,0 +1,109 @@ +const { BatchUtils } = require('@rudderstack/workflow-engine'); +const config = require('./config'); +const { constructPayload } = require('../../../../v0/util'); + +/** + * This fucntion constructs payloads based upon mappingConfig for all calls + * We build context as it has some specific payloads with default values so just breaking them down + * @param {*} message + * @returns + */ +const constructFullPayload = (message) => { + const context = constructPayload( + message?.context || {}, + config.mappingConfig[config.ConfigCategories.CONTEXT.name], + ); + const payload = constructPayload( + message, + config.mappingConfig[config.ConfigCategories.GENERAL.name], + ); + let typeSpecifcPayload; + switch (message.type) { + case 'track': + typeSpecifcPayload = constructPayload( + message, + config.mappingConfig[config.ConfigCategories.TRACK.name], + ); + break; + case 'identify': + typeSpecifcPayload = constructPayload( + message, + config.mappingConfig[config.ConfigCategories.IDENTIFY.name], + ); + break; + case 'page': + typeSpecifcPayload = constructPayload( + message, + config.mappingConfig[config.ConfigCategories.PAGE.name], + ); + break; + default: + break; + } + payload.context = context; + return { ...payload, ...typeSpecifcPayload }; // merge base and type-specific payloads; +}; + +const getEndpoint = (Config) => { + const { organisationId, environment } = Config; + return config.batchEndpoint + .replace('{{organisationId}}', organisationId) + .replace('{{environment}}', environment); +}; + +const mergeMetadata = (batch) => { + const metadata = []; + batch.forEach((event) => { + metadata.push(event.metadata); + }); + return metadata; +}; + +const getMergedEvents = (batch) => { + const events = []; + batch.forEach((event) => { + events.push(event.output); + }); + return events; +}; + +const batchBuilder = (batch) => ({ + batchedRequest: { + body: { + JSON: { events: getMergedEvents(batch) }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + version: '1', + type: 'REST', + method: 'POST', + endpoint: getEndpoint(batch[0].destination.Config), + headers: { + 'Content-Type': 'application/json', + }, + params: {}, + files: {}, + }, + metadata: mergeMetadata(batch), + batched: true, + statusCode: 200, + destination: batch[0].destination, +}); + +/** + * This fucntions make chunk of successful events based on MAX_BATCH_SIZE + * and then build the response for each chunk to be returned as object of an array + * @param {*} events + * @returns + */ +const batchResponseBuilder = (events) => { + const batches = BatchUtils.chunkArrayBySizeAndLength(events, { maxItems: config.MAX_BATCH_SIZE }); + const response = []; + batches.items.forEach((batch) => { + response.push(batchBuilder(batch)); + }); + return response; +}; + +module.exports = { constructFullPayload, getEndpoint, batchResponseBuilder }; diff --git a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml index 8a956e905ca..1122a80404c 100644 --- a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml @@ -246,4 +246,4 @@ steps: }, "params": $.outputs.checkSendTestEventConfig, "files": {} - })[] \ No newline at end of file + })[] diff --git a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml index 227942dfea8..215ead12b12 100644 --- a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml +++ b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml @@ -12,7 +12,7 @@ steps: If sendTestEvent is enabled, we send test event to the destination ref: https://help.pinterest.com/en/business/article/track-conversions-with-the-conversions-api template: | - ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} + ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {} - name: transform externalWorkflow: diff --git a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json index e04765faede..db5d36fc4d1 100644 --- a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json +++ b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json @@ -1,25 +1,34 @@ [ { - "sourceKeys": "properties.orderId", + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": true, "destKey": "ord" }, { - "sourceKeys": ["properties.tr", "properties.ranSiteID"], + "sourceKeys": ["properties.tr", "properties.ran_site_id", "properties.ranSiteID"], "required": true, "destKey": "tr" }, { - "sourceKeys": ["properties.land", "properties.landTime"], + "sourceKeys": ["properties.land", "properties.land_time", "properties.landTime"], "required": true, "destKey": "land" }, { - "sourceKeys": ["properties.date", "properties.orderCompletedTime"], + "sourceKeys": [ + "properties.date", + "properties.order_completed_time", + "properties.orderCompletedTime" + ], "destKey": "date" }, { - "sourceKeys": ["properties.altord", "properties.alterOrderId"], + "sourceKeys": [ + "properties.alt_ord", + "properties.altord", + "properties.alter_order_id", + "properties.alterOrderId" + ], "destKey": "altord" }, { @@ -27,15 +36,15 @@ "destKey": "cur" }, { - "sourceKeys": "properties.creditCardType", + "sourceKeys": ["properties.credit_card_type", "properties.creditCardType"], "destKey": "cc" }, { - "sourceKeys": "properties.commReason", + "sourceKeys": ["properties.comm_reason", "properties.commReason"], "destKey": "commreason" }, { - "sourceKeys": "properties.isComm", + "sourceKeys": ["properties.is_comm", "properties.isComm"], "destKey": "iscomm" }, { @@ -47,27 +56,48 @@ "destKey": "coupon" }, { - "sourceKeys": ["properties.custId", "properties.customerId", "properties.userId"], + "sourceKeys": [ + "properties.cust_id", + "properties.custId", + "properties.customer_id", + "properties.customerId", + "properties.userId" + ], "destKey": "custid" }, { - "sourceKeys": ["properties.custScore", "properties.customerScore"], + "sourceKeys": [ + "properties.cust_score", + "properties.custScore", + "properties.customer_score", + "properties.customerScore" + ], "destKey": "custscore" }, { - "sourceKeys": ["properties.custStatus", "properties.customerStatus"], + "sourceKeys": [ + "properties.cust_status", + "properties.custStatus", + "properties.customer_status", + "properties.customerStatus" + ], "destKey": "custstatus" }, { - "sourceKeys": ["properties.dId", "properties.advertisingId"], + "sourceKeys": ["properties.dId", "properties.advertising_id", "properties.advertisingId"], "destKey": "did" }, { - "sourceKeys": ["properties.disamt", "properties.discountAmout"], + "sourceKeys": ["properties.disamt", "properties.discount_amount", "properties.discountAmount"], "destKey": "disamt" }, { - "sourceKeys": ["properties.ordStatus", "properties.orderStatus"], + "sourceKeys": [ + "properties.ord_status", + "properties.ordStatus", + "properties.order_status", + "properties.orderStatus" + ], "destKey": "ordstatus" }, { @@ -75,7 +105,7 @@ "destKey": "segment" }, { - "sourceKeys": "properties.shipcountry", + "sourceKeys": ["properties.ship_country", "properties.shipcountry"], "destKey": "shipcountry" }, { @@ -83,15 +113,25 @@ "destKey": "shipped" }, { - "sourceKeys": ["properties.sitename", "properties.url", "context.page.url"], + "sourceKeys": [ + "properties.site_name", + "properties.sitename", + "properties.url", + "context.page.url" + ], "destKey": "sitename" }, { - "sourceKeys": "properties.storeId", + "sourceKeys": ["properties.store_id", "properties.storeId"], "destKey": "storeid" }, { - "sourceKeys": ["properties.storecat", "properties.storeCategory"], + "sourceKeys": [ + "properties.store_cat", + "properties.storecat", + "properties.store_category", + "properties.storeCategory" + ], "destKey": "storecat" }, { diff --git a/src/cdk/v2/destinations/rakuten/utils.js b/src/cdk/v2/destinations/rakuten/utils.js index fe37455a572..ef6b197db7a 100644 --- a/src/cdk/v2/destinations/rakuten/utils.js +++ b/src/cdk/v2/destinations/rakuten/utils.js @@ -59,7 +59,7 @@ const constructLineItems = (properties) => { } if (product.price) { - return product.quantity * product.price * 100; + return product.quantity * product.price * 100; } return product.amount * 100; }); diff --git a/src/cdk/v2/destinations/reddit/procWorkflow.yaml b/src/cdk/v2/destinations/reddit/procWorkflow.yaml index e6c05ef86d3..59725c1257a 100644 --- a/src/cdk/v2/destinations/reddit/procWorkflow.yaml +++ b/src/cdk/v2/destinations/reddit/procWorkflow.yaml @@ -57,12 +57,14 @@ steps: - name: customFields condition: $.outputs.prepareTrackPayload.eventType.tracking_type === "Purchase" + reference: 'https://ads-api.reddit.com/docs/v2/#tag/Conversions/paths/~1api~1v2.0~1conversions~1events~1%7Baccount_id%7D/post' template: | + const revenue_in_cents = .message.properties.revenue ? Math.round(Number(.message.properties.revenue)*100) const customFields = .message.().({ "currency": .properties.currency, - "value_decimal": .properties.revenue !== undefined ? Number(.properties.revenue) : undefined, + "value_decimal": revenue_in_cents ? revenue_in_cents / 100, "item_count": (Array.isArray(.properties.products) && .properties.products.length) || (.properties.itemCount && Number(.properties.itemCount)), - "value": .properties.revenue !== undefined ? Number(.properties.revenue)*100 : undefined, + "value": revenue_in_cents, "conversion_id": .properties.conversionId || .messageId, }); $.removeUndefinedAndNullValues(customFields) diff --git a/src/cdk/v2/destinations/sprig/procWorkflow.yaml b/src/cdk/v2/destinations/sprig/procWorkflow.yaml index 18b46913fd6..4dcebeffcda 100644 --- a/src/cdk/v2/destinations/sprig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/sprig/procWorkflow.yaml @@ -69,5 +69,3 @@ steps: "authorization": "API-Key " + .destination.Config.apiKey }; response - - diff --git a/src/cdk/v2/destinations/statsig/procWorkflow.yaml b/src/cdk/v2/destinations/statsig/procWorkflow.yaml index b3c85e31dcf..6d3328b87e2 100644 --- a/src/cdk/v2/destinations/statsig/procWorkflow.yaml +++ b/src/cdk/v2/destinations/statsig/procWorkflow.yaml @@ -26,4 +26,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml index cd84ecbc876..5862fbf3724 100644 --- a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml +++ b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml @@ -1,4 +1,3 @@ - bindings: - name: EventType path: ../../../../constants @@ -48,7 +47,6 @@ steps: "action": $.ACTION_MAP[action], })[] - - name: buildResponseForProcessTransformation description: build response template: | diff --git a/src/cdk/v2/destinations/zapier/procWorkflow.yaml b/src/cdk/v2/destinations/zapier/procWorkflow.yaml index 82d15cdec0f..9f1512836ea 100644 --- a/src/cdk/v2/destinations/zapier/procWorkflow.yaml +++ b/src/cdk/v2/destinations/zapier/procWorkflow.yaml @@ -44,4 +44,3 @@ steps: "content-type": "application/json" }; response - diff --git a/src/controllers/__tests__/delivery.test.ts b/src/controllers/__tests__/delivery.test.ts new file mode 100644 index 00000000000..0f91913f9d0 --- /dev/null +++ b/src/controllers/__tests__/delivery.test.ts @@ -0,0 +1,186 @@ +import request from 'supertest'; +import { createHttpTerminator } from 'http-terminator'; +import Koa from 'koa'; +import bodyParser from 'koa-bodyparser'; +import { applicationRoutes } from '../../routes'; +import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration'; +import { ServiceSelector } from '../../helpers/serviceSelector'; + +let server: any; +const OLD_ENV = process.env; + +beforeAll(async () => { + process.env = { ...OLD_ENV }; // Make a copy + const app = new Koa(); + app.use( + bodyParser({ + jsonLimit: '200mb', + }), + ); + applicationRoutes(app); + server = app.listen(9090); +}); + +afterAll(async () => { + process.env = OLD_ENV; // Restore old environment + const httpTerminator = createHttpTerminator({ + server, + }); + await httpTerminator.terminate(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +const getData = () => { + return { body: { JSON: { a: 'b' } }, metadata: [{ a1: 'b1' }], destinationConfig: { a2: 'b2' } }; +}; + +describe('Delivery controller tests', () => { + describe('Delivery V0 tests', () => { + test('successful delivery', async () => { + const testOutput = { status: 200, message: 'success' }; + const mockDestinationService = new NativeIntegrationDestinationService(); + mockDestinationService.deliver = jest + .fn() + .mockImplementation((event, destinationType, requestMetadata, version) => { + expect(event).toEqual(getData()); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + return testOutput; + }); + const getNativeDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/v0/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ output: testOutput }); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1); + }); + + test('delivery failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + mockDestinationService.deliver = jest + .fn() + .mockImplementation((event, destinationType, requestMetadata, version) => { + expect(event).toEqual(getData()); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + throw new Error('test error'); + }); + const getNativeDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/v0/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + const expectedResp = { + output: { + message: 'test error', + statTags: { + errorCategory: 'transformation', + }, + destinationResponse: '', + status: 500, + }, + }; + expect(response.status).toEqual(500); + expect(response.body).toEqual(expectedResp); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1); + }); + }); + + describe('Delivery V1 tests', () => { + test('successful delivery', async () => { + const testOutput = { status: 200, message: 'success' }; + const mockDestinationService = new NativeIntegrationDestinationService(); + mockDestinationService.deliver = jest + .fn() + .mockImplementation((event, destinationType, requestMetadata, version) => { + expect(event).toEqual(getData()); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v1'); + return testOutput; + }); + const getNativeDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/v1/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ output: testOutput }); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1); + }); + + test('delivery failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + mockDestinationService.deliver = jest + .fn() + .mockImplementation((event, destinationType, requestMetadata, version) => { + expect(event).toEqual(getData()); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v1'); + throw new Error('test error'); + }); + const getNativeDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/v1/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + const expectedResp = { + output: { + message: 'test error', + statTags: { + errorCategory: 'transformation', + }, + status: 500, + response: [{ error: 'test error', metadata: { a1: 'b1' }, statusCode: 500 }], + }, + }; + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedResp); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/controllers/__tests__/destination.test.ts b/src/controllers/__tests__/destination.test.ts new file mode 100644 index 00000000000..3c49a9a0aff --- /dev/null +++ b/src/controllers/__tests__/destination.test.ts @@ -0,0 +1,337 @@ +import request from 'supertest'; +import { createHttpTerminator } from 'http-terminator'; +import Koa from 'koa'; +import bodyParser from 'koa-bodyparser'; +import { applicationRoutes } from '../../routes'; +import { ServiceSelector } from '../../helpers/serviceSelector'; +import { DynamicConfigParser } from '../../util/dynamicConfigParser'; +import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration'; + +let server: any; +const OLD_ENV = process.env; + +beforeAll(async () => { + process.env = { ...OLD_ENV }; // Make a copy + const app = new Koa(); + app.use( + bodyParser({ + jsonLimit: '200mb', + }), + ); + applicationRoutes(app); + server = app.listen(9090); +}); + +afterAll(async () => { + process.env = OLD_ENV; // Restore old environment + const httpTerminator = createHttpTerminator({ + server, + }); + await httpTerminator.terminate(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +const getData = () => { + return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }]; +}; + +const getRouterTransformInputData = () => { + return { + input: [ + { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } }, + { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } }, + ], + destType: '__rudder_test__', + }; +}; + +describe('Destination controller tests', () => { + describe('Destination processor transform tests', () => { + test('successful transformation at processor', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + const expectedOutput = [ + { + event: { a: 'b1' }, + request: { query: {} }, + message: {}, + }, + { + event: { a: 'b2' }, + request: { query: {} }, + message: {}, + }, + ]; + mockDestinationService.doProcessorTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + expect(events).toEqual(expectedOutput); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + + return events; + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/v0/destinations/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doProcessorTransformation).toHaveBeenCalledTimes(1); + }); + + test('transformation at processor failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + const expectedOutput = [ + { + statusCode: 500, + error: 'Processor transformation failed', + statTags: { errorCategory: 'transformation' }, + }, + { + statusCode: 500, + error: 'Processor transformation failed', + statTags: { errorCategory: 'transformation' }, + }, + ]; + + mockDestinationService.doProcessorTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + + throw new Error('Processor transformation failed'); + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/v0/destinations/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doProcessorTransformation).toHaveBeenCalledTimes(1); + }); + }); + + describe('Destination router transform tests', () => { + test('successful transformation at router', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + const expectedOutput = [ + { + message: { a: 'b1' }, + destination: {}, + metadata: { jobId: 1 }, + request: { query: {} }, + }, + { + message: { a: 'b2' }, + destination: {}, + metadata: { jobId: 2 }, + request: { query: {} }, + }, + ]; + + mockDestinationService.doRouterTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + expect(events).toEqual(expectedOutput); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + + return events; + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/routerTransform') + .set('Accept', 'application/json') + .send(getRouterTransformInputData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ output: expectedOutput }); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doRouterTransformation).toHaveBeenCalledTimes(1); + }); + + test('transformation at router failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + mockDestinationService.doRouterTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + throw new Error('Router transformation failed'); + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/routerTransform') + .set('Accept', 'application/json') + .send(getRouterTransformInputData()); + + const expectedOutput = [ + { + metadata: [{ jobId: 1 }, { jobId: 2 }], + batched: false, + statusCode: 500, + error: 'Router transformation failed', + statTags: { errorCategory: 'transformation' }, + }, + ]; + expect(response.status).toEqual(200); + expect(response.body).toEqual({ output: expectedOutput }); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doRouterTransformation).toHaveBeenCalledTimes(1); + }); + }); + + describe('Batch transform tests', () => { + test('successful batching at router', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + const expectedOutput = [ + { + message: { a: 'b1' }, + destination: {}, + metadata: { jobId: 1 }, + request: { query: {} }, + }, + { + message: { a: 'b2' }, + destination: {}, + metadata: { jobId: 2 }, + request: { query: {} }, + }, + ]; + + mockDestinationService.doBatchTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + expect(events).toEqual(expectedOutput); + expect(destinationType).toEqual('__rudder_test__'); + expect(version).toEqual('v0'); + + return events; + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/batch') + .set('Accept', 'application/json') + .send(getRouterTransformInputData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doBatchTransformation).toHaveBeenCalledTimes(1); + }); + + test('batch transformation failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + mockDestinationService.doBatchTransformation = jest + .fn() + .mockImplementation((events, destinationType, version, requestMetadata) => { + throw new Error('Batch transformation failed'); + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + DynamicConfigParser.process = jest.fn().mockImplementation((events) => { + return events; + }); + + const response = await request(server) + .post('/batch') + .set('Accept', 'application/json') + .send(getRouterTransformInputData()); + + const expectedOutput = [ + { + metadata: [{ jobId: 1 }, { jobId: 2 }], + batched: false, + statusCode: 500, + error: 'Batch transformation failed', + statTags: { errorCategory: 'transformation' }, + }, + ]; + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.doBatchTransformation).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/controllers/__tests__/regulation.test.ts b/src/controllers/__tests__/regulation.test.ts new file mode 100644 index 00000000000..55cd8f2d376 --- /dev/null +++ b/src/controllers/__tests__/regulation.test.ts @@ -0,0 +1,107 @@ +import request from 'supertest'; +import { createHttpTerminator } from 'http-terminator'; +import Koa from 'koa'; +import bodyParser from 'koa-bodyparser'; +import { applicationRoutes } from '../../routes'; +import { ServiceSelector } from '../../helpers/serviceSelector'; +import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration'; + +let server: any; +const OLD_ENV = process.env; + +beforeAll(async () => { + process.env = { ...OLD_ENV }; // Make a copy + const app = new Koa(); + app.use( + bodyParser({ + jsonLimit: '200mb', + }), + ); + applicationRoutes(app); + server = app.listen(9090); +}); + +afterAll(async () => { + process.env = OLD_ENV; // Restore old environment + const httpTerminator = createHttpTerminator({ + server, + }); + await httpTerminator.terminate(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +const getDeletionData = () => { + return [ + { userAttributes: [{ a: 'b1' }], destType: '__rudder_test__' }, + { userAttributes: [{ a: 'b1' }], destType: '__rudder_test__' }, + ]; +}; + +describe('Regulation controller tests', () => { + describe('Delete users tests', () => { + test('successful delete users request', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + const expectedOutput = [{ statusCode: 400 }, { statusCode: 200 }]; + + mockDestinationService.processUserDeletion = jest + .fn() + .mockImplementation((reqs, destInfo) => { + expect(reqs).toEqual(getDeletionData()); + expect(destInfo).toEqual({ a: 'test' }); + + return expectedOutput; + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/deleteUsers') + .set('Accept', 'application/json') + .set('x-rudder-dest-info', '{"a": "test"}') + .send(getDeletionData()); + + expect(response.status).toEqual(400); + expect(response.body).toEqual(expectedOutput); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.processUserDeletion).toHaveBeenCalledTimes(1); + }); + + test('delete users request failure', async () => { + const mockDestinationService = new NativeIntegrationDestinationService(); + + mockDestinationService.processUserDeletion = jest + .fn() + .mockImplementation((reqs, destInfo) => { + expect(reqs).toEqual(getDeletionData()); + expect(destInfo).toEqual({ a: 'test' }); + + throw new Error('processUserDeletion error'); + }); + const getDestinationServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeDestinationService') + .mockImplementation(() => { + return mockDestinationService; + }); + + const response = await request(server) + .post('/deleteUsers') + .set('Accept', 'application/json') + .set('x-rudder-dest-info', '{"a": "test"}') + .send(getDeletionData()); + + expect(response.status).toEqual(500); + expect(response.body).toEqual([{ error: {}, statusCode: 500 }]); + + expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1); + expect(mockDestinationService.processUserDeletion).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/controllers/__tests__/source.test.ts b/src/controllers/__tests__/source.test.ts new file mode 100644 index 00000000000..565f39d559f --- /dev/null +++ b/src/controllers/__tests__/source.test.ts @@ -0,0 +1,220 @@ +import request from 'supertest'; +import { createHttpTerminator } from 'http-terminator'; +import Koa from 'koa'; +import bodyParser from 'koa-bodyparser'; +import { applicationRoutes } from '../../routes'; +import { NativeIntegrationSourceService } from '../../services/source/nativeIntegration'; +import { ServiceSelector } from '../../helpers/serviceSelector'; +import { ControllerUtility } from '../util/index'; + +let server: any; +const OLD_ENV = process.env; + +beforeAll(async () => { + process.env = { ...OLD_ENV }; // Make a copy + const app = new Koa(); + app.use( + bodyParser({ + jsonLimit: '200mb', + }), + ); + applicationRoutes(app); + server = app.listen(9090); +}); + +afterAll(async () => { + process.env = OLD_ENV; // Restore old environment + const httpTerminator = createHttpTerminator({ + server, + }); + await httpTerminator.terminate(); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +const getData = () => { + return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }]; +}; + +describe('Source controller tests', () => { + describe('V0 Source transform tests', () => { + test('successful source transform', async () => { + const sourceType = '__rudder_test__'; + const version = 'v0'; + const testOutput = [{ event: { a: 'b' } }]; + + const mockSourceService = new NativeIntegrationSourceService(); + mockSourceService.sourceTransformRoutine = jest + .fn() + .mockImplementation((i, s, v, requestMetadata) => { + expect(i).toEqual(getData()); + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + return testOutput; + }); + const getNativeSourceServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeSourceService') + .mockImplementation(() => { + return mockSourceService; + }); + + const adaptInputToVersionSpy = jest + .spyOn(ControllerUtility, 'adaptInputToVersion') + .mockImplementation((s, v, e) => { + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + expect(e).toEqual(getData()); + return { implementationVersion: version, input: e }; + }); + + const response = await request(server) + .post('/v0/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(testOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1); + expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1); + expect(mockSourceService.sourceTransformRoutine).toHaveBeenCalledTimes(1); + }); + + test('failing source transform', async () => { + const sourceType = '__rudder_test__'; + const version = 'v0'; + + const mockSourceService = new NativeIntegrationSourceService(); + const getNativeSourceServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeSourceService') + .mockImplementation(() => { + return mockSourceService; + }); + + const adaptInputToVersionSpy = jest + .spyOn(ControllerUtility, 'adaptInputToVersion') + .mockImplementation((s, v, e) => { + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + expect(e).toEqual(getData()); + throw new Error('test error'); + }); + + const response = await request(server) + .post('/v0/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + const expectedResp = [ + { + error: 'test error', + statTags: { + errorCategory: 'transformation', + }, + statusCode: 500, + }, + ]; + + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedResp); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1); + expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('V1 Source transform tests', () => { + test('successful source transform', async () => { + const sourceType = '__rudder_test__'; + const version = 'v1'; + const testOutput = [{ event: { a: 'b' }, source: { id: 'id' } }]; + + const mockSourceService = new NativeIntegrationSourceService(); + mockSourceService.sourceTransformRoutine = jest + .fn() + .mockImplementation((i, s, v, requestMetadata) => { + expect(i).toEqual(getData()); + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + return testOutput; + }); + const getNativeSourceServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeSourceService') + .mockImplementation(() => { + return mockSourceService; + }); + + const adaptInputToVersionSpy = jest + .spyOn(ControllerUtility, 'adaptInputToVersion') + .mockImplementation((s, v, e) => { + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + expect(e).toEqual(getData()); + return { implementationVersion: version, input: e }; + }); + + const response = await request(server) + .post('/v1/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(testOutput); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1); + expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1); + expect(mockSourceService.sourceTransformRoutine).toHaveBeenCalledTimes(1); + }); + + test('failing source transform', async () => { + const sourceType = '__rudder_test__'; + const version = 'v1'; + const mockSourceService = new NativeIntegrationSourceService(); + const getNativeSourceServiceSpy = jest + .spyOn(ServiceSelector, 'getNativeSourceService') + .mockImplementation(() => { + return mockSourceService; + }); + + const adaptInputToVersionSpy = jest + .spyOn(ControllerUtility, 'adaptInputToVersion') + .mockImplementation((s, v, e) => { + expect(s).toEqual(sourceType); + expect(v).toEqual(version); + expect(e).toEqual(getData()); + throw new Error('test error'); + }); + + const response = await request(server) + .post('/v1/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + const expectedResp = [ + { + error: 'test error', + statTags: { + errorCategory: 'transformation', + }, + statusCode: 500, + }, + ]; + + expect(response.status).toEqual(200); + expect(response.body).toEqual(expectedResp); + + expect(response.header['apiversion']).toEqual('2'); + + expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1); + expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts index 71075d1b4ce..d8b3c945247 100644 --- a/src/controllers/destination.ts +++ b/src/controllers/destination.ts @@ -15,6 +15,7 @@ import logger from '../logger'; import { getIntegrationVersion } from '../util/utils'; import tags from '../v0/util/tags'; import { DynamicConfigParser } from '../util/dynamicConfigParser'; +import { checkInvalidRtTfEvents } from '../v0/util'; export class DestinationController { public static async destinationTransformAtProcessor(ctx: Context) { @@ -101,6 +102,20 @@ export class DestinationController { const routerRequest = ctx.request.body as RouterTransformationRequest; const destination = routerRequest.destType; let events = routerRequest.input; + const errorRespEvents = checkInvalidRtTfEvents(events); + if (errorRespEvents.length > 0) { + errorRespEvents[0].metadata = [ + { + destType: destination, + }, + ]; + logger.debug( + `[${destination}] Invalid router transform payload structure: ${JSON.stringify(events)}`, + ); + ctx.body = { output: errorRespEvents }; + ControllerUtility.postProcess(ctx); + return ctx; + } const metaTags = MiscService.getMetaTags(events[0].metadata); stats.histogram('dest_transform_input_events', events.length, { destination, diff --git a/src/controllers/obs.delivery.js b/src/controllers/obs.delivery.js index 4a93afe1dcb..8e99650af64 100644 --- a/src/controllers/obs.delivery.js +++ b/src/controllers/obs.delivery.js @@ -1,7 +1,7 @@ /** * -------------------------------------- * -------------------------------------- - * ---------TO BE DEPRICIATED------------ + * ---------TO BE DEPRECATED------------- * -------------------------------------- * -------------------------------------- */ @@ -96,11 +96,15 @@ const DestProxyController = { destination, }); - response = generateErrorObject(err, { - [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), - [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, - [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, - }, false); + response = generateErrorObject( + err, + { + [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(), + [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION, + [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY, + }, + false, + ); response.message = `[TransformerProxyTest] Error occurred while testing proxy for destination ("${destination}"): "${err.message}"`; logger.error(response.message); logger.error(err); diff --git a/src/controllers/regulation.ts b/src/controllers/regulation.ts index a50541780d6..318b5ed4e78 100644 --- a/src/controllers/regulation.ts +++ b/src/controllers/regulation.ts @@ -34,7 +34,7 @@ export class RegulationController { rudderDestInfo, ); ctx.body = resplist; - ctx.status = resplist[0].statusCode; + ctx.status = resplist[0].statusCode; // TODO: check if this is the right way to set status } catch (error: CatchErr) { const metaTO = integrationService.getTags( userDeletionRequests[0].destType, @@ -46,8 +46,8 @@ export class RegulationController { const errResp = DestinationPostTransformationService.handleUserDeletionFailureEvents( error, metaTO, - ); - ctx.body = [{ error, statusCode: 500 }] as UserDeletionResponse[]; + ); // TODO: this is not used. Fix it. + ctx.body = [{ error, statusCode: 500 }] as UserDeletionResponse[]; // TODO: responses array length is always 1. Is that okay? ctx.status = 500; } stats.timing('dest_transform_request_latency', startTime, { diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts index 75d3d8ffa7c..c5bf7ab3580 100644 --- a/src/controllers/util/index.ts +++ b/src/controllers/util/index.ts @@ -51,7 +51,7 @@ export class ControllerUtility { private static convertSourceInputv0Tov1(sourceEvents: unknown[]): SourceInput[] { return sourceEvents.map( - (sourceEvent) => ({ event: sourceEvent, source: undefined } as SourceInput), + (sourceEvent) => ({ event: sourceEvent, source: undefined }) as SourceInput, ); } diff --git a/src/features.json b/src/features.json index 8709dce4326..5460111a22e 100644 --- a/src/features.json +++ b/src/features.json @@ -65,7 +65,8 @@ "TIKTOK_AUDIENCE": true, "REDDIT": true, "THE_TRADE_DESK": true, - "INTERCOM": true + "INTERCOM": true, + "NINETAILED": true }, "regulations": [ "BRAZE", diff --git a/src/helpers/__tests__/fetchHandlers.test.ts b/src/helpers/__tests__/fetchHandlers.test.ts new file mode 100644 index 00000000000..2135317cafb --- /dev/null +++ b/src/helpers/__tests__/fetchHandlers.test.ts @@ -0,0 +1,36 @@ +import { FetchHandler } from '../fetchHandlers'; +import { MiscService } from '../../services/misc'; + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('FetchHandlers Service', () => { + test('should save the handlers in the respective maps', async () => { + const dest = 'dest'; + const source = 'source'; + const version = 'version'; + + MiscService.getDestHandler = jest.fn().mockImplementation((dest, version) => { + return {}; + }); + MiscService.getSourceHandler = jest.fn().mockImplementation((source, version) => { + return {}; + }); + MiscService.getDeletionHandler = jest.fn().mockImplementation((source, version) => { + return {}; + }); + + expect(FetchHandler['sourceHandlerMap'].get(dest)).toBeUndefined(); + FetchHandler.getSourceHandler(dest, version); + expect(FetchHandler['sourceHandlerMap'].get(dest)).toBeDefined(); + + expect(FetchHandler['destHandlerMap'].get(dest)).toBeUndefined(); + FetchHandler.getDestHandler(dest, version); + expect(FetchHandler['destHandlerMap'].get(dest)).toBeDefined(); + + expect(FetchHandler['deletionHandlerMap'].get(dest)).toBeUndefined(); + FetchHandler.getDeletionHandler(dest, version); + expect(FetchHandler['deletionHandlerMap'].get(dest)).toBeDefined(); + }); +}); diff --git a/src/helpers/__tests__/serviceSelector.test.ts b/src/helpers/__tests__/serviceSelector.test.ts new file mode 100644 index 00000000000..c48d6bbe8b7 --- /dev/null +++ b/src/helpers/__tests__/serviceSelector.test.ts @@ -0,0 +1,105 @@ +import { ServiceSelector } from '../serviceSelector'; +import { INTEGRATION_SERVICE } from '../../routes/utils/constants'; +import { ProcessorTransformationRequest } from '../../types/index'; +import { CDKV1DestinationService } from '../../services/destination/cdkV1Integration'; +import { CDKV2DestinationService } from '../../services/destination/cdkV2Integration'; +import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration'; + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('ServiceSelector Service', () => { + test('should save the service in the cache', async () => { + expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_DEST)).toBeUndefined(); + expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_SOURCE)).toBeUndefined(); + + ServiceSelector.getNativeDestinationService(); + ServiceSelector.getNativeSourceService(); + + expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_DEST)).toBeDefined(); + expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_SOURCE)).toBeDefined(); + }); + + test('fetchCachedService should throw error for invalidService', async () => { + expect(() => ServiceSelector['fetchCachedService']('invalidService')).toThrow( + 'Invalid Service', + ); + }); + + test('isCdkDestination should return true', async () => { + const destinationDefinitionConfig = { + cdkEnabled: true, + }; + expect(ServiceSelector['isCdkDestination'](destinationDefinitionConfig)).toBe(true); + }); + + test('isCdkDestination should return false', async () => { + const destinationDefinitionConfig = { + cdkEnabledXYZ: true, + }; + expect(ServiceSelector['isCdkDestination'](destinationDefinitionConfig)).toBe(false); + }); + + test('isCdkV2Destination should return true', async () => { + const destinationDefinitionConfig = { + cdkV2Enabled: true, + }; + expect(ServiceSelector['isCdkV2Destination'](destinationDefinitionConfig)).toBe(true); + }); + + test('isCdkV2Destination should return false', async () => { + const destinationDefinitionConfig = { + cdkV2EnabledXYZ: true, + }; + expect(ServiceSelector['isCdkV2Destination'](destinationDefinitionConfig)).toBe(false); + }); + + test('getPrimaryDestinationService should return cdk v1 dest service', async () => { + const events = [ + { + destination: { + DestinationDefinition: { + Config: { + cdkEnabled: true, + }, + }, + }, + }, + ] as ProcessorTransformationRequest[]; + expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf( + CDKV1DestinationService, + ); + }); + + test('getPrimaryDestinationService should return cdk v2 dest service', async () => { + const events = [ + { + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + }, + }, + ] as ProcessorTransformationRequest[]; + expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf( + CDKV2DestinationService, + ); + }); + + test('getPrimaryDestinationService should return native dest service', async () => { + const events = [{}] as ProcessorTransformationRequest[]; + expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf( + NativeIntegrationDestinationService, + ); + }); + + test('getDestinationService should return native dest service', async () => { + const events = [{}] as ProcessorTransformationRequest[]; + expect(ServiceSelector.getDestinationService(events)).toBeInstanceOf( + NativeIntegrationDestinationService, + ); + }); +}); diff --git a/src/helpers/serviceSelector.ts b/src/helpers/serviceSelector.ts index 89678e94074..faa1c58240c 100644 --- a/src/helpers/serviceSelector.ts +++ b/src/helpers/serviceSelector.ts @@ -79,7 +79,7 @@ export class ServiceSelector { // eslint-disable-next-line @typescript-eslint/no-unused-vars public static getSourceService(arg: unknown) { - // Implement source event based descision logic for selecting service + // Implement source event based decision logic for selecting service } public static getDestinationService( diff --git a/src/legacy/router.js b/src/legacy/router.js index f8deb3fe622..9dd83b5988f 100644 --- a/src/legacy/router.js +++ b/src/legacy/router.js @@ -7,7 +7,7 @@ const Router = require('@koa/router'); const lodash = require('lodash'); const fs = require('fs'); const path = require('path'); -const { PlatformError } = require('@rudderstack/integrations-lib'); +const { PlatformError, getErrorRespEvents } = require('@rudderstack/integrations-lib'); const logger = require('../logger'); const stats = require('../util/stats'); const { SUPPORTED_VERSIONS, API_VERSION } = require('../routes/utils/constants'); @@ -18,7 +18,6 @@ const { isNonFuncObject, getMetadata, generateErrorObject, - getErrorRespEvents, isCdkDestination, checkAndCorrectUserId, } = require('../v0/util'); diff --git a/src/services/__tests__/misc.test.ts b/src/services/__tests__/misc.test.ts new file mode 100644 index 00000000000..5dcd948b34e --- /dev/null +++ b/src/services/__tests__/misc.test.ts @@ -0,0 +1,26 @@ +import { DestHandlerMap } from '../../constants/destinationCanonicalNames'; +import { MiscService } from '../misc'; + +describe('Misc tests', () => { + test('should return the right transform', async () => { + const version = 'v0'; + + Object.keys(DestHandlerMap).forEach((key) => { + expect(MiscService.getDestHandler(key, version)).toEqual( + require(`../../${version}/destinations/${DestHandlerMap[key]}/transform`), + ); + }); + + expect(MiscService.getDestHandler('am', version)).toEqual( + require(`../../${version}/destinations/am/transform`), + ); + + expect(MiscService.getSourceHandler('shopify', version)).toEqual( + require(`../../${version}/sources/shopify/transform`), + ); + + expect(MiscService.getDeletionHandler('intercom', version)).toEqual( + require(`../../${version}/destinations/intercom/deleteUsers`), + ); + }); +}); diff --git a/src/services/destination/__tests__/nativeIntegration.test.ts b/src/services/destination/__tests__/nativeIntegration.test.ts new file mode 100644 index 00000000000..59c8b418817 --- /dev/null +++ b/src/services/destination/__tests__/nativeIntegration.test.ts @@ -0,0 +1,100 @@ +import { NativeIntegrationDestinationService } from '../nativeIntegration'; +import { DestinationPostTransformationService } from '../postTransformation'; +import { + ProcessorTransformationRequest, + ProcessorTransformationOutput, + ProcessorTransformationResponse, +} from '../../../types/index'; +import { FetchHandler } from '../../../helpers/fetchHandlers'; + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('NativeIntegration Service', () => { + test('doProcessorTransformation - success', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + const requestMetadata = {}; + const event = { message: { a: 'b' } } as ProcessorTransformationRequest; + const events: ProcessorTransformationRequest[] = [event, event]; + + const tevent = { version: 'v0', endpoint: 'http://abc' } as ProcessorTransformationOutput; + const tresp = { output: tevent, statusCode: 200 } as ProcessorTransformationResponse; + const tresponse: ProcessorTransformationResponse[] = [tresp, tresp]; + + FetchHandler.getDestHandler = jest.fn().mockImplementation((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + process: jest.fn(() => { + return tevent; + }), + }; + }); + + const postTransformSpy = jest + .spyOn(DestinationPostTransformationService, 'handleProcessorTransformSucessEvents') + .mockImplementation((e, p, d) => { + expect(e).toEqual(event); + expect(p).toEqual(tevent); + return [tresp]; + }); + + const service = new NativeIntegrationDestinationService(); + const resp = await service.doProcessorTransformation( + events, + destType, + version, + requestMetadata, + ); + + expect(resp).toEqual(tresponse); + + expect(postTransformSpy).toHaveBeenCalledTimes(2); + }); + + test('doProcessorTransformation - failure', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + const requestMetadata = {}; + const event = { message: { a: 'b' } } as ProcessorTransformationRequest; + const events: ProcessorTransformationRequest[] = [event, event]; + + FetchHandler.getDestHandler = jest.fn().mockImplementation((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + process: jest.fn(() => { + throw new Error('test error'); + }), + }; + }); + + const service = new NativeIntegrationDestinationService(); + const resp = await service.doProcessorTransformation( + events, + destType, + version, + requestMetadata, + ); + + const expected = [ + { + metadata: undefined, + statusCode: 500, + error: 'test error', + statTags: { errorCategory: 'transformation' }, + }, + { + metadata: undefined, + statusCode: 500, + error: 'test error', + statTags: { errorCategory: 'transformation' }, + }, + ]; + + console.log('resp:', resp); + expect(resp).toEqual(expected); + }); +}); diff --git a/src/services/destination/__tests__/postTransformation.test.ts b/src/services/destination/__tests__/postTransformation.test.ts new file mode 100644 index 00000000000..f961dcbce7e --- /dev/null +++ b/src/services/destination/__tests__/postTransformation.test.ts @@ -0,0 +1,22 @@ +import { MetaTransferObject, ProcessorTransformationRequest } from '../../../types/index'; +import { DestinationPostTransformationService } from '../postTransformation'; +import { ProcessorTransformationResponse } from '../../../types'; + +describe('PostTransformation Service', () => { + test('should handleProcessorTransformFailureEvents', async () => { + const e = new Error('test error'); + const metaTo = { errorContext: 'error Context' } as MetaTransferObject; + const resp = DestinationPostTransformationService.handleProcessorTransformFailureEvents( + e, + metaTo, + ); + + const expected = { + statusCode: 500, + error: 'test error', + statTags: { errorCategory: 'transformation' }, + } as ProcessorTransformationResponse; + + expect(resp).toEqual(expected); + }); +}); diff --git a/src/services/destination/__tests__/preTransformation.test.ts b/src/services/destination/__tests__/preTransformation.test.ts new file mode 100644 index 00000000000..c10bab78acb --- /dev/null +++ b/src/services/destination/__tests__/preTransformation.test.ts @@ -0,0 +1,23 @@ +import { createMockContext } from '@shopify/jest-koa-mocks'; +import { ProcessorTransformationRequest } from '../../../types/index'; +import { DestinationPreTransformationService } from '../../destination/preTransformation'; + +describe('PreTransformation Service', () => { + test('should enhance events with query params', async () => { + const ctx = createMockContext(); + ctx.request.query = { cycle: 'true', x: 'y' }; + + const events: ProcessorTransformationRequest[] = [ + { message: { a: 'b' } } as ProcessorTransformationRequest, + ]; + const expected: ProcessorTransformationRequest[] = [ + { + message: { a: 'b' }, + request: { query: { cycle: 'true', x: 'y' } }, + } as ProcessorTransformationRequest, + ]; + + const resp = DestinationPreTransformationService.preProcess(events, ctx); + expect(resp).toEqual(expected); + }); +}); diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts index 2dd78b58e29..2bb82fc602f 100644 --- a/src/services/destination/nativeIntegration.ts +++ b/src/services/destination/nativeIntegration.ts @@ -209,10 +209,14 @@ export class NativeIntegrationDestinationService implements DestinationService { const jobStates = (deliveryRequest as ProxyV1Request).metadata.map( (metadata) => ({ - error: JSON.stringify(v0Response.destinationResponse?.response), + error: JSON.stringify( + v0Response.destinationResponse?.response === undefined + ? v0Response.destinationResponse + : v0Response.destinationResponse?.response, + ), statusCode: v0Response.status, metadata, - } as DeliveryJobState), + }) as DeliveryJobState, ); responseProxy = { response: jobStates, diff --git a/src/services/source/__tests__/nativeIntegration.test.ts b/src/services/source/__tests__/nativeIntegration.test.ts new file mode 100644 index 00000000000..bb40438811f --- /dev/null +++ b/src/services/source/__tests__/nativeIntegration.test.ts @@ -0,0 +1,89 @@ +import { NativeIntegrationSourceService } from '../nativeIntegration'; +import { SourcePostTransformationService } from '../postTransformation'; +import { SourceTransformationResponse, RudderMessage } from '../../../types/index'; +import stats from '../../../util/stats'; +import { FetchHandler } from '../../../helpers/fetchHandlers'; + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe('NativeIntegration Source Service', () => { + test('sourceTransformRoutine - success', async () => { + const sourceType = '__rudder_test__'; + const version = 'v0'; + const requestMetadata = {}; + + const event = { message: { a: 'b' } }; + const events = [event, event]; + + const tevent = { anonymousId: 'test' } as RudderMessage; + const tresp = { output: { batch: [tevent] }, statusCode: 200 } as SourceTransformationResponse; + + const tresponse = [ + { output: { batch: [{ anonymousId: 'test' }] }, statusCode: 200 }, + { output: { batch: [{ anonymousId: 'test' }] }, statusCode: 200 }, + ]; + + FetchHandler.getSourceHandler = jest.fn().mockImplementationOnce((d, v) => { + expect(d).toEqual(sourceType); + expect(v).toEqual(version); + return { + process: jest.fn(() => { + return tevent; + }), + }; + }); + + const postTransformSpy = jest + .spyOn(SourcePostTransformationService, 'handleSuccessEventsSource') + .mockImplementation((e) => { + expect(e).toEqual(tevent); + return tresp; + }); + + const service = new NativeIntegrationSourceService(); + const resp = await service.sourceTransformRoutine(events, sourceType, version, requestMetadata); + + expect(resp).toEqual(tresponse); + + expect(postTransformSpy).toHaveBeenCalledTimes(2); + }); + + test('sourceTransformRoutine - failure', async () => { + const sourceType = '__rudder_test__'; + const version = 'v0'; + const requestMetadata = {}; + + const event = { message: { a: 'b' } }; + const events = [event, event]; + + const tresp = { error: 'error' } as SourceTransformationResponse; + + const tresponse = [{ error: 'error' }, { error: 'error' }]; + + FetchHandler.getSourceHandler = jest.fn().mockImplementationOnce((d, v) => { + expect(d).toEqual(sourceType); + expect(v).toEqual(version); + return { + process: jest.fn(() => { + throw new Error('test error'); + }), + }; + }); + + const postTransformSpy = jest + .spyOn(SourcePostTransformationService, 'handleFailureEventsSource') + .mockImplementation((e, m) => { + return tresp; + }); + jest.spyOn(stats, 'increment').mockImplementation(() => {}); + + const service = new NativeIntegrationSourceService(); + const resp = await service.sourceTransformRoutine(events, sourceType, version, requestMetadata); + + expect(resp).toEqual(tresponse); + + expect(postTransformSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/services/source/__tests__/postTransformation.test.ts b/src/services/source/__tests__/postTransformation.test.ts new file mode 100644 index 00000000000..e5efbe8194f --- /dev/null +++ b/src/services/source/__tests__/postTransformation.test.ts @@ -0,0 +1,49 @@ +import { + MetaTransferObject, + RudderMessage, + SourceTransformationResponse, +} from '../../../types/index'; +import { SourcePostTransformationService } from '../../source/postTransformation'; + +describe('Source PostTransformation Service', () => { + test('should handleFailureEventsSource', async () => { + const e = new Error('test error'); + const metaTo = { errorContext: 'error Context' } as MetaTransferObject; + const resp = SourcePostTransformationService.handleFailureEventsSource(e, metaTo); + + const expected = { + statusCode: 500, + error: 'test error', + statTags: { errorCategory: 'transformation' }, + } as SourceTransformationResponse; + + expect(resp).toEqual(expected); + }); + + test('should return the event as SourceTransformationResponse if it has outputToSource property', () => { + const event = { + outputToSource: {}, + output: { batch: [{ anonymousId: 'test' }] }, + } as SourceTransformationResponse; + + const result = SourcePostTransformationService.handleSuccessEventsSource(event); + + expect(result).toEqual(event); + }); + + test('should return the events as batch in SourceTransformationResponse if it is an array', () => { + const events = [{ anonymousId: 'test' }, { anonymousId: 'test' }] as RudderMessage[]; + + const result = SourcePostTransformationService.handleSuccessEventsSource(events); + + expect(result).toEqual({ output: { batch: events } }); + }); + + test('should return the event as batch in SourceTransformationResponse if it is a single object', () => { + const event = { anonymousId: 'test' } as RudderMessage; + + const result = SourcePostTransformationService.handleSuccessEventsSource(event); + + expect(result).toEqual({ output: { batch: [event] } }); + }); +}); diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts index bf34e3d82a3..bae833c86a5 100644 --- a/src/services/userTransform.ts +++ b/src/services/userTransform.ts @@ -158,7 +158,7 @@ export class UserTransformService { statusCode: status, metadata: e.metadata, error: errorString, - } as ProcessorTransformationResponse), + }) as ProcessorTransformationResponse, ), ); stats.counter('user_transform_errors', eventsToProcess.length, { diff --git a/src/util/customTransformer-v1.js b/src/util/customTransformer-v1.js index 60f8e493faa..7e854a3714b 100644 --- a/src/util/customTransformer-v1.js +++ b/src/util/customTransformer-v1.js @@ -55,7 +55,7 @@ async function userTransformHandlerV1( testMode = false, ) { if (!userTransformation.versionId) { - return { transformedEvents : events }; + return { transformedEvents: events }; } const isolatevmFactory = await getFactory( @@ -88,9 +88,9 @@ async function userTransformHandlerV1( const tags = { identifier: 'v1', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); } diff --git a/src/util/customTransformer.js b/src/util/customTransformer.js index 001fe3216cf..a87c12dd6e2 100644 --- a/src/util/customTransformer.js +++ b/src/util/customTransformer.js @@ -254,9 +254,9 @@ async function runUserTransform( const tags = { identifier: 'v0', errored: transformationError ? true : false, - ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}, - ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {} - } + ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}), + ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}), + }; stats.counter('user_transform_function_input_events', events.length, tags); stats.timing('user_transform_function_latency', invokeTime, tags); diff --git a/src/util/customTransformerFactory.js b/src/util/customTransformerFactory.js index 1bf10e5d458..ee535319460 100644 --- a/src/util/customTransformerFactory.js +++ b/src/util/customTransformerFactory.js @@ -1,12 +1,6 @@ -const { - setOpenFaasUserTransform, - runOpenFaasUserTransform, -} = require('./customTransformer-faas'); +const { setOpenFaasUserTransform, runOpenFaasUserTransform } = require('./customTransformer-faas'); -const { - userTransformHandlerV1, - setUserTransformHandlerV1, -} = require('./customTransformer-v1'); +const { userTransformHandlerV1, setUserTransformHandlerV1 } = require('./customTransformer-v1'); const UserTransformHandlerFactory = (userTransformation) => { return { @@ -23,20 +17,10 @@ const UserTransformHandlerFactory = (userTransformation) => { switch (userTransformation.language) { case 'pythonfaas': case 'python': - return runOpenFaasUserTransform( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return runOpenFaasUserTransform(events, userTransformation, libraryVersionIds, testMode); default: - return userTransformHandlerV1( - events, - userTransformation, - libraryVersionIds, - testMode - ); + return userTransformHandlerV1(events, userTransformation, libraryVersionIds, testMode); } }, }; diff --git a/src/util/error-extractor/index.ts b/src/util/error-extractor/index.ts index 68ebac9aca7..6ff374b8697 100644 --- a/src/util/error-extractor/index.ts +++ b/src/util/error-extractor/index.ts @@ -1,19 +1,18 @@ /* eslint-disable max-classes-per-file */ -import { MessageDetails, StatusCode, Stat } from "./types"; +import { MessageDetails, StatusCode, Stat } from './types'; export class ErrorDetailsExtractor { status: StatusCode; messageDetails: MessageDetails; - stat : Stat + stat: Stat; - constructor (builder: ErrorDetailsExtractorBuilder) { + constructor(builder: ErrorDetailsExtractorBuilder) { this.status = builder.getStatus(); this.messageDetails = builder.getMessageDetails(); this.stat = builder.getStat(); } - } export class ErrorDetailsExtractorBuilder { @@ -22,27 +21,28 @@ export class ErrorDetailsExtractorBuilder { messageDetails: MessageDetails; stat: Stat; + constructor() { this.status = 0; this.messageDetails = {}; this.stat = {}; } - + setStatus(status: number): ErrorDetailsExtractorBuilder { this.status = status; return this; } setStat(stat: Record): ErrorDetailsExtractorBuilder { - this.stat = stat + this.stat = stat; return this; } /** * This means we need to set a message from a specific field that we see from the destination's response - * + * * @param {string} fieldPath -- Path of the field which should be set as "error message" - * @returns + * @returns */ setMessageField(fieldPath: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.message) { @@ -50,16 +50,16 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - field: fieldPath - } + field: fieldPath, + }; return this; } /** * This means we need to set the message provided - * + * * @param {string} msg - error message - * @returns + * @returns */ setMessage(msg: string): ErrorDetailsExtractorBuilder { if (this.messageDetails?.field) { @@ -67,13 +67,13 @@ export class ErrorDetailsExtractorBuilder { return this; } this.messageDetails = { - message: msg - } + message: msg, + }; return this; } build(): ErrorDetailsExtractor { - return new ErrorDetailsExtractor(this) + return new ErrorDetailsExtractor(this); } getStatus(): number { @@ -83,10 +83,8 @@ export class ErrorDetailsExtractorBuilder { getStat(): Record { return this.stat; } - + getMessageDetails(): Record { return this.messageDetails; } } - - diff --git a/src/util/error-extractor/types.ts b/src/util/error-extractor/types.ts index ff7290b4ff6..b93d2fafe50 100644 --- a/src/util/error-extractor/types.ts +++ b/src/util/error-extractor/types.ts @@ -1,3 +1,3 @@ export type MessageDetails = Record; export type StatusCode = number; -export type Stat = Record \ No newline at end of file +export type Stat = Record; diff --git a/src/util/ivmFactory.js b/src/util/ivmFactory.js index 2ab5f9548aa..9a6419295d6 100644 --- a/src/util/ivmFactory.js +++ b/src/util/ivmFactory.js @@ -23,7 +23,9 @@ async function evaluateModule(isolate, context, moduleCode) { } async function loadModule(isolateInternal, contextInternal, moduleName, moduleCode) { - const module = await isolateInternal.compileModule(moduleCode, { filename: `library ${moduleName}` }); + const module = await isolateInternal.compileModule(moduleCode, { + filename: `library ${moduleName}`, + }); await module.instantiate(contextInternal, () => {}); return module; } @@ -256,7 +258,7 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) } }); - await jail.set('extractStackTrace', function(trace, stringLiterals) { + await jail.set('extractStackTrace', function (trace, stringLiterals) { return extractStackTraceUptoLastSubstringMatch(trace, stringLiterals); }); @@ -346,7 +348,9 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode) // Now we can execute the script we just compiled: const bootstrapScriptResult = await bootstrap.run(context); // const customScript = await isolate.compileScript(`${library} ;\n; ${code}`); - const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { filename: 'base transformation' }); + const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { + filename: 'base transformation', + }); await customScriptModule.instantiate(context, async (spec) => { if (librariesMap[spec]) { return compiledModules[spec].module; diff --git a/src/util/prometheus.js b/src/util/prometheus.js index d7ba3b7c611..0fa17dc9bda 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -497,7 +497,7 @@ class Prometheus { name: 'shopify_anon_id_resolve', help: 'shopify_anon_id_resolve', type: 'counter', - labelNames: ['method', 'writeKey', 'shopifyTopic'], + labelNames: ['method', 'writeKey', 'shopifyTopic', 'source'], }, { name: 'shopify_redis_calls', @@ -533,7 +533,15 @@ class Prometheus { name: 'outgoing_request_count', help: 'Outgoing HTTP requests count', type: 'counter', - labelNames: ['feature', 'destType', 'endpointPath', 'success', 'statusCode'], + labelNames: [ + 'feature', + 'destType', + 'endpointPath', + 'success', + 'statusCode', + 'requestMethod', + 'module', + ], }, // Gauges @@ -573,7 +581,7 @@ class Prometheus { name: 'outgoing_request_latency', help: 'Outgoing HTTP requests duration in seconds', type: 'histogram', - labelNames: ['feature', 'destType', 'endpointPath'], + labelNames: ['feature', 'destType', 'endpointPath', 'requestMethod', 'module'], }, { name: 'http_request_duration', diff --git a/src/util/stats.js b/src/util/stats.js index e57ab85731e..9a32fd1de3b 100644 --- a/src/util/stats.js +++ b/src/util/stats.js @@ -13,17 +13,19 @@ function init() { switch (statsClientType) { case 'statsd': - logger.info("setting up statsd client") + logger.info('setting up statsd client'); statsClient = new statsd.Statsd(); break; case 'prometheus': - logger.info("setting up prometheus client") + logger.info('setting up prometheus client'); statsClient = new prometheus.Prometheus(); break; default: - logger.error(`invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`) + logger.error( + `invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`, + ); } } diff --git a/src/v0/destinations/active_campaign/transform.js b/src/v0/destinations/active_campaign/transform.js index 981dbd7520d..3978f868b18 100644 --- a/src/v0/destinations/active_campaign/transform.js +++ b/src/v0/destinations/active_campaign/transform.js @@ -62,6 +62,8 @@ const syncContact = async (contactPayload, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: endPoint, + requestMethod: 'POST', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to create new contact'); @@ -129,6 +131,8 @@ const customTagProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/tags`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -253,6 +257,8 @@ const customFieldProcessor = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/fields`, + requestMethod: 'GET', + module: 'router', }); promises.push(resp); } @@ -351,6 +357,8 @@ const customListProcessor = async (message, category, destination, contactId) => destType: 'active_campaign', feature: 'transformation', endpointPath: mergeListWithContactUrl, + requestMethod: 'POST', + module: 'router', }); promises.push(res); } @@ -409,6 +417,8 @@ const screenRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { errorHandler(res, 'Failed to retrieve events'); @@ -473,6 +483,8 @@ const trackRequestHandler = async (message, category, destination) => { destType: 'active_campaign', feature: 'transformation', endpointPath: `/api/3/eventTrackingEvents`, + requestMethod: 'GET', + module: 'router', }); if (res.success === false) { diff --git a/src/v0/destinations/adobe_analytics/transform.js b/src/v0/destinations/adobe_analytics/transform.js index b4281387249..5d3d6e7d00f 100644 --- a/src/v0/destinations/adobe_analytics/transform.js +++ b/src/v0/destinations/adobe_analytics/transform.js @@ -18,6 +18,7 @@ const { getIntegrationsObj, removeUndefinedAndNullValues, simpleProcessRouterDest, + validateEventAndLowerCaseConversion, } = require('../../util'); const { @@ -307,7 +308,7 @@ const processTrackEvent = (message, adobeEventName, destinationConfig, extras = destinationConfig; const { event: rawMessageEvent, properties } = message; const { overrideEventString, overrideProductString } = properties; - const event = rawMessageEvent.toLowerCase(); + const event = validateEventAndLowerCaseConversion(rawMessageEvent, true, true); const adobeEventArr = adobeEventName ? adobeEventName.split(',') : []; // adobeEventArr is an array of events which is defined as // ["eventName", "mapped Adobe Event=mapped merchproperty's value", "mapped Adobe Event=mapped merchproperty's value", . . .] diff --git a/src/v0/destinations/adobe_analytics/utils.js b/src/v0/destinations/adobe_analytics/utils.js index 97dc6e90bbd..ceba177ff14 100644 --- a/src/v0/destinations/adobe_analytics/utils.js +++ b/src/v0/destinations/adobe_analytics/utils.js @@ -93,7 +93,7 @@ function escapeToHTML(inputString) { '&': '&', '<': '<', '>': '>', - }[match]), + })[match], ); } diff --git a/src/v0/destinations/af/deleteUsers.js b/src/v0/destinations/af/deleteUsers.js index bb711292c00..ab515642aaa 100644 --- a/src/v0/destinations/af/deleteUsers.js +++ b/src/v0/destinations/af/deleteUsers.js @@ -39,6 +39,8 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = destType: 'af', feature: 'deleteUsers', endpointPath: `appsflyer.com/api/gdpr/v1/opendsr_requests`, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -48,6 +50,7 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) = handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js index 57629b9483f..d6c41937a1e 100644 --- a/src/v0/destinations/af/transform.js +++ b/src/v0/destinations/af/transform.js @@ -113,7 +113,12 @@ function getEventValueForUnIdentifiedTrackEvent(message) { return { eventValue }; } -function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport, addPropertiesAtRoot) { +function getEventValueMapFromMappingJson( + message, + mappingJson, + isMultiSupport, + addPropertiesAtRoot, +) { let eventValue = {}; if (addPropertiesAtRoot) { diff --git a/src/v0/destinations/am/config.js b/src/v0/destinations/am/config.js index 3e51a671371..78f8d43e948 100644 --- a/src/v0/destinations/am/config.js +++ b/src/v0/destinations/am/config.js @@ -136,5 +136,5 @@ module.exports = { batchEventsWithUserIdLengthLowerThanFive, IDENTIFY_AM, AMBatchSizeLimit, - AMBatchEventLimit + AMBatchEventLimit, }; diff --git a/src/v0/destinations/am/deleteUsers.js b/src/v0/destinations/am/deleteUsers.js index 6de9cf64a18..96c4f7b19c7 100644 --- a/src/v0/destinations/am/deleteUsers.js +++ b/src/v0/destinations/am/deleteUsers.js @@ -43,6 +43,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'am', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -51,6 +53,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js index bc08315fa82..2d78479ced8 100644 --- a/src/v0/destinations/am/transform.js +++ b/src/v0/destinations/am/transform.js @@ -336,7 +336,6 @@ const getDefaultResponseData = (message, rawPayload, evType, groupInfo) => { return { groups, rawPayload }; }; - const getResponseData = (evType, destination, rawPayload, message, groupInfo) => { let groups; diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js index 4d4fd5dc379..190a5c1baeb 100644 --- a/src/v0/destinations/am/utils.js +++ b/src/v0/destinations/am/utils.js @@ -123,7 +123,6 @@ const validateEventType = (evType) => { } }; - const userPropertiesPostProcess = (rawPayload) => { const operationList = [ '$setOnce', @@ -187,5 +186,5 @@ module.exports = { getEventId, getUnsetObj, validateEventType, - userPropertiesPostProcess + userPropertiesPostProcess, }; diff --git a/src/v0/destinations/bqstream/transform.js b/src/v0/destinations/bqstream/transform.js index 598a97946d3..8ee96aecf15 100644 --- a/src/v0/destinations/bqstream/transform.js +++ b/src/v0/destinations/bqstream/transform.js @@ -5,7 +5,6 @@ const { EventType } = require('../../../constants'); const { defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, } = require('../../util'); @@ -130,10 +129,6 @@ const processEachTypedEventList = ( }; const processRouterDest = (inputs) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs, DESTINATION); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const finalResp = []; const batchedEvents = groupEventsByType(inputs); diff --git a/src/v0/destinations/braze/braze.util.test.js b/src/v0/destinations/braze/braze.util.test.js index 9e82a235f11..7b6a93d3597 100644 --- a/src/v0/destinations/braze/braze.util.test.js +++ b/src/v0/destinations/braze/braze.util.test.js @@ -305,7 +305,14 @@ describe('dedup utility tests', () => { }, timeout: 10000, }, - { destType: 'braze', feature: 'transformation' }, + { + destType: 'braze', + feature: 'transformation', + endpointPath: '/users/export/ids', + feature: 'transformation', + module: 'router', + requestMethod: 'POST', + }, ); }); diff --git a/src/v0/destinations/braze/deleteUsers.js b/src/v0/destinations/braze/deleteUsers.js index b94d901138c..33c0f2ef7fa 100644 --- a/src/v0/destinations/braze/deleteUsers.js +++ b/src/v0/destinations/braze/deleteUsers.js @@ -22,7 +22,7 @@ const userDeletionHandler = async (userAttributes, config) => { // Endpoints different for different data centers. // DOC: https://www.braze.com/docs/user_guide/administrative/access_braze/braze_instances/ let endPoint; - const endpointPath = '/users/delete'; // TODO: to handle for destinations dynamically by extracting from endpoint + const endpointPath = '/users/delete'; const dataCenterArr = dataCenter.trim().split('-'); if (dataCenterArr[0].toLowerCase() === 'eu') { endPoint = 'https://rest.fra-01.braze.eu/users/delete'; @@ -46,6 +46,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'braze', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -54,6 +56,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/braze/transform.js b/src/v0/destinations/braze/transform.js index 6549f5658fc..d45640272ed 100644 --- a/src/v0/destinations/braze/transform.js +++ b/src/v0/destinations/braze/transform.js @@ -223,6 +223,9 @@ async function processIdentify(message, destination) { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/identify', }, ); if (!isHttpStatusSuccess(brazeIdentifyResp.status)) { diff --git a/src/v0/destinations/braze/util.js b/src/v0/destinations/braze/util.js index 40b9a7eadab..5f1f1e62057 100644 --- a/src/v0/destinations/braze/util.js +++ b/src/v0/destinations/braze/util.js @@ -163,6 +163,9 @@ const BrazeDedupUtility = { { destType: 'braze', feature: 'transformation', + requestMethod: 'POST', + module: 'router', + endpointPath: '/users/export/ids', }, ); stats.counter('braze_lookup_failure_count', 1, { diff --git a/src/v0/destinations/campaign_manager/transform.js b/src/v0/destinations/campaign_manager/transform.js index 3b480dbac23..14bc6d2c19f 100644 --- a/src/v0/destinations/campaign_manager/transform.js +++ b/src/v0/destinations/campaign_manager/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, getSuccessRespEvents, isDefinedAndNotNull, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -245,11 +244,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/canny/util.js b/src/v0/destinations/canny/util.js index f514a01e5c5..1d03eed4b9b 100644 --- a/src/v0/destinations/canny/util.js +++ b/src/v0/destinations/canny/util.js @@ -46,6 +46,8 @@ const retrieveUserId = async (apiKey, message) => { destType: 'canny', feature: 'transformation', endpointPath: `/v1/users/retrieve`, + requestMethod: 'POST', + module: 'processor', }, ); logger.debug(response); diff --git a/src/v0/destinations/clevertap/deleteUsers.js b/src/v0/destinations/clevertap/deleteUsers.js index 3c07a63d93c..52119bf0f19 100644 --- a/src/v0/destinations/clevertap/deleteUsers.js +++ b/src/v0/destinations/clevertap/deleteUsers.js @@ -53,6 +53,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'clevertap', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -62,6 +64,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/clevertap/transform.js b/src/v0/destinations/clevertap/transform.js index efcd101668a..51ed5c851e3 100644 --- a/src/v0/destinations/clevertap/transform.js +++ b/src/v0/destinations/clevertap/transform.js @@ -22,7 +22,6 @@ const { handleRtTfSingleEventError, batchMultiplexedEvents, getSuccessRespEvents, - checkInvalidRtTfEvents, } = require('../../util'); const { generateClevertapBatchedPayload } = require('./utils'); @@ -83,16 +82,21 @@ const responseWrapper = (payload, destination) => { } * } - * This function stringify the payload attributes if it's an array or objects. + * This function stringify the payload attributes if it's an array or objects. The keys that are not stringified are present in the `stringifyExcludeList` array. * @param {*} payload * @returns * return the final payload after converting to the relevant data-types. */ const convertObjectAndArrayToString = (payload, event) => { const finalPayload = {}; + const stringifyExcludeList = ['category-unsubscribe', 'category-resubscribe']; if (payload) { Object.keys(payload).forEach((key) => { - if (payload[key] && (Array.isArray(payload[key]) || typeof payload[key] === 'object')) { + if ( + payload[key] && + (Array.isArray(payload[key]) || typeof payload[key] === 'object') && + !stringifyExcludeList.includes(key) + ) { finalPayload[key] = JSON.stringify(payload[key]); } else { finalPayload[key] = payload[key]; @@ -384,13 +388,6 @@ const processEvent = (message, destination) => { const process = (event) => processEvent(event.message, event.destination); const processRouterDest = (inputs, reqMetadata) => { - // const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); - // return respList; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; const errorRespList = []; // const { destination } = inputs[0]; diff --git a/src/v0/destinations/clickup/util.js b/src/v0/destinations/clickup/util.js index 148fe1bd073..74e961906cf 100644 --- a/src/v0/destinations/clickup/util.js +++ b/src/v0/destinations/clickup/util.js @@ -217,6 +217,9 @@ const retrieveCustomFields = async (listId, apiToken) => { const customFieldsResponse = await httpGET(endpoint, requestOptions, { destType: 'clickup', feature: 'transformation', + endpointPath: '/list/listId/field', + requestMethod: 'GET', + module: 'router', }); const processedCustomFieldsResponse = processAxiosResponse(customFieldsResponse); diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js index 921cf953bdb..690768a170a 100644 --- a/src/v0/destinations/custify/deleteUsers.js +++ b/src/v0/destinations/custify/deleteUsers.js @@ -38,6 +38,9 @@ const userDeletionHandler = async (userAttributes, config) => { const deletionResponse = await httpDELETE(requestUrl, requestOptions, { destType: 'custify', feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/people', + module: 'deletion', }); const processedDeletionRequest = processAxiosResponse(deletionResponse); if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) { diff --git a/src/v0/destinations/custify/util.js b/src/v0/destinations/custify/util.js index 8ecabccd2e2..b6f34465039 100644 --- a/src/v0/destinations/custify/util.js +++ b/src/v0/destinations/custify/util.js @@ -41,6 +41,8 @@ const createUpdateCompany = async (companyPayload, Config) => { destType: 'custify', feature: 'transformation', endpointPath: `/company`, + requestMethod: 'POST', + module: 'router', }, ); const processedCompanyResponse = processAxiosResponse(companyResponse); diff --git a/src/v0/destinations/customerio/transform.js b/src/v0/destinations/customerio/transform.js index be4486717c1..6f2e053001f 100644 --- a/src/v0/destinations/customerio/transform.js +++ b/src/v0/destinations/customerio/transform.js @@ -5,7 +5,6 @@ const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, addExternalIdToTraits, @@ -174,10 +173,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/delighted/util.js b/src/v0/destinations/delighted/util.js index 2c92685fd77..c690bf5f5ca 100644 --- a/src/v0/destinations/delighted/util.js +++ b/src/v0/destinations/delighted/util.js @@ -61,7 +61,13 @@ const userValidity = async (channel, Config, userId) => { }, params: paramsdata, }, - { destType: 'delighted', feature: 'transformation' }, + { + destType: 'delighted', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/people.json', + module: 'router', + }, ); if (response && response.data && response.status === 200 && Array.isArray(response.data)) { return response.data.length > 0; diff --git a/src/v0/destinations/drip/util.js b/src/v0/destinations/drip/util.js index a502cf0d202..b7015c9351c 100644 --- a/src/v0/destinations/drip/util.js +++ b/src/v0/destinations/drip/util.js @@ -31,7 +31,13 @@ const userExists = async (Config, id) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/subscribers/id', + module: 'router', + }, ); if (response && response.status) { return response.status === 200; @@ -70,7 +76,13 @@ const createUpdateUser = async (finalpayload, Config, basicAuth) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'drip', feature: 'transformation' }, + { + destType: 'drip', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/subscribers', + module: 'router', + }, ); if (response) { return response.status === 200 || response.status === 201; diff --git a/src/v0/destinations/engage/deleteUsers.js b/src/v0/destinations/engage/deleteUsers.js index a3c3055c7d9..3616d2408d2 100644 --- a/src/v0/destinations/engage/deleteUsers.js +++ b/src/v0/destinations/engage/deleteUsers.js @@ -42,6 +42,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'engage', feature: 'deleteUsers', + requestMethod: 'DELETE', + endpointPath: '/users/userId', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(response); @@ -51,6 +54,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/facebook_conversions/utils.js b/src/v0/destinations/facebook_conversions/utils.js index 26204ec61a3..c6e3993e33e 100644 --- a/src/v0/destinations/facebook_conversions/utils.js +++ b/src/v0/destinations/facebook_conversions/utils.js @@ -93,28 +93,26 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - let contentType; + let defaultContentType; if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; } else if (contentCategory) { contentIds.push(contentCategory); contents.push({ id: contentCategory, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } + const contentType = + message.properties?.content_type || + getContentType(message, defaultContentType, categoryToContent, DESTINATION.toLowerCase()); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; break; @@ -125,18 +123,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego case 'payment info entered': case 'product added to wishlist': { const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); const { contentIds, contents } = populateContentsAndContentIDs([message.properties]); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), }; validateProductSearchedData(eventTypeCustomData); @@ -151,18 +151,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego ); const contentCategory = eventTypeCustomData.content_category; - const contentType = eventTypeCustomData.content_type; + const contentType = + message.properties?.content_type || + getContentType( + message, + eventTypeCustomData.content_type, + categoryToContent, + DESTINATION.toLowerCase(), + ); eventTypeCustomData = { ...eventTypeCustomData, content_ids: contentIds, contents, - content_type: getContentType( - message, - contentType, - categoryToContent, - DESTINATION.toLowerCase(), - ), + content_type: contentType, content_category: getContentCategory(contentCategory), num_items: contentIds.length, }; diff --git a/src/v0/destinations/facebook_pixel/utils.js b/src/v0/destinations/facebook_pixel/utils.js index 8a63a0b0fe2..cfa625ee3db 100644 --- a/src/v0/destinations/facebook_pixel/utils.js +++ b/src/v0/destinations/facebook_pixel/utils.js @@ -53,13 +53,9 @@ const getActionSource = (payload, channel) => { * Handles order completed and checkout started types of specific events */ const handleOrder = (message, categoryToContent) => { - const { products, revenue } = message.properties; - const value = formatRevenue(revenue); - - const contentType = getContentType(message, 'product', categoryToContent); - const contentIds = []; - const contents = []; const { + products, + revenue, category, quantity, price, @@ -67,6 +63,12 @@ const handleOrder = (message, categoryToContent) => { contentName, delivery_category: deliveryCategory, } = message.properties; + const value = formatRevenue(revenue); + let { content_type: contentType } = message.properties; + contentType = contentType || getContentType(message, 'product', categoryToContent); + const contentIds = []; + const contents = []; + if (products) { if (products.length > 0 && Array.isArray(products)) { products.forEach((singleProduct) => { @@ -109,10 +111,17 @@ const handleOrder = (message, categoryToContent) => { * Handles product list viewed */ const handleProductListViewed = (message, categoryToContent) => { - let contentType; + let defaultContentType; const contentIds = []; const contents = []; - const { products, category, quantity, value, contentName } = message.properties; + const { + products, + category, + quantity, + value, + contentName, + content_type: contentType, + } = message.properties; if (products && products.length > 0 && Array.isArray(products)) { products.forEach((product, index) => { if (isObject(product)) { @@ -132,7 +141,7 @@ const handleProductListViewed = (message, categoryToContent) => { } if (contentIds.length > 0) { - contentType = 'product'; + defaultContentType = 'product'; // for viewContent event content_ids and content arrays are not mandatory } else if (category) { contentIds.push(category); @@ -140,12 +149,12 @@ const handleProductListViewed = (message, categoryToContent) => { id: category, quantity: 1, }); - contentType = 'product_group'; + defaultContentType = 'product_group'; } return { content_ids: contentIds, - content_type: getContentType(message, contentType, categoryToContent), + content_type: contentType || getContentType(message, defaultContentType, categoryToContent), contents, content_category: getContentCategory(category), content_name: contentName, @@ -165,7 +174,8 @@ const handleProduct = (message, categoryToContent, valueFieldIdentifier) => { const useValue = valueFieldIdentifier === 'properties.value'; const contentId = message.properties?.product_id || message.properties?.sku || message.properties?.id; - const contentType = getContentType(message, 'product', categoryToContent); + const contentType = + message.properties?.content_type || getContentType(message, 'product', categoryToContent); const contentName = message.properties.product_name || message.properties.name || ''; const contentCategory = message.properties.category || ''; const currency = message.properties.currency || 'USD'; diff --git a/src/v0/destinations/freshmarketer/utils.js b/src/v0/destinations/freshmarketer/utils.js index 5e3ba6e67ec..c80711ff8d4 100644 --- a/src/v0/destinations/freshmarketer/utils.js +++ b/src/v0/destinations/freshmarketer/utils.js @@ -50,6 +50,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -95,6 +97,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -145,6 +149,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'GET', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -165,6 +171,8 @@ const createOrUpdateListDetails = async (listName, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/lists`, + requestMethod: 'POST', + module: 'router', }); listResponse = processAxiosResponse(listResponse); if (listResponse.status !== 200) { @@ -240,6 +248,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -314,6 +324,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { @@ -400,6 +412,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshmarketer', feature: 'transformation', endpointPath: `/crm/sales/api/selector/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js index 5008fedc2da..977bde0abb3 100644 --- a/src/v0/destinations/freshsales/utils.js +++ b/src/v0/destinations/freshsales/utils.js @@ -48,6 +48,8 @@ const createUpdateAccount = async (payload, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_accounts/upsert`, + requestMethod: 'POST', + module: 'router', }); accountResponse = processAxiosResponse(accountResponse); if (accountResponse.status !== 200 && accountResponse.status !== 201) { @@ -92,6 +94,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert?include=sales_accounts`, + requestMethod: 'POST', + module: 'router', }); userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse); if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) { @@ -148,6 +152,8 @@ const getContactsDetails = async (userEmail, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/contacts/upsert`, + requestMethod: 'POST', + module: 'router', }); userResponse = processAxiosResponse(userResponse); if (userResponse.status !== 200 && userResponse.status !== 201) { @@ -239,6 +245,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/sales_activity_types`, + requestMethod: 'GET', + module: 'router', }); salesActivityResponse = processAxiosResponse(salesActivityResponse); if (salesActivityResponse.status !== 200) { @@ -319,6 +327,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => { destType: 'freshsales', feature: 'transformation', endpointPath: `/crm/sales/api/lifecycle_stages`, + requestMethod: 'GET', + module: 'router', }); lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse); if (lifeCycleStagesResponse.status !== 200) { diff --git a/src/v0/destinations/ga/deleteUsers.js b/src/v0/destinations/ga/deleteUsers.js index 06e674048a6..524e2c14b47 100644 --- a/src/v0/destinations/ga/deleteUsers.js +++ b/src/v0/destinations/ga/deleteUsers.js @@ -81,6 +81,8 @@ const userDeletionHandler = async (userAttributes, config, rudderDestInfo) => { destType: 'ga', feature: 'deleteUsers', endpointPath: '/userDeletion/userDeletionRequests:upsert', + requestMethod: 'POST', + module: 'deletion', }, ); // process the response to know about refreshing scenario diff --git a/src/v0/destinations/gainsight/util.js b/src/v0/destinations/gainsight/util.js index 39e666c1a58..4c7fd581934 100644 --- a/src/v0/destinations/gainsight/util.js +++ b/src/v0/destinations/gainsight/util.js @@ -22,7 +22,13 @@ const searchGroup = async (groupName, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/query/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -56,7 +62,13 @@ const createGroup = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; @@ -93,7 +105,13 @@ const updateGroup = async (payload, Config) => { keys: 'Name', }, }, - { destType: 'gainsight', feature: 'transformation' }, + { + destType: 'gainsight', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/data/objects/Company', + module: 'router', + }, ); } catch (error) { let errMessage = ''; diff --git a/src/v0/destinations/gainsight_px/util.js b/src/v0/destinations/gainsight_px/util.js index 5109286b3f3..83d23566dd8 100644 --- a/src/v0/destinations/gainsight_px/util.js +++ b/src/v0/destinations/gainsight_px/util.js @@ -6,13 +6,13 @@ const { getDynamicErrorType } = require('../../../adapters/utils/networkUtils'); const { JSON_MIME_TYPE } = require('../../util/constant'); const handleErrorResponse = (error, customErrMessage, expectedErrStatus, defaultStatus = 400) => { + let destResp; let errMessage = ''; let errorStatus = defaultStatus; if (error.response && error.response.data) { - errMessage = error.response.data.externalapierror - ? JSON.stringify(error.response.data.externalapierror) - : JSON.stringify(error.response.data); + destResp = error.response?.data?.externalapierror ?? error.response?.data; + errMessage = JSON.stringify(destResp); errorStatus = error.response.status; @@ -26,7 +26,7 @@ const handleErrorResponse = (error, customErrMessage, expectedErrStatus, default { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(errorStatus), }, - error, + destResp, ); }; @@ -56,7 +56,13 @@ const objectExists = async (id, Config, objectType) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'GET', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 200) { return { success: true, err: null }; @@ -88,7 +94,13 @@ const createAccount = async (payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'POST', + endpointPath: '/accounts', + module: 'router', + }, ); if (response && response.status === 201) { return { success: true, err: null }; @@ -121,7 +133,13 @@ const updateAccount = async (accountId, payload, Config) => { 'Content-Type': JSON_MIME_TYPE, }, }, - { destType: 'gainsight_px', feature: 'transformation' }, + { + destType: 'gainsight_px', + feature: 'transformation', + requestMethod: 'PUT', + endpointPath: '/accounts/accountId', + module: 'router', + }, ); if (response && response.status === 204) { return { success: true, err: null }; diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index b4590fb71ce..3ea985e7734 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -45,6 +45,8 @@ const getConversionActionId = async (method, headers, params) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); if (!isHttpStatusSuccess(gaecConversionActionIdResponse.status)) { @@ -98,6 +100,8 @@ const ProxyRequest = async (request) => { destType: 'google_adwords_enhanced_conversions', feature: 'proxy', endpointPath: `/googleAds:uploadOfflineUserData`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json index aeecc3e01b8..9c88e59ddb3 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json +++ b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json @@ -1,10 +1,7 @@ [ { "destKey": "operations.create.transaction_attribute.store_attribute.store_code", - "sourceKeys": [ - "properties.store_code", - "properties.storeCode" - ], + "sourceKeys": ["properties.store_code", "properties.storeCode"], "required": false, "metadata": { "type": "toString" @@ -25,10 +22,7 @@ }, { "destKey": "operations.create.transaction_attribute.order_id", - "sourceKeys": [ - "properties.order_id", - "properties.orderId" - ], + "sourceKeys": ["properties.order_id", "properties.orderId"], "required": false, "metadata": { "type": "toString" @@ -52,4 +46,4 @@ ], "required": true } -] \ No newline at end of file +] diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 318b7802dfa..5541fd6e1e3 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -34,6 +34,8 @@ const createJob = async (endpoint, headers, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/create`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); createJobResponse = processAxiosResponse(createJobResponse); @@ -59,6 +61,8 @@ const addConversionToJob = async (endpoint, headers, jobId, payload) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/addOperations`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); addConversionToJobResponse = processAxiosResponse(addConversionToJobResponse); @@ -83,6 +87,8 @@ const runTheJob = async (endpoint, headers, payload, jobId) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/run`, + requestMethod: 'POST', + module: 'dataDelivery', }, ); return executeJobResponse; @@ -110,6 +116,8 @@ const getConversionCustomVariable = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'proxy', endpointPath: `/searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { @@ -247,6 +255,8 @@ const ProxyRequest = async (request) => { feature: 'proxy', destType: 'gogole_adwords_offline_conversions', endpointPath: `/proxy`, + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 46cde727713..68d4d01fa73 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -9,7 +9,6 @@ const { handleRtTfSingleEventError, defaultBatchRequestConfig, getSuccessRespEvents, - checkInvalidRtTfEvents, combineBatchRequestsWithSameJobIds, } = require('../../util'); const { @@ -186,11 +185,6 @@ const batchEvents = (storeSalesEvents) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const storeSalesEvents = []; // list containing store sales events in batched format const clickCallEvents = []; // list containing click and call events in batched format const errorRespList = []; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 599a163c548..ee677373a34 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -64,6 +64,8 @@ const getConversionActionId = async (headers, params) => { destType: 'google_adwords_offline_conversions', feature: 'transformation', endpointPath: `/googleAds:searchStream`, + requestMethod: 'POST', + module: 'dataDelivery', }); searchStreamResponse = processAxiosResponse(searchStreamResponse); if (!isHttpStatusSuccess(searchStreamResponse.status)) { diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index dbd055f1a1e..3045c1713f5 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -41,6 +41,8 @@ const createJob = async (endpoint, headers, method, params) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/customers/create', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -65,6 +67,8 @@ const addUserToJob = async (endpoint, headers, method, jobId, body) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/addOperations', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; @@ -87,6 +91,8 @@ const runTheJob = async (endpoint, headers, method, jobId) => { destType: 'google_adwords_remarketing_lists', feature: 'proxy', endpointPath: '/run', + requestMethod: 'POST', + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index 9526973fb8f..9ab415346a2 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -218,7 +218,7 @@ const processEvent = async (metadata, message, destination) => { } Object.values(createdPayload).forEach((data) => { - const consentObj = populateConsentForGoogleDestinations(message.properties); + const consentObj = populateConsentForGoogleDestinations(destination.Config); response.push(responseBuilder(metadata, data, destination, message, consentObj)); }); return response; diff --git a/src/v0/destinations/google_cloud_function/transform.js b/src/v0/destinations/google_cloud_function/transform.js index b218615b44d..5e870b95817 100644 --- a/src/v0/destinations/google_cloud_function/transform.js +++ b/src/v0/destinations/google_cloud_function/transform.js @@ -1,9 +1,5 @@ const lodash = require('lodash'); -const { - getSuccessRespEvents, - checkInvalidRtTfEvents, - handleRtTfSingleEventError, -} = require('../../util'); +const { getSuccessRespEvents, handleRtTfSingleEventError } = require('../../util'); const { generateBatchedPayload, validateDestinationConfig } = require('./util'); @@ -40,11 +36,6 @@ function batchEvents(successRespList, maxBatchSize = 10) { // Router transform with batching by default const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const successResponseList = []; const errorRespList = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/googlesheets/transform.js b/src/v0/destinations/googlesheets/transform.js index 6e27f6192c1..79dcf1bdf27 100644 --- a/src/v0/destinations/googlesheets/transform.js +++ b/src/v0/destinations/googlesheets/transform.js @@ -5,7 +5,6 @@ const { getValueFromMessage, getSuccessRespEvents, handleRtTfSingleEventError, - checkInvalidRtTfEvents, } = require('../../util'); const SOURCE_KEYS = ['properties', 'traits', 'context.traits']; @@ -111,10 +110,6 @@ const process = (event) => { const processRouterDest = async (inputs, reqMetadata) => { const successRespList = []; const errorRespList = []; - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } await Promise.all( inputs.map(async (input) => { try { diff --git a/src/v0/destinations/hs/transform.js b/src/v0/destinations/hs/transform.js index c26e024a6c5..9eed244af4b 100644 --- a/src/v0/destinations/hs/transform.js +++ b/src/v0/destinations/hs/transform.js @@ -1,11 +1,7 @@ const get = require('get-value'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const { EventType } = require('../../../constants'); -const { - checkInvalidRtTfEvents, - handleRtTfSingleEventError, - getDestinationExternalIDInfoForRetl, -} = require('../../util'); +const { handleRtTfSingleEventError, getDestinationExternalIDInfoForRetl } = require('../../util'); const { API_VERSION } = require('./config'); const { processLegacyIdentify, @@ -71,10 +67,6 @@ const process = async (event) => { // we are batching by default at routerTransform const processRouterDest = async (inputs, reqMetadata) => { let tempInputs = inputs; - const errorRespEvents = checkInvalidRtTfEvents(tempInputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const successRespList = []; const errorRespList = []; diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js index e905ee63c47..359c93dc1a5 100644 --- a/src/v0/destinations/hs/util.js +++ b/src/v0/destinations/hs/util.js @@ -19,6 +19,7 @@ const { getHashFromArray, getDestinationExternalIDInfoForRetl, getValueFromMessage, + isNull, } = require('../../util'); const { CONTACT_PROPERTY_MAP_ENDPOINT, @@ -104,6 +105,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties`, + requestMethod: 'GET', + module: 'router', }); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); } else { @@ -116,6 +119,8 @@ const getProperties = async (destination) => { destType: 'hs', feature: 'transformation', endpointPath: `/properties/v1/contacts/properties?hapikey`, + requestMethod: 'GET', + module: 'router', }, ); hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse); @@ -219,7 +224,9 @@ const getTransformedJSON = async (message, destination, propertyMap) => { // lowercase and replace ' ' & '.' with '_' const hsSupportedKey = formatKey(traitsKey); if (!rawPayload[traitsKey] && propertyMap[hsSupportedKey]) { - let propValue = traits[traitsKey]; + // HS accepts empty string to remove the property from contact + // https://community.hubspot.com/t5/APIs-Integrations/Clearing-values-of-custom-properties-in-Hubspot-contact-using/m-p/409156 + let propValue = isNull(traits[traitsKey]) ? '' : traits[traitsKey]; if (propertyMap[hsSupportedKey] === 'date') { propValue = getUTCMidnightTimeStampValue(propValue); } @@ -365,6 +372,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); @@ -375,6 +384,8 @@ const searchContacts = async (message, destination) => { destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); searchContactsResponse = processAxiosResponse(searchContactsResponse); } @@ -539,6 +550,8 @@ const performHubSpotSearch = async ( destType: 'hs', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(searchResponse); diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js index b91f520adec..2c35f29e533 100644 --- a/src/v0/destinations/intercom/deleteUsers.js +++ b/src/v0/destinations/intercom/deleteUsers.js @@ -39,6 +39,8 @@ const userDeletionHandler = async (userAttributes, config) => { destType: 'intercom', feature: 'deleteUsers', endpointPath: '/user_delete_requests', + requestMethod: 'POST', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -47,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/iterable/deleteUsers.js b/src/v0/destinations/iterable/deleteUsers.js index a179a8930f0..015a9de9a05 100644 --- a/src/v0/destinations/iterable/deleteUsers.js +++ b/src/v0/destinations/iterable/deleteUsers.js @@ -36,6 +36,9 @@ const userDeletionHandler = async (userAttributes, config) => { const resp = await httpDELETE(url, requestOptions, { destType: 'iterable', feature: 'deleteUsers', + endpointPath: '/users/byUserId/uId', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(resp); if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) { @@ -46,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/iterable/transform.js b/src/v0/destinations/iterable/transform.js index 64bdcfcfa46..207a8d11865 100644 --- a/src/v0/destinations/iterable/transform.js +++ b/src/v0/destinations/iterable/transform.js @@ -18,7 +18,6 @@ const { const { constructPayload, defaultRequestConfig, - checkInvalidRtTfEvents, defaultPostRequestConfig, handleRtTfSingleEventError, removeUndefinedAndNullValues, @@ -162,11 +161,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchedEvents = batchEvents(inputs); const response = await Promise.all( batchedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/kafka/transform.js b/src/v0/destinations/kafka/transform.js index b08c7174753..78f278575a5 100644 --- a/src/v0/destinations/kafka/transform.js +++ b/src/v0/destinations/kafka/transform.js @@ -6,7 +6,6 @@ const { getHashFromArray, removeUndefinedAndNullValues, getSuccessRespEvents, - getErrorRespEvents, } = require('../../util'); const filterConfigTopics = (message, destination) => { @@ -38,10 +37,6 @@ const filterConfigTopics = (message, destination) => { const batch = (destEvents) => { const respList = []; - if (!Array.isArray(destEvents) || destEvents.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } // Grouping the events by topic const groupedEvents = groupBy(destEvents, (event) => event.message.topic); diff --git a/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json b/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json index e128f2666c8..b358919bc1e 100644 --- a/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json +++ b/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json @@ -57,7 +57,12 @@ "traits.address.region", "context.traits.region", "context.traits.address.region", - "properties.region" + "properties.region", + "traits.state", + "traits.address.state", + "context.traits.address.state", + "context.traits.state", + "properties.state" ], "required": false }, @@ -77,14 +82,19 @@ "sourceKeys": [ "traits.zip", "traits.postalcode", + "traits.postalCode", "traits.address.zip", "traits.address.postalcode", + "traits.address.postalCode", "context.traits.zip", "context.traits.postalcode", + "context.traits.postalCode", "context.traits.address.zip", "context.traits.address.postalcode", + "context.traits.address.postalCode", "properties.zip", - "properties.postalcode" + "properties.postalcode", + "properties.postalCode" ], "required": false }, @@ -97,5 +107,16 @@ "destKey": "location.timezone", "sourceKeys": ["traits.timezone", "context.traits.timezone", "properties.timezone"], "required": false + }, + { + "destKey": "location.address1", + "sourceKeys": [ + "traits.street", + "traits.address.street", + "context.traits.street", + "context.traits.address.street", + "properties.street" + ], + "required": false } ] diff --git a/src/v0/destinations/klaviyo/data/KlaviyoProfile.json b/src/v0/destinations/klaviyo/data/KlaviyoProfile.json index e2a8d86085b..329ecd978f5 100644 --- a/src/v0/destinations/klaviyo/data/KlaviyoProfile.json +++ b/src/v0/destinations/klaviyo/data/KlaviyoProfile.json @@ -41,7 +41,12 @@ "traits.address.region", "context.traits.region", "context.traits.address.region", - "properties.region" + "properties.region", + "traits.state", + "traits.address.state", + "context.traits.address.state", + "context.traits.state", + "properties.state" ], "required": false }, @@ -61,14 +66,19 @@ "sourceKeys": [ "traits.zip", "traits.postalcode", + "traits.postalCode", "traits.address.zip", "traits.address.postalcode", + "traits.address.postalCode", "context.traits.zip", "context.traits.postalcode", + "context.traits.postalCode", "context.traits.address.zip", "context.traits.address.postalcode", + "context.traits.address.postalCode", "properties.zip", - "properties.postalcode" + "properties.postalcode", + "properties.postalCode" ], "required": false }, @@ -81,5 +91,16 @@ "destKey": "$image", "sourceKeys": ["traits.image", "context.traits.image", "properties.image"], "required": false + }, + { + "destKey": "$address1", + "sourceKeys": [ + "traits.street", + "traits.address.street", + "context.traits.street", + "context.traits.address.street", + "properties.street" + ], + "required": false } ] diff --git a/src/v0/destinations/klaviyo/transform.js b/src/v0/destinations/klaviyo/transform.js index 3c2f8137f27..a0fe3e81a70 100644 --- a/src/v0/destinations/klaviyo/transform.js +++ b/src/v0/destinations/klaviyo/transform.js @@ -32,7 +32,6 @@ const { addExternalIdToTraits, adduserIdFromExternalId, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, flattenJson, isNewStatusCodesAccepted, @@ -320,10 +319,6 @@ const getEventChunks = (event, subscribeRespList, nonSubscribeRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const subscribeRespList = []; diff --git a/src/v0/destinations/klaviyo/util.js b/src/v0/destinations/klaviyo/util.js index 60b334f3a2f..df2dbb47121 100644 --- a/src/v0/destinations/klaviyo/util.js +++ b/src/v0/destinations/klaviyo/util.js @@ -45,6 +45,8 @@ const getIdFromNewOrExistingProfile = async (endpoint, payload, requestOptions) destType: 'klaviyo', feature: 'transformation', endpointPath, + requestMethod: 'POST', + module: 'router', }, ); diff --git a/src/v0/destinations/kustomer/util.js b/src/v0/destinations/kustomer/util.js index 571a03f1399..530983bb26b 100644 --- a/src/v0/destinations/kustomer/util.js +++ b/src/v0/destinations/kustomer/util.js @@ -139,7 +139,13 @@ const fetchKustomer = async (url, destination) => { Authorization: `Bearer ${destination.Config.apiKey}`, }, }, - { destType: 'kustomer', feature: 'transformation' }, + { + destType: 'kustomer', + feature: 'transformation', + endpointPath: '/customers/email', + requestMethod: 'GET', + module: 'processor', + }, ); } catch (err) { if (err.response) { diff --git a/src/v0/destinations/lambda/transform.js b/src/v0/destinations/lambda/transform.js index 1570a69ec31..efc68b89d6f 100644 --- a/src/v0/destinations/lambda/transform.js +++ b/src/v0/destinations/lambda/transform.js @@ -1,5 +1,6 @@ const _ = require('lodash'); -const { getErrorRespEvents, getSuccessRespEvents } = require('../../util'); +const { getErrorRespEvents } = require('@rudderstack/integrations-lib'); +const { getSuccessRespEvents } = require('../../util'); const { ConfigurationError } = require('@rudderstack/integrations-lib'); const DEFAULT_INVOCATION_TYPE = 'Event'; // asynchronous invocation diff --git a/src/v0/destinations/mailchimp/transform.js b/src/v0/destinations/mailchimp/transform.js index 894f70672a5..87f547c1248 100644 --- a/src/v0/destinations/mailchimp/transform.js +++ b/src/v0/destinations/mailchimp/transform.js @@ -3,7 +3,6 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ const { defaultPutRequestConfig, handleRtTfSingleEventError, - checkInvalidRtTfEvents, constructPayload, defaultPostRequestConfig, isDefinedAndNotNull, @@ -162,10 +161,6 @@ const getEventChunks = (event, identifyRespList, trackRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let batchResponseList = []; const batchErrorRespList = []; const identifyRespList = []; diff --git a/src/v0/destinations/mailchimp/utils.js b/src/v0/destinations/mailchimp/utils.js index e1e2e9883b7..1f4fc03ee5b 100644 --- a/src/v0/destinations/mailchimp/utils.js +++ b/src/v0/destinations/mailchimp/utils.js @@ -163,7 +163,13 @@ const checkIfMailExists = async (apiKey, datacenterId, audienceId, email) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId/members/email', + requestMethod: 'GET', + module: 'router', + }, ); if (response?.data?.contact_id) { userStatus.exists = true; @@ -194,7 +200,13 @@ const checkIfDoubleOptIn = async (apiKey, datacenterId, audienceId) => { Authorization: `Basic ${basicAuth}`, }, }, - { destType: 'mailchimp', feature: 'transformation' }, + { + destType: 'mailchimp', + feature: 'transformation', + endpointPath: '/lists/audienceId', + requestMethod: 'GET', + module: 'router', + }, ); } catch (error) { const status = error.status || 400; diff --git a/src/v0/destinations/mailjet/transform.js b/src/v0/destinations/mailjet/transform.js index 9156bf45e95..78b4f766d1d 100644 --- a/src/v0/destinations/mailjet/transform.js +++ b/src/v0/destinations/mailjet/transform.js @@ -1,7 +1,6 @@ const lodash = require('lodash'); const { TransformationError, InstrumentationError } = require('@rudderstack/integrations-lib'); const { - getErrorRespEvents, getSuccessRespEvents, defaultRequestConfig, defaultPostRequestConfig, @@ -121,10 +120,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/mailmodo/transform.js b/src/v0/destinations/mailmodo/transform.js index 756522939da..a61ca8a73d0 100644 --- a/src/v0/destinations/mailmodo/transform.js +++ b/src/v0/destinations/mailmodo/transform.js @@ -10,7 +10,6 @@ const { defaultPostRequestConfig, defaultBatchRequestConfig, removeUndefinedAndNullValues, - getErrorRespEvents, getSuccessRespEvents, handleRtTfSingleEventError, } = require('../../util'); @@ -191,11 +190,6 @@ function getEventChunks(event, identifyEventChunks, eventResponseList) { } const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const identifyEventChunks = []; // list containing identify events in batched format const eventResponseList = []; // list containing other events in batched format const errorRespList = []; diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js index 1d4b316e8d4..ac555accfe2 100644 --- a/src/v0/destinations/marketo/networkHandler.js +++ b/src/v0/destinations/marketo/networkHandler.js @@ -4,7 +4,7 @@ const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network const { processAxiosResponse } = require('../../../adapters/utils/networkUtils'); const responseHandler = (responseParams) => { - const { destinationResponse, destType,rudderJobMetadata } = responseParams; + const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); diff --git a/src/v0/destinations/marketo/transform.js b/src/v0/destinations/marketo/transform.js index 5000ef506b3..b811596f95a 100644 --- a/src/v0/destinations/marketo/transform.js +++ b/src/v0/destinations/marketo/transform.js @@ -7,6 +7,7 @@ const { InstrumentationError, ConfigurationError, UnauthorizedError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const stats = require('../../../util/stats'); const { EventType, MappedToDestinationKey } = require('../../../constants'); @@ -28,10 +29,8 @@ const { getFieldValueFromMessage, getDestinationExternalID, getSuccessRespEvents, - getErrorRespEvents, isDefinedAndNotNull, generateErrorObject, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const Cache = require('../../util/cache'); @@ -456,10 +455,6 @@ const process = async (event) => { const processRouterDest = async (inputs, reqMetadata) => { // Token needs to be generated for marketo which will be done on input level. // If destination information is not present Error should be thrown - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let token; try { token = await getAuthToken(formatConfig(inputs[0].destination)); diff --git a/src/v0/destinations/marketo/util.js b/src/v0/destinations/marketo/util.js index 54ff70708a7..b3a24fb4110 100644 --- a/src/v0/destinations/marketo/util.js +++ b/src/v0/destinations/marketo/util.js @@ -248,6 +248,8 @@ const sendGetRequest = async (url, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; @@ -264,6 +266,8 @@ const sendPostRequest = async (url, data, options) => { destType: 'marketo', feature: 'transformation', endpointPath: `/v1/leads`, + requestMethod: 'POST', + module: 'router', }); const processedResponse = processAxiosResponse(clientResponse); return processedResponse; diff --git a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js index e6f56620003..db3b13eeb84 100644 --- a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js +++ b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js @@ -33,6 +33,9 @@ const getJobsStatus = async (event, type, accessToken) => { const { processedResponse: resp } = await handleHttpRequest('get', url, requestOptions, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/', + requestMethod: 'GET', + module: 'router', }); const endTime = Date.now(); const requestTime = endTime - startTime; diff --git a/src/v0/destinations/marketo_bulk_upload/fileUpload.js b/src/v0/destinations/marketo_bulk_upload/fileUpload.js index 9c42fdc98d0..b49a265fd53 100644 --- a/src/v0/destinations/marketo_bulk_upload/fileUpload.js +++ b/src/v0/destinations/marketo_bulk_upload/fileUpload.js @@ -198,6 +198,9 @@ const getImportID = async (input, config, accessToken, csvHeader) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads.json', + requestMethod: 'POST', + module: 'router', }, ); const endTime = Date.now(); diff --git a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js index 875b0d8280a..aa4b3aacc4f 100644 --- a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js +++ b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js @@ -296,6 +296,10 @@ describe('getAccessToken', () => { expect(handleHttpRequest).toHaveBeenCalledWith('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + feature: 'transformation', + module: 'router', + requestMethod: 'GET', }); }); diff --git a/src/v0/destinations/marketo_bulk_upload/poll.js b/src/v0/destinations/marketo_bulk_upload/poll.js index db7a6347744..f53347d6e52 100644 --- a/src/v0/destinations/marketo_bulk_upload/poll.js +++ b/src/v0/destinations/marketo_bulk_upload/poll.js @@ -26,6 +26,9 @@ const getPollStatus = async (event) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/batch/importId.json', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(pollStatus.status)) { diff --git a/src/v0/destinations/marketo_bulk_upload/util.js b/src/v0/destinations/marketo_bulk_upload/util.js index fac04af4316..4c99ba7483f 100644 --- a/src/v0/destinations/marketo_bulk_upload/util.js +++ b/src/v0/destinations/marketo_bulk_upload/util.js @@ -128,6 +128,9 @@ const getAccessToken = async (config) => { const { processedResponse: accessTokenResponse } = await handleHttpRequest('get', url, { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/identity/oauth/token', + requestMethod: 'GET', + module: 'router', }); // sample response : {response: '[ENOTFOUND] :: DNS lookup failed', status: 400} @@ -352,6 +355,9 @@ const getFieldSchemaMap = async (accessToken, munchkinId) => { { destType: 'marketo_bulk_upload', feature: 'transformation', + endpointPath: '/leads/describe2.json', + requestMethod: 'GET', + module: 'router', }, ); diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js index 9e73cd1f91a..086378cf6ac 100644 --- a/src/v0/destinations/marketo_static_list/networkHandler.js +++ b/src/v0/destinations/marketo_static_list/networkHandler.js @@ -7,7 +7,7 @@ const { DESTINATION } = require('./config'); const responseHandler = (responseParams) => { const { destinationResponse, destType, rudderJobMetadata } = responseParams; const message = 'Request Processed Successfully'; - const { status} = destinationResponse; + const { status } = destinationResponse; const authCache = v0Utils.getDestAuthCacheInstance(destType); // check for marketo application level failures marketoResponseHandler( diff --git a/src/v0/destinations/marketo_static_list/transform.js b/src/v0/destinations/marketo_static_list/transform.js index 294e34f91bc..92c137c614d 100644 --- a/src/v0/destinations/marketo_static_list/transform.js +++ b/src/v0/destinations/marketo_static_list/transform.js @@ -1,6 +1,10 @@ const lodash = require('lodash'); const cloneDeep = require('lodash/cloneDeep'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -9,11 +13,7 @@ const { } = require('../../util'); const { AUTH_CACHE_TTL, JSON_MIME_TYPE } = require('../../util/constant'); const { getIds, validateMessageType } = require('./util'); -const { - getDestinationExternalID, - defaultRequestConfig, - getErrorRespEvents, -} = require('../../util'); +const { getDestinationExternalID, defaultRequestConfig } = require('../../util'); const { formatConfig, MAX_LEAD_IDS_SIZE } = require('./config'); const Cache = require('../../util/cache'); const { getAuthToken } = require('../marketo/transform'); diff --git a/src/v0/destinations/marketo_static_list/transformV2.js b/src/v0/destinations/marketo_static_list/transformV2.js index 912d548d098..73d4bec8f81 100644 --- a/src/v0/destinations/marketo_static_list/transformV2.js +++ b/src/v0/destinations/marketo_static_list/transformV2.js @@ -1,5 +1,9 @@ const lodash = require('lodash'); -const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib'); +const { + InstrumentationError, + UnauthorizedError, + getErrorRespEvents, +} = require('@rudderstack/integrations-lib'); const { defaultPostRequestConfig, defaultDeleteRequestConfig, @@ -7,7 +11,6 @@ const { getSuccessRespEvents, isDefinedAndNotNull, generateErrorObject, - getErrorRespEvents, } = require('../../util'); const { JSON_MIME_TYPE } = require('../../util/constant'); const { MAX_LEAD_IDS_SIZE } = require('./config'); diff --git a/src/v0/destinations/mautic/utils.js b/src/v0/destinations/mautic/utils.js index d8ad8dbffcd..7a1827e769a 100644 --- a/src/v0/destinations/mautic/utils.js +++ b/src/v0/destinations/mautic/utils.js @@ -183,6 +183,9 @@ const searchContactIds = async (message, Config, baseUrl) => { { destType: 'mautic', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }, ); searchContactsResponse = processAxiosResponse(searchContactsResponse); diff --git a/src/v0/destinations/monday/util.js b/src/v0/destinations/monday/util.js index 736f0133fde..872fad42a75 100644 --- a/src/v0/destinations/monday/util.js +++ b/src/v0/destinations/monday/util.js @@ -195,6 +195,8 @@ const getBoardDetails = async (url, boardID, apiToken) => { destType: 'monday', feature: 'transformation', endpointPath: '/v2', + requestMethod: 'POST', + module: 'router', }, ); const boardDetailsResponse = processAxiosResponse(clientResponse); diff --git a/src/v0/destinations/mp/deleteUsers.js b/src/v0/destinations/mp/deleteUsers.js index f01475ef2b8..e1240c609d6 100644 --- a/src/v0/destinations/mp/deleteUsers.js +++ b/src/v0/destinations/mp/deleteUsers.js @@ -49,6 +49,8 @@ const deleteProfile = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { @@ -57,6 +59,7 @@ const deleteProfile = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); @@ -104,6 +107,8 @@ const createDeletionTask = async (userAttributes, config) => { destType: 'mp', feature: 'deleteUsers', endpointPath, + requestMethod: 'POST', + module: 'deletion', }, ); if (!isHttpStatusSuccess(handledDelResponse.status)) { diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js index 24890c0eb1e..10271bebefc 100644 --- a/src/v0/destinations/mp/transform.js +++ b/src/v0/destinations/mp/transform.js @@ -14,7 +14,6 @@ const { removeUndefinedValues, toUnixTimestampInMS, getFieldValueFromMessage, - checkInvalidRtTfEvents, handleRtTfSingleEventError, groupEventsByType, parseConfigArray, @@ -37,6 +36,7 @@ const { groupEventsByEndpoint, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { CommonUtils } = require('../../../util/common'); @@ -298,17 +298,25 @@ const processIdentifyEvents = async (message, type, destination) => { }; const processPageOrScreenEvents = (message, type, destination) => { + const { + token, + identityMergeApi, + useUserDefinedPageEventName, + userDefinedPageEventTemplate, + useUserDefinedScreenEventName, + userDefinedScreenEventTemplate, + } = destination.Config; const mappedProperties = constructPayload(message, mPEventPropertiesConfigJson); let properties = { ...get(message, 'context.traits'), ...message.properties, ...mappedProperties, - token: destination.Config.token, + token, distinct_id: message.userId || message.anonymousId, time: toUnixTimestampInMS(message.timestamp || message.originalTimestamp), ...buildUtmParams(message.context?.campaign), }; - if (destination.Config?.identityMergeApi === 'simplified') { + if (identityMergeApi === 'simplified') { properties = { ...properties, distinct_id: message.userId || `$device:${message.anonymousId}`, @@ -327,7 +335,18 @@ const processPageOrScreenEvents = (message, type, destination) => { properties.$browser = browser.name; properties.$browser_version = browser.version; } - const eventName = type === 'page' ? 'Loaded a Page' : 'Loaded a Screen'; + + let eventName; + if (type === 'page') { + eventName = useUserDefinedPageEventName + ? generatePageOrScreenCustomEventName(message, userDefinedPageEventTemplate) + : 'Loaded a Page'; + } else { + eventName = useUserDefinedScreenEventName + ? generatePageOrScreenCustomEventName(message, userDefinedScreenEventTemplate) + : 'Loaded a Screen'; + } + const payload = { event: eventName, properties, @@ -460,11 +479,6 @@ const process = (event) => processSingleMessage(event.message, event.destination // Ref: https://help.mixpanel.com/hc/en-us/articles/115004613766-Default-Properties-Collected-by-Mixpanel // Ref: https://help.mixpanel.com/hc/en-us/articles/115004561786-Track-UTM-Tags const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const groupedEvents = groupEventsByType(inputs); const response = await Promise.all( groupedEvents.map(async (listOfEvents) => { diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js index 8e943f41dd0..f56242d88b9 100644 --- a/src/v0/destinations/mp/util.js +++ b/src/v0/destinations/mp/util.js @@ -1,7 +1,7 @@ const lodash = require('lodash'); const set = require('set-value'); const get = require('get-value'); -const { InstrumentationError } = require('@rudderstack/integrations-lib'); +const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib'); const { isDefined, constructPayload, @@ -16,6 +16,7 @@ const { IsGzipSupported, isObject, isDefinedAndNotNullAndNotEmpty, + isDefinedAndNotNull, } = require('../../util'); const { ConfigCategory, @@ -301,6 +302,46 @@ function trimTraits(traits, contextTraits, setOnceProperties) { }; } +/** + * Generates a custom event name for a page or screen. + * + * @param {Object} message - The message object + * @param {string} userDefinedEventTemplate - The user-defined event template to be used for generating the event name. + * @throws {ConfigurationError} If the event template is missing. + * @returns {string} The generated custom event name. + * @example + * const userDefinedEventTemplate = "Viewed {{ category }} {{ name }} Page"; + * const message = {name: 'Home', properties: {category: 'Index'}}; + * output: "Viewed Index Home Page" + */ +const generatePageOrScreenCustomEventName = (message, userDefinedEventTemplate) => { + if (!userDefinedEventTemplate) { + throw new ConfigurationError( + 'Event name template is not configured. Please provide a valid value for the `Page/Screen Event Name Template` in the destination dashboard.', + ); + } + + let eventName = userDefinedEventTemplate; + + if (isDefinedAndNotNull(message.properties?.category)) { + // Replace {{ category }} with actual values + eventName = eventName.replace(/{{\s*category\s*}}/g, message.properties.category); + } else { + // find {{ category }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*category\s*}}\s/g, ' '); + } + + if (isDefinedAndNotNull(message.name)) { + // Replace {{ name }} with actual values + eventName = eventName.replace(/{{\s*name\s*}}/g, message.name); + } else { + // find {{ name }} surrounded by whitespace characters and replace it with a single whitespace character + eventName = eventName.replace(/\s{{\s*name\s*}}\s/g, ' '); + } + + return eventName; +}; + module.exports = { createIdentifyResponse, isImportAuthCredentialsAvailable, @@ -309,4 +350,5 @@ module.exports = { generateBatchedPayloadForArray, batchEvents, trimTraits, + generatePageOrScreenCustomEventName, }; diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js index 866119a336c..40cdb346492 100644 --- a/src/v0/destinations/mp/util.test.js +++ b/src/v0/destinations/mp/util.test.js @@ -4,45 +4,88 @@ const { generateBatchedPayloadForArray, buildUtmParams, trimTraits, + generatePageOrScreenCustomEventName, } = require('./util'); const { FEATURE_GZIP_SUPPORT } = require('../../util/constant'); - -const destinationMock = { - Config: { - token: 'test_api_token', - prefixProperties: true, - useNativeSDK: false, - useOldMapping: true, - }, - DestinationDefinition: { - DisplayName: 'Mixpanel', - ID: 'test_destination_definition_id', - Name: 'MP', - }, - Enabled: true, - ID: 'test_id', - Name: 'Mixpanel', - Transformations: [], -}; +const { ConfigurationError } = require('@rudderstack/integrations-lib'); const maxBatchSizeMock = 2; -describe('Mixpanel utils test', () => { - describe('Unit test cases for groupEventsByEndpoint', () => { - it('should return an object with empty arrays for all properties when the events array is empty', () => { - const events = []; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [], - groupsEvents: [], - trackEvents: [], - importEvents: [], - batchErrorRespList: [], - }); +describe('Unit test cases for groupEventsByEndpoint', () => { + it('should return an object with empty arrays for all properties when the events array is empty', () => { + const events = []; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [], + groupsEvents: [], + trackEvents: [], + importEvents: [], + batchErrorRespList: [], }); + }); - it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { - const events = [ + it('should return an object with all properties containing their respective events when the events array contains events of all types', () => { + const events = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:1}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{prop:2}]', + }, + }, + userId: 'user2', + }, + }, + { + message: { + endpoint: '/groups', + body: { + JSON_ARRAY: { + batch: '[{prop:3}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/track', + body: { + JSON_ARRAY: { + batch: '[{prop:4}]', + }, + }, + userId: 'user1', + }, + }, + { + message: { + endpoint: '/import', + body: { + JSON_ARRAY: { + batch: '[{prop:5}]', + }, + }, + userId: 'user2', + }, + }, + { error: 'Message type abc not supported' }, + ]; + const result = groupEventsByEndpoint(events); + expect(result).toEqual({ + engageEvents: [ { message: { endpoint: '/engage', @@ -65,6 +108,8 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, + ], + groupsEvents: [ { message: { endpoint: '/groups', @@ -76,6 +121,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + trackEvents: [ { message: { endpoint: '/track', @@ -87,6 +134,8 @@ describe('Mixpanel utils test', () => { userId: 'user1', }, }, + ], + importEvents: [ { message: { endpoint: '/import', @@ -98,371 +147,359 @@ describe('Mixpanel utils test', () => { userId: 'user2', }, }, - { error: 'Message type abc not supported' }, - ]; - const result = groupEventsByEndpoint(events); - expect(result).toEqual({ - engageEvents: [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:1}]', - }, - }, - userId: 'user1', - }, - }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{prop:2}]', - }, - }, - userId: 'user2', - }, - }, - ], - groupsEvents: [ - { - message: { - endpoint: '/groups', - body: { - JSON_ARRAY: { - batch: '[{prop:3}]', - }, - }, - userId: 'user1', - }, - }, - ], - trackEvents: [ - { - message: { - endpoint: '/track', - body: { - JSON_ARRAY: { - batch: '[{prop:4}]', - }, - }, - userId: 'user1', - }, - }, - ], - importEvents: [ - { - message: { - endpoint: '/import', - body: { - JSON_ARRAY: { - batch: '[{prop:5}]', - }, - }, - userId: 'user2', - }, - }, - ], - batchErrorRespList: [{ error: 'Message type abc not supported' }], - }); + ], + batchErrorRespList: [{ error: 'Message type abc not supported' }], }); }); +}); - describe('Unit test cases for batchEvents', () => { - it('should return an array of batched events with correct payload and metadata', () => { - const successRespList = [ - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":1}]', - }, +describe('Unit test cases for batchEvents', () => { + it('should return an array of batched events with correct payload and metadata', () => { + const successRespList = [ + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":1}]', }, - headers: {}, - params: {}, - userId: 'user1', }, - metadata: { jobId: 3 }, + headers: {}, + params: {}, + userId: 'user1', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":2}]', - }, + metadata: { jobId: 3 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":2}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 4 }, + headers: {}, + params: {}, + userId: 'user2', }, - { - message: { - endpoint: '/engage', - body: { - JSON_ARRAY: { - batch: '[{"prop":3}]', - }, + metadata: { jobId: 4 }, + }, + { + message: { + endpoint: '/engage', + body: { + JSON_ARRAY: { + batch: '[{"prop":3}]', }, - headers: {}, - params: {}, - userId: 'user2', }, - metadata: { jobId: 6 }, + headers: {}, + params: {}, + userId: 'user2', }, - ]; - - const result = batchEvents(successRespList, maxBatchSizeMock); - - expect(result).toEqual([ - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 3 }, { jobId: 4 }], - statusCode: 200, + metadata: { jobId: 6 }, + }, + ]; + + const result = batchEvents(successRespList, maxBatchSizeMock); + + expect(result).toEqual([ + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":1},{"prop":2}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - { - batched: true, - batchedRequest: { - body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, - endpoint: '/engage', - files: {}, - headers: {}, - method: 'POST', - params: {}, - type: 'REST', - version: '1', - }, - destination: undefined, - metadata: [{ jobId: 6 }], - statusCode: 200, + destination: undefined, + metadata: [{ jobId: 3 }, { jobId: 4 }], + statusCode: 200, + }, + { + batched: true, + batchedRequest: { + body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} }, + endpoint: '/engage', + files: {}, + headers: {}, + method: 'POST', + params: {}, + type: 'REST', + version: '1', }, - ]); - }); + destination: undefined, + metadata: [{ jobId: 6 }], + statusCode: 200, + }, + ]); + }); - it('should return an empty array when successRespList is empty', () => { - const successRespList = []; - const result = batchEvents(successRespList, maxBatchSizeMock); - expect(result).toEqual([]); - }); + it('should return an empty array when successRespList is empty', () => { + const successRespList = []; + const result = batchEvents(successRespList, maxBatchSizeMock); + expect(result).toEqual([]); }); +}); - describe('Unit test cases for generateBatchedPayloadForArray', () => { - it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/import', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: {}, - XML: {}, - GZIP: { - payload: '[{"event":"event1"},{"event":"event2"}]', - }, - }, +describe('Unit test cases for generateBatchedPayloadForArray', () => { + it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/import', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/import', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + GZIP: { + payload: '[{"event":"event1"},{"event":"event2"}]', + }, + }, + endpoint: '/import', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); - it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { - const events = [ - { - body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - { - body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, - endpoint: '/endpoint', - headers: { 'Content-Type': 'application/json' }, - params: {}, - }, - ]; - const expectedBatchedRequest = { - body: { - FORM: {}, - JSON: {}, - JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, - XML: {}, - }, + expect(result).toEqual(expectedBatchedRequest); + }); + + it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => { + const events = [ + { + body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } }, endpoint: '/endpoint', - files: {}, headers: { 'Content-Type': 'application/json' }, - method: 'POST', params: {}, - type: 'REST', - version: '1', - }; - - const result = generateBatchedPayloadForArray(events, { - features: { [FEATURE_GZIP_SUPPORT]: true }, - }); - - expect(result).toEqual(expectedBatchedRequest); + }, + { + body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } }, + endpoint: '/endpoint', + headers: { 'Content-Type': 'application/json' }, + params: {}, + }, + ]; + const expectedBatchedRequest = { + body: { + FORM: {}, + JSON: {}, + JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' }, + XML: {}, + }, + endpoint: '/endpoint', + files: {}, + headers: { 'Content-Type': 'application/json' }, + method: 'POST', + params: {}, + type: 'REST', + version: '1', + }; + + const result = generateBatchedPayloadForArray(events, { + features: { [FEATURE_GZIP_SUPPORT]: true }, }); + + expect(result).toEqual(expectedBatchedRequest); }); +}); - describe('Unit test cases for buildUtmParams', () => { - it('should return an empty object when campaign is undefined', () => { - const campaign = undefined; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); +describe('Unit test cases for buildUtmParams', () => { + it('should return an empty object when campaign is undefined', () => { + const campaign = undefined; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is an empty object', () => { - const campaign = {}; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is an empty object', () => { + const campaign = {}; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should return an empty object when campaign is not an object', () => { - const campaign = [{ name: 'test' }]; - const result = buildUtmParams(campaign); - expect(result).toEqual({}); - }); + it('should return an empty object when campaign is not an object', () => { + const campaign = [{ name: 'test' }]; + const result = buildUtmParams(campaign); + expect(result).toEqual({}); + }); - it('should handle campaign object with null/undefined values', () => { - const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; - const result = buildUtmParams(campaign); - expect(result).toEqual({ - utm_campaign: null, - utm_source: 'rudder', - utm_medium: 'rudder', - test: undefined, - }); + it('should handle campaign object with null/undefined values', () => { + const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined }; + const result = buildUtmParams(campaign); + expect(result).toEqual({ + utm_campaign: null, + utm_source: 'rudder', + utm_medium: 'rudder', + test: undefined, }); }); - describe('Unit test cases for trimTraits', () => { - // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties. - it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email']; - - const result = trimTraits(traits, contextTraits, setOnceProperties); - - expect(result).toEqual({ - traits: { - age: 30, - }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); +}); +describe('Unit test cases for trimTraits', () => { + // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties. + it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email']; + + const result = trimTraits(traits, contextTraits, setOnceProperties); + + expect(result).toEqual({ + traits: { + age: 30, + }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property. - it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => { - const traits = {}; - const contextTraits = {}; - const setOnceProperties = ['name', 'email']; + // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property. + it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => { + const traits = {}; + const contextTraits = {}; + const setOnceProperties = ['name', 'email']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: {}, - contextTraits: {}, - setOnce: {}, - }); + expect(result).toEqual({ + traits: {}, + contextTraits: {}, + setOnce: {}, }); + }); - // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property. - it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = []; + // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property. + it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = []; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { name: 'John', age: 30 }, - contextTraits: { email: 'john@example.com' }, - setOnce: {}, - }); + expect(result).toEqual({ + traits: { name: 'John', age: 30 }, + contextTraits: { email: 'john@example.com' }, + setOnce: {}, }); + }); - // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. - it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => { - const traits = { name: 'John', age: 30 }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address']; + // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. + it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => { + const traits = { name: 'John', age: 30 }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30 }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + expect(result).toEqual({ + traits: { age: 30 }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. - it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => { - const traits = { name: 'John', age: 30, address: 'kolkata' }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address.city']; + // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property. + it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => { + const traits = { name: 'John', age: 30, address: 'kolkata' }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address.city']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30, address: 'kolkata' }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com' }, - }); + expect(result).toEqual({ + traits: { age: 30, address: 'kolkata' }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com' }, }); + }); - it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => { - const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false }; - const contextTraits = { email: 'john@example.com' }; - const setOnceProperties = ['name', 'email', 'address.city']; + it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => { + const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false }; + const contextTraits = { email: 'john@example.com' }; + const setOnceProperties = ['name', 'email', 'address.city']; - const result = trimTraits(traits, contextTraits, setOnceProperties); + const result = trimTraits(traits, contextTraits, setOnceProperties); - expect(result).toEqual({ - traits: { age: 30, address: {}, isAdult: false }, - contextTraits: {}, - setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' }, - }); + expect(result).toEqual({ + traits: { age: 30, address: {}, isAdult: false }, + contextTraits: {}, + setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' }, }); }); }); + +describe('generatePageOrScreenCustomEventName', () => { + it('should throw a ConfigurationError when userDefinedEventTemplate is not provided', () => { + const message = { name: 'Home' }; + const userDefinedEventTemplate = undefined; + expect(() => { + generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + }).toThrow(ConfigurationError); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and message object is provided', () => { + let message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page'; + let expected = 'Viewed Integration Doc page'; + let result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + + message = { name: true, properties: { category: 0 } }; + expected = 'Viewed 0 true page'; + result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains event template and category or name is missing in message object', () => { + const message = { name: 'Doc', properties: { category: undefined } }; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed Doc page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should generate a custom event name when userDefinedEventTemplate contains only category or name placeholder and message object is provided', () => { + const message = { name: 'Doc', properties: { category: 'Integration' } }; + const userDefinedEventTemplate = 'Viewed {{ name }} page'; + const expected = 'Viewed Doc page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return the userDefinedEventTemplate when it does not contain placeholder {{}}', () => { + const message = { name: 'Index' }; + const userDefinedEventTemplate = 'Viewed a Home page'; + const expected = 'Viewed a Home page'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); + + it('should return a event name when message object is not provided/empty', () => { + const message = {}; + const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword'; + const expected = 'Viewed page someKeyword'; + const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate); + expect(result).toBe(expected); + }); +}); diff --git a/src/v0/destinations/ometria/transform.js b/src/v0/destinations/ometria/transform.js index 55038e10b83..5eff77bd15d 100644 --- a/src/v0/destinations/ometria/transform.js +++ b/src/v0/destinations/ometria/transform.js @@ -14,7 +14,6 @@ const { getFieldValueFromMessage, getIntegrationsObj, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util/index'); const { @@ -250,10 +249,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const inputChunks = returnArrayOfSubarrays(inputs, MAX_BATCH_SIZE); const successList = []; const errorList = []; diff --git a/src/v0/destinations/one_signal/transform.js b/src/v0/destinations/one_signal/transform.js index a072aef0e4f..b025660fa42 100644 --- a/src/v0/destinations/one_signal/transform.js +++ b/src/v0/destinations/one_signal/transform.js @@ -122,7 +122,7 @@ const trackResponseBuilder = (message, { Config }) => { if (!externalUserId) { throw new InstrumentationError('userId is required for track events/updating a device'); } - endpoint = `${endpoint}/${appId}/users/${externalUserId}`; + endpoint = `${endpoint}/${appId}/users/${encodeURIComponent(externalUserId)}`; const payload = {}; const tags = {}; /* Populating event as true in tags. @@ -163,7 +163,7 @@ const groupResponseBuilder = (message, { Config }) => { if (!externalUserId) { throw new InstrumentationError('userId is required for group events'); } - endpoint = `${endpoint}/${appId}/users/${externalUserId}`; + endpoint = `${endpoint}/${appId}/users/${encodeURIComponent(externalUserId)}`; const payload = {}; const tags = { groupId, diff --git a/src/v0/destinations/pardot/transform.js b/src/v0/destinations/pardot/transform.js index b32b8967bd2..3dbe57ecc74 100644 --- a/src/v0/destinations/pardot/transform.js +++ b/src/v0/destinations/pardot/transform.js @@ -44,7 +44,6 @@ const { getFieldValueFromMessage, removeUndefinedValues, getSuccessRespEvents, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getAccessToken, } = require('../../util'); @@ -150,11 +149,6 @@ const processEvent = (metadata, message, destination) => { const process = (event) => processEvent(event.metadata, event.message, event.destination); const processRouterDest = (events, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(events); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const responseList = events.map((event) => { try { return getSuccessRespEvents(process(event), [event.metadata], event.destination); diff --git a/src/v0/destinations/pinterest_tag/transform.js b/src/v0/destinations/pinterest_tag/transform.js index ee7e2e5b192..f8ccfd48eab 100644 --- a/src/v0/destinations/pinterest_tag/transform.js +++ b/src/v0/destinations/pinterest_tag/transform.js @@ -5,7 +5,6 @@ const { defaultRequestConfig, defaultPostRequestConfig, getSuccessRespEvents, - getErrorRespEvents, constructPayload, defaultBatchRequestConfig, removeUndefinedAndNullValues, @@ -172,11 +171,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } - const successRespList = []; const batchErrorRespList = []; inputs.forEach((input) => { diff --git a/src/v0/destinations/profitwell/utils.js b/src/v0/destinations/profitwell/utils.js index acc4db20353..1b235617217 100644 --- a/src/v0/destinations/profitwell/utils.js +++ b/src/v0/destinations/profitwell/utils.js @@ -188,6 +188,9 @@ const getSubscriptionHistory = async (endpoint, options) => { const res = await httpGET(endpoint, requestOptions, { destType: 'profitwell', feature: 'transformation', + endpointPath: '/users/userId', + requestMethod: 'GET', + module: 'router', }); return res; }; diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js index 6c89d83947d..4c97a23e518 100644 --- a/src/v0/destinations/rakuten/networkHandler.js +++ b/src/v0/destinations/rakuten/networkHandler.js @@ -18,7 +18,13 @@ const proxyRequest = async (request, destType) => { headers, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType, + endpointPath: '/ep', + requestMethod: 'GET', + module: 'dataDelivery', + }); return response; }; const extractContent = (xmlPayload, tagName) => { diff --git a/src/v0/destinations/salesforce/transform.js b/src/v0/destinations/salesforce/transform.js index 5ada9dfaa07..b8f032c5bfe 100644 --- a/src/v0/destinations/salesforce/transform.js +++ b/src/v0/destinations/salesforce/transform.js @@ -3,6 +3,7 @@ const cloneDeep = require('lodash/cloneDeep'); const { InstrumentationError, NetworkInstrumentationError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const { EventType, MappedToDestinationKey } = require('../../../constants'); const { @@ -20,10 +21,8 @@ const { constructPayload, getFirstAndLastName, getSuccessRespEvents, - getErrorRespEvents, addExternalIdToTraits, getDestinationExternalIDObjectForRetl, - checkInvalidRtTfEvents, handleRtTfSingleEventError, generateErrorObject, isHttpStatusSuccess, @@ -120,6 +119,9 @@ async function getSaleforceIdForRecord( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); if (!isHttpStatusSuccess(processedsfSearchResponse.status)) { @@ -233,6 +235,9 @@ async function getSalesforceIdFromPayload( { destType: 'salesforce', feature: 'transformation', + endpointPath: '/parameterizedSearch', + requestMethod: 'GET', + module: 'router', }, ); @@ -348,10 +353,6 @@ async function process(event) { } const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } let authInfo; try { authInfo = await collectAuthorizationInfo(inputs[0]); diff --git a/src/v0/destinations/salesforce/utils.js b/src/v0/destinations/salesforce/utils.js index 96735ecc174..85061ce2b2a 100644 --- a/src/v0/destinations/salesforce/utils.js +++ b/src/v0/destinations/salesforce/utils.js @@ -133,6 +133,9 @@ const getAccessToken = async (destination) => { { destType: 'salesforce', feature: 'transformation', + endpointPath: '/services/oauth2/token', + requestMethod: 'POST', + module: 'router', }, ); // If the request fails, throwing error. diff --git a/src/v0/destinations/sendgrid/deleteUsers.js b/src/v0/destinations/sendgrid/deleteUsers.js index 8410f41296f..ccd277d90d6 100644 --- a/src/v0/destinations/sendgrid/deleteUsers.js +++ b/src/v0/destinations/sendgrid/deleteUsers.js @@ -85,6 +85,9 @@ const userDeletionHandler = async (userAttributes, config) => { const deletionResponse = await httpDELETE(endpoint, requestOptions, { destType: 'sendgrid', feature: 'deleteUsers', + endpointPath: '/marketing/contacts', + requestMethod: 'DELETE', + module: 'deletion', }); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -94,6 +97,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/sendgrid/transform.js b/src/v0/destinations/sendgrid/transform.js index 5038fedf7b7..c32e34c4890 100644 --- a/src/v0/destinations/sendgrid/transform.js +++ b/src/v0/destinations/sendgrid/transform.js @@ -9,7 +9,6 @@ const { ErrorMessage, isEmptyObject, constructPayload, - getErrorRespEvents, extractCustomFields, getValueFromMessage, defaultRequestConfig, @@ -236,10 +235,6 @@ const batchEvents = (successRespList) => { }; const processRouterDest = async (inputs, reqMetadata) => { - if (!Array.isArray(inputs) || inputs.length <= 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); - return [respEvents]; - } let batchResponseList = []; const batchErrorRespList = []; const successRespList = []; diff --git a/src/v0/destinations/sendgrid/util.js b/src/v0/destinations/sendgrid/util.js index 1df34bfe69d..7105c5cda5f 100644 --- a/src/v0/destinations/sendgrid/util.js +++ b/src/v0/destinations/sendgrid/util.js @@ -445,6 +445,9 @@ const fetchCustomFields = async (destination) => { const resonse = await httpGET(endpoint, requestOptions, { destType: 'sendgrid', feature: 'transformation', + endpointPath: '/marketing/field_definitions', + requestMethod: 'GET', + module: 'router', }); const processedResponse = processAxiosResponse(resonse); if (isHttpStatusSuccess(processedResponse.status)) { diff --git a/src/v0/destinations/sendinblue/util.js b/src/v0/destinations/sendinblue/util.js index 9ad37fc9b79..9fded8e493b 100644 --- a/src/v0/destinations/sendinblue/util.js +++ b/src/v0/destinations/sendinblue/util.js @@ -60,6 +60,9 @@ const checkIfContactExists = async (identifier, apiKey) => { const contactDetailsResponse = await httpGET(endpoint, requestOptions, { destType: 'sendinblue', feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', }); const processedContactDetailsResponse = processAxiosResponse(contactDetailsResponse); diff --git a/src/v0/destinations/sfmc/config.js b/src/v0/destinations/sfmc/config.js index f856c44d6b3..1b1f5c323ba 100644 --- a/src/v0/destinations/sfmc/config.js +++ b/src/v0/destinations/sfmc/config.js @@ -4,6 +4,7 @@ const ENDPOINTS = { GET_TOKEN: `auth.marketingcloudapis.com/v2/token`, CONTACTS: `rest.marketingcloudapis.com/contacts/v1/contacts`, INSERT_CONTACTS: `rest.marketingcloudapis.com/hub/v1/dataevents/key:`, + EVENT: 'rest.marketingcloudapis.com/interaction/v1/events', }; const CONFIG_CATEGORIES = { diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js index 7623d751f1a..53925bc7ed5 100644 --- a/src/v0/destinations/sfmc/transform.js +++ b/src/v0/destinations/sfmc/transform.js @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ /* eslint-disable no-nested-ternary */ const { NetworkError, @@ -44,7 +45,13 @@ const getToken = async (clientId, clientSecret, subdomain) => { { 'Content-Type': JSON_MIME_TYPE, }, - { destType: 'sfmc', feature: 'transformation' }, + { + destType: 'sfmc', + feature: 'transformation', + endpointPath: '/token', + requestMethod: 'POST', + module: 'router', + }, ); if (resp && resp.data) { return resp.data.access_token; @@ -182,6 +189,26 @@ const responseBuilderForInsertData = ( return response; }; +// DOC : https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_interaction/postEvent.html + +const responseBuilderForMessageEvent = (message, subDomain, authToken, hashMapEventDefinition) => { + const contactKey = message.properties.contactId; + delete message.properties.contactId; + const response = defaultRequestConfig(); + response.method = defaultPostRequestConfig.requestMethod; + response.endpoint = `https://${subDomain}.${ENDPOINTS.EVENT}`; + response.headers = { + 'Content-Type': JSON_MIME_TYPE, + Authorization: `Bearer ${authToken}`, + }; + response.body.JSON = { + ContactKey: contactKey, + EventDefinitionKey: hashMapEventDefinition[message.event.toLowerCase()], + Data: { ...message.properties }, + }; + return response; +}; + const responseBuilderSimple = async (message, category, destination) => { const { clientId, @@ -192,6 +219,7 @@ const responseBuilderSimple = async (message, category, destination) => { eventToExternalKey, eventToPrimaryKey, eventToUUID, + eventToDefinitionMapping, } = destination.Config; // map from an event name to an external key of a data extension. const hashMapExternalKey = getHashFromArray(eventToExternalKey, 'from', 'to'); @@ -201,6 +229,8 @@ const responseBuilderSimple = async (message, category, destination) => { const hashMapUUID = getHashFromArray(eventToUUID, 'event', 'uuid'); // token needed for authorization for subsequent calls const authToken = await getToken(clientId, clientSecret, subDomain); + // map from an event name to an event definition key. + const hashMapEventDefinition = getHashFromArray(eventToDefinitionMapping, 'from', 'to'); // if createOrUpdateContacts is true identify calls for create and update of contacts will not occur. if (category.type === 'identify' && !createOrUpdateContacts) { // first call to identify the contact @@ -234,10 +264,12 @@ const responseBuilderSimple = async (message, category, destination) => { if (typeof message.event !== 'string') { throw new ConfigurationError('Event name must be a string'); } + if (hashMapEventDefinition[message.event.toLowerCase()]) { + return responseBuilderForMessageEvent(message, subDomain, authToken, hashMapEventDefinition); + } if (!isDefinedAndNotNull(hashMapExternalKey[message.event.toLowerCase()])) { throw new ConfigurationError('Event not mapped for this track call'); } - return responseBuilderForInsertData( message, hashMapExternalKey[message.event.toLowerCase()], @@ -287,4 +319,9 @@ const processRouterDest = async (inputs, reqMetadata) => { return respList; }; -module.exports = { process, processRouterDest, responseBuilderSimple }; +module.exports = { + process, + processRouterDest, + responseBuilderSimple, + responseBuilderForMessageEvent, +}; diff --git a/src/v0/destinations/sfmc/transform.test.js b/src/v0/destinations/sfmc/transform.test.js index c49c49017c6..8d382ef6499 100644 --- a/src/v0/destinations/sfmc/transform.test.js +++ b/src/v0/destinations/sfmc/transform.test.js @@ -1,7 +1,7 @@ const { ConfigurationError } = require('@rudderstack/integrations-lib'); const axios = require('axios'); const MockAxiosAdapter = require('axios-mock-adapter'); -const { responseBuilderSimple } = require('./transform'); +const { responseBuilderSimple, responseBuilderForMessageEvent } = require('./transform'); beforeAll(() => { const mock = new MockAxiosAdapter(axios); mock @@ -122,4 +122,44 @@ describe('responseBuilderSimple', () => { expect(response).toHaveProperty('body.JSON'); expect(response).toHaveProperty('headers'); }); + + it('should build response object with correct details for message event', () => { + const message = { + userId: 'u123', + event: 'testEvent', + properties: { + contactId: '12345', + prop1: 'value1', + prop2: 'value2', + }, + }; + const subDomain = 'subdomain'; + const authToken = 'token'; + const hashMapEventDefinition = { + testevent: 'eventDefinitionKey', + }; + + const response = responseBuilderForMessageEvent( + message, + subDomain, + authToken, + hashMapEventDefinition, + ); + expect(response.method).toBe('POST'); + expect(response.endpoint).toBe( + 'https://subdomain.rest.marketingcloudapis.com/interaction/v1/events', + ); + expect(response.headers).toEqual({ + 'Content-Type': 'application/json', + Authorization: 'Bearer token', + }); + expect(response.body.JSON).toEqual({ + ContactKey: '12345', + EventDefinitionKey: 'eventDefinitionKey', + Data: { + prop1: 'value1', + prop2: 'value2', + }, + }); + }); }); diff --git a/src/v0/destinations/snapchat_conversion/transform.js b/src/v0/destinations/snapchat_conversion/transform.js index 37d321a468d..6fec6313a4a 100644 --- a/src/v0/destinations/snapchat_conversion/transform.js +++ b/src/v0/destinations/snapchat_conversion/transform.js @@ -12,7 +12,6 @@ const { getSuccessRespEvents, isAppleFamily, getValidDynamicFormConfig, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -358,11 +357,6 @@ const process = (event) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; inputs.forEach((event) => { diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index feedaea3e33..6044216293a 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -43,6 +43,9 @@ const scAudienceProxyRequest = async (request) => { const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'snapchat_custom_audience', + endpointPath: '/segments/segmentId/users', + requestMethod: requestOptions?.method, + module: 'dataDelivery', }); return response; }; diff --git a/src/v0/destinations/sprig/deleteUsers.js b/src/v0/destinations/sprig/deleteUsers.js index a886bbbafc0..01044adcd1c 100644 --- a/src/v0/destinations/sprig/deleteUsers.js +++ b/src/v0/destinations/sprig/deleteUsers.js @@ -49,7 +49,9 @@ const userDeletionHandler = async (userAttributes, config) => { { destType: 'sprig', feature: 'deleteUsers', - endpointPath: 'api.sprig.com/v2/purge/visitors', + endpointPath: '/purge/visitors', + requestMethod: 'POST', + module: 'deletion', }, ); const handledDelResponse = processAxiosResponse(deletionResponse); @@ -59,6 +61,7 @@ const userDeletionHandler = async (userAttributes, config) => { handledDelResponse.status, { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status), + [tags.TAG_NAMES.STATUS]: handledDelResponse.status, }, handledDelResponse, ); diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js index 4d9e5321e77..aebbfc0785e 100644 --- a/src/v0/destinations/the_trade_desk/networkHandler.js +++ b/src/v0/destinations/the_trade_desk/networkHandler.js @@ -37,7 +37,13 @@ const proxyRequest = async (request) => { headers: ProxyHeaders, method, }; - const response = await httpSend(requestOptions, { feature: 'proxy', destType: 'the_trade_desk' }); + const response = await httpSend(requestOptions, { + feature: 'proxy', + destType: 'the_trade_desk', + endpointPath: '/track/realtimeconversion', + requestMethod: 'POST', + module: 'dataDelivery', + }); return response; }; diff --git a/src/v0/destinations/tiktok_ads/transform.js b/src/v0/destinations/tiktok_ads/transform.js index bdf3a0defe3..ba852b9a977 100644 --- a/src/v0/destinations/tiktok_ads/transform.js +++ b/src/v0/destinations/tiktok_ads/transform.js @@ -17,7 +17,6 @@ const { getDestinationExternalID, getFieldValueFromMessage, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, batchMultiplexedEvents, } = require('../../util'); @@ -130,12 +129,10 @@ const getTrackResponse = (message, Config, event) => { const trackResponseBuilder = async (message, { Config }) => { const { eventsToStandard, sendCustomEvents } = Config; - - let event = message.event?.toLowerCase().trim(); - if (!event) { - throw new InstrumentationError('Event name is required'); + if (!message.event || typeof message.event !== 'string') { + throw new InstrumentationError('Either event name is not present or it is not a string'); } - + let event = message.event?.toLowerCase().trim(); const standardEventsMap = getHashFromArrayWithDuplicate(eventsToStandard); if (!sendCustomEvents && eventNameMapping[event] === undefined && !standardEventsMap[event]) { @@ -248,10 +245,6 @@ const processRouterDest = async (inputs, reqMetadata) => { if (Config?.version === 'v2') { return processRouterDestV2(inputs, reqMetadata); } - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks diff --git a/src/v0/destinations/tiktok_ads/transformV2.js b/src/v0/destinations/tiktok_ads/transformV2.js index 98f7d61e1ea..48c5b19e648 100644 --- a/src/v0/destinations/tiktok_ads/transformV2.js +++ b/src/v0/destinations/tiktok_ads/transformV2.js @@ -13,7 +13,6 @@ const { isDefinedAndNotNullAndNotEmpty, getDestinationExternalID, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, } = require('../../util'); const { getContents, hashUserField } = require('./util'); @@ -690,10 +689,6 @@ const batchEvents = (eventsChunk) => { return events; }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const trackResponseList = []; // list containing single track event in batched format const eventsChunk = []; // temporary variable to divide payload into chunks const errorRespList = []; diff --git a/src/v0/destinations/tiktok_ads_offline_events/config.js b/src/v0/destinations/tiktok_ads_offline_events/config.js index 3c58b42a449..4bb3bda850e 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/config.js +++ b/src/v0/destinations/tiktok_ads_offline_events/config.js @@ -19,23 +19,23 @@ const CONFIG_CATEGORIES = { const PARTNER_NAME = 'RudderStack'; const EVENT_NAME_MAPPING = { - 'addpaymentinfo': 'AddPaymentInfo', - 'addtocart': 'AddToCart', - 'addtowishlist': 'AddToWishlist', + addpaymentinfo: 'AddPaymentInfo', + addtocart: 'AddToCart', + addtowishlist: 'AddToWishlist', 'checkout started': 'InitiateCheckout', 'checkout step completed': 'CompletePayment', - 'clickbutton': 'ClickButton', - 'completeregistration': 'CompleteRegistration', - 'contact': 'Contact', - 'download': 'Download', + clickbutton: 'ClickButton', + completeregistration: 'CompleteRegistration', + contact: 'Contact', + download: 'Download', 'order completed': 'PlaceAnOrder', 'payment info entered': 'AddPaymentInfo', 'product added': 'AddToCart', 'product added to wishlist': 'AddToWishlist', - 'search': 'Search', - 'submitform': 'SubmitForm', - 'subscribe': 'Subscribe', - 'viewcontent': 'ViewContent', + search: 'Search', + submitform: 'SubmitForm', + subscribe: 'Subscribe', + viewcontent: 'ViewContent', }; const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); diff --git a/src/v0/destinations/tiktok_ads_offline_events/transform.js b/src/v0/destinations/tiktok_ads_offline_events/transform.js index 945c31ea632..dbe9a06dd6e 100644 --- a/src/v0/destinations/tiktok_ads_offline_events/transform.js +++ b/src/v0/destinations/tiktok_ads_offline_events/transform.js @@ -9,7 +9,6 @@ const { removeUndefinedAndNullValues, isDefinedAndNotNullAndNotEmpty, getHashFromArrayWithDuplicate, - checkInvalidRtTfEvents, handleRtTfSingleEventError, getSuccessRespEvents, defaultBatchRequestConfig, @@ -199,11 +198,6 @@ const batchEvents = (eventChunksArray) => { }; const processRouterDest = async (inputs, reqMetadata) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const batchErrorRespList = []; const eventChunksArray = []; const { destination } = inputs[0]; diff --git a/src/v0/destinations/trengo/transform.js b/src/v0/destinations/trengo/transform.js index 06e5496a1eb..01c5cfeb254 100644 --- a/src/v0/destinations/trengo/transform.js +++ b/src/v0/destinations/trengo/transform.js @@ -90,7 +90,13 @@ const lookupContact = async (term, destination) => { Authorization: `Bearer ${destination.Config.apiToken}`, }, }, - { destType: 'trengo', feature: 'transformation' }, + { + destType: 'trengo', + feature: 'transformation', + endpointPath: '/contacts', + requestMethod: 'GET', + module: 'router', + }, ); } catch (err) { // check if exists err.response && err.response.status else 500 diff --git a/src/v0/destinations/twitter_ads/config.js b/src/v0/destinations/twitter_ads/config.js index 601675fc2ff..6b0db0622c8 100644 --- a/src/v0/destinations/twitter_ads/config.js +++ b/src/v0/destinations/twitter_ads/config.js @@ -14,5 +14,5 @@ const mappingConfig = getMappingConfig(ConfigCategories, __dirname); module.exports = { mappingConfig, ConfigCategories, - BASE_URL + BASE_URL, }; diff --git a/src/v0/destinations/twitter_ads/util.js b/src/v0/destinations/twitter_ads/util.js index 2f237b1dd86..ad59a812673 100644 --- a/src/v0/destinations/twitter_ads/util.js +++ b/src/v0/destinations/twitter_ads/util.js @@ -2,23 +2,20 @@ const crypto = require('crypto'); const oauth1a = require('oauth-1.0a'); function getAuthHeaderForRequest(request, oAuthObject) { - const oauth = oauth1a({ - consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, - signature_method: 'HMAC-SHA1', - hash_function(base_string, k) { - return crypto - .createHmac('sha1', k) - .update(base_string) - .digest('base64') - }, - }) + const oauth = oauth1a({ + consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret }, + signature_method: 'HMAC-SHA1', + hash_function(base_string, k) { + return crypto.createHmac('sha1', k).update(base_string).digest('base64'); + }, + }); - const authorization = oauth.authorize(request, { - key: oAuthObject.accessToken, - secret: oAuthObject.accessTokenSecret, - }); + const authorization = oauth.authorize(request, { + key: oAuthObject.accessToken, + secret: oAuthObject.accessTokenSecret, + }); - return oauth.toHeader(authorization); + return oauth.toHeader(authorization); } -module.exports = { getAuthHeaderForRequest }; \ No newline at end of file +module.exports = { getAuthHeaderForRequest }; diff --git a/src/v0/destinations/user/utils.js b/src/v0/destinations/user/utils.js index 52fba2167ea..f332d7a4a71 100644 --- a/src/v0/destinations/user/utils.js +++ b/src/v0/destinations/user/utils.js @@ -238,6 +238,8 @@ const createCompany = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'POST', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -279,6 +281,8 @@ const updateCompany = async (message, destination, company) => { destType: 'user', feature: 'transformation', endpointPath: `/companies/`, + requestMethod: 'PUT', + module: 'router', }); const data = processAxiosResponse(response); return data.response; @@ -306,6 +310,8 @@ const getUserByUserKey = async (apiKey, userKey, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); if (processedUserResponse.status === 200) { @@ -340,6 +346,8 @@ const getUserByEmail = async (apiKey, email, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?email`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -379,6 +387,8 @@ const getUserByPhoneNumber = async (apiKey, phoneNumber, appSubdomain) => { destType: 'user', feature: 'transformation', endpointPath: `/users/search/?phone_number`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -424,6 +434,8 @@ const getUserByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/users-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); @@ -460,6 +472,8 @@ const getCompanyByCustomId = async (message, destination) => { destType: 'user', feature: 'transformation', endpointPath: `/companies-by-id/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(response); if (processedUserResponse.status === 200) { diff --git a/src/v0/destinations/wootric/util.js b/src/v0/destinations/wootric/util.js index eb61a472cf2..c2505c635be 100644 --- a/src/v0/destinations/wootric/util.js +++ b/src/v0/destinations/wootric/util.js @@ -47,6 +47,8 @@ const getAccessToken = async (destination) => { destType: 'wootric', feature: 'transformation', endpointPath: `/oauth/token`, + requestMethod: 'POST', + module: 'router', }); const processedAuthResponse = processAxiosResponse(wootricAuthResponse); // If the request fails, throwing error. @@ -100,6 +102,8 @@ const retrieveUserDetails = async (endUserId, externalId, accessToken) => { destType: 'wootric', feature: 'transformation', endpointPath: `/v1/end_users/`, + requestMethod: 'GET', + module: 'router', }); const processedUserResponse = processAxiosResponse(userResponse); diff --git a/src/v0/destinations/yahoo_dsp/util.js b/src/v0/destinations/yahoo_dsp/util.js index d41716935fd..255f84d1c91 100644 --- a/src/v0/destinations/yahoo_dsp/util.js +++ b/src/v0/destinations/yahoo_dsp/util.js @@ -137,6 +137,9 @@ const getAccessToken = async (destination) => { const dspAuthorisationData = await httpSend(request, { destType: 'yahoo_dsp', feature: 'transformation', + endpointPath: '/identity/oauth2/access_token', + requestMethod: 'POST', + module: 'router', }); // If the request fails, throwing error. if (dspAuthorisationData.success === false) { diff --git a/src/v0/destinations/zendesk/transform.js b/src/v0/destinations/zendesk/transform.js index bf2bc01ed28..58620147845 100644 --- a/src/v0/destinations/zendesk/transform.js +++ b/src/v0/destinations/zendesk/transform.js @@ -36,7 +36,7 @@ const tags = require('../../util/tags'); const { JSON_MIME_TYPE } = require('../../util/constant'); const CONTEXT_TRAITS_KEY_PATH = 'context.traits'; - +const endpointPath = '/users/search.json'; function responseBuilder(message, headers, payload, endpoint) { const response = defaultRequestConfig(); @@ -102,6 +102,9 @@ const payloadBuilderforUpdatingEmail = async (userId, headers, userEmail, baseEn const res = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: 'users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (res?.response?.data?.count > 0) { const { identities } = res.response.data; @@ -147,6 +150,9 @@ async function createUserFields(url, config, newFields, fieldJson) { const response = await myAxios.post(url, fieldData, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/identities', + requestMethod: 'POST', + module: 'router', }); if (response.status !== 201) { logger.debug(`${NAME}:: Failed to create User Field : `, field); @@ -176,6 +182,8 @@ async function checkAndCreateUserFields( const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + requestMethod: 'POST', + module: 'router', }); const fields = get(response.data, fieldJson); if (response.data && fields) { @@ -253,6 +261,9 @@ const getUserIdByExternalId = async (message, headers, baseEndpoint) => { const resp = await httpGET(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (resp?.response?.data?.count > 0) { @@ -283,6 +294,9 @@ async function getUserId(message, headers, baseEndpoint, type) { const resp = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!resp || !resp.data || resp.data.count === 0) { logger.debug(`${NAME}:: User not found`); @@ -307,6 +321,9 @@ async function isUserAlreadyAssociated(userId, orgId, headers, baseEndpoint) { const response = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if (response?.data?.organization_memberships?.[0]?.organization_id === orgId) { return true; @@ -339,6 +356,9 @@ async function createUser(message, headers, destinationConfig, baseEndpoint, typ const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.user || !resp.data.user.id) { @@ -420,6 +440,9 @@ async function createOrganization(message, category, headers, destinationConfig, const resp = await myAxios.post(url, payload, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/organizations/create_or_update.json', + requestMethod: 'POST', + module: 'router', }); if (!resp.data || !resp.data.organization) { @@ -488,6 +511,9 @@ async function processIdentify(message, destinationConfig, headers, baseEndpoint const response = await myAxios.get(membershipUrl, config, { destType: 'zendesk', feature: 'transformation', + endpointPath: '/users/userId/organization_memberships.json', + requestMethod: 'GET', + module: 'router', }); if ( response.data && @@ -535,6 +561,9 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) { const userResponse = await myAxios.get(url, config, { destType: 'zendesk', feature: 'transformation', + endpointPath, + requestMethod: 'GET', + module: 'router', }); if (!get(userResponse, 'data.users.0.id') || userResponse.data.count === 0) { const { zendeskUserId, email } = await createUser( diff --git a/src/v0/sources/formsort/transform.js b/src/v0/sources/formsort/transform.js index 18d7b8fc0eb..dd37482bc49 100644 --- a/src/v0/sources/formsort/transform.js +++ b/src/v0/sources/formsort/transform.js @@ -1,18 +1,16 @@ -const path = require("path"); -const fs = require("fs"); -const { generateUUID, isDefinedAndNotNull } = require("../../util"); -const Message = require("../message"); +const path = require('path'); +const fs = require('fs'); +const { generateUUID, isDefinedAndNotNull } = require('../../util'); +const Message = require('../message'); // import mapping json using JSON.parse to preserve object key order -const mapping = JSON.parse( - fs.readFileSync(path.resolve(__dirname, "./mapping.json"), "utf-8") -); +const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8')); function process(event) { const message = new Message(`Formsort`); // we are setting event type as track always - message.setEventType("track"); + message.setEventType('track'); message.setPropertiesV2(event, mapping); @@ -23,9 +21,9 @@ function process(event) { // setting event Name if (event.finalized) { - message.setEventName("FlowFinalized"); + message.setEventName('FlowFinalized'); } else { - message.setEventName("FlowLoaded"); + message.setEventName('FlowLoaded'); } return message; diff --git a/src/v0/sources/formsort/transform.test.js b/src/v0/sources/formsort/transform.test.js index 9b0d814d6af..e3d686fcefb 100644 --- a/src/v0/sources/formsort/transform.test.js +++ b/src/v0/sources/formsort/transform.test.js @@ -1,52 +1,51 @@ const { process } = require('./transform'); it(`Transform.js Tests`, () => { - const data = { - input: { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + const data = { + input: { + answers: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + responder_uuid: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + flow_label: 'new-flow-2022-11-25', + variant_label: 'main', + variant_uuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + finalized: false, + created_at: '2022-11-25T14:41:22+00:00', + }, + output: { + context: { + library: { + name: 'unknown', + version: 'unknown', }, - output: { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } - }; - const output = process(data.input); - expect(output).toEqual(data.output); - -}); \ No newline at end of file + integration: { + name: 'Formsort', + }, + page: { + title: 'new-flow-2022-11-25', + }, + variantLabel: 'main', + variantUuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea', + }, + integrations: { + Formsort: false, + }, + type: 'track', + userId: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7', + originalTimestamp: '2022-11-25T14:41:22+00:00', + properties: { + yes: true, + enter_email: 'test@user.com', + enter_name: '2022-11-17', + yes_or_no: false, + }, + event: 'FlowLoaded', + }, + }; + const output = process(data.input); + expect(output).toEqual(data.output); +}); diff --git a/src/v0/sources/shopify/shopify.util.test.js b/src/v0/sources/shopify/shopify.util.test.js index 9c570dde41b..d058db36b5c 100644 --- a/src/v0/sources/shopify/shopify.util.test.js +++ b/src/v0/sources/shopify/shopify.util.test.js @@ -1,5 +1,4 @@ -const { getShopifyTopic, -} = require('./util'); +const { getShopifyTopic } = require('./util'); jest.mock('ioredis', () => require('../../../../test/__mocks__/redis')); describe('Shopify Utils Test', () => { describe('Fetching Shopify Topic Test Cases', () => { @@ -58,5 +57,4 @@ describe('Shopify Utils Test', () => { } }); }); - }); diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js index c8d872e90e9..de73b0fb05a 100644 --- a/src/v0/util/googleUtils/index.js +++ b/src/v0/util/googleUtils/index.js @@ -8,21 +8,27 @@ const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DEN * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent */ -const populateConsentForGoogleDestinations = (properties) => { +const populateConsentForGoogleDestinations = (config) => { const consent = {}; - if ( - properties?.userDataConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.userDataConsent) - ) { - consent.adUserData = properties.userDataConsent; + if (config?.userDataConsent) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.userDataConsent)) { + consent.adUserData = config.userDataConsent; + } else { + consent.adUserData = 'UNKNOWN'; + } + } else { + consent.adUserData = 'UNSPECIFIED'; } - if ( - properties?.personalizationConsent && - GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.personalizationConsent) - ) { - consent.adPersonalization = properties.personalizationConsent; + if (config?.personalizationConsent) { + if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config.personalizationConsent)) { + consent.adPersonalization = config.personalizationConsent; + } else { + consent.adPersonalization = 'UNKNOWN'; + } + } else { + consent.adPersonalization = 'UNSPECIFIED'; } return consent; }; diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js index 27eff2a7936..9d1aa5e51a9 100644 --- a/src/v0/util/googleUtils/index.test.js +++ b/src/v0/util/googleUtils/index.test.js @@ -1,50 +1,58 @@ const { populateConsentForGoogleDestinations } = require('./index'); describe('unit test for populateConsentForGoogleDestinations', () => { - // Returns an empty object when no properties are provided. - it('should return an empty object when no properties are provided', () => { + it('should return an UNSPECIFIED object when no properties are provided', () => { const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Sets adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses. it('should set adUserData property of consent object when userDataConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { userDataConsent: 'GRANTED' }; const result = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adUserData: 'GRANTED' }); + expect(result).toEqual({ adUserData: 'GRANTED', adPersonalization: 'UNSPECIFIED' }); }); - // Sets adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses. it('should set adPersonalization property of consent object when personalizationConsent property is provided and its value is one of the allowed consent statuses', () => { const properties = { personalizationConsent: 'DENIED' }; const result = populateConsentForGoogleDestinations(properties); - expect(result).toEqual({ adPersonalization: 'DENIED' }); + expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'UNSPECIFIED' }); }); - // Returns an empty object when properties parameter is not provided. - it('should return an empty object when properties parameter is not provided', () => { + it('should return an UNSPECIFIED object when properties parameter is not provided', () => { const result = populateConsentForGoogleDestinations(); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is null. - it('should return an empty object when properties parameter is null', () => { + it('should return an UNSPECIFIED object when properties parameter is null', () => { const result = populateConsentForGoogleDestinations(null); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter is an empty object', () => { + it('should return an UNSPECIFIED object when properties parameter is an UNSPECIFIED object', () => { const result = populateConsentForGoogleDestinations({}); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }); }); - // Returns an empty object when properties parameter is an empty object. - it('should return an empty object when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { + it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => { const result = populateConsentForGoogleDestinations({ - adUserData: 'RANDOM', + userDataConsent: 'RANDOM', personalizationConsent: 'RANDOM', }); - expect(result).toEqual({}); + expect(result).toEqual({ + adPersonalization: 'UNKNOWN', + adUserData: 'UNKNOWN', + }); }); }); diff --git a/src/v0/util/index.js b/src/v0/util/index.js index 0cc66b2d7a5..c1debce0888 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -22,6 +22,7 @@ const { PlatformError, TransformationError, OAuthSecretError, + getErrorRespEvents, } = require('@rudderstack/integrations-lib'); const logger = require('../../logger'); const stats = require('../../util/stats'); @@ -51,6 +52,7 @@ const removeUndefinedAndNullAndEmptyValues = (obj) => lodash.pickBy(obj, isDefinedAndNotNullAndNotEmpty); const isBlank = (value) => lodash.isEmpty(lodash.toString(value)); const flattenMap = (collection) => lodash.flatMap(collection, (x) => x); +const isNull = (x) => lodash.isNull(x); // ======================================================================== // GENERIC UTLITY // ======================================================================== @@ -482,16 +484,6 @@ const getSuccessRespEvents = ( destination, }); -// Router transformer -// Error responses -const getErrorRespEvents = (metadata, statusCode, error, statTags, batched = false) => ({ - metadata, - batched, - statusCode, - error, - statTags, -}); - // ======================================================================== // Error Message UTILITIES // ======================================================================== @@ -1661,7 +1653,7 @@ function getValidDynamicFormConfig( */ const checkInvalidRtTfEvents = (inputs) => { if (!Array.isArray(inputs) || inputs.length === 0) { - const respEvents = getErrorRespEvents(null, 400, 'Invalid event array'); + const respEvents = getErrorRespEvents([], 400, 'Invalid event array'); return [respEvents]; } return []; @@ -1723,11 +1715,6 @@ const handleRtTfSingleEventError = (input, error, reqMetadata) => { * @returns */ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } - const respList = await Promise.all( inputs.map(async (input) => { try { @@ -1755,10 +1742,6 @@ const simpleProcessRouterDest = async (inputs, singleTfFunc, reqMetadata, proces * @returns */ const simpleProcessRouterDestSync = async (inputs, singleTfFunc, reqMetadata, processParams) => { - const errorRespEvents = checkInvalidRtTfEvents(inputs); - if (errorRespEvents.length > 0) { - return errorRespEvents; - } const respList = []; // eslint-disable-next-line no-restricted-syntax for (const input of inputs) { @@ -2218,6 +2201,25 @@ const combineBatchRequestsWithSameJobIds = (inputBatches) => { return combineBatches(combineBatches(inputBatches)); }; +/** + * This function validates the event and return it as string. + * @param {*} isMandatory The event is a required field. + * @param {*} convertToLowerCase The event should be converted to lower-case. + * @returns {string} Event name converted to string. + */ +const validateEventAndLowerCaseConversion = (event, isMandatory, convertToLowerCase) => { + if (!isDefined(event) || typeof event === 'object' || typeof event === 'boolean') { + throw new InstrumentationError('Event should not be a object, NaN, boolean or undefined'); + } + + // handling 0 as it is a valid value + if (isMandatory && !event && event !== 0) { + throw new InstrumentationError('Event is a required field'); + } + + return convertToLowerCase ? event.toString().toLowerCase() : event.toString(); +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2254,7 +2256,6 @@ module.exports = { getDestinationExternalIDInfoForRetl, getDestinationExternalIDObjectForRetl, getDeviceModel, - getErrorRespEvents, getEventTime, getFieldValueFromMessage, getFirstAndLastName, @@ -2285,6 +2286,7 @@ module.exports = { isDefinedAndNotNullAndNotEmpty, isEmpty, isNotEmpty, + isNull, isEmptyObject, isHttpStatusRetryable, isHttpStatusSuccess, @@ -2334,4 +2336,5 @@ module.exports = { findExistingBatch, removeDuplicateMetadata, combineBatchRequestsWithSameJobIds, + validateEventAndLowerCaseConversion, }; diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js index 4dc62556911..810eb5a9d48 100644 --- a/src/v0/util/index.test.js +++ b/src/v0/util/index.test.js @@ -1,4 +1,4 @@ -const { TAG_NAMES } = require('@rudderstack/integrations-lib'); +const { TAG_NAMES, InstrumentationError } = require('@rudderstack/integrations-lib'); const utilities = require('.'); const { getFuncTestData } = require('../../../test/testHelper'); const { FilteredEventsError } = require('./errorTypes'); @@ -7,6 +7,7 @@ const { flattenJson, generateExclusionList, combineBatchRequestsWithSameJobIds, + validateEventAndLowerCaseConversion, } = require('./index'); // Names of the utility functions to test @@ -36,7 +37,6 @@ describe('Utility Functions Tests', () => { test.each(funcTestData)('$description', async ({ description, input, output }) => { try { let result; - // This is to allow sending multiple arguments to the function if (Array.isArray(input)) { result = utilities[funcName](...input); @@ -456,3 +456,53 @@ describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => { expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput); }); }); + +describe('validateEventAndLowerCaseConversion Tests', () => { + it('should return string conversion of number types', () => { + const ev = 0; + expect(validateEventAndLowerCaseConversion(ev, false, true)).toBe('0'); + expect(validateEventAndLowerCaseConversion(ev, true, false)).toBe('0'); + }); + + it('should convert string types to lowercase', () => { + const ev = 'Abc'; + expect(validateEventAndLowerCaseConversion(ev, true, true)).toBe('abc'); + }); + + it('should throw error if event is object type', () => { + expect(() => { + validateEventAndLowerCaseConversion({}, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion([1, 2], false, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion({ a: 1 }, true, true); + }).toThrow(InstrumentationError); + }); + + it('should convert string to lowercase', () => { + expect(validateEventAndLowerCaseConversion('Abc', true, true)).toBe('abc'); + expect(validateEventAndLowerCaseConversion('ABC', true, false)).toBe('ABC'); + expect(validateEventAndLowerCaseConversion('abc55', false, true)).toBe('abc55'); + expect(validateEventAndLowerCaseConversion(123, false, true)).toBe('123'); + }); + + it('should throw error for null and undefined', () => { + expect(() => { + validateEventAndLowerCaseConversion(null, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion(undefined, false, true); + }).toThrow(InstrumentationError); + }); + + it('should throw error for boolean values', () => { + expect(() => { + validateEventAndLowerCaseConversion(true, true, true); + }).toThrow(InstrumentationError); + expect(() => { + validateEventAndLowerCaseConversion(false, false, false); + }).toThrow(InstrumentationError); + }); +}); diff --git a/src/v0/util/tags.js b/src/v0/util/tags.js index 18f00f963fa..dce8c0a338a 100644 --- a/src/v0/util/tags.js +++ b/src/v0/util/tags.js @@ -13,6 +13,7 @@ const TAG_NAMES = { DESTINATION_ID: 'destinationId', WORKSPACE_ID: 'workspaceId', SOURCE_ID: 'sourceId', + STATUS: 'statusCode', }; const MODULES = { @@ -51,7 +52,7 @@ const ERROR_TYPES = { OAUTH_SECRET: 'oAuthSecret', UNSUPPORTED: 'unsupported', REDIS: 'redis', - FILTERED: 'filtered' + FILTERED: 'filtered', }; const METADATA = { diff --git a/src/warehouse/index.js b/src/warehouse/index.js index 3305a52762f..b3d1c5e4bc7 100644 --- a/src/warehouse/index.js +++ b/src/warehouse/index.js @@ -23,7 +23,7 @@ const whPageColumnMappingRules = require('./config/WHPageConfig.js'); const whScreenColumnMappingRules = require('./config/WHScreenConfig.js'); const whGroupColumnMappingRules = require('./config/WHGroupConfig.js'); const whAliasColumnMappingRules = require('./config/WHAliasConfig.js'); -const {isDataLakeProvider, isBlank} = require('./config/helpers'); +const { isDataLakeProvider, isBlank } = require('./config/helpers'); const { InstrumentationError } = require('@rudderstack/integrations-lib'); const whExtractEventTableColumnMappingRules = require('./config/WHExtractEventTableConfig.js'); diff --git a/src/warehouse/util.js b/src/warehouse/util.js index 11d72bfbfd5..b4b22721fd3 100644 --- a/src/warehouse/util.js +++ b/src/warehouse/util.js @@ -4,7 +4,7 @@ const get = require('get-value'); const v0 = require('./v0/util'); const v1 = require('./v1/util'); const { PlatformError, InstrumentationError } = require('@rudderstack/integrations-lib'); -const {isBlank} = require('./config/helpers'); +const { isBlank } = require('./config/helpers'); const minTimeInMs = Date.parse('0001-01-01T00:00:00Z'); const maxTimeInMs = Date.parse('9999-12-31T23:59:59.999Z'); diff --git a/test/__mocks__/data/sources/shopify/response.json b/test/__mocks__/data/sources/shopify/response.json index ead25067e63..4eef747b940 100644 --- a/test/__mocks__/data/sources/shopify/response.json +++ b/test/__mocks__/data/sources/shopify/response.json @@ -31,4 +31,4 @@ "shopify_test_set_redis_error": { "itemsHash": "EMPTY" } -} \ No newline at end of file +} diff --git a/test/__tests__/data/customerio_source_input.json b/test/__tests__/data/customerio_source_input.json index 5b825d6a00d..769c1b7fd3c 100644 --- a/test/__tests__/data/customerio_source_input.json +++ b/test/__tests__/data/customerio_source_input.json @@ -111,9 +111,7 @@ "customer_id": "user-123", "delivery_id": "RAECAAFwnUSneIa0ZXkmq8EdkAM==", "headers": { - "Custom-Header": [ - "custom-value" - ] + "Custom-Header": ["custom-value"] }, "identifiers": { "id": "user-123" @@ -389,4 +387,4 @@ "metric": "delivered", "timestamp": 1585751830 } -] \ No newline at end of file +] diff --git a/test/__tests__/data/customerio_source_output.json b/test/__tests__/data/customerio_source_output.json index 24b964d01b4..52df88e833f 100644 --- a/test/__tests__/data/customerio_source_output.json +++ b/test/__tests__/data/customerio_source_output.json @@ -648,4 +648,4 @@ "originalTimestamp": "2020-04-01T14:37:10.000Z", "sentAt": "2020-04-01T14:37:10.000Z" } -] \ No newline at end of file +] diff --git a/test/__tests__/data/formsort_source.json b/test/__tests__/data/formsort_source.json index a12d84a98a3..d94cfd677bd 100644 --- a/test/__tests__/data/formsort_source.json +++ b/test/__tests__/data/formsort_source.json @@ -1,94 +1,94 @@ [ - { - "description": "when we receive finalized as false", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": false, - "created_at": "2022-11-25T14:41:22+00:00" + { + "description": "when we receive finalized as false", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": false, + "created_at": "2022-11-25T14:41:22+00:00" + }, + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowLoaded" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowLoaded" + } + }, + { + "description": "when we receive finalized as true", + "input": { + "answers": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "flow_label": "new-flow-2022-11-25", + "variant_label": "main", + "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", + "finalized": true, + "created_at": "2022-11-25T14:41:22+00:00" }, - { - "description": "when we receive finalized as true", - "input": { - "answers": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "flow_label": "new-flow-2022-11-25", - "variant_label": "main", - "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea", - "finalized": true, - "created_at": "2022-11-25T14:41:22+00:00" + "output": { + "context": { + "library": { + "name": "unknown", + "version": "unknown" + }, + "integration": { + "name": "Formsort" + }, + "page": { + "title": "new-flow-2022-11-25" }, - "output": { - "context": { - "library": { - "name": "unknown", - "version": "unknown" - }, - "integration": { - "name": "Formsort" - }, - "page": { - "title": "new-flow-2022-11-25" - }, - "variantLabel": "main", - "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" - }, - "integrations": { - "Formsort": false - }, - "type": "track", - "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", - "originalTimestamp": "2022-11-25T14:41:22+00:00", - "properties": { - "yes": true, - "enter_email": "test@user.com", - "enter_name": "2022-11-17", - "yes_or_no": false - }, - "event": "FlowFinalized" - } + "variantLabel": "main", + "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea" + }, + "integrations": { + "Formsort": false + }, + "type": "track", + "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7", + "originalTimestamp": "2022-11-25T14:41:22+00:00", + "properties": { + "yes": true, + "enter_email": "test@user.com", + "enter_name": "2022-11-17", + "yes_or_no": false + }, + "event": "FlowFinalized" } -] \ No newline at end of file + } +] diff --git a/test/__tests__/data/proxy_input.json b/test/__tests__/data/proxy_input.json index a647238f9f8..0d7ff24ab7d 100644 --- a/test/__tests__/data/proxy_input.json +++ b/test/__tests__/data/proxy_input.json @@ -263,4 +263,4 @@ "destination": "any" } } -] \ No newline at end of file +] diff --git a/test/__tests__/data/shopify.json b/test/__tests__/data/shopify.json index 0153df4d26a..48ca8c75a01 100644 --- a/test/__tests__/data/shopify.json +++ b/test/__tests__/data/shopify.json @@ -4,9 +4,7 @@ "input": { "id": "shopify_test3", "query_parameters": { - "topic": [ - "carts_create" - ] + "topic": ["carts_create"] }, "token": "shopify_test3", "line_items": [], @@ -33,12 +31,8 @@ "description": "Invalid topic", "input": { "query_parameters": { - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -50,12 +44,8 @@ "input": { "query_parameters": { "topic": [], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -66,15 +56,9 @@ "description": "Unsupported Event Type", "input": { "query_parameters": { - "topic": [ - "random_event" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["random_event"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] } }, "output": { @@ -89,15 +73,9 @@ "description": "Identify Call for customers create event", "input": { "query_parameters": { - "topic": [ - "customers_create" - ], - "signature": [ - "rudderstack" - ], - "writeKey": [ - "sample-write-key" - ] + "topic": ["customers_create"], + "signature": ["rudderstack"], + "writeKey": ["sample-write-key"] }, "id": 5747017285820, "email": "anuraj@rudderstack.com", @@ -256,15 +234,9 @@ "description": "Unsupported checkout event", "input": { "query_parameters": { - "topic": [ - "checkout_delete" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["checkout_delete"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "admin_graphql_api_id": "gid://shopify/Fulfillment/4124667937024", "created_at": "2022-01-05T18:13:02+05:30", @@ -292,13 +264,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -313,15 +281,9 @@ "description": "Track Call -> Fullfillments updated event", "input": { "query_parameters": { - "topic": [ - "fulfillments_update" - ], - "writeKey": [ - "sample-write-key" - ], - "signature": [ - "rudderstack" - ] + "topic": ["fulfillments_update"], + "writeKey": ["sample-write-key"], + "signature": ["rudderstack"] }, "shipping_address": { "address1": "11 Rani Sankari Lane Patuapara Bhowanipore" @@ -420,13 +382,9 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", - "tracking_urls": [ - "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" - ], + "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-01-05T18:16:48+05:30" }, "output": { @@ -462,9 +420,7 @@ "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "Sample001test", - "tracking_numbers": [ - "Sample001test" - ], + "tracking_numbers": ["Sample001test"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": [ "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530" @@ -556,4 +512,4 @@ } } } -] \ No newline at end of file +] diff --git a/test/apitests/service.api.test.ts b/test/apitests/service.api.test.ts index cbc2abb3b25..266619b6ac2 100644 --- a/test/apitests/service.api.test.ts +++ b/test/apitests/service.api.test.ts @@ -6,6 +6,8 @@ import Koa from 'koa'; import bodyParser from 'koa-bodyparser'; import setValue from 'set-value'; import { applicationRoutes } from '../../src/routes'; +import { FetchHandler } from '../../src/helpers/fetchHandlers'; +import networkHandlerFactory from '../../src/adapters/networkHandlerFactory'; let server: any; const OLD_ENV = process.env; @@ -30,6 +32,10 @@ afterAll(async () => { await httpTerminator.terminate(); }); +afterEach(() => { + jest.clearAllMocks(); +}); + const getDataFromPath = (pathInput) => { const testDataFile = fs.readFileSync(path.resolve(__dirname, pathInput)); return JSON.parse(testDataFile.toString()); @@ -76,6 +82,332 @@ describe('features tests', () => { }); }); +describe('Api tests with a mock source/destination', () => { + test('(mock destination) Processor transformation scenario with single event', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + + const getInputData = () => { + return [ + { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } }, + { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } }, + ]; + }; + const tevent = { version: 'v0', endpoint: 'http://abc' }; + + const getDestHandlerSpy = jest + .spyOn(FetchHandler, 'getDestHandler') + .mockImplementationOnce((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + process: jest.fn(() => { + return tevent; + }), + }; + }); + + const expected = [ + { + output: { version: 'v0', endpoint: 'http://abc', userId: '' }, + metadata: { jobId: 1 }, + statusCode: 200, + }, + { + output: { version: 'v0', endpoint: 'http://abc', userId: '' }, + metadata: { jobId: 2 }, + statusCode: 200, + }, + ]; + + const response = await request(server) + .post('/v0/destinations/__rudder_test__') + .set('Accept', 'application/json') + .send(getInputData()); + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual(expected); + expect(getDestHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock destination) Batching', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + + const getBatchInputData = () => { + return { + input: [ + { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } }, + { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } }, + ], + destType: destType, + }; + }; + const tevent = [ + { + batchedRequest: { version: 'v0', endpoint: 'http://abc' }, + metadata: [{ jobId: 1 }, { jobId: 2 }], + statusCode: 200, + }, + ]; + + const getDestHandlerSpy = jest + .spyOn(FetchHandler, 'getDestHandler') + .mockImplementationOnce((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + batch: jest.fn(() => { + return tevent; + }), + }; + }); + + const response = await request(server) + .post('/batch') + .set('Accept', 'application/json') + .send(getBatchInputData()); + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual(tevent); + expect(getDestHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock destination) Router transformation', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + + const getRouterTransformInputData = () => { + return { + input: [ + { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } }, + { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } }, + ], + destType: destType, + }; + }; + const tevent = [ + { + batchedRequest: { version: 'v0', endpoint: 'http://abc' }, + metadata: [{ jobId: 1 }, { jobId: 2 }], + statusCode: 200, + }, + ]; + + const getDestHandlerSpy = jest + .spyOn(FetchHandler, 'getDestHandler') + .mockImplementationOnce((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + processRouterDest: jest.fn(() => { + return tevent; + }), + }; + }); + + const response = await request(server) + .post('/routerTransform') + .set('Accept', 'application/json') + .send(getRouterTransformInputData()); + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual({ output: tevent }); + expect(getDestHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock destination) v0 proxy', async () => { + const destType = '__rudder_test__'; + const version = 'v0'; + + const getData = () => { + return { + body: { JSON: { a: 'b' } }, + metadata: { a1: 'b1' }, + destinationConfig: { a2: 'b2' }, + }; + }; + + const proxyResponse = { success: true, response: { response: 'response', code: 200 } }; + + const mockNetworkHandler = { + proxy: jest.fn((r, d) => { + expect(r).toEqual(getData()); + expect(d).toEqual(destType); + return proxyResponse; + }), + processAxiosResponse: jest.fn((r) => { + expect(r).toEqual(proxyResponse); + return { response: 'response', status: 200 }; + }), + responseHandler: jest.fn((o, d) => { + expect(o.destinationResponse).toEqual({ response: 'response', status: 200 }); + expect(o.rudderJobMetadata).toEqual({ a1: 'b1' }); + expect(o.destType).toEqual(destType); + return { status: 200, message: 'response', destinationResponse: 'response' }; + }), + }; + + const getNetworkHandlerSpy = jest + .spyOn(networkHandlerFactory, 'getNetworkHandler') + .mockImplementationOnce((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + networkHandler: mockNetworkHandler, + handlerVersion: version, + }; + }); + + const response = await request(server) + .post('/v0/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual({ + output: { status: 200, message: 'response', destinationResponse: 'response' }, + }); + expect(getNetworkHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock destination) v1 proxy', async () => { + const destType = '__rudder_test__'; + const version = 'v1'; + + const getData = () => { + return { + body: { JSON: { a: 'b' } }, + metadata: [{ a1: 'b1' }], + destinationConfig: { a2: 'b2' }, + }; + }; + + const proxyResponse = { success: true, response: { response: 'response', code: 200 } }; + const respHandlerResponse = { + status: 200, + message: 'response', + destinationResponse: 'response', + response: [{ statusCode: 200, metadata: { a1: 'b1' } }], + }; + + const mockNetworkHandler = { + proxy: jest.fn((r, d) => { + expect(r).toEqual(getData()); + expect(d).toEqual(destType); + return proxyResponse; + }), + processAxiosResponse: jest.fn((r) => { + expect(r).toEqual(proxyResponse); + return { response: 'response', status: 200 }; + }), + responseHandler: jest.fn((o, d) => { + expect(o.destinationResponse).toEqual({ response: 'response', status: 200 }); + expect(o.rudderJobMetadata).toEqual([{ a1: 'b1' }]); + expect(o.destType).toEqual(destType); + return respHandlerResponse; + }), + }; + + const getNetworkHandlerSpy = jest + .spyOn(networkHandlerFactory, 'getNetworkHandler') + .mockImplementationOnce((d, v) => { + expect(d).toEqual(destType); + expect(v).toEqual(version); + return { + networkHandler: mockNetworkHandler, + handlerVersion: version, + }; + }); + + const response = await request(server) + .post('/v1/destinations/__rudder_test__/proxy') + .set('Accept', 'application/json') + .send(getData()); + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual({ + output: respHandlerResponse, + }); + expect(getNetworkHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock source) v0 source transformation', async () => { + const sourceType = '__rudder_test__'; + const version = 'v0'; + + const getData = () => { + return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }]; + }; + + const tevent = { event: 'clicked', type: 'track' }; + + const getSourceHandlerSpy = jest + .spyOn(FetchHandler, 'getSourceHandler') + .mockImplementationOnce((s, v) => { + expect(s).toEqual(sourceType); + return { + process: jest.fn(() => { + return tevent; + }), + }; + }); + + const response = await request(server) + .post('/v0/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + const expected = [ + { output: { batch: [{ event: 'clicked', type: 'track' }] } }, + { output: { batch: [{ event: 'clicked', type: 'track' }] } }, + ]; + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual(expected); + expect(getSourceHandlerSpy).toHaveBeenCalledTimes(1); + }); + + test('(mock source) v1 source transformation', async () => { + const sourceType = '__rudder_test__'; + const version = 'v1'; + + const getData = () => { + return [ + { event: { a: 'b1' }, source: { id: 'id' } }, + { event: { a: 'b2' }, source: { id: 'id' } }, + ]; + }; + + const tevent = { event: 'clicked', type: 'track' }; + + const getSourceHandlerSpy = jest + .spyOn(FetchHandler, 'getSourceHandler') + .mockImplementationOnce((s, v) => { + expect(s).toEqual(sourceType); + return { + process: jest.fn(() => { + return tevent; + }), + }; + }); + + const response = await request(server) + .post('/v1/sources/__rudder_test__') + .set('Accept', 'application/json') + .send(getData()); + + const expected = [ + { output: { batch: [{ event: 'clicked', type: 'track' }] } }, + { output: { batch: [{ event: 'clicked', type: 'track' }] } }, + ]; + + expect(response.status).toEqual(200); + expect(JSON.parse(response.text)).toEqual(expected); + expect(getSourceHandlerSpy).toHaveBeenCalledTimes(1); + }); +}); + describe('Destination api tests', () => { describe('Processor transform tests', () => { test('(webhook) success scenario with single event', async () => { @@ -183,6 +515,7 @@ describe('Destination api tests', () => { expect(response.status).toEqual(200); expect(JSON.parse(response.text)).toEqual(data.output); }); + test('(pinterest_tag) failure router transform(partial failure)', async () => { const data = getDataFromPath('./data_scenarios/destination/router/failure_test.json'); const response = await request(server) diff --git a/test/integrations/destinations/adj/processor/data.ts b/test/integrations/destinations/adj/processor/data.ts index 2c208d0d084..e28a25cf59d 100644 --- a/test/integrations/destinations/adj/processor/data.ts +++ b/test/integrations/destinations/adj/processor/data.ts @@ -2179,7 +2179,8 @@ export const data = [ status: 200, body: [ { - error: 'App Token is not present. Please configure your app token from config dashbaord', + error: + 'App Token is not present. Please configure your app token from config dashbaord', statTags: { destType: 'ADJ', errorCategory: 'dataValidation', @@ -2205,24 +2206,24 @@ export const data = [ body: [ { message: { - "type": "track", - "event": "Application Installed", - "sentAt": "2022-09-28T20:14:44.995Z", - "userId": "sample_user_id", - "context": { - "device": { - "id": "sample_device_id", - "type": "android", - "advertisingId": "_sample" + type: 'track', + event: 'Application Installed', + sentAt: '2022-09-28T20:14:44.995Z', + userId: 'sample_user_id', + context: { + device: { + id: 'sample_device_id', + type: 'android', + advertisingId: '_sample', + }, + traits: { + userId: '_sample_uid', + anonymousId: '_sample_anonid', }, - "traits": { - "userId": "_sample_uid", - "anonymousId": "_sample_anonid" - } - }, - "timestamp": "2022-09-28T20:14:43.314Z", - "request_ip": "71.189.106.156", - "originalTimestamp": "2022-09-28T20:14:44.995Z" + }, + timestamp: '2022-09-28T20:14:43.314Z', + request_ip: '71.189.106.156', + originalTimestamp: '2022-09-28T20:14:44.995Z', }, destination: { ID: '1i3Em7GMU9xVEiDlZUN8c88BMS9', @@ -2245,8 +2246,7 @@ export const data = [ }, Config: { appToken: 'testAppToken', - customMappings: [ - { from: 'Application Installed', to: '3fdmll' }], + customMappings: [{ from: 'Application Installed', to: '3fdmll' }], partnerParamsKeys: [ { from: 'key1', to: 'partnerParamKey-1' }, { from: 'key2', to: 'partnerParamKey-2' }, @@ -2277,10 +2277,10 @@ export const data = [ endpoint: 'https://s2s.adjust.com/event', headers: { Accept: '*/*' }, params: { - event_token: "3fdmll", - ip_address: "71.189.106.156", + event_token: '3fdmll', + ip_address: '71.189.106.156', android_id: 'sample_device_id', - gps_adid: "_sample", + gps_adid: '_sample', s2s: 1, app_token: 'testAppToken', environment: 'production', @@ -2294,4 +2294,5 @@ export const data = [ ], }, }, - },]; + }, +]; diff --git a/test/integrations/destinations/algolia/processor/data.ts b/test/integrations/destinations/algolia/processor/data.ts index 0cbdd8b31b2..7c37c9642ae 100644 --- a/test/integrations/destinations/algolia/processor/data.ts +++ b/test/integrations/destinations/algolia/processor/data.ts @@ -380,7 +380,7 @@ export const data = [ body: [ { error: - 'Either filters or objectIds is required.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required.', + 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.', statTags: { destType: 'ALGOLIA', errorCategory: 'dataValidation', @@ -1417,4 +1417,214 @@ export const data = [ }, }, }, + { + name: 'algolia', + description: 'Eventype must be one of click, conversion pr view', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + email: 'testone@gmail.com', + firstName: 'test', + lastName: 'one', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + page: { + path: '/destinations/ometria', + referrer: '', + search: '', + title: '', + url: 'https://docs.rudderstack.com/destinations/ometria', + category: 'destination', + initial_referrer: 'https://docs.rudderstack.com', + initial_referring_domain: 'docs.rudderstack.com', + }, + }, + type: 'track', + messageId: '84e26acc-56a5-4835-8233-591137fca468', + session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'product clicked', + userId: 'testuserId1', + properties: { + index: 'products', + filters: ['field1:hello', 'val1:val2'], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + excludeKeys: [], + includeKeys: [], + }, + }, + Config: { + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'product clicked', + to: 'abcd', + }, + ], + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'eventType can be either click, view or conversion: Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: eventType can be either click, view or conversion', + statTags: { + destType: 'ALGOLIA', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'destId', + workspaceId: 'wspId', + }, + statusCode: 400, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, + { + name: 'algolia', + description: 'filters or objectIds must be non empty', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'product clicked', + sentAt: '2024-02-25T17:55:36.882Z', + userId: '12345', + channel: 'web', + properties: { + index: 'products', + list_id: 'search_results_page', + queryId: '8e737', + products: [], + eventName: 'productListView', + list_name: 'Search Results Page', + objectIds: [], + positions: [], + userToken: 'e494', + additional_attributes: {}, + }, + receivedAt: '2024-02-25T17:55:38.089Z', + request_ip: '107.130.37.100', + anonymousId: '68e9f4b8-fd4d-4c56-8ca4-858de2fd1df8', + integrations: { + All: true, + }, + originalTimestamp: '2024-02-25T17:55:36.880Z', + }, + destination: { + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + excludeKeys: [], + includeKeys: [], + }, + }, + Config: { + apiKey: 'dummyApiKey', + applicationId: 'O2YARRI15I', + eventTypeSettings: [ + { + from: 'product clicked', + to: 'cLick ', + }, + ], + }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.', + statTags: { + destType: 'ALGOLIA', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'destId', + workspaceId: 'wspId', + }, + statusCode: 400, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts index c4eb23ee398..57a647e6849 100644 --- a/test/integrations/destinations/clevertap/network.ts +++ b/test/integrations/destinations/clevertap/network.ts @@ -65,7 +65,8 @@ const dataDeliveryMocksData = [ method: 'POST', }, httpRes: { - data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, status: 401 + data: { status: 'fail', error: 'Invalid Credentials', code: 401 }, + status: 401, }, }, { diff --git a/test/integrations/destinations/clevertap/processor/data.ts b/test/integrations/destinations/clevertap/processor/data.ts index d79ebaa8da5..6309c5ec8a1 100644 --- a/test/integrations/destinations/clevertap/processor/data.ts +++ b/test/integrations/destinations/clevertap/processor/data.ts @@ -52,6 +52,7 @@ export const data = [ state: 'WB', street: '', }, + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, integrations: { All: true, @@ -98,10 +99,11 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + 'category-unsubscribe': { email: ['Marketing', 'Transactional'] }, }, identity: 'anon_id', }, @@ -242,8 +244,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -968,8 +970,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1111,8 +1113,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -1692,8 +1694,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', }, @@ -2077,8 +2079,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', @@ -2230,8 +2232,8 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', first_name: 'John', diff --git a/test/integrations/destinations/clevertap/router/data.ts b/test/integrations/destinations/clevertap/router/data.ts index 4f1723a7da4..5f25bbe83e7 100644 --- a/test/integrations/destinations/clevertap/router/data.ts +++ b/test/integrations/destinations/clevertap/router/data.ts @@ -162,10 +162,10 @@ export const data = [ msgSms: true, msgemail: true, msgwhatsapp: false, - custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', - custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', address: '{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}', + custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}', + custom_tags: '["Test_User","Interested_User","DIY_Hobby"]', }, objectId: 'anon_id', }, diff --git a/test/integrations/destinations/clickup/network.ts b/test/integrations/destinations/clickup/network.ts index 1a26209923e..2cb7cde34fc 100644 --- a/test/integrations/destinations/clickup/network.ts +++ b/test/integrations/destinations/clickup/network.ts @@ -1,247 +1,247 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId123/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [ - { - "id": "19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e", - "name": "Labels", - "type": "labels", - "type_config": { - "options": [ - { - "id": "32c81c1c-cf53-4829-92f5-0f0270d27a45", - "label": "Option 1", - "color": {} - }, - { - "id": "7e24f329-9dd9-4e68-b426-2c70af6f9347", - "label": "Option 2", - "color": {} - } - ] - }, - "date_created": "1661964865880", - "hide_from_guests": false, - "required": false - }, - { - "id": "22eaffee-ffec-4c3b-bdae-56e69d55eecd", - "name": "Payment Status", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "e109e36b-a052-4a31-af16-25da7324990f", - "name": "Sent Request", - "color": "#FF7FAB", - "orderindex": 0 - }, - { - "id": "3a3b4512-2896-44f7-8075-2ff37777fe24", - "name": "Quote sent", - "color": "#EA80FC", - "orderindex": 1 - }, - { - "id": "7afcb6fb-cec8-41d8-bf0c-039a9db28460", - "name": "Pending", - "color": "#ff7800", - "orderindex": 2 - }, - { - "id": "890ecf28-bdd4-4f53-92cc-bc4edb696fcd", - "name": "Payment Recieved", - "color": "#2ecd6f", - "orderindex": 3 - }, - { - "id": "e89f7dd7-fd24-4b32-ac4d-f174d8ca914f", - "name": "n/a", - "color": "#b5bcc2", - "orderindex": 4 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4b7a29be-e261-4340-8f3f-e6de838473e5", - "name": "Plan", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "new_drop_down": true, - "options": [ - { - "id": "4b9366a7-2592-4b7a-909a-ed4af705e27c", - "name": "Unlimited", - "color": "#02BCD4", - "orderindex": 0 - }, - { - "id": "c5032049-8c05-44e9-a000-3a071d457b8f", - "name": "Business", - "color": "#1bbc9c", - "orderindex": 1 - }, - { - "id": "9fb08801-1130-4650-8e2e-28578344ff3c", - "name": "Enterprise", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "4bfebc00-9d4a-40d1-aef8-5a87b610186c", - "name": "Contact Title", - "type": "text", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "666f74bf-6d87-41f3-8735-ccf0efe066dd", - "name": "Date", - "type": "date", - "type_config": {}, - "date_created": "1662379321069", - "hide_from_guests": false, - "required": false - }, - { - "id": "a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c", - "name": "Industry", - "type": "drop_down", - "type_config": { - "default": 0, - "placeholder": {}, - "options": [ - { - "id": "75173398-257f-42b6-8bae-4cf767fa99ab", - "name": "Engineering", - "color": "#04A9F4", - "orderindex": 0 - }, - { - "id": "c7f9b6f5-cd98-4609-af10-68a8710cc1bf", - "name": "Retail", - "color": "#ff7800", - "orderindex": 1 - }, - { - "id": "dbe84940-b4e8-4a29-8491-e1aa5f2be4e2", - "name": "Hospitality", - "color": "#2ecd6f", - "orderindex": 2 - } - ] - }, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "b01b32fd-94d3-43e6-9f31-2c855ff169cd", - "name": "Url", - "type": "url", - "type_config": {}, - "date_created": "1661970432587", - "hide_from_guests": false, - "required": false - }, - { - "id": "c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6", - "name": "Phone Number", - "type": "phone", - "type_config": {}, - "date_created": "1661970795061", - "hide_from_guests": false, - "required": false - }, - { - "id": "d0201829-ddcd-4b97-b71f-0f9e672488f2", - "name": "Account Size", - "type": "number", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "ea6c1e48-2abf-4328-b228-79c213e147c8", - "name": "Location", - "type": "location", - "type_config": {}, - "date_created": "1662229589329", - "hide_from_guests": false, - "required": false - }, - { - "id": "ebe825fb-92de-41ce-a29c-25018da039b4", - "name": "Email", - "type": "email", - "type_config": {}, - "date_created": "1660124553414", - "hide_from_guests": false, - "required": {} - }, - { - "id": "f431cda3-a575-4a05-ba8d-583d9b6cb2df", - "name": "Rating", - "type": "emoji", - "type_config": { - "count": 5, - "code_point": "2b50" - }, - "date_created": "1661963909454", - "hide_from_guests": false, - "required": false - }, - { - "id": "ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb", - "name": "Money In INR", - "type": "currency", - "type_config": { - "default": {}, - "precision": 2, - "currency_type": "INR" - }, - "date_created": "1661428276019", - "hide_from_guests": false, - "required": false - } - ] - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId123/field', + method: 'GET', }, - { - httpReq: { - url: 'https://api.clickup.com/api/v2/list/correctListId456/field', - method: 'GET', - }, - httpRes: { - data: { - "fields": [] + httpRes: { + data: { + fields: [ + { + id: '19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e', + name: 'Labels', + type: 'labels', + type_config: { + options: [ + { + id: '32c81c1c-cf53-4829-92f5-0f0270d27a45', + label: 'Option 1', + color: {}, + }, + { + id: '7e24f329-9dd9-4e68-b426-2c70af6f9347', + label: 'Option 2', + color: {}, + }, + ], + }, + date_created: '1661964865880', + hide_from_guests: false, + required: false, + }, + { + id: '22eaffee-ffec-4c3b-bdae-56e69d55eecd', + name: 'Payment Status', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: 'e109e36b-a052-4a31-af16-25da7324990f', + name: 'Sent Request', + color: '#FF7FAB', + orderindex: 0, + }, + { + id: '3a3b4512-2896-44f7-8075-2ff37777fe24', + name: 'Quote sent', + color: '#EA80FC', + orderindex: 1, + }, + { + id: '7afcb6fb-cec8-41d8-bf0c-039a9db28460', + name: 'Pending', + color: '#ff7800', + orderindex: 2, + }, + { + id: '890ecf28-bdd4-4f53-92cc-bc4edb696fcd', + name: 'Payment Recieved', + color: '#2ecd6f', + orderindex: 3, + }, + { + id: 'e89f7dd7-fd24-4b32-ac4d-f174d8ca914f', + name: 'n/a', + color: '#b5bcc2', + orderindex: 4, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4b7a29be-e261-4340-8f3f-e6de838473e5', + name: 'Plan', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + new_drop_down: true, + options: [ + { + id: '4b9366a7-2592-4b7a-909a-ed4af705e27c', + name: 'Unlimited', + color: '#02BCD4', + orderindex: 0, + }, + { + id: 'c5032049-8c05-44e9-a000-3a071d457b8f', + name: 'Business', + color: '#1bbc9c', + orderindex: 1, + }, + { + id: '9fb08801-1130-4650-8e2e-28578344ff3c', + name: 'Enterprise', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '4bfebc00-9d4a-40d1-aef8-5a87b610186c', + name: 'Contact Title', + type: 'text', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: '666f74bf-6d87-41f3-8735-ccf0efe066dd', + name: 'Date', + type: 'date', + type_config: {}, + date_created: '1662379321069', + hide_from_guests: false, + required: false, + }, + { + id: 'a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c', + name: 'Industry', + type: 'drop_down', + type_config: { + default: 0, + placeholder: {}, + options: [ + { + id: '75173398-257f-42b6-8bae-4cf767fa99ab', + name: 'Engineering', + color: '#04A9F4', + orderindex: 0, + }, + { + id: 'c7f9b6f5-cd98-4609-af10-68a8710cc1bf', + name: 'Retail', + color: '#ff7800', + orderindex: 1, + }, + { + id: 'dbe84940-b4e8-4a29-8491-e1aa5f2be4e2', + name: 'Hospitality', + color: '#2ecd6f', + orderindex: 2, + }, + ], + }, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'b01b32fd-94d3-43e6-9f31-2c855ff169cd', + name: 'Url', + type: 'url', + type_config: {}, + date_created: '1661970432587', + hide_from_guests: false, + required: false, + }, + { + id: 'c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6', + name: 'Phone Number', + type: 'phone', + type_config: {}, + date_created: '1661970795061', + hide_from_guests: false, + required: false, + }, + { + id: 'd0201829-ddcd-4b97-b71f-0f9e672488f2', + name: 'Account Size', + type: 'number', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'ea6c1e48-2abf-4328-b228-79c213e147c8', + name: 'Location', + type: 'location', + type_config: {}, + date_created: '1662229589329', + hide_from_guests: false, + required: false, + }, + { + id: 'ebe825fb-92de-41ce-a29c-25018da039b4', + name: 'Email', + type: 'email', + type_config: {}, + date_created: '1660124553414', + hide_from_guests: false, + required: {}, + }, + { + id: 'f431cda3-a575-4a05-ba8d-583d9b6cb2df', + name: 'Rating', + type: 'emoji', + type_config: { + count: 5, + code_point: '2b50', }, - status: 200 - }, - } + date_created: '1661963909454', + hide_from_guests: false, + required: false, + }, + { + id: 'ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb', + name: 'Money In INR', + type: 'currency', + type_config: { + default: {}, + precision: 2, + currency_type: 'INR', + }, + date_created: '1661428276019', + hide_from_guests: false, + required: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.clickup.com/api/v2/list/correctListId456/field', + method: 'GET', + }, + httpRes: { + data: { + fields: [], + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts index 3c5a461f69d..22a120770a1 100644 --- a/test/integrations/destinations/custify/deleteUsers/data.ts +++ b/test/integrations/destinations/custify/deleteUsers/data.ts @@ -129,8 +129,7 @@ export const data = [ }, { - description: - 'Test 4: should fail when one of the userAttributes does not contain `userId`', + description: 'Test 4: should fail when one of the userAttributes does not contain `userId`', input: { request: { body: [ @@ -140,8 +139,7 @@ export const data = [ { userId: 'rudder1', }, - { - }, + {}, ], config: { apiKey: 'dummyApiKey', diff --git a/test/integrations/destinations/delighted/network.ts b/test/integrations/destinations/delighted/network.ts index 15b0a414e6b..d9896a25e81 100644 --- a/test/integrations/destinations/delighted/network.ts +++ b/test/integrations/destinations/delighted/network.ts @@ -1,30 +1,30 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, - params: { - email: "identified_user@email.com" - } - }, - httpRes: { - data: ["user data"], - status: 200 - }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` }, + params: { + email: 'identified_user@email.com', + }, }, - { - httpReq: { - url: 'https://api.delighted.com/v1/people.json', - method: 'GET', - headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, - params: { - email: "unidentified_user@email.com" - } - }, - httpRes: { - data: [], - status: 200 - }, - } + httpRes: { + data: ['user data'], + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.delighted.com/v1/people.json', + method: 'GET', + headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` }, + params: { + email: 'unidentified_user@email.com', + }, + }, + httpRes: { + data: [], + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/facebook_conversions/processor/data.ts b/test/integrations/destinations/facebook_conversions/processor/data.ts index beb7eb32aa0..6eb90942a77 100644 --- a/test/integrations/destinations/facebook_conversions/processor/data.ts +++ b/test/integrations/destinations/facebook_conversions/processor/data.ts @@ -1434,4 +1434,104 @@ export const data = [ }, mockFns: defaultMockFns, }, + { + name: 'facebook_conversions', + description: 'Track event with standard event order completed with content_type in properties', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + channel: 'web', + context: { + traits: { + email: ' aBc@gmail.com ', + address: { + zip: 1234, + }, + anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + }, + }, + event: 'order completed', + integrations: { + All: true, + }, + message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8', + properties: { + content_type: 'product_group', + revenue: 400, + additional_bet_index: 0, + products: [ + { + product_id: 1234, + quantity: 5, + price: 55, + }, + ], + }, + timestamp: '2023-11-12T15:46:51.693229+05:30', + type: 'track', + }, + destination: { + Config: { + limitedDataUsage: true, + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: false, + }, + ], + accessToken: '09876', + datasetId: 'dummyID', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + removeExternalId: true, + actionSource: 'website', + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://graph.facebook.com/v18.0/dummyID/events?access_token=09876', + headers: {}, + params: {}, + body: { + JSON: {}, + XML: {}, + JSON_ARRAY: {}, + FORM: { + data: [ + '{"user_data":{"em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4"},"event_name":"Purchase","event_time":1699784211,"action_source":"website","custom_data":{"content_type":"product_group","revenue":400,"additional_bet_index":0,"products":[{"product_id":1234,"quantity":5,"price":55}],"content_ids":[1234],"contents":[{"id":1234,"quantity":5,"item_price":55}],"currency":"USD","value":400,"num_items":1}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + mockFns: defaultMockFns, + }, ]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts new file mode 100644 index 00000000000..9ac709978d6 --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts @@ -0,0 +1,258 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; + +export const testFormData = { + data: [ + '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', + ], +}; +export const statTags = { + destType: 'FACEBOOK_PIXEL', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_scenario_1', + name: 'facebook_pixel', + description: 'app event fails due to access token error', + successCriteria: 'Should return 400 with invalid access token error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid OAuth 2.0 access token', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: 'Invalid OAuth 2.0 access token', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_2', + name: 'facebook_pixel', + description: 'app event sent successfully', + successCriteria: 'Should return 200', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`, + params: { + destination: 'facebook_pixel', + }, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '{"events_received":1,"fbtrace_id":"facebook_trace_id"}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_3', + name: 'facebook_pixel', + description: 'app event fails due to invalid timestamp', + successCriteria: 'Should return 400 with invalid timestamp error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Event Timestamp Too Old', + statTags, + response: [ + { + error: 'Event Timestamp Too Old', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_4', + name: 'facebook_pixel', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statTags, + response: [ + { + error: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_5', + name: 'facebook_pixel', + description: 'app event fails due to invalid parameter', + successCriteria: 'Should return 400 with Invalid parameter error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid Parameter', + statTags, + response: [ + { + error: 'Invalid Parameter', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_scenario_6', + name: 'facebook_pixel', + description: 'app event fails due to invalid parameter', + successCriteria: 'Should return 400 with Invalid parameter error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid Parameter', + statTags, + response: [ + { + error: 'Invalid Parameter', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts index 239fa93a6ab..dcc633e1a85 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts @@ -1,6 +1,15 @@ import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; +import { testScenariosForV1API, testFormData, statTags as baseStatTags } from './business'; +import { otherScenariosV1 } from './other'; +import { oauthScenariosV1 } from './oauth'; -export const data = [ +const statTags = { + ...baseStatTags, + destinationId: 'Non-determininable', + workspaceId: 'Non-determininable', +}; + +export const v0TestData = [ { name: 'facebook_pixel', description: 'Test 0', @@ -12,11 +21,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654773112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -51,15 +56,10 @@ export const data = [ status: 500, }, statTags: { - destType: 'FACEBOOK_PIXEL', + ...statTags, errorCategory: 'dataValidation', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', errorType: 'configuration', meta: 'accessTokenExpired', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', }, }, }, @@ -77,11 +77,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654773112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -126,11 +122,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -169,16 +161,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -195,11 +178,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -234,14 +213,8 @@ export const data = [ status: 500, }, statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + ...statTags, errorType: 'throttled', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', }, }, }, @@ -259,11 +232,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -300,16 +269,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -326,11 +286,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"d58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -365,16 +321,7 @@ export const data = [ }, status: 404, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -391,11 +338,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -430,16 +373,7 @@ export const data = [ }, status: 400, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -456,11 +390,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -495,16 +425,7 @@ export const data = [ }, status: 500, }, - statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, + statTags, }, }, }, @@ -521,11 +442,7 @@ export const data = [ body: { body: { XML: {}, - FORM: { - data: [ - '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}', - ], - }, + FORM: testFormData, JSON: {}, JSON_ARRAY: {}, }, @@ -561,14 +478,8 @@ export const data = [ status: 412, }, statTags: { - destType: 'FACEBOOK_PIXEL', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', + ...statTags, errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', meta: 'unhandledStatusCode', }, }, @@ -577,3 +488,10 @@ export const data = [ }, }, ]; + +export const data = [ + ...v0TestData, + ...testScenariosForV1API, + ...otherScenariosV1, + ...oauthScenariosV1, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts new file mode 100644 index 00000000000..c6d938c627b --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts @@ -0,0 +1,45 @@ +import { testFormData, statTags } from './business'; +import { generateProxyV1Payload, generateMetadata } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; + +export const oauthScenariosV1: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_oauth_scenario_1', + name: 'facebook_pixel', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Capability or permissions issue.', + statTags, + response: [ + { + error: 'Capability or permissions issue.', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts new file mode 100644 index 00000000000..e25cc8e07c4 --- /dev/null +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts @@ -0,0 +1,93 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config'; +import { testFormData, statTags } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'facebook_pixel_v1_other_scenario_1', + name: 'facebook_pixel', + description: 'user update request is throttled due to too many calls', + successCriteria: 'Should return 429 with message there have been too many calls', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, + params: { + destination: 'facebook_pixel', + }, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + message: 'API User Too Many Calls', + statTags: { + ...statTags, + errorType: 'throttled', + }, + response: [ + { + error: 'API User Too Many Calls', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'facebook_pixel_v1_other_scenario_2', + name: 'facebook_pixel', + description: 'app event fails due to Unhandled random error', + successCriteria: 'Should return 500 with Unhandled random error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`, + FORM: testFormData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 500, + message: 'Unhandled random error', + statTags: { + ...statTags, + errorType: 'retryable', + meta: 'unhandledStatusCode', + }, + response: [ + { + error: 'Unhandled random error', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/facebook_pixel/network.ts b/test/integrations/destinations/facebook_pixel/network.ts index 05b3a05fd0b..a61fa44eab8 100644 --- a/test/integrations/destinations/facebook_pixel/network.ts +++ b/test/integrations/destinations/facebook_pixel/network.ts @@ -1,4 +1,4 @@ -import { data } from './dataDelivery/data'; +import { testFormData } from './dataDelivery/business'; import { getFormData } from '../../../../src/adapters/network'; import { VERSION } from '../../../../src/v0/destinations/facebook_pixel/config'; @@ -6,7 +6,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`, - data: getFormData(data[0].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -26,7 +26,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, - data: getFormData(data[2].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -51,7 +51,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, - data: getFormData(data[3].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -71,7 +71,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, - data: getFormData(data[4].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -93,7 +93,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`, - data: getFormData(data[5].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -114,7 +114,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`, - data: getFormData(data[6].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -135,7 +135,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`, - data: getFormData(data[7].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -156,7 +156,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`, - data: getFormData(data[8].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -177,7 +177,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`, - data: getFormData(data[1].input.request.body.body.FORM).toString(), + data: getFormData(testFormData).toString(), params: { destination: 'facebook_pixel' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', diff --git a/test/integrations/destinations/facebook_pixel/processor/data.ts b/test/integrations/destinations/facebook_pixel/processor/data.ts index 557bc7066c3..f6a5cd1e209 100644 --- a/test/integrations/destinations/facebook_pixel/processor/data.ts +++ b/test/integrations/destinations/facebook_pixel/processor/data.ts @@ -6460,4 +6460,111 @@ export const data = [ }, }, }, + { + name: 'facebook_pixel', + description: + 'Test 51: properties.content_type is given priority over populating it from categoryToContent mapping.', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + channel: 'web', + type: 'track', + messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be', + originalTimestamp: '2023-10-14T15:46:51.693229+05:30', + anonymousId: '00000000000000000000000000', + userId: '12345', + event: 'order completed', + properties: { + content_type: 'product_group', + category: ['clothing', 'fishing'], + order_id: 'rudderstackorder1', + revenue: 12.24, + currency: 'INR', + products: [ + { + quantity: 1, + price: 24.75, + name: 'my product', + sku: 'p-298', + }, + { + quantity: 3, + price: 24.75, + name: 'other product', + sku: 'p-299', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T11:15:53.296Z', + }, + destination: { + Config: { + blacklistPiiProperties: [ + { + blacklistPiiProperties: '', + blacklistPiiHash: true, + }, + ], + categoryToContent: [ + { + from: 'clothing', + to: 'product', + }, + ], + accessToken: '09876', + pixelId: 'dummyPixelId', + eventsToEvents: [ + { + from: '', + to: '', + }, + ], + valueFieldIdentifier: 'properties.price', + advancedMapping: false, + }, + Enabled: true, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`, + headers: {}, + params: {}, + body: { + JSON: {}, + JSON_ARRAY: {}, + XML: {}, + FORM: { + data: [ + '{"user_data":{"external_id":"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"content_type":"product_group","category[0]":"clothing","category[1]":"fishing","order_id":"rudderstackorder1","revenue":12.24,"currency":"INR","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":3,"products[1].price":24.75,"products[1].name":"other product","products[1].sku":"p-299","content_category":"clothing,fishing","content_ids":["p-298","p-299"],"value":12.24,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":3,"item_price":24.75}],"num_items":2}}', + ], + }, + }, + files: {}, + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ].map((d) => ({ ...d, mockFns })); diff --git a/test/integrations/destinations/fb/dataDelivery/business.ts b/test/integrations/destinations/fb/dataDelivery/business.ts new file mode 100644 index 00000000000..156dc265725 --- /dev/null +++ b/test/integrations/destinations/fb/dataDelivery/business.ts @@ -0,0 +1,226 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/fb/config'; + +export const testData1 = { + event: 'CUSTOM_APP_EVENTS', + advertiser_id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R', + 'ud[em]': '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08', + 'ud[fn]': '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', + 'ud[ge]': '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a', + 'ud[ln]': '3547cb112ac4489af2310c0626cdba6f3097a2ad5a3b42ddd3b59c76c7a079a3', + 'ud[ph]': '588211a01b10feacbf7988d97a06e86c18af5259a7f457fd8759b7f7409a7d1f', + extinfo: + '["a2","","","","8.1.0","Redmi 6","","","Banglalink",640,480,"1.23",0,0,0,"Europe/Berlin"]', + app_user_id: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1', + custom_events: + '[{"_logTime":1567333011693,"_eventName":"spin_result","_valueToSum":400,"fb_currency":"GBP","additional_bet_index":0,"battle_id":"N/A","bet_amount":9,"bet_level":1,"bet_multiplier":1,"coin_balance":9466052,"current_module_name":"CasinoGameModule","days_in_game":0,"extra_param":"N/A","fb_profile":"0","featureGameType":"N/A","game_fps":30,"game_id":"fireEagleBase","game_name":"FireEagleSlots","gem_balance":0,"graphicsQuality":"HD","idfa":"2bf99787-33d2-4ae2-a76a-c49672f97252","internetReachability":"ReachableViaLocalAreaNetwork","isLowEndDevice":"False","is_auto_spin":"False","is_turbo":"False","isf":"False","ishighroller":"False","jackpot_win_amount":90,"jackpot_win_type":"Silver","level":6,"lifetime_gem_balance":0,"no_of_spin":1,"player_total_battles":0,"player_total_shields":0,"start_date":"2019-08-01","total_payments":0,"tournament_id":"T1561970819","userId":"c82cbdff-e5be-4009-ac78-cdeea09ab4b1","versionSessionCount":2,"win_amount":0,"fb_content_id":["123","345","567"]}]', + advertiser_tracking_enabled: '0', + application_tracking_enabled: '0', +}; + +export const testData2 = { + extinfo: '["a2","","","","8.1.0","Redmi 6","","","Banglalink",0,100,"50.00",0,0,0,""]', + custom_events: + '[{"_logTime":1567333011693,"_eventName":"Viewed Screen","fb_description":"Main.1233"}]', + 'ud[em]': '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08', + advertiser_tracking_enabled: '0', + application_tracking_enabled: '0', + event: 'CUSTOM_APP_EVENTS', +}; + +export const statTags = { + destType: 'FB', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'fb_v1_scenario_1', + name: 'fb', + description: 'app event fails due to access token error', + successCriteria: 'Should return 400 with invalid access token error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=invalid_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Invalid OAuth 2.0 access token', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: 'Invalid OAuth 2.0 access token', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_2', + name: 'fb', + description: 'app event sent successfully', + successCriteria: 'Should return 200', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=my_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: '{"events_received":1,"fbtrace_id":"facebook_trace_id"}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_3', + name: 'fb', + description: 'app event fails due to invalid timestamp', + successCriteria: 'Should return 400 with invalid timestamp error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Event Timestamp Too Old', + statTags, + response: [ + { + error: 'Event Timestamp Too Old', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fb_v1_scenario_4', + name: 'fb', + description: 'app event fails due to missing permissions', + successCriteria: 'Should return 400 with missing permissions error', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`, + headers: { + 'x-forwarded-for': '1.2.3.4', + }, + params: { + destination: 'fb', + }, + FORM: testData2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statTags, + response: [ + { + error: + "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation", + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb/dataDelivery/data.ts b/test/integrations/destinations/fb/dataDelivery/data.ts index 3b37d03f46a..9ee19af265c 100644 --- a/test/integrations/destinations/fb/dataDelivery/data.ts +++ b/test/integrations/destinations/fb/dataDelivery/data.ts @@ -1,6 +1,8 @@ import { VERSION } from '../../../../../src/v0/destinations/fb/config'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'fb', description: 'Test 0', @@ -371,3 +373,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/fb/dataDelivery/other.ts b/test/integrations/destinations/fb/dataDelivery/other.ts new file mode 100644 index 00000000000..9ac3f14fb54 --- /dev/null +++ b/test/integrations/destinations/fb/dataDelivery/other.ts @@ -0,0 +1,51 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { VERSION } from '../../../../../src/v0/destinations/fb/config'; +import { testData2 as testData, statTags } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'fb_v1_other_scenario_1', + name: 'fb', + description: 'user update request is throttled due to too many calls', + successCriteria: 'Should return 429 with message there have been too many calls', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`, + params: { + destination: 'fb', + }, + FORM: testData, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 429, + message: 'API User Too Many Calls', + statTags: { + ...statTags, + errorType: 'throttled', + }, + response: [ + { + error: 'API User Too Many Calls', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb/network.ts b/test/integrations/destinations/fb/network.ts index 1a2f114d74c..31bbaf0b6ee 100644 --- a/test/integrations/destinations/fb/network.ts +++ b/test/integrations/destinations/fb/network.ts @@ -12,7 +12,7 @@ const fbPixelTcs = data return nw.httpReq.url === fbendpoint; })[0]; const clonedFbpTc = cloneDeep(fbpTc); - const clonedFormData = cloneDeep(d.input.request.body.body.FORM); + const clonedFormData = cloneDeep(d.input.request.body.body?.FORM); clonedFbpTc.httpReq.data = getFormData(clonedFormData).toString(); return clonedFbpTc; }); @@ -21,7 +21,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=invalid_access_token`, - data: getFormData(data[0].input.request.body.body.FORM).toString(), + data: getFormData(data[0].input.request.body.body?.FORM).toString(), params: { destination: 'fb' }, headers: { 'User-Agent': 'RudderLabs' }, method: 'POST', @@ -41,7 +41,7 @@ export const networkCallsData = [ { httpReq: { url: `https://graph.facebook.com/${VERSION}/RudderFbApp/activities?access_token=my_access_token`, - data: getFormData(data[1].input.request.body.body.FORM).toString(), + data: getFormData(data[1].input.request.body.body?.FORM).toString(), params: { destination: 'fb' }, headers: { 'x-forwarded-for': '1.2.3.4', 'User-Agent': 'RudderLabs' }, method: 'POST', diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts new file mode 100644 index 00000000000..c48ad227ab7 --- /dev/null +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/business.ts @@ -0,0 +1,427 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; + +export const statTags = { + destType: 'FB_CUSTOM_AUDIENCE', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +const testParams1 = { + access_token: 'ABC', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: [ + 'EMAIL', + 'DOBM', + 'DOBD', + 'DOBY', + 'PHONE', + 'GEN', + 'FI', + 'MADID', + 'ZIP', + 'ST', + 'COUNTRY', + ], + data: [ + [ + 'shrouti@abc.com', + '2', + '13', + '2013', + '@09432457768', + 'f', + 'Ms.', + 'ABC', + 'ZIP ', + '123abc ', + 'IN', + ], + ], + }, +}; + +export const testParams2 = { + access_token: 'ABC', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: ['DOBY', 'PHONE', 'GEN', 'FI', 'MADID', 'ZIP', 'ST', 'COUNTRY'], + data: [['2013', '@09432457768', 'f', 'Ms.', 'ABC', 'ZIP ', '123abc ', 'IN']], + }, +}; + +const testParams3 = { + access_token: 'BCD', + payload: { + is_raw: true, + data_source: { + sub_type: 'ANYTHING', + }, + schema: ['DOBM', 'DOBD', 'DOBY', 'PHONE', 'GEN', 'FI', 'MADID', 'ZIP', 'ST', 'COUNTRY'], + data: [['2', '13', '2013', '@09432457768', 'f', 'Ms.', 'ABC', 'ZIP ', '123abc ', 'IN']], + }, +}; + +export const testScenariosForV1API: ProxyV1TestData[] = [ + { + id: 'fbca_v1_scenario_1', + name: 'fb_custom_audience', + description: 'successfully delete users from audience', + successCriteria: 'Should return 200 with no error with destination response', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'successResponse', + }, + params: testParams1, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: + '{"audience_id":"aud1","session_id":"123","num_received":4,"num_invalid_entries":0,"invalid_entry_samples":{}}', + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_2', + name: 'fb_custom_audience', + description: 'user addition failed due to missing permission', + successCriteria: 'Fail with status code 400 due to missing permissions', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'POST', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'permissionMissingError', + }, + params: testParams3, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Missing permission. Please make sure you have ads_management permission and the application is included in the allowlist', + statTags, + response: [ + { + error: + 'Missing permission. Please make sure you have ads_management permission and the application is included in the allowlist', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_3', + name: 'fb_custom_audience', + description: 'user deletion failed due to unavailable audience error', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'audienceUnavailableError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Custom Audience Unavailable: The custom audience you are trying to use has not been shared with your ad account', + statTags, + response: [ + { + error: + 'Custom Audience Unavailable: The custom audience you are trying to use has not been shared with your ad account', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_4', + name: 'fb_custom_audience', + description: 'user deletion failed because the custom audience has been deleted', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'audienceDeletedError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Custom Audience Has Been Deleted', + statTags, + response: [ + { + error: 'Custom Audience Has Been Deleted', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_5', + name: 'fb_custom_audience', + description: 'Failed to update the custom audience for unknown reason', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'failedToUpdateAudienceError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'Failed to update the custom audience', + statTags, + response: [ + { + error: 'Failed to update the custom audience', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_6', + name: 'fb_custom_audience', + description: + 'Failed to update the custom audience as excessive number of parameters were passed in the request', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'parameterExceededError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: 'The number of parameters exceeded the maximum for this operation', + statTags, + response: [ + { + error: 'The number of parameters exceeded the maximum for this operation', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_7', + name: 'fb_custom_audience', + description: 'user having permission issue while updating audience', + successCriteria: 'Fail with status code 403', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'code200PermissionError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 403, + message: '(#200) The current user can not update audience 23861283180290489', + statTags, + response: [ + { + error: '(#200) The current user can not update audience 23861283180290489', + statusCode: 403, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, + { + id: 'fbca_v1_scenario_8', + name: 'fb_custom_audience', + description: 'user deletion failed due expired access token error', + successCriteria: 'Fail with status code 400', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'accessTokenInvalidError', + }, + params: testParams2, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 400, + message: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + statTags: { + ...statTags, + errorCategory: 'dataValidation', + errorType: 'configuration', + meta: 'accessTokenExpired', + }, + response: [ + { + error: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts index 5ce15e0ea0c..b41c656d9f9 100644 --- a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts @@ -1,6 +1,8 @@ import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; +import { testScenariosForV1API } from './business'; +import { otherScenariosV1 } from './other'; -export const data = [ +export const existingTestData = [ { name: 'fb_custom_audience', description: 'successfully adding users to audience', @@ -645,3 +647,5 @@ export const data = [ }, }, ]; + +export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1]; diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts new file mode 100644 index 00000000000..52138604b07 --- /dev/null +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/other.ts @@ -0,0 +1,53 @@ +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; +import { ProxyV1TestData } from '../../../testTypes'; +import { getEndPoint } from '../../../../../src/v0/destinations/fb_custom_audience/config'; +import { statTags, testParams2 as testParams } from './business'; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'fbca_v1_other_scenario_1', + name: 'fb_custom_audience', + description: 'user update request is throttled due to too many calls to the ad account', + successCriteria: + 'Should return 429 with message there have been too many calls to this ad-account', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + method: 'DELETE', + endpoint: getEndPoint('aud1'), + headers: { + 'test-dest-response-key': 'tooManyCallsError', + }, + params: testParams, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'There have been too many calls to this ad-account.', + statTags: { + ...statTags, + errorType: 'throttled', + }, + status: 429, + response: [ + { + error: 'There have been too many calls to this ad-account.', + statusCode: 429, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/fb_custom_audience/network.ts b/test/integrations/destinations/fb_custom_audience/network.ts index fa11f28370c..9b498bc07e5 100644 --- a/test/integrations/destinations/fb_custom_audience/network.ts +++ b/test/integrations/destinations/fb_custom_audience/network.ts @@ -512,14 +512,15 @@ export const networkCallsData = [ httpRes: { data: { error: { - message: 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + message: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', type: 'OAuthException', code: 190, error_subcode: 463, - fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi' + fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi', }, }, status: 400, }, - } + }, ]; diff --git a/test/integrations/destinations/freshmarketer/network.ts b/test/integrations/destinations/freshmarketer/network.ts index 51f1a0c1154..9d661f26867 100644 --- a/test/integrations/destinations/freshmarketer/network.ts +++ b/test/integrations/destinations/freshmarketer/network.ts @@ -1,487 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, ]; - - - diff --git a/test/integrations/destinations/freshsales/network.ts b/test/integrations/destinations/freshsales/network.ts index f6043b265f9..9d661f26867 100644 --- a/test/integrations/destinations/freshsales/network.ts +++ b/test/integrations/destinations/freshsales/network.ts @@ -1,484 +1,495 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "sales_account": { - "id": 70003771396, - "name": "postman2.0", - "address": "Red Colony", - "city": "Pune", - "state": "Goa", - "zipcode": null, - "country": null, - "number_of_employees": 11, - "annual_revenue": 1000, - "website": null, - "owner_id": null, - "phone": "919191919191", - "open_deals_amount": null, - "open_deals_count": null, - "won_deals_amount": null, - "won_deals_count": null, - "last_contacted": null, - "last_contacted_mode": null, - "facebook": null, - "twitter": null, - "linkedin": null, - "links": { - "conversations": "/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "document_associations": "/crm/sales/sales_accounts/70003771396/document_associations", - "notes": "/crm/sales/sales_accounts/70003771396/notes?include=creater", - "tasks": "/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note" - }, - "custom_field": {}, - "created_at": "2022-08-17T04:15:00-04:00", - "updated_at": "2022-08-24T06:03:31-04:00", - "avatar": null, - "parent_sales_account_id": null, - "recent_note": null, - "last_contacted_via_sales_activity": null, - "last_contacted_sales_activity_mode": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "last_assigned_at": null, - "tags": [], - "is_deleted": false, - "team_user_ids": null, - "has_connections": false, - "record_type_id": "71010794477" - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/sales_accounts/upsert', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70042006456, - "first_name": "Rk", - "last_name": "Mishra", - "display_name": "Rk Mishra", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "testuser@google.com", - "emails": [ - { - "id": 70037311213, - "value": "testuser@google.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": "IST", - "work_number": "9988776655", - "mobile_number": "19265559504", - "address": null, - "last_seen": null, - "lead_score": 26, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70042006456/timeline_feeds", - "document_associations": "/crm/sales/contacts/70042006456/document_associations", - "notes": "/crm/sales/contacts/70042006456/notes?include=creater", - "tasks": "/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70042006456/duplicates", - "connections": "/crm/sales/contacts/70042006456/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-08-09T03:22:12-04:00", - "updated_at": "2022-08-30T00:33:27-04:00", - "keyword": "drilling", - "medium": "facebook", - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": "2022-08-29T05:51:24-04:00", - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": "ea5cfab2-3961-4d8a-8187-3d1858c99099", - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 2, - "record_type_id": "71010794476", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [], - "sales_accounts": [ - { - "partial": true, - "id": 70003771198, - "name": "div-quer", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": true - }, - { - "partial": true, - "id": 70003825177, - "name": "BisleriGroup", - "avatar": null, - "website": null, - "last_contacted": null, - "record_type_id": "71010794477", - "is_primary": false - } - ] - } - }, - status: 200 + httpRes: { + data: { + sales_account: { + id: 70003771396, + name: 'postman2.0', + address: 'Red Colony', + city: 'Pune', + state: 'Goa', + zipcode: null, + country: null, + number_of_employees: 11, + annual_revenue: 1000, + website: null, + owner_id: null, + phone: '919191919191', + open_deals_amount: null, + open_deals_count: null, + won_deals_amount: null, + won_deals_count: null, + last_contacted: null, + last_contacted_mode: null, + facebook: null, + twitter: null, + linkedin: null, + links: { + conversations: + '/crm/sales/sales_accounts/70003771396/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + document_associations: '/crm/sales/sales_accounts/70003771396/document_associations', + notes: '/crm/sales/sales_accounts/70003771396/notes?include=creater', + tasks: + '/crm/sales/sales_accounts/70003771396/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/sales_accounts/70003771396/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + }, + custom_field: {}, + created_at: '2022-08-17T04:15:00-04:00', + updated_at: '2022-08-24T06:03:31-04:00', + avatar: null, + parent_sales_account_id: null, + recent_note: null, + last_contacted_via_sales_activity: null, + last_contacted_sales_activity_mode: null, + completed_sales_sequences: null, + active_sales_sequences: null, + last_assigned_at: null, + tags: [], + is_deleted: false, + team_user_ids: null, + has_connections: false, + record_type_id: '71010794477', }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', - method: 'GET' - }, - httpRes: { - data: { - "sales_activity_types": [ - { - "partial": true, - "id": 70000666879, - "name": "own-calender", - "internal_name": "cappointment", - "show_in_conversation": true, - "position": 1, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663932, - "name": "fb-support", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 2, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000663746, - "name": "twitter sales", - "internal_name": "twitter", - "show_in_conversation": true, - "position": 3, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000646396, - "name": "linked sales", - "internal_name": "linkedin", - "show_in_conversation": true, - "position": 4, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000642330, - "name": "facebook sales", - "internal_name": "facebook", - "show_in_conversation": true, - "position": 5, - "is_default": false, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612897, - "name": "Chat", - "internal_name": "chat", - "show_in_conversation": true, - "position": 6, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612898, - "name": "Phone", - "internal_name": "phone", - "show_in_conversation": true, - "position": 7, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612899, - "name": "Meeting", - "internal_name": "appointment", - "show_in_conversation": true, - "position": 8, - "is_default": true, - "is_checkedin": true - }, - { - "partial": true, - "id": 70000612900, - "name": "Task", - "internal_name": "task", - "show_in_conversation": true, - "position": 9, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612901, - "name": "Email", - "internal_name": "email", - "show_in_conversation": true, - "position": 10, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612902, - "name": "SMS Outgoing", - "internal_name": "sms_outgoing", - "show_in_conversation": true, - "position": 11, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612903, - "name": "Reminder", - "internal_name": "reminder", - "show_in_conversation": false, - "position": 12, - "is_default": true, - "is_checkedin": false - }, - { - "partial": true, - "id": 70000612904, - "name": "SMS Incoming", - "internal_name": "sms_incoming", - "show_in_conversation": true, - "position": 13, - "is_default": true, - "is_checkedin": false - } - ] - }, - status: 200 - }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert?include=sales_accounts', + method: 'POST', }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', - method: 'POST' - }, - httpRes: { - data: { - "contact": { - "id": 70054866612, - "first_name": null, - "last_name": null, - "display_name": "jamessampleton120@gmail.com", - "avatar": null, - "job_title": null, - "city": null, - "state": null, - "zipcode": null, - "country": null, - "email": "jamessampleton120@gmail.com", - "emails": [ - { - "id": 70047409219, - "value": "jamessampleton120@gmail.com", - "is_primary": true, - "label": null, - "_destroy": false - } - ], - "time_zone": null, - "work_number": null, - "mobile_number": null, - "address": null, - "last_seen": null, - "lead_score": 0, - "last_contacted": null, - "open_deals_amount": null, - "won_deals_amount": null, - "links": { - "conversations": "/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3", - "timeline_feeds": "/crm/sales/contacts/70054866612/timeline_feeds", - "document_associations": "/crm/sales/contacts/70054866612/document_associations", - "notes": "/crm/sales/contacts/70054866612/notes?include=creater", - "tasks": "/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type", - "appointments": "/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note", - "reminders": "/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable", - "duplicates": "/crm/sales/contacts/70054866612/duplicates", - "connections": "/crm/sales/contacts/70054866612/connections" - }, - "last_contacted_sales_activity_mode": null, - "custom_field": {}, - "created_at": "2022-10-11T08:42:15-04:00", - "updated_at": "2022-10-11T08:42:15-04:00", - "keyword": null, - "medium": null, - "last_contacted_mode": null, - "recent_note": null, - "won_deals_count": null, - "last_contacted_via_sales_activity": null, - "completed_sales_sequences": null, - "active_sales_sequences": null, - "web_form_ids": null, - "open_deals_count": null, - "last_assigned_at": null, - "tags": [], - "facebook": null, - "twitter": null, - "linkedin": null, - "is_deleted": false, - "team_user_ids": null, - "external_id": null, - "work_email": null, - "subscription_status": 1, - "subscription_types": "2;3;4;5;1", - "customer_fit": 0, - "record_type_id": "71012139284", - "whatsapp_subscription_status": 2, - "sms_subscription_status": 2, - "last_seen_chat": null, - "first_seen_chat": null, - "locale": null, - "total_sessions": null, - "phone_numbers": [] - } + httpRes: { + data: { + contact: { + id: 70042006456, + first_name: 'Rk', + last_name: 'Mishra', + display_name: 'Rk Mishra', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'testuser@google.com', + emails: [ + { + id: 70037311213, + value: 'testuser@google.com', + is_primary: true, + label: null, + _destroy: false, + }, + ], + time_zone: 'IST', + work_number: '9988776655', + mobile_number: '19265559504', + address: null, + last_seen: null, + lead_score: 26, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70042006456/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70042006456/timeline_feeds', + document_associations: '/crm/sales/contacts/70042006456/document_associations', + notes: '/crm/sales/contacts/70042006456/notes?include=creater', + tasks: + '/crm/sales/contacts/70042006456/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70042006456/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70042006456/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70042006456/duplicates', + connections: '/crm/sales/contacts/70042006456/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-08-09T03:22:12-04:00', + updated_at: '2022-08-30T00:33:27-04:00', + keyword: 'drilling', + medium: 'facebook', + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: '2022-08-29T05:51:24-04:00', + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: 'ea5cfab2-3961-4d8a-8187-3d1858c99099', + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 2, + record_type_id: '71010794476', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], + sales_accounts: [ + { + partial: true, + id: 70003771198, + name: 'div-quer', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: true, + }, + { + partial: true, + id: 70003825177, + name: 'BisleriGroup', + avatar: null, + website: null, + last_contacted: null, + record_type_id: '71010794477', + is_primary: false, }, - status: 200 + ], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', - method: 'GET' - }, - httpRes: { - data: { - "lists": [ - { - "id": 70000053624, - "name": "Sample list" - }, - { - "id": 70000056575, - "name": "list1-test" - }, - { - "id": 70000058627, - "name": "Jio 5G Group" - }, - { - "id": 70000058628, - "name": "Airtel 5G Group" - }, - { - "id": 70000059716, - "name": "Voda 5G" - } - ], - "meta": { - "total_pages": 1, - "total": 5 - } + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/sales_activity_types', + method: 'GET', + }, + httpRes: { + data: { + sales_activity_types: [ + { + partial: true, + id: 70000666879, + name: 'own-calender', + internal_name: 'cappointment', + show_in_conversation: true, + position: 1, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663932, + name: 'fb-support', + internal_name: 'facebook', + show_in_conversation: true, + position: 2, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000663746, + name: 'twitter sales', + internal_name: 'twitter', + show_in_conversation: true, + position: 3, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000646396, + name: 'linked sales', + internal_name: 'linkedin', + show_in_conversation: true, + position: 4, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000642330, + name: 'facebook sales', + internal_name: 'facebook', + show_in_conversation: true, + position: 5, + is_default: false, + is_checkedin: false, + }, + { + partial: true, + id: 70000612897, + name: 'Chat', + internal_name: 'chat', + show_in_conversation: true, + position: 6, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612898, + name: 'Phone', + internal_name: 'phone', + show_in_conversation: true, + position: 7, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612899, + name: 'Meeting', + internal_name: 'appointment', + show_in_conversation: true, + position: 8, + is_default: true, + is_checkedin: true, + }, + { + partial: true, + id: 70000612900, + name: 'Task', + internal_name: 'task', + show_in_conversation: true, + position: 9, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612901, + name: 'Email', + internal_name: 'email', + show_in_conversation: true, + position: 10, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612902, + name: 'SMS Outgoing', + internal_name: 'sms_outgoing', + show_in_conversation: true, + position: 11, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612903, + name: 'Reminder', + internal_name: 'reminder', + show_in_conversation: false, + position: 12, + is_default: true, + is_checkedin: false, + }, + { + partial: true, + id: 70000612904, + name: 'SMS Incoming', + internal_name: 'sms_incoming', + show_in_conversation: true, + position: 13, + is_default: true, + is_checkedin: false, + }, + ], + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/contacts/upsert', + method: 'POST', + }, + httpRes: { + data: { + contact: { + id: 70054866612, + first_name: null, + last_name: null, + display_name: 'jamessampleton120@gmail.com', + avatar: null, + job_title: null, + city: null, + state: null, + zipcode: null, + country: null, + email: 'jamessampleton120@gmail.com', + emails: [ + { + id: 70047409219, + value: 'jamessampleton120@gmail.com', + is_primary: true, + label: null, + _destroy: false, }, - status: 200 + ], + time_zone: null, + work_number: null, + mobile_number: null, + address: null, + last_seen: null, + lead_score: 0, + last_contacted: null, + open_deals_amount: null, + won_deals_amount: null, + links: { + conversations: + '/crm/sales/contacts/70054866612/conversations/all?include=email_conversation_recipients%2Ctargetable%2Cphone_number%2Cphone_caller%2Cnote%2Cuser&per_page=3', + timeline_feeds: '/crm/sales/contacts/70054866612/timeline_feeds', + document_associations: '/crm/sales/contacts/70054866612/document_associations', + notes: '/crm/sales/contacts/70054866612/notes?include=creater', + tasks: + '/crm/sales/contacts/70054866612/tasks?include=creater,owner,updater,targetable,users,task_type', + appointments: + '/crm/sales/contacts/70054866612/appointments?include=creater,owner,updater,targetable,appointment_attendees,conference,note', + reminders: + '/crm/sales/contacts/70054866612/reminders?include=creater,owner,updater,targetable', + duplicates: '/crm/sales/contacts/70054866612/duplicates', + connections: '/crm/sales/contacts/70054866612/connections', + }, + last_contacted_sales_activity_mode: null, + custom_field: {}, + created_at: '2022-10-11T08:42:15-04:00', + updated_at: '2022-10-11T08:42:15-04:00', + keyword: null, + medium: null, + last_contacted_mode: null, + recent_note: null, + won_deals_count: null, + last_contacted_via_sales_activity: null, + completed_sales_sequences: null, + active_sales_sequences: null, + web_form_ids: null, + open_deals_count: null, + last_assigned_at: null, + tags: [], + facebook: null, + twitter: null, + linkedin: null, + is_deleted: false, + team_user_ids: null, + external_id: null, + work_email: null, + subscription_status: 1, + subscription_types: '2;3;4;5;1', + customer_fit: 0, + record_type_id: '71012139284', + whatsapp_subscription_status: 2, + sms_subscription_status: 2, + last_seen_chat: null, + first_seen_chat: null, + locale: null, + total_sessions: null, + phone_numbers: [], }, + }, + status: 200, }, - { - httpReq: { - url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', - method: 'GET' - }, - httpRes: { - data: { - "lifecycle_stages": [ - { - "id": 71012139274, - "name": "Sales Qualified Lead start", - "position": 1, - "disabled": false, - "default": true, - "type": "Sales Qualified Lead", - "contact_status_ids": [70000697858, 70000697859, 70000697860] - }, - { - "id": 71012139273, - "name": "Lead", - "position": 2, - "disabled": false, - "default": true, - "type": "Lead", - "contact_status_ids": [70000697854, 70000697855, 70000697856, 70000697857] - }, - { - "id": 71012806409, - "name": "final Customer", - "position": 3, - "disabled": false, - "default": false, - "type": "Custom", - "contact_status_ids": [70000736543, 70000736544] - }, - { - "id": 71012139275, - "name": "Customer", - "position": 4, - "disabled": false, - "default": true, - "type": "Customer", - "contact_status_ids": [70000697861, 70000697862] - } - ] - }, - status: 200 + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/lists', + method: 'GET', + }, + httpRes: { + data: { + lists: [ + { + id: 70000053624, + name: 'Sample list', + }, + { + id: 70000056575, + name: 'list1-test', + }, + { + id: 70000058627, + name: 'Jio 5G Group', + }, + { + id: 70000058628, + name: 'Airtel 5G Group', + }, + { + id: 70000059716, + name: 'Voda 5G', + }, + ], + meta: { + total_pages: 1, + total: 5, }, - } -]; \ No newline at end of file + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://domain-rudder.myfreshworks.com/crm/sales/api/selector/lifecycle_stages', + method: 'GET', + }, + httpRes: { + data: { + lifecycle_stages: [ + { + id: 71012139274, + name: 'Sales Qualified Lead start', + position: 1, + disabled: false, + default: true, + type: 'Sales Qualified Lead', + contact_status_ids: [70000697858, 70000697859, 70000697860], + }, + { + id: 71012139273, + name: 'Lead', + position: 2, + disabled: false, + default: true, + type: 'Lead', + contact_status_ids: [70000697854, 70000697855, 70000697856, 70000697857], + }, + { + id: 71012806409, + name: 'final Customer', + position: 3, + disabled: false, + default: false, + type: 'Custom', + contact_status_ids: [70000736543, 70000736544], + }, + { + id: 71012139275, + name: 'Customer', + position: 4, + disabled: false, + default: true, + type: 'Customer', + contact_status_ids: [70000697861, 70000697862], + }, + ], + }, + status: 200, + }, + }, +]; diff --git a/test/integrations/destinations/gainsight/network.ts b/test/integrations/destinations/gainsight/network.ts index c8adf871b9a..4c5a0268474 100644 --- a/test/integrations/destinations/gainsight/network.ts +++ b/test/integrations/destinations/gainsight/network.ts @@ -1,71 +1,71 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', - method: 'POST', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "47d9c8be-4912-4610-806c-0eec22b73236", - "data": { - "records": [] - }, - "message": null - }, - status: 200 - }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/query/Company', + method: 'POST', }, - { - httpReq: { - url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', - method: 'POST', + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '47d9c8be-4912-4610-806c-0eec22b73236', + data: { + records: [], }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "3ce46d4a-6a83-4a92-97b3-d9788a296af8", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + message: null, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company', + method: 'POST', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '3ce46d4a-6a83-4a92-97b3-d9788a296af8', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, + message: null, + }, + status: 200, }, - { - httpReq: { - url: "https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name", - method: 'GET', - }, - httpRes: { - data: { - "result": true, - "errorCode": null, - "errorDesc": null, - "requestId": "30630809-40a7-45d2-9673-ac2e80d06f33", - "data": { - "count": 1, - "errors": null, - "records": [ - { - "Gsid": "1P0203VCESP7AUQMV9E953G" - } - ] - }, - "message": null + }, + { + httpReq: { + url: 'https://demo-domain.gainsightcloud.com/v1/data/objects/Company?keys=Name', + method: 'GET', + }, + httpRes: { + data: { + result: true, + errorCode: null, + errorDesc: null, + requestId: '30630809-40a7-45d2-9673-ac2e80d06f33', + data: { + count: 1, + errors: null, + records: [ + { + Gsid: '1P0203VCESP7AUQMV9E953G', }, - status: 200 + ], }, - } + message: null, + }, + status: 200, + }, + }, ]; diff --git a/test/integrations/destinations/gainsight_px/network.ts b/test/integrations/destinations/gainsight_px/network.ts index d9dd6bbaa0e..99f51d9d8ef 100644 --- a/test/integrations/destinations/gainsight_px/network.ts +++ b/test/integrations/destinations/gainsight_px/network.ts @@ -1,222 +1,251 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/sample-user-id', - method: 'GET', - }, - httpRes: { - data: { - "aptrinsicId": "347c4c87-98c7-4ca6-a6da-678ed6924c22", - "identifyId": "sample-user-id", - "type": "USER", - "gender": "MALE", - "email": "user@email.com", - "firstName": "Sample", - "lastName": "User", - "lastSeenDate": 0, - "signUpDate": 1624431528295, - "firstVisitDate": 0, - "title": "engineer", - "phone": "", - "score": 0, - "role": "", - "subscriptionId": "", - "accountId": "", - "numberOfVisits": 1, - "location": { - "countryName": "USA", - "countryCode": "US", - "stateName": "", - "stateCode": "", - "city": "New York", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624431528295, - "lastModifiedDate": 1624431528295, - "customAttributes": null, - "globalUnsubscribe": false, - "sfdcContactId": "", - "lastVisitedUserAgentData": null, - "id": "sample-user-id", - "lastInferredLocation": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - } - }, - status: 200 - }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/sample-user-id', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'GET', + httpRes: { + data: { + aptrinsicId: '347c4c87-98c7-4ca6-a6da-678ed6924c22', + identifyId: 'sample-user-id', + type: 'USER', + gender: 'MALE', + email: 'user@email.com', + firstName: 'Sample', + lastName: 'User', + lastSeenDate: 0, + signUpDate: 1624431528295, + firstVisitDate: 0, + title: 'engineer', + phone: '', + score: 0, + role: '', + subscriptionId: '', + accountId: '', + numberOfVisits: 1, + location: { + countryName: 'USA', + countryCode: 'US', + stateName: '', + stateCode: '', + city: 'New York', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + propertyKeys: ['AP-XABC-123'], + createDate: 1624431528295, + lastModifiedDate: 1624431528295, + customAttributes: null, + globalUnsubscribe: false, + sfdcContactId: '', + lastVisitedUserAgentData: null, + id: 'sample-user-id', + lastInferredLocation: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + }, + status: 200, }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', - method: 'PUT', - }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 204 + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'GET', + }, + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/accounts/ecorp-id', + method: 'PUT', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/absent-id', - method: 'GET', + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - externalapierror: { - status: "NOT_FOUND", - message: "User was not found for parameters {id=absent-id}", - debugMessage: null, - subErrors: null - } - }, - status: 404 + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 204, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/absent-id', + method: 'GET', + }, + httpRes: { + data: { + externalapierror: { + status: 'NOT_FOUND', + message: 'User was not found for parameters {id=absent-id}', + debugMessage: null, + subErrors: null, }, + }, + status: 404, + }, + }, + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', + method: 'GET', }, - { - httpReq: { - url: 'https://api.aptrinsic.com/v1/users/stanley-kubrick', - method: 'GET', + httpRes: { + data: { + id: 'ecorp-id', + name: 'ECorp', + trackedSubscriptionId: '', + sfdcId: '', + lastSeenDate: 0, + dunsNumber: '', + industry: 'software', + numberOfEmployees: 400, + sicCode: '', + website: 'www.ecorp.com', + naicsCode: '', + plan: 'premium', + location: { + countryName: '', + countryCode: '', + stateName: '', + stateCode: '', + city: '', + street: '', + postalCode: '', + continent: '', + regionName: '', + timeZone: '', + coordinates: { + latitude: 0.0, + longitude: 0.0, + }, }, - httpRes: { - data: { - "id": "ecorp-id", - "name": "ECorp", - "trackedSubscriptionId": "", - "sfdcId": "", - "lastSeenDate": 0, - "dunsNumber": "", - "industry": "software", - "numberOfEmployees": 400, - "sicCode": "", - "website": "www.ecorp.com", - "naicsCode": "", - "plan": "premium", - "location": { - "countryName": "", - "countryCode": "", - "stateName": "", - "stateCode": "", - "city": "", - "street": "", - "postalCode": "", - "continent": "", - "regionName": "", - "timeZone": "", - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - } - }, - "numberOfUsers": 0, - "propertyKeys": ["AP-XABC-123"], - "createDate": 1624261864923, - "lastModifiedDate": 1624261864923, - "customAttributes": null, - "parentGroupId": "" - }, - status: 200 + numberOfUsers: 0, + propertyKeys: ['AP-XABC-123'], + createDate: 1624261864923, + lastModifiedDate: 1624261864923, + customAttributes: null, + parentGroupId: '', + }, + status: 200, + }, + }, + // Axios Error + { + httpReq: { + url: 'https://api.aptrinsic.com/v1/users/myUId', + headers: { 'X-APTRINSIC-API-KEY': 'sample-api-key', 'Content-Type': 'application/json' }, + method: 'GET', + }, + httpRes: { + message: 'Request failed with status code 403', + name: 'AxiosError', + stack: + 'AxiosError: Request failed with status code 403\n at settle (/Users/saisankeerth/rudderstack/rudder-transformer/node_modules/axios/lib/core/settle.js:19:12)\n at IncomingMessage.handleStreamEnd (/Users/saisankeerth/rudderstack/rudder-transformer/node_modules/axios/lib/adapters/http.js:589:11)\n at IncomingMessage.emit (node:events:529:35)\n at IncomingMessage.emit (node:domain:489:12)\n at endReadableNT (node:internal/streams/readable:1400:12)\n at processTicksAndRejections (node:internal/process/task_queues:82:21)', + config: { + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + 'X-APTRINSIC-API-KEY': 'sample-api-key', + 'User-Agent': 'axios/1.6.5', + 'Accept-Encoding': 'gzip, compress, deflate, br', }, - } + method: 'get', + dummy: 'upgrade required', // keyword + url: 'https://api.aptrinsic.com/v1/users/myUId', + }, + code: 'FORBIDDEN', + status: 403, + data: '\u003c!doctype html\u003e\u003cmeta charset="utf-8"\u003e\u003cmeta name=viewport content="width=device-width, initial-scale=1"\u003e\u003ctitle\u003e403\u003c/title\u003e403 Forbidden', + }, + }, ]; diff --git a/test/integrations/destinations/gainsight_px/router/data.ts b/test/integrations/destinations/gainsight_px/router/data.ts index 7dc131127da..1b3d5be8756 100644 --- a/test/integrations/destinations/gainsight_px/router/data.ts +++ b/test/integrations/destinations/gainsight_px/router/data.ts @@ -1,3 +1,75 @@ +const metadata = { + userId: '9a7820d0-0ff2-4451-b655-682cec15cbd2', + jobId: 1, + sourceId: '1s9eG8UCer6YSKsD8ZlQCyLa3pj', + destinationId: 'desId2', + attemptNum: 0, + receivedAt: '2021-06-25T14:29:52.911+05:30', + createdAt: '2021-06-25T08:59:56.329Z', + firstAttemptedAt: '', + transformAt: 'router', +}; +const destination2 = { + ID: 'desId2', + Name: 'gainsight-px-dest', + DestinationDefinition: { + ID: 'destDef1', + Name: 'GAINSIGHT_PX', + DisplayName: 'Gainsight PX', + Config: { + destConfig: { + defaultConfig: [ + 'apiKey', + 'productTagKey', + 'userAttributeMap', + 'accountAttributeMap', + 'globalContextMap', + ], + }, + excludeKeys: [], + includeKeys: [], + saveDestinationResponse: true, + secretKeys: ['apiKey', 'productTagKey'], + supportedSourceTypes: [ + 'android', + 'ios', + 'web', + 'unity', + 'amp', + 'cloud', + 'reactnative', + 'flutter', + ], + transformAt: 'router', + transformAtV1: 'router', + }, + ResponseRules: {}, + }, + Config: { + accountAttributeMap: [ + { from: 'LAST_INVOICE_DATE', to: 'last_invoice_date' }, + { from: 'LAST_INVOICE_PLAN', to: 'last_invoice_plan' }, + { from: 'LANGUAGE', to: 'language' }, + { from: 'REGION', to: 'region2' }, + { from: 'LAST_INVOICE_CURRENCY', to: 'last_invoice_currency' }, + { from: 'IBR_PLAN', to: 'ibr_plan' }, + { from: 'WH_COUNTRY', to: 'wh_country' }, + { from: 'inboxready_signup_date', to: 'inboxready_signup_date' }, + { from: 'gpt_setup', to: 'gpt_setup' }, + ], + oneTrustCookieCategories: [], + apiKey: 'sample-api-key', + eventDelivery: false, + eventDeliveryTS: 1624472902670, + globalContextMap: [{ from: 'kubrickTest', to: 'value' }], + productTagKey: 'AP-SAMPLE-2', + userAttributeMap: [{ from: 'hobbyCustomField', to: 'hobby' }], + }, + Enabled: true, + Transformations: [], + IsProcessorEnabled: true, +}; + export const data = [ { name: 'gainsight_px', @@ -463,4 +535,85 @@ export const data = [ }, }, }, + { + name: 'gainsight_px', + description: 'Test 1: Group call -- AxiosError thrown', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + type: 'group', + sentAt: '2024-02-16T06:00:54.075Z', + traits: { + name: ',sleep(100)', + REGION: 'MEA', + USERID: 'myUId', + groupId: 'myGId', + IBR_PLAN: 'free_ir', + LANGUAGE: 'EN', + gpt_setup: false, + ACCOUNT_ID: 'myGId', + WH_COUNTRY: 'MA', + LAST_INVOICE_DATE: 1706810675000, + LAST_INVOICE_PLAN: 'foundation_trial', + LAST_INVOICE_CURRENCY: 'USD', + inboxready_signup_date: 1680254544705, + }, + userId: 'myUId', + channel: 'sources', + context: { + sources: { + job_run_id: 'cn7fjonu4d9b3u706u2g', + task_run_id: 'cn7fjonu4d9b3u706u3g', + }, + }, + recordId: '111111', + rudderId: 'dummy-rudder-id', + timestamp: '2024-02-16T06:00:52.581Z', + receivedAt: '2024-02-16T06:00:52.582Z', + request_ip: '10.7.150.126', + anonymousId: 'myUId', + integrations: { limitAPIForGroup: true }, + originalTimestamp: '2024-02-16T06:00:54.075Z', + }, + metadata, + destination: destination2, + }, + ], + destType: 'gainsight_px', + }, + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + error: + '{"message":"error while fetching user: \\"403403 Forbidden\\"","destinationResponse":"403403 Forbidden"}', + statTags: { + destType: 'GAINSIGHT_PX', + destinationId: destination2.ID, + errorCategory: 'network', + errorType: 'aborted', + feature: 'router', + implementation: 'native', + module: 'destination', + }, + statusCode: 403, + metadata: [metadata], + batched: false, + destination: destination2, + }, + ], + }, + }, + }, + }, ]; diff --git a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts index 62ee03c46d3..dff0f772d31 100644 --- a/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_enhanced_conversions/router/data.ts @@ -1,248 +1,347 @@ -export const data = [ +const events = [ + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 1, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, + { + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + jobId: 2, + userId: 'u1', + }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + type: 'identify', + traits: { status: 'elizabeth' }, + userId: 'emrichardson820+22822@gmail.com', + channel: 'sources', + context: { + sources: { + job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', + task_id: 'vw_rs_mailchimp_mocked_hg_data', + version: 'v1.8.1', + batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', + job_run_id: 'c8el40l6e87v0c4hkbl0', + task_run_id: 'c8el40l6e87v0c4hkblg', + }, + externalId: [ + { + id: 'emrichardson820+22822@gmail.com', + type: 'MAILCHIMP-92e1f1ad2c', + identifierType: 'email_address', + }, + ], + mappedToDestination: 'true', + }, + recordId: '1', + rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', + messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', + }, + }, + { + metadata: { secret: {}, jobId: 3, userId: 'u1' }, + destination: { + Config: { + rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', + customerId: '1234567890', + subAccount: true, + loginCustomerId: '11', + listOfConversions: [{ conversions: 'Page View' }, { conversions: 'Product Added' }], + authStatus: 'active', + }, + }, + message: { + channel: 'web', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: '1.0.0', + }, + traits: { + phone: '912382193', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'UK', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { name: '', version: '' }, + screen: { density: 2 }, + }, + event: 'Page View', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gclid: 'gclid1234', + conversionDateTime: '2022-01-01 12:32:45-08:00', + adjustedValue: '10', + currency: 'INR', + adjustmentDateTime: '2022-01-01 12:32:45-08:00', + partialFailure: true, + campaignId: '1', + templateId: '0', + order_id: 10000, + total: 1000, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + price: '19', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + quantity: '2', + }, + { + product_id: '507f1f77bcf86cd7994390112', + sku: '45790-322', + name: 'Monopoly: 3rd Edition2', + price: '192', + quantity: 22, + position: '12', + category: 'Cars2', + url: 'https://www.example.com/product/path2', + image_url: 'https://www.example.com/product/path.jpg2', + }, + ], + }, + integrations: { All: true }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + }, +]; + +const invalidRtTfCases = [ { name: 'google_adwords_enhanced_conversions', - description: 'Test 0', + description: 'Test 1 - should abort events, invalid router transform structure', feature: 'router', module: 'destination', version: 'v0', input: { request: { body: { - input: [ + input: events[0], + destType: 'google_adwords_enhanced_conversions', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 1, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: 'google_adwords_enhanced_conversions', }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 2 - should abort events, invalid router transform structure without destType in payload & empty object as input', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: {}, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { - secret: { - access_token: 'abcd1234', - refresh_token: 'efgh5678', - developer_token: 'ijkl91011', - }, - jobId: 2, - userId: 'u1', - }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - type: 'identify', - traits: { status: 'elizabeth' }, - userId: 'emrichardson820+22822@gmail.com', - channel: 'sources', - context: { - sources: { - job_id: '24c5HJxHomh6YCngEOCgjS5r1KX/Syncher', - task_id: 'vw_rs_mailchimp_mocked_hg_data', - version: 'v1.8.1', - batch_id: 'f252c69d-c40d-450e-bcd2-2cf26cb62762', - job_run_id: 'c8el40l6e87v0c4hkbl0', - task_run_id: 'c8el40l6e87v0c4hkblg', - }, - externalId: [ - { - id: 'emrichardson820+22822@gmail.com', - type: 'MAILCHIMP-92e1f1ad2c', - identifierType: 'email_address', - }, - ], - mappedToDestination: 'true', + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - recordId: '1', - rudderId: '4d5d0ed0-9db8-41cc-9bb0-a032f6bfa97a', - messageId: 'b3bee036-fc26-4f6d-9867-c17f85708a82', - }, + ], + batched: false, + statusCode: 400, }, + ], + }, + }, + }, + }, + { + name: 'google_adwords_enhanced_conversions', + description: + 'Test 3 - should abort events, invalid router transform structure without input & destType', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: {}, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ { - metadata: { secret: {}, jobId: 3, userId: 'u1' }, - destination: { - Config: { - rudderAccountId: '25u5whFH7gVTnCiAjn4ykoCLGoC', - customerId: '1234567890', - subAccount: true, - loginCustomerId: '11', - listOfConversions: [ - { conversions: 'Page View' }, - { conversions: 'Product Added' }, - ], - authStatus: 'active', - }, - }, - message: { - channel: 'web', - context: { - app: { - build: '1.0.0', - name: 'RudderLabs JavaScript SDK', - namespace: 'com.rudderlabs.javascript', - version: '1.0.0', - }, - traits: { - phone: '912382193', - firstName: 'John', - lastName: 'Gomes', - city: 'London', - state: 'UK', - streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', - }, - library: { name: 'RudderLabs JavaScript SDK', version: '1.0.0' }, - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - locale: 'en-US', - ip: '0.0.0.0', - os: { name: '', version: '' }, - screen: { density: 2 }, - }, - event: 'Page View', - type: 'track', - messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', - originalTimestamp: '2019-10-14T11:15:18.299Z', - anonymousId: '00000000000000000000000000', - userId: '12345', - properties: { - gclid: 'gclid1234', - conversionDateTime: '2022-01-01 12:32:45-08:00', - adjustedValue: '10', - currency: 'INR', - adjustmentDateTime: '2022-01-01 12:32:45-08:00', - partialFailure: true, - campaignId: '1', - templateId: '0', - order_id: 10000, - total: 1000, - products: [ - { - product_id: '507f1f77bcf86cd799439011', - sku: '45790-32', - name: 'Monopoly: 3rd Edition', - price: '19', - position: '1', - category: 'cars', - url: 'https://www.example.com/product/path', - image_url: 'https://www.example.com/product/path.jpg', - quantity: '2', - }, - { - product_id: '507f1f77bcf86cd7994390112', - sku: '45790-322', - name: 'Monopoly: 3rd Edition2', - price: '192', - quantity: 22, - position: '12', - category: 'Cars2', - url: 'https://www.example.com/product/path2', - image_url: 'https://www.example.com/product/path.jpg2', - }, - ], + error: 'Invalid event array', + metadata: [ + { + destType: undefined, }, - integrations: { All: true }, - name: 'ApplicationLoaded', - sentAt: '2019-10-14T11:15:53.296Z', - }, + ], + batched: false, + statusCode: 400, }, ], + }, + }, + }, + }, +]; + +export const data = [ + { + name: 'google_adwords_enhanced_conversions', + description: 'Test 0', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: events, destType: 'google_adwords_enhanced_conversions', }, method: 'POST', @@ -405,4 +504,5 @@ export const data = [ }, }, }, + ...invalidRtTfCases, ]; diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts index 414e46ea19f..fe16ffef471 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/dataDelivery/data.ts @@ -21,7 +21,7 @@ export const data = [ destination: 'google_adwords_remarketing_lists', listId: '709078448', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -101,7 +101,7 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -200,7 +200,7 @@ export const data = [ listId: '709078448', customerId: '7693729833', destination: 'google_adwords_remarketing_lists', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/network.ts b/test/integrations/destinations/google_adwords_remarketing_lists/network.ts index 8e1edd21aa8..8e7c0acbcf7 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/network.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/network.ts @@ -7,7 +7,10 @@ export const networkCallsData = [ data: { job: { type: 'CUSTOMER_MATCH_USER_LIST', - customerMatchUserListMetadata: { userList: 'customers/7693729833/userLists/709078448' }, + customerMatchUserListMetadata: { + userList: 'customers/7693729833/userLists/709078448', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, }, }, headers: { @@ -86,7 +89,10 @@ export const networkCallsData = [ data: { job: { type: 'CUSTOMER_MATCH_USER_LIST', - customerMatchUserListMetadata: { userList: 'customers/7693729833/userLists/709078448' }, + customerMatchUserListMetadata: { + userList: 'customers/7693729833/userLists/709078448', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, }, }, headers: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts index 804efec220a..a846e0370d5 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/processor/data.ts @@ -79,7 +79,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -213,7 +213,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -332,7 +332,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -1434,7 +1434,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -2829,7 +2829,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -2909,7 +2909,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -4122,7 +4122,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -5422,7 +5422,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -6799,7 +6799,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -8109,7 +8109,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -9409,7 +9409,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -10804,7 +10804,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -10884,7 +10884,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11059,7 +11059,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11137,7 +11137,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11281,7 +11281,7 @@ export const data = [ params: { listId: 'list111', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11488,7 +11488,7 @@ export const data = [ params: { listId: 'aud1234', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11627,7 +11627,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11765,7 +11765,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, }, body: { JSON: { @@ -11836,6 +11836,8 @@ export const data = [ userSchema: ['email', 'phone', 'addressInfo'], isHashRequired: true, typeOfList: 'General', + userDataConsent: 'UNSPECIFIED', + personalizationConsent: 'GRANTED', }, }, message: { @@ -11860,8 +11862,6 @@ export const data = [ event: 'Add_Audience', messageId: 'bd2d67ca-0c9a-4d3b-a2f8-35a3c3f75ba7', properties: { - userDataConsent: 'UNSPECIFIED', - personalizationConsent: 'GRANTED', listData: { add: [ { @@ -11979,6 +11979,8 @@ export const data = [ userSchema: ['email', 'phone', 'addressInfo'], isHashRequired: true, typeOfList: 'General', + userDataConsent: 'RANDOM', + personalizationConsent: 'RANDOM', }, }, message: { @@ -12003,8 +12005,6 @@ export const data = [ event: 'Add_Audience', messageId: 'bd2d67ca-0c9a-4d3b-a2f8-35a3c3f75ba7', properties: { - userDataConsent: 'RANDOM', - personalizationConsent: 'RANDOM', listData: { add: [ { @@ -12048,7 +12048,7 @@ export const data = [ params: { listId: '830441345', customerId: '7693729833', - consent: {}, + consent: { adPersonalization: 'UNKNOWN', adUserData: 'UNKNOWN' }, }, body: { JSON: { diff --git a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts index 8c122254004..31d5c72694c 100644 --- a/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts +++ b/test/integrations/destinations/google_adwords_remarketing_lists/router/data.ts @@ -228,7 +228,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -305,7 +309,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -359,7 +367,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -436,7 +448,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, @@ -484,7 +500,11 @@ export const data = [ 'Content-Type': 'application/json', 'developer-token': 'ijkl9101', }, - params: { listId: '7090784486', customerId: '7693729833', consent: {} }, + params: { + listId: '7090784486', + customerId: '7693729833', + consent: { adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED' }, + }, body: { JSON: { enablePartialFailure: true, diff --git a/test/integrations/destinations/hs/processor/data.ts b/test/integrations/destinations/hs/processor/data.ts index 03ad9d0a3b3..f45f3a719bf 100644 --- a/test/integrations/destinations/hs/processor/data.ts +++ b/test/integrations/destinations/hs/processor/data.ts @@ -1,3 +1,45 @@ +import { Destination } from '../../../../../src/types'; +import { generateMetadata, generateSimplifiedIdentifyPayload } from '../../../testUtils'; + +const commonOutputHeaders = { + 'Content-Type': 'application/json', + Authorization: 'Bearer dummy-access-token', +}; + +const destination: Destination = { + Config: { + authorizationType: 'newPrivateAppApi', + accessToken: 'dummy-access-token', + hubID: 'dummy-hubId', + apiKey: 'dummy-apikey', + apiVersion: 'newApi', + lookupField: 'email', + hubspotEvents: [], + eventFilteringOption: 'disable', + blacklistedEvents: [ + { + eventName: '', + }, + ], + whitelistedEvents: [ + { + eventName: '', + }, + ], + }, + Enabled: true, + ID: '123', + Name: 'hs', + DestinationDefinition: { + ID: '123', + Name: 'hs', + DisplayName: 'Hubspot', + Config: {}, + }, + WorkspaceID: '123', + Transformations: [], +}; + export const data = [ { name: 'hs', @@ -5269,4 +5311,66 @@ export const data = [ }, }, }, + { + name: 'hs', + description: 'Test coversion of null to string values', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: generateSimplifiedIdentifyPayload({ + userId: '12345', + context: { + traits: { + email: 'noname@email.com', + firstname: null, + gender: '', + lookupField: 'email', + }, + }, + }), + metadata: generateMetadata(1), + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + userId: '', + method: 'POST', + endpoint: 'https://api.hubapi.com/crm/v3/objects/contacts', + files: {}, + headers: commonOutputHeaders, + operation: 'createContacts', + params: {}, + body: { + FORM: {}, + JSON: { + properties: { + email: 'noname@email.com', + firstname: '', + gender: '', + }, + }, + JSON_ARRAY: {}, + XML: {}, + }, + }, + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/iterable/deleteUsers/data.ts b/test/integrations/destinations/iterable/deleteUsers/data.ts new file mode 100644 index 00000000000..79d801f4eee --- /dev/null +++ b/test/integrations/destinations/iterable/deleteUsers/data.ts @@ -0,0 +1,186 @@ +const destType = 'iterable'; + +export const data = [ + { + name: destType, + description: 'Test 0: should fail when config is not being sent', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + ], + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'Config for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 1: should fail when apiKey is not present in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder2', + }, + ], + config: { + apiToken: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: 'api key for deletion not present', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 2: should fail when one of the user-deletion requests fails', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder1', + }, + { + userId: 'rudder2', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 400, + body: [ + { + statusCode: 400, + error: + 'User deletion request failed for userIds : [{"userId":"rudder2","Reason":"User does not exist. Email: UserId: rudder2"}]', + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 3: should fail when invalid api key is set in config', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder3', + }, + { + userId: 'rudder4', + }, + ], + config: { + apiKey: 'invalidKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 401, + body: [ + { + error: 'User deletion request failed : Invalid API key', + statusCode: 401, + }, + ], + }, + }, + }, + { + name: destType, + description: 'Test 4: should pass when proper apiKey & valid users are sent to destination', + feature: 'userDeletion', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destType: destType.toUpperCase(), + userAttributes: [ + { + userId: 'rudder5', + }, + { + userId: 'rudder6', + }, + ], + config: { + apiKey: 'dummyApiKey', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 200, + status: 'successful', + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/iterable/network.ts b/test/integrations/destinations/iterable/network.ts new file mode 100644 index 00000000000..39544b26477 --- /dev/null +++ b/test/integrations/destinations/iterable/network.ts @@ -0,0 +1,109 @@ +const deleteNwData = [ + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder1', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder1 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder2', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'User does not exist. Email: UserId: rudder2', + code: 'BadParams', + params: null, + }, + status: 400, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder3', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder3', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder4', + headers: { + api_key: 'invalidKey', + }, + }, + httpRes: { + data: { + msg: 'Invalid API key', + code: 'Success', + params: { + endpoint: '/api/users/byUserId/rudder4', + }, + }, + status: 401, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder5', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, + { + httpReq: { + method: 'delete', + url: 'https://api.iterable.com/api/users/byUserId/rudder6', + headers: { + api_key: 'dummyApiKey', + }, + }, + httpRes: { + data: { + msg: 'All users associated with rudder6 were successfully deleted', + code: 'Success', + params: null, + }, + status: 200, + }, + }, +]; +export const networkCallsData = [...deleteNwData]; diff --git a/test/integrations/destinations/klaviyo/network.ts b/test/integrations/destinations/klaviyo/network.ts index aa788a60dad..d76d235c6f3 100644 --- a/test/integrations/destinations/klaviyo/network.ts +++ b/test/integrations/destinations/klaviyo/network.ts @@ -1,75 +1,73 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', - method: 'GET', - }, - httpRes: { - status: 200 - }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/subscribe', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', - method: 'GET', - }, - httpRes: { - status: 200 - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - data: { - attributes: { - email: "test3@rudderstack.com" - } - } - }, - httpRes: { - status: 409, - data: { - } - }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/v2/list/XUepkK/members', + method: 'GET', }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'GET', - }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } - }, + httpRes: { + status: 200, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', - headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' } - }, - httpRes: { + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + data: { + attributes: { + email: 'test3@rudderstack.com', }, + }, + }, + httpRes: { + status: 409, + data: {}, }, - { - httpReq: { - url: 'https://a.klaviyo.com/api/profiles', - method: 'POST', + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'GET', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - httpRes: { - status: 201, - data: { - data: { - id: '01GW3PHVY0MTCDGS0A1612HARX', - attributes: {} - }, - } + }, + }, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + headers: { Authorization: 'Klaviyo-API-Key dummyPrivateApiKeyforfailure' }, + }, + httpRes: {}, + }, + { + httpReq: { + url: 'https://a.klaviyo.com/api/profiles', + method: 'POST', + }, + httpRes: { + status: 201, + data: { + data: { + id: '01GW3PHVY0MTCDGS0A1612HARX', + attributes: {}, }, - } + }, + }, + }, ]; diff --git a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts index f632cb767c3..0dd47511330 100644 --- a/test/integrations/destinations/klaviyo/processor/identifyTestData.ts +++ b/test/integrations/destinations/klaviyo/processor/identifyTestData.ts @@ -47,6 +47,8 @@ const commonTraits = { }, }; +const commonTraits2 = { ...commonTraits, street: '63, Shibuya' }; + const commonOutputUserProps = { external_id: 'user@1', email: 'test@rudderstack.com', @@ -67,6 +69,12 @@ const commonOutputUserProps = { }, }; +const commonOutputUserProps2 = { + ...commonOutputUserProps, + location: { ...commonOutputUserProps.location, address1: '63, Shibuya' }, + properties: { ...commonOutputUserProps.properties, street: '63, Shibuya' }, +}; + const commonOutputSubscriptionProps = { list_id: 'XUepkK', subscriptions: [ @@ -116,7 +124,7 @@ export const identifyData: ProcessorTestData[] = [ destination, message: generateSimplifiedIdentifyPayload({ context: { - traits: commonTraits, + traits: commonTraits2, }, anonymousId, userId, @@ -140,7 +148,7 @@ export const identifyData: ProcessorTestData[] = [ JSON: { data: { type: 'profile', - attributes: commonOutputUserProps, + attributes: commonOutputUserProps2, id: '01GW3PHVY0MTCDGS0A1612HARX', }, }, @@ -188,7 +196,7 @@ export const identifyData: ProcessorTestData[] = [ userId, context: { traits: { - ...commonTraits, + ...commonTraits2, friend: { names: { first: 'Alice', @@ -221,9 +229,9 @@ export const identifyData: ProcessorTestData[] = [ type: 'profile', id: '01GW3PHVY0MTCDGS0A1612HARX', attributes: { - ...commonOutputUserProps, + ...commonOutputUserProps2, properties: { - ...commonOutputUserProps.properties, + ...commonOutputUserProps2.properties, 'friend.age': 25, 'friend.names.first': 'Alice', 'friend.names.last': 'Smith', @@ -278,7 +286,7 @@ export const identifyData: ProcessorTestData[] = [ userId, context: { traits: { - ...commonTraits, + ...commonTraits2, email: 'test3@rudderstack.com', }, }, @@ -334,7 +342,7 @@ export const identifyData: ProcessorTestData[] = [ userId, context: { traits: { - ...commonTraits, + ...commonTraits2, properties: { ...commonTraits.properties, subscribe: false }, }, }, @@ -358,7 +366,7 @@ export const identifyData: ProcessorTestData[] = [ JSON: { data: { type: 'profile', - attributes: commonOutputUserProps, + attributes: commonOutputUserProps2, id: '01GW3PHVY0MTCDGS0A1612HARX', }, }, @@ -390,7 +398,7 @@ export const identifyData: ProcessorTestData[] = [ sentAt, userId, context: { - traits: commonTraits, + traits: commonTraits2, }, anonymousId, originalTimestamp, @@ -414,9 +422,9 @@ export const identifyData: ProcessorTestData[] = [ data: { type: 'profile', attributes: removeUndefinedAndNullValues({ - ...commonOutputUserProps, + ...commonOutputUserProps2, properties: { - ...commonOutputUserProps.properties, + ...commonOutputUserProps2.properties, _id: userId, }, // remove external_id from the payload @@ -546,7 +554,7 @@ export const identifyData: ProcessorTestData[] = [ userId, context: { traits: removeUndefinedAndNullValues({ - ...commonTraits, + ...commonTraits2, email: undefined, phone: undefined, }), diff --git a/test/integrations/destinations/mp/processor/data.ts b/test/integrations/destinations/mp/processor/data.ts index dfa94352c9a..5b2d0fbfffd 100644 --- a/test/integrations/destinations/mp/processor/data.ts +++ b/test/integrations/destinations/mp/processor/data.ts @@ -121,7 +121,10 @@ export const data = [ request: { body: [ { - destination: sampleDestination, + destination: overrideDestination(sampleDestination, { + useUserDefinedPageEventName: true, + userDefinedPageEventTemplate: 'Viewed a {{ name }} page', + }), message: { anonymousId: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca', channel: 'web', @@ -195,7 +198,7 @@ export const data = [ JSON: {}, JSON_ARRAY: { batch: - '[{"event":"Loaded a Page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', + '[{"event":"Viewed a Contact Us page","properties":{"ip":"0.0.0.0","$user_id":"hjikl","$current_url":"https://docs.rudderstack.com/destinations/mixpanel","$screen_dpi":2,"mp_lib":"RudderLabs JavaScript SDK","$app_build_number":"1.0.0","$app_version_string":"1.0.5","$insert_id":"dd266c67-9199-4a52-ba32-f46ddde67312","token":"dummyApiKey","distinct_id":"hjikl","time":1579847342402,"name":"Contact Us","category":"Contact","$browser":"Chrome","$browser_version":"79.0.3945.117"}}]', }, XML: {}, FORM: {}, diff --git a/test/integrations/destinations/ninetailed/commonConfig.ts b/test/integrations/destinations/ninetailed/commonConfig.ts new file mode 100644 index 00000000000..3b5d4149f27 --- /dev/null +++ b/test/integrations/destinations/ninetailed/commonConfig.ts @@ -0,0 +1,112 @@ +export const destination = { + ID: 'random_id', + Name: 'ninetailed', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + organisationId: 'dummyOrganisationId', + environment: 'main', + }, +}; + +export const metadata = { + destinationId: 'dummyDestId', +}; +export const commonProperties = { + segment: 'SampleSegment', + shipcountry: 'USA', + shipped: '20240129_1500', + sitename: 'SampleSiteName', + storeId: '12345', + storecat: 'Electronics', +}; +export const traits = { + email: 'test@user.com', + firstname: 'John', + lastname: 'Doe', + phone: '+1(123)456-7890', + gender: 'Male', + birthday: '1980-01-02', + city: 'San Francisco', +}; +export const context = { + app: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'campign_123', + source: 'social marketing', + medium: 'facebook', + term: '1 year', + }, + library: { + name: 'RudderstackSDK', + version: 'Ruddderstack SDK version', + }, + locale: 'en-US', + page: { + path: '/signup', + referrer: 'https://rudderstack.medium.com/', + search: '?type=freetrial', + url: 'https://app.rudderstack.com/signup?type=freetrial', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + location: { + coordinates: { + latitude: 40.7128, + longitude: -74.006, + }, + city: 'San Francisco', + postalCode: '94107', + region: 'CA', + regionCode: 'CA', + country: ' United States', + countryCode: 'United States of America', + continent: 'North America', + timezone: 'America/Los_Angeles', + }, +}; + +export const commonInput = { + anonymousId: 'anon_123', + messageId: 'dummy_msg_id', + context, + channel: 'web', + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', +}; + +export const commonOutput = { + anonymousId: 'anon_123', + messageId: 'dummy_msg_id', + context, + channel: 'web', + originalTimestamp: '2021-01-25T15:32:56.409Z', +}; + +export const endpoint = + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events'; +export const routerInstrumentationErrorStatTags = { + destType: 'NINETAILED', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'router', + implementation: 'cdkV2', + module: 'destination', +}; +export const processInstrumentationErrorStatTags = { + destType: 'NINETAILED', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + destinationId: 'dummyDestId', +}; diff --git a/test/integrations/destinations/ninetailed/mocks.ts b/test/integrations/destinations/ninetailed/mocks.ts new file mode 100644 index 00000000000..a16b2760533 --- /dev/null +++ b/test/integrations/destinations/ninetailed/mocks.ts @@ -0,0 +1,5 @@ +import config from '../../../../src/cdk/v2/destinations/ninetailed/config'; + +export const defaultMockFns = () => { + jest.replaceProperty(config, 'MAX_BATCH_SIZE', 2); +}; diff --git a/test/integrations/destinations/ninetailed/processor/data.ts b/test/integrations/destinations/ninetailed/processor/data.ts new file mode 100644 index 00000000000..4e5fa72365d --- /dev/null +++ b/test/integrations/destinations/ninetailed/processor/data.ts @@ -0,0 +1,5 @@ +import { validationFailures } from './validation'; +import { track } from './track'; +import { page } from './page'; +import { identify } from './identify'; +export const data = [...identify, ...page, ...track, ...validationFailures]; diff --git a/test/integrations/destinations/ninetailed/processor/identify.ts b/test/integrations/destinations/ninetailed/processor/identify.ts new file mode 100644 index 00000000000..fbd7379e196 --- /dev/null +++ b/test/integrations/destinations/ninetailed/processor/identify.ts @@ -0,0 +1,155 @@ +import { + destination, + traits, + commonInput, + metadata, + processInstrumentationErrorStatTags, +} from '../commonConfig'; +import { transformResultBuilder } from '../../../testUtils'; +export const identify = [ + { + id: 'ninetailed-test-identify-success-1', + name: 'ninetailed', + description: 'identify call with all mappings available', + scenario: 'Framework+Buisness', + successCriteria: 'Response should contain all the mappings and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'identify', + ...commonInput, + userId: 'sajal12', + traits: traits, + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'dummyDestId', + }, + output: transformResultBuilder({ + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + JSON: { + events: [ + { + context: { + app: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'campign_123', + source: 'social marketing', + medium: 'facebook', + term: '1 year', + }, + library: { + name: 'RudderstackSDK', + version: 'Ruddderstack SDK version', + }, + locale: 'en-US', + page: { + path: '/signup', + referrer: 'https://rudderstack.medium.com/', + search: '?type=freetrial', + url: 'https://app.rudderstack.com/signup?type=freetrial', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + location: { + coordinates: { + latitude: 40.7128, + longitude: -74.006, + }, + city: 'San Francisco', + postalCode: '94107', + region: 'CA', + regionCode: 'CA', + country: ' United States', + countryCode: 'United States of America', + continent: 'North America', + timezone: 'America/Los_Angeles', + }, + }, + type: 'identify', + channel: 'web', + userId: 'sajal12', + messageId: 'dummy_msg_id', + traits: traits, + anonymousId: 'anon_123', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + ], + }, + userId: '', + }), + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'ninetailed-test-identify-failure-1', + name: 'ninetailed', + description: 'identify call with no userId available', + scenario: 'Framework', + successCriteria: + 'Error should be thrown for required field userId not present and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + ...commonInput, + type: 'identify', + channel: 'mobile', + messageId: 'dummy_msg_id', + traits: traits, + }, + metadata, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Missing required value from "userIdOnly": Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: Missing required value from "userIdOnly"', + metadata: { + destinationId: 'dummyDestId', + }, + statTags: processInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/ninetailed/processor/page.ts b/test/integrations/destinations/ninetailed/processor/page.ts new file mode 100644 index 00000000000..93a086ceea0 --- /dev/null +++ b/test/integrations/destinations/ninetailed/processor/page.ts @@ -0,0 +1,108 @@ +import { destination, context, commonProperties, metadata } from '../commonConfig'; +import { transformResultBuilder } from '../../../testUtils'; +export const page = [ + { + id: 'ninetailed-test-page-success-1', + name: 'ninetailed', + description: 'page call with all mappings available', + scenario: 'Framework+Buisness', + successCriteria: 'Response should contain all the mappings and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + context, + type: 'page', + event: 'product purchased', + userId: 'sajal12', + channel: 'mobile', + messageId: 'dummy_msg_id', + properties: commonProperties, + anonymousId: 'anon_123', + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'dummyDestId', + }, + output: transformResultBuilder({ + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + JSON: { + events: [ + { + context: { + app: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'campign_123', + source: 'social marketing', + medium: 'facebook', + term: '1 year', + }, + library: { + name: 'RudderstackSDK', + version: 'Ruddderstack SDK version', + }, + locale: 'en-US', + page: { + path: '/signup', + referrer: 'https://rudderstack.medium.com/', + search: '?type=freetrial', + url: 'https://app.rudderstack.com/signup?type=freetrial', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + location: { + coordinates: { + latitude: 40.7128, + longitude: -74.006, + }, + city: 'San Francisco', + postalCode: '94107', + region: 'CA', + regionCode: 'CA', + country: ' United States', + countryCode: 'United States of America', + continent: 'North America', + timezone: 'America/Los_Angeles', + }, + }, + type: 'page', + channel: 'mobile', + messageId: 'dummy_msg_id', + properties: commonProperties, + anonymousId: 'anon_123', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + ], + }, + userId: '', + }), + statusCode: 200, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/ninetailed/processor/track.ts b/test/integrations/destinations/ninetailed/processor/track.ts new file mode 100644 index 00000000000..6b6a1e7831d --- /dev/null +++ b/test/integrations/destinations/ninetailed/processor/track.ts @@ -0,0 +1,204 @@ +import { destination, context, commonProperties, metadata } from '../commonConfig'; +import { transformResultBuilder } from '../../../testUtils'; +export const track = [ + { + id: 'ninetailed-test-track-success-1', + name: 'ninetailed', + description: 'Track call with all mappings available', + scenario: 'Framework+Buisness', + successCriteria: 'Response should contain all the mappings and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + context: { + app: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'campign_123', + source: 'social marketing', + medium: 'facebook', + term: '1 year', + }, + library: { + name: 'RudderstackSDK', + version: 'Ruddderstack SDK version', + }, + locale: 'en-US', + page: { + path: '/signup', + referrer: 'https://rudderstack.medium.com/', + search: '?type=freetrial', + url: 'https://app.rudderstack.com/signup?type=freetrial', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + location: { + coordinates: { + latitude: 40.7128, + longitude: -74.006, + }, + city: 'San Francisco', + postalCode: '94107', + region: 'CA', + regionCode: 'CA', + country: ' United States', + countryCode: 'United States of America', + continent: 'North America', + timezone: 'America/Los_Angeles', + }, + }, + type: 'track', + event: 'product purchased', + userId: 'sajal12', + channel: 'mobile', + messageId: 'dummy_msg_id', + properties: commonProperties, + anonymousId: 'anon_123', + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'dummyDestId', + }, + output: transformResultBuilder({ + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + JSON: { + events: [ + { + context: { + app: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'campign_123', + source: 'social marketing', + medium: 'facebook', + term: '1 year', + }, + library: { + name: 'RudderstackSDK', + version: 'Ruddderstack SDK version', + }, + locale: 'en-US', + page: { + path: '/signup', + referrer: 'https://rudderstack.medium.com/', + search: '?type=freetrial', + url: 'https://app.rudderstack.com/signup?type=freetrial', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + location: { + coordinates: { + latitude: 40.7128, + longitude: -74.006, + }, + city: 'San Francisco', + postalCode: '94107', + region: 'CA', + regionCode: 'CA', + country: ' United States', + countryCode: 'United States of America', + continent: 'North America', + timezone: 'America/Los_Angeles', + }, + }, + type: 'track', + event: 'product purchased', + channel: 'mobile', + messageId: 'dummy_msg_id', + properties: commonProperties, + anonymousId: 'anon_123', + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + ], + }, + userId: '', + }), + statusCode: 200, + }, + ], + }, + }, + }, + { + id: 'ninetailed-test-track-failure-1', + name: 'ninetailed', + description: 'track call with no event available', + scenario: 'Framework', + successCriteria: + 'Error should be thrown for required field event not present and status code should be 200', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + context, + type: 'track', + channel: 'mobile', + messageId: 'dummy_msg_id', + properties: commonProperties, + anonymousId: 'anon_123', + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Missing required value from "event": Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: Missing required value from "event"', + metadata: { + destinationId: 'dummyDestId', + }, + statTags: { + destType: 'NINETAILED', + destinationId: 'dummyDestId', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/ninetailed/processor/validation.ts b/test/integrations/destinations/ninetailed/processor/validation.ts new file mode 100644 index 00000000000..68c025faad1 --- /dev/null +++ b/test/integrations/destinations/ninetailed/processor/validation.ts @@ -0,0 +1,115 @@ +import { processInstrumentationErrorStatTags, destination, context } from '../commonConfig'; + +export const validationFailures = [ + { + id: 'Ninetailed-validation-test-1', + name: 'ninetailed', + description: 'Required field anonymousId not present', + scenario: 'Framework', + successCriteria: 'Transformationn Error for anonymousId not present', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'track', + event: 'product purchased', + sentAt: '2021-01-25T16:12:02.048Z', + userId: 'sajal12', + context, + properties: { + products: [{}], + }, + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata: { + destinationId: 'dummyDestId', + jobId: '1', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Missing required value from "anonymousId": Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: Missing required value from "anonymousId"', + metadata: { + destinationId: 'dummyDestId', + jobId: '1', + }, + statTags: processInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, + { + id: 'Ninetailed-test-4', + name: 'ninetailed', + description: 'Unsupported message type -> group', + scenario: 'Framework', + successCriteria: 'Transformationn Error for Unsupported message type', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination, + message: { + type: 'group', + sentAt: '2021-01-25T16:12:02.048Z', + userId: 'sajal12', + channel: 'mobile', + rudderId: 'b7b24f86-f7bf-46d8-b2b4-ccafc080239c', + messageId: 'dummy_msg_id', + traits: { + orderId: 'ord 123', + products: [], + }, + anonymousId: 'anon_123', + integrations: { + All: true, + }, + originalTimestamp: '2021-01-25T15:32:56.409Z', + }, + metadata: { + destinationId: 'dummyDestId', + jobId: '1', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'message type group is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type group is not supported', + metadata: { + destinationId: 'dummyDestId', + jobId: '1', + }, + statTags: processInstrumentationErrorStatTags, + statusCode: 400, + }, + ], + }, + }, + }, +]; diff --git a/test/integrations/destinations/ninetailed/router/basicProperties.ts b/test/integrations/destinations/ninetailed/router/basicProperties.ts new file mode 100644 index 00000000000..5725f3d4451 --- /dev/null +++ b/test/integrations/destinations/ninetailed/router/basicProperties.ts @@ -0,0 +1,33 @@ +export const trackProperties = { + index: 'products', + queryId: '43b15df305339e827f0ac0bdc5ebcaa7', + products: [ + { objectId: 'ecommerce-sample-data-919', position: 7 }, + { objectId: '9780439784542', position: 8 }, + ], +}; + +export const pageProperties = { + title: 'Sample Page', + url: 'https://example.com/?utm_campaign=example_campaign&utm_content=example_content', + path: '/', + hash: '', + search: '?utm_campaign=example_campaign&utm_content=example_content', + width: '1920', + height: '1080', + query: { + utm_campaign: 'example_campaign', + utm_content: 'example_content', + }, + referrer: '', +}; + +export const traits = { + email: 'test@user.com', + firstname: 'John', + lastname: 'Doe', + phone: '+1(123)456-7890', + gender: 'Male', + birthday: '1980-01-02', + city: 'San Francisco', +}; diff --git a/test/integrations/destinations/ninetailed/router/data.ts b/test/integrations/destinations/ninetailed/router/data.ts new file mode 100644 index 00000000000..05105f4aed8 --- /dev/null +++ b/test/integrations/destinations/ninetailed/router/data.ts @@ -0,0 +1,393 @@ +import { + commonInput, + destination, + commonOutput, + routerInstrumentationErrorStatTags, +} from '../commonConfig'; +import { trackProperties, pageProperties, traits } from './basicProperties'; +import { defaultMockFns } from '../mocks'; + +export const data = [ + { + name: 'ninetailed', + id: 'Test 0 - router', + description: 'Batch calls with all three type of calls as success', + scenario: 'Framework+Buisness', + successCriteria: 'All events should be transformed successfully and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + ...commonInput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + metadata: { jobId: 1, userId: 'u1' }, + destination, + }, + { + message: { + ...commonInput, + type: 'page', + properties: pageProperties, + }, + metadata: { jobId: 2, userId: 'u1' }, + destination, + }, + { + message: { + type: 'identify', + ...commonInput, + userId: 'testuserId1', + traits, + integrations: { All: true }, + }, + metadata: { jobId: 3, userId: 'u1' }, + destination, + }, + ], + destType: 'ninetailed', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + params: {}, + body: { + FORM: {}, + JSON: { + events: [ + { + ...commonOutput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + { + ...commonOutput, + type: 'page', + properties: pageProperties, + }, + { + type: 'identify', + ...commonOutput, + userId: 'testuserId1', + traits, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + headers: { + 'Content-Type': 'application/json', + }, + files: {}, + }, + metadata: [ + { jobId: 1, userId: 'u1' }, + { jobId: 2, userId: 'u1' }, + { jobId: 3, userId: 'u1' }, + ], + batched: true, + statusCode: 200, + destination, + }, + ], + }, + }, + }, + }, + { + name: 'ninetailed', + id: 'Test 1 - router', + description: 'Batch calls with one fail invalid event and two valid events', + scenario: 'Framework+Buisness', + successCriteria: + 'Two events should be transformed successfully and one should fail and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + ...commonInput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + metadata: { jobId: 1, userId: 'u1' }, + destination, + }, + { + message: { + ...commonInput, + type: 'page', + properties: { + title: 'Sample Page', + url: 'https://example.com/?utm_campaign=example_campaign&utm_content=example_content', + path: '/', + hash: '', + search: '?utm_campaign=example_campaign&utm_content=example_content', + width: '1920', + height: '1080', + query: { + utm_campaign: 'example_campaign', + utm_content: 'example_content', + }, + referrer: '', + }, + }, + metadata: { jobId: 2, userId: 'u1' }, + destination, + }, + { + message: { + type: 'identify', + ...commonInput, + traits, + integrations: { All: true }, + }, + metadata: { jobId: 3, userId: 'u1' }, + destination, + }, + ], + destType: 'ninetailed', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + destination, + error: 'Missing required value from "userIdOnly"', + metadata: [{ jobId: 3, userId: 'u1' }], + statTags: routerInstrumentationErrorStatTags, + statusCode: 400, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + params: {}, + body: { + FORM: {}, + JSON: { + events: [ + { + ...commonOutput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + { + ...commonOutput, + type: 'page', + properties: pageProperties, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + headers: { + 'Content-Type': 'application/json', + }, + files: {}, + }, + metadata: [ + { jobId: 1, userId: 'u1' }, + { jobId: 2, userId: 'u1' }, + ], + batched: true, + statusCode: 200, + destination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, + { + name: 'ninetailed', + id: 'Test 2 - router', + description: 'Batch calls with 3 succesfull events and 1 failed event', + scenario: 'Framework+Buisness', + successCriteria: + '3 successful events should be distributed in two and 1 failed in one hence total batches should be 3 and status code should be 200', + feature: 'router', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + input: [ + { + message: { + ...commonInput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + metadata: { jobId: 1, userId: 'u1' }, + destination, + }, + { + message: { + ...commonInput, + type: 'page', + properties: pageProperties, + }, + metadata: { jobId: 2, userId: 'u1' }, + destination, + }, + { + message: { + type: 'identify', + ...commonInput, + userId: 'testuserId1', + traits, + integrations: { All: true }, + }, + metadata: { jobId: 3, userId: 'u1' }, + destination, + }, + { + message: { + type: 'identify', + ...commonInput, + traits, + integrations: { All: true }, + }, + metadata: { jobId: 4, userId: 'u1' }, + destination, + }, + ], + destType: 'ninetailed', + }, + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: [ + { + batched: false, + destination, + error: 'Missing required value from "userIdOnly"', + metadata: [{ jobId: 4, userId: 'u1' }], + statTags: routerInstrumentationErrorStatTags, + statusCode: 400, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + params: {}, + body: { + FORM: {}, + JSON: { + events: [ + { + ...commonOutput, + type: 'track', + event: 'product list viewed', + properties: trackProperties, + }, + { + ...commonOutput, + type: 'page', + properties: pageProperties, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + headers: { + 'Content-Type': 'application/json', + }, + files: {}, + }, + metadata: [ + { jobId: 1, userId: 'u1' }, + { jobId: 2, userId: 'u1' }, + ], + batched: true, + statusCode: 200, + destination, + }, + { + batchedRequest: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: + 'https://experience.ninetailed.co/v2/organizations/dummyOrganisationId/environments/main/events', + params: {}, + body: { + FORM: {}, + JSON: { + events: [ + { + type: 'identify', + ...commonOutput, + userId: 'testuserId1', + traits, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + }, + headers: { + 'Content-Type': 'application/json', + }, + files: {}, + }, + metadata: [{ jobId: 3, userId: 'u1' }], + batched: true, + statusCode: 200, + destination, + }, + ], + }, + }, + }, + mockFns: defaultMockFns, + }, +]; diff --git a/test/integrations/destinations/one_signal/processor/data.ts b/test/integrations/destinations/one_signal/processor/data.ts index 7f244aa7114..4171157aef7 100644 --- a/test/integrations/destinations/one_signal/processor/data.ts +++ b/test/integrations/destinations/one_signal/processor/data.ts @@ -702,7 +702,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', userId: '', }, statusCode: 200, @@ -789,7 +789,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', userId: '', }, statusCode: 200, @@ -870,7 +870,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', userId: '', }, statusCode: 200, @@ -945,7 +945,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', userId: '', }, statusCode: 200, @@ -1025,7 +1025,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', userId: '', }, statusCode: 200, diff --git a/test/integrations/destinations/one_signal/router/data.ts b/test/integrations/destinations/one_signal/router/data.ts index fe8460e45df..a27da5a7456 100644 --- a/test/integrations/destinations/one_signal/router/data.ts +++ b/test/integrations/destinations/one_signal/router/data.ts @@ -199,7 +199,7 @@ export const data = [ headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, version: '1', endpoint: - 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user@27', + 'https://onesignal.com/api/v1/apps/random-818c-4a28-b98e-6cd8a994eb22/users/user%4027', }, metadata: [{ jobId: 2, userId: 'u1' }], batched: false, diff --git a/test/integrations/destinations/rakuten/processor/transformationFailure.ts b/test/integrations/destinations/rakuten/processor/transformationFailure.ts index 906ddafd6ac..e35ab26b691 100644 --- a/test/integrations/destinations/rakuten/processor/transformationFailure.ts +++ b/test/integrations/destinations/rakuten/processor/transformationFailure.ts @@ -46,7 +46,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from "properties.orderId": Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from "properties.orderId"', + 'Missing required value from ["properties.order_id","properties.orderId"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.order_id","properties.orderId"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -245,7 +245,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.tr","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ranSiteID"]', + 'Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.tr","properties.ran_site_id","properties.ranSiteID"]', metadata: { destinationId: 'dummyDestId', jobId: '1', @@ -312,7 +312,7 @@ export const transformationFailures = [ body: [ { error: - 'Missing required value from ["properties.land","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.landTime"]', + 'Missing required value from ["properties.land","properties.land_time","properties.landTime"]: Workflow: procWorkflow, Step: prepareTrackPayload, ChildStep: undefined, OriginalError: Missing required value from ["properties.land","properties.land_time","properties.landTime"]', metadata: { destinationId: 'dummyDestId', jobId: '1', diff --git a/test/integrations/destinations/reddit/dataDelivery/business.ts b/test/integrations/destinations/reddit/dataDelivery/business.ts new file mode 100644 index 00000000000..2c4714ef13a --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/business.ts @@ -0,0 +1,131 @@ +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const validRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: validRequestPayload, +}; + +export const testScenariosForV0API = [ + { + id: 'reddit_v0_scenario_1', + name: 'reddit', + description: + '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + destResp: { + response: { + message: 'Successfully processed 1 conversion events.', + }, + status: 200, + }, + message: 'Request Processed Successfully', + status: 200, + }, + }, + }, + }, + }, +]; + +export const testScenariosForV1API = [ + { + id: 'reddit_v1_scenario_1', + name: 'reddit', + description: + '[Proxy v1 API] :: Test for a valid request with a successful 200 response from the destination', + scenario: 'Business', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + message: 'Request Processed Successfully', + response: [ + { + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/data.ts b/test/integrations/destinations/reddit/dataDelivery/data.ts new file mode 100644 index 00000000000..54728ecb90e --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/data.ts @@ -0,0 +1,9 @@ +import { testScenariosForV0API, testScenariosForV1API } from './business'; +import { v0oauthScenarios, v1oauthScenarios } from './oauth'; + +export const data = [ + ...v0oauthScenarios, + ...v1oauthScenarios, + ...testScenariosForV0API, + ...testScenariosForV1API, +]; diff --git a/test/integrations/destinations/reddit/dataDelivery/oauth.ts b/test/integrations/destinations/reddit/dataDelivery/oauth.ts new file mode 100644 index 00000000000..90368cd60b0 --- /dev/null +++ b/test/integrations/destinations/reddit/dataDelivery/oauth.ts @@ -0,0 +1,147 @@ +import { + generateMetadata, + generateProxyV1Payload, + generateProxyV0Payload, +} from '../../../testUtils'; + +const authorizationRequiredRequestPayload = { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'ViewContent', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 3, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], +}; + +const commonHeaders = { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: authorizationRequiredRequestPayload, +}; + +const expectedStatTags = { + destType: 'REDDIT', + destinationId: 'default-destinationId', + errorCategory: 'network', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', + workspaceId: 'default-workspaceId', +}; + +export const v0oauthScenarios = [ + { + id: 'reddit_v0_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v0 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + destinationResponse: { + response: 'Authorization Required', + status: 401, + }, + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; + +export const v1oauthScenarios = [ + { + id: 'reddit_v1_oauth_scenario_1', + name: 'reddit', + description: '[Proxy v1 API] :: Oauth where Authorization Required response from destination', + successCriteria: 'Should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload( + { + ...commonRequestParameters, + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', + }, + [generateMetadata(1)], + ), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + authErrorCategory: 'REFRESH_TOKEN', + message: + "Request failed due to Authorization Required 'during reddit response transformation'", + response: [ + { + error: '"Authorization Required"', + metadata: generateMetadata(1), + statusCode: 500, + }, + ], + statTags: expectedStatTags, + status: 500, + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/reddit/delivery/data.ts b/test/integrations/destinations/reddit/delivery/data.ts deleted file mode 100644 index 66c1e2863f3..00000000000 --- a/test/integrations/destinations/reddit/delivery/data.ts +++ /dev/null @@ -1,174 +0,0 @@ -export const data = [ - { - name: 'reddit', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'Purchase', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - destResp: { - response: { - message: 'Successfully processed 1 conversion events.', - }, - status: 200, - }, - message: 'Request Processed Successfully', - status: 200, - }, - }, - }, - }, - }, - { - name: 'reddit', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_gsddXXXfsfd', - headers: { - Authorization: 'Bearer dummyAccessToken', - 'Content-Type': 'application/json', - }, - params: {}, - body: { - JSON: { - events: [ - { - event_at: '2019-10-14T09:03:17.562Z', - event_type: { - tracking_type: 'ViewContent', - }, - user: { - aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', - email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', - external_id: '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', - ip_address: 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', - user_agent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', - screen_dimensions: {}, - }, - event_metadata: { - item_count: 3, - products: [ - { - id: '123', - name: 'Monopoly', - category: 'Games', - }, - { - id: '345', - name: 'UNO', - category: 'Games', - }, - ], - }, - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - userId: '', - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - authErrorCategory: 'REFRESH_TOKEN', - destinationResponse: { - response: 'Authorization Required', - status: 401, - }, - message: - "Request failed due to Authorization Required 'during reddit response transformation'", - statTags: { - destType: 'REDDIT', - destinationId: 'Non-determininable', - errorCategory: 'network', - errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - workspaceId: 'Non-determininable', - }, - status: 500, - }, - }, - }, - }, - }, -]; diff --git a/test/integrations/destinations/reddit/processor/data.ts b/test/integrations/destinations/reddit/processor/data.ts index f3cd4ebf7bb..49e0cd2baae 100644 --- a/test/integrations/destinations/reddit/processor/data.ts +++ b/test/integrations/destinations/reddit/processor/data.ts @@ -166,6 +166,173 @@ export const data = [ }, }, }, + { + name: 'reddit', + description: 'Track call with order completed event with floating point values for revenue', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + context: { + traits: { + email: 'testone@gmail.com', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + ip: '54.100.200.255', + device: { + advertisingId: 'asfds7fdsihf734b34j43f', + }, + os: { + name: 'android', + }, + }, + type: 'track', + session_id: '16733896350494', + originalTimestamp: '2019-10-14T09:03:17.562Z', + anonymousId: '123456', + event: 'Order Completed', + userId: 'testuserId1', + properties: { + checkout_id: '12345', + order_id: '1234', + affiliation: 'Apple Store', + total: 20, + revenue: 14.985, + shipping: 4, + tax: 1, + discount: 1.5, + coupon: 'ImagePro', + currency: 'USD', + products: [ + { + product_id: '123', + sku: 'G-32', + name: 'Monopoly', + price: 14, + quantity: 1, + category: 'Games', + url: 'https://www.website.com/product/path', + image_url: 'https://www.website.com/product/path.jpg', + }, + { + product_id: '345', + sku: 'F-32', + name: 'UNO', + price: 3.45, + quantity: 2, + category: 'Games', + }, + ], + }, + integrations: { + All: true, + }, + sentAt: '2019-10-14T09:03:22.563Z', + }, + destination: { + Config: { + accountId: 'a2_fsddXXXfsfd', + hashData: true, + eventsMapping: [ + { + from: 'Order Completed', + to: 'Purchase', + }, + ], + }, + DestinationDefinition: { Config: { cdkV2Enabled: true } }, + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://ads-api.reddit.com/api/v2.0/conversions/events/a2_fsddXXXfsfd', + headers: { + Authorization: 'Bearer dummyAccessToken', + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + events: [ + { + event_at: '2019-10-14T09:03:17.562Z', + event_type: { + tracking_type: 'Purchase', + }, + user: { + aaid: 'c12d34889302d3c656b5699fa9190b51c50d6f62fce57e13bd56b503d66c487a', + email: 'ac144532d9e4efeab19475d9253a879173ea12a3d2238d1cb8a332a7b3a105f2', + external_id: + '7b023241a3132b792a5a33915a5afb3133cbb1e13d72879689bf6504de3b036d', + ip_address: + 'e80bd55a3834b7c2a34ade23c7ecb54d2a49838227080f50716151e765a619db', + user_agent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + screen_dimensions: {}, + }, + event_metadata: { + item_count: 2, + currency: 'USD', + value: 1499, + value_decimal: 14.99, + products: [ + { + id: '123', + name: 'Monopoly', + category: 'Games', + }, + { + id: '345', + name: 'UNO', + category: 'Games', + }, + ], + }, + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + secret: { + accessToken: 'dummyAccessToken', + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, { name: 'reddit', description: 'Track call with product list viewed event', diff --git a/test/integrations/destinations/sfmc/processor/data.ts b/test/integrations/destinations/sfmc/processor/data.ts index 406ed82ace5..b2839908ad6 100644 --- a/test/integrations/destinations/sfmc/processor/data.ts +++ b/test/integrations/destinations/sfmc/processor/data.ts @@ -1732,4 +1732,166 @@ export const data = [ }, }, }, + { + name: 'sfmc', + description: 'Test 12', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + event: 'message event', + context: { + app: { + build: '1.0.0', + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + campaign: { + name: 'Demo Campaign', + source: 'facebook', + medium: 'online', + term: 'Demo terms', + content: 'Demo content', + }, + traits: { + email: 'tonmoy@rudderstack.com', + name: 'Tonmoy Labs', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-GB', + ip: '0.0.0.0', + screen: { + density: 2, + height: 860, + width: 1280, + }, + }, + type: 'track', + userId: '12345', + properties: { + id: 'id101', + contactId: 'cid101', + email: 'testemail@gmail.com', + accountNumber: '99110099', + patronName: 'SP', + }, + sentAt: '2019-10-14T09:03:22.563Z', + integrations: { + All: true, + }, + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'SFMC', + DestinationDefinition: { + ID: '1pYpYSeQd8OeN6xPdw6VGDzqUd1', + Name: 'SFMC', + DisplayName: 'Salesforce Marketing Cloud', + Config: { + destConfig: [], + excludeKeys: [], + includeKeys: [], + saveDestinationResponse: false, + supportedSourceTypes: [], + transformAt: 'processor', + }, + ResponseRules: {}, + }, + Config: { + clientId: 'vcn7AQ2W9GGIAZSsN6Mfq', + clientSecret: 'vcn7AQ2W9GGIAZSsN6Mfq', + createOrUpdateContacts: false, + eventDelivery: true, + eventDeliveryTS: 1615371070621, + eventToExternalKey: [ + { + from: 'Event Name', + to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB', + }, + { + from: 'Watch', + to: 'C500FD37-155C-49BD-A21B-AFCEF3D1A9CB', + }, + ], + eventToPrimaryKey: [ + { + from: 'userId', + to: 'User Key', + }, + { + from: 'watch', + to: 'Guest Key, Contact Key', + }, + ], + eventToUUID: [ + { + event: 'Event Name', + uuid: true, + }, + ], + eventToDefinitionMapping: [ + { + from: 'message event', + to: 'test-event-definition', + }, + ], + externalKey: 'f3ffa19b-e0b3-4967-829f-549b781080e6', + subDomain: 'vcn7AQ2W9GGIAZSsN6Mfq', + }, + Enabled: true, + Transformations: [], + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + body: { + XML: {}, + JSON_ARRAY: {}, + FORM: {}, + JSON: { + ContactKey: 'cid101', + Data: { + accountNumber: '99110099', + email: 'testemail@gmail.com', + id: 'id101', + patronName: 'SP', + }, + EventDefinitionKey: 'test-event-definition', + }, + }, + type: 'REST', + files: {}, + method: 'POST', + params: {}, + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer yourAuthToken', + }, + version: '1', + endpoint: + 'https://vcn7AQ2W9GGIAZSsN6Mfq.rest.marketingcloudapis.com/interaction/v1/events', + userId: '', + }, + statusCode: 200, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/snapchat_custom_audience/dataDelivery/business.ts b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/business.ts new file mode 100644 index 00000000000..4ee646bedbf --- /dev/null +++ b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/business.ts @@ -0,0 +1,118 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const commonHeaders = { + Authorization: 'Bearer abcd123', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + users: [ + { + schema: ['EMAIL_SHA256'], + data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], + }, + ], + }, +}; + +export const businessV0TestScenarios = [ + { + id: 'snapchat_custom_audience_v0_oauth_scenario_1', + name: 'snapchat_custom_audience', + description: '[Proxy v0 API] :: successfull call', + successCriteria: 'Proper response from destination is received', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/123/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + destinationResponse: { + response: { + request_status: 'SUCCESS', + request_id: '12345', + users: [ + { + sub_request_status: 'SUCCESS', + user: { + number_uploaded_users: 1, + }, + }, + ], + }, + status: 200, + }, + }, + }, + }, + }, + }, +]; + +export const businessV1TestScenarios: ProxyV1TestData[] = [ + { + id: 'snapchat_custom_audience_v1_oauth_scenario_1', + name: 'snapchat_custom_audience', + description: '[Proxy v1 API] :: successfull oauth', + successCriteria: 'Proper response from destination is received', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/123/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + status: 200, + message: 'Request Processed Successfully', + response: [ + { + error: `{\"request_status\":\"SUCCESS\",\"request_id\":\"12345\",\"users\":[{\"sub_request_status\":\"SUCCESS\",\"user\":{\"number_uploaded_users\":1}}]}`, + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/snapchat_custom_audience/dataDelivery/data.ts b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/data.ts index d8ec365a82d..4991ed1d38a 100644 --- a/test/integrations/destinations/snapchat_custom_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/data.ts @@ -1,206 +1,11 @@ +import { businessV0TestScenarios, businessV1TestScenarios } from './business'; +import { v0OauthScenarios, v1OauthScenarios } from './oauth'; +import { otherScenariosV1 } from './other'; + export const data = [ - { - name: 'snapchat_custom_audience', - description: 'Test 0', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://adsapi.snapchat.com/v1/segments/123/users', - headers: { - Authorization: 'Bearer abcd123', - 'Content-Type': 'application/json', - }, - body: { - JSON: { - users: [ - { - schema: ['EMAIL_SHA256'], - data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'snapchat_custom_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 200, - body: { - output: { - status: 200, - message: 'Request Processed Successfully', - destinationResponse: { - response: { - request_status: 'SUCCESS', - request_id: '12345', - users: [ - { - sub_request_status: 'SUCCESS', - user: { - number_uploaded_users: 1, - }, - }, - ], - }, - status: 200, - }, - }, - }, - }, - }, - }, - { - name: 'snapchat_custom_audience', - description: 'Test 1', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'POST', - endpoint: 'https://adsapi.snapchat.com/v1/segments/456/users', - headers: { - Authorization: 'Bearer abcd123', - 'Content-Type': 'application/json', - }, - body: { - JSON: { - users: [ - { - schema: ['EMAIL_SHA256'], - data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'snapchat_custom_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 500, - body: { - output: { - status: 500, - destinationResponse: { - response: 'unauthorized', - status: 401, - }, - message: - 'Failed with unauthorized during snapchat_custom_audience response transformation', - statTags: { - destType: 'SNAPCHAT_CUSTOM_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'retryable', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - authErrorCategory: 'REFRESH_TOKEN', - }, - }, - }, - }, - }, - { - name: 'snapchat_custom_audience', - description: 'Test 2', - feature: 'dataDelivery', - module: 'destination', - version: 'v0', - input: { - request: { - body: { - version: '1', - type: 'REST', - method: 'DELETE', - endpoint: 'https://adsapi.snapchat.com/v1/segments/789/users', - headers: { - Authorization: 'Bearer abcd123', - 'Content-Type': 'application/json', - }, - body: { - JSON: { - users: [ - { - id: '123456', - schema: ['EMAIL_SHA256'], - data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], - }, - ], - }, - JSON_ARRAY: {}, - XML: {}, - FORM: {}, - }, - files: {}, - params: { - destination: 'snapchat_custom_audience', - }, - }, - method: 'POST', - }, - }, - output: { - response: { - status: 400, - body: { - output: { - authErrorCategory: 'AUTH_STATUS_INACTIVE', - status: 400, - destinationResponse: { - response: { - request_status: 'ERROR', - request_id: '98e2a602-3cf4-4596-a8f9-7f034161f89a', - debug_message: 'Caller does not have permission', - display_message: - "We're sorry, but the requested resource is not available at this time", - error_code: 'E3002', - }, - status: 403, - }, - message: 'undefined during snapchat_custom_audience response transformation', - statTags: { - destType: 'SNAPCHAT_CUSTOM_AUDIENCE', - errorCategory: 'network', - destinationId: 'Non-determininable', - workspaceId: 'Non-determininable', - errorType: 'aborted', - feature: 'dataDelivery', - implementation: 'native', - module: 'destination', - }, - }, - }, - }, - }, - }, + ...v0OauthScenarios, + ...v1OauthScenarios, + ...businessV0TestScenarios, + ...businessV1TestScenarios, + ...otherScenariosV1, ]; diff --git a/test/integrations/destinations/snapchat_custom_audience/dataDelivery/oauth.ts b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/oauth.ts new file mode 100644 index 00000000000..e4bf5d45882 --- /dev/null +++ b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/oauth.ts @@ -0,0 +1,244 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { + generateMetadata, + generateProxyV0Payload, + generateProxyV1Payload, +} from '../../../testUtils'; + +const commonHeaders = { + Authorization: 'Bearer abcd123', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', +}; + +const commonRequestParameters = { + headers: commonHeaders, + JSON: { + users: [ + { + schema: ['EMAIL_SHA256'], + data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], + }, + ], + }, +}; + +const commonDeleteRequestParameters = { + headers: commonHeaders, + JSON: { + users: [ + { + id: '123456', + schema: ['EMAIL_SHA256'], + data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], + }, + ], + }, +}; + +const retryStatTags = { + destType: 'SNAPCHAT_CUSTOM_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +const abortStatTags = { + destType: 'SNAPCHAT_CUSTOM_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'aborted', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const v0OauthScenarios = [ + { + id: 'snapchat_custom_audience_v0_oauth_scenario_2', + name: 'snapchat_custom_audience', + description: + '[Proxy v0 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/456/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + status: 500, + destinationResponse: { + response: 'unauthorized', + status: 401, + }, + message: + 'Failed with unauthorized during snapchat_custom_audience response transformation', + statTags: retryStatTags, + authErrorCategory: 'REFRESH_TOKEN', + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v0_oauth_scenario_3', + name: 'snapchat_custom_audience', + description: + '[Proxy v0 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: generateProxyV0Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/999/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + authErrorCategory: 'AUTH_STATUS_INACTIVE', + status: 400, + destinationResponse: { + response: { + request_status: 'ERROR', + request_id: '98e2a602-3cf4-4596-a8f9-7f034161f89a', + debug_message: 'Caller does not have permission', + display_message: + "We're sorry, but the requested resource is not available at this time", + error_code: 'E3002', + }, + status: 403, + }, + message: 'undefined during snapchat_custom_audience response transformation', + statTags: abortStatTags, + }, + }, + }, + }, + }, +]; + +export const v1OauthScenarios: ProxyV1TestData[] = [ + { + id: 'snapchat_custom_audience_v1_oauth_scenario_1', + name: 'snapchat_custom_audience', + description: + '[Proxy v1 API] :: Oauth where valid credentials are missing as mock response from destination', + successCriteria: + 'Since the error from the destination is 401 - the proxy should return 500 with authErrorCategory as REFRESH_TOKEN', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/456/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 500, + body: { + output: { + response: [ + { + error: '"unauthorized"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: retryStatTags, + authErrorCategory: 'REFRESH_TOKEN', + message: + 'Failed with unauthorized during snapchat_custom_audience response transformation', + status: 500, + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v1_oauth_scenario_2', + name: 'snapchat_custom_audience', + description: + '[Proxy v1 API] :: Oauth where ACCESS_TOKEN_SCOPE_INSUFFICIENT error as mock response from destination', + successCriteria: + 'Since the error from the destination is 403 - the proxy should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE', + scenario: 'Oauth', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + ...commonRequestParameters, + endpoint: 'https://adsapi.snapchat.com/v1/segments/999/users', + params: { + destination: 'snapchat_custom_audience', + }, + }), + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + response: [ + { + error: `{"request_status":"ERROR","request_id":"98e2a602-3cf4-4596-a8f9-7f034161f89a","debug_message":"Caller does not have permission","display_message":"We're sorry, but the requested resource is not available at this time","error_code":"E3002"}`, + statusCode: 400, + metadata: generateMetadata(1), + }, + ], + statTags: abortStatTags, + message: 'undefined during snapchat_custom_audience response transformation', + status: 400, + authErrorCategory: 'AUTH_STATUS_INACTIVE', + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/snapchat_custom_audience/dataDelivery/other.ts b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/other.ts new file mode 100644 index 00000000000..90508c2481c --- /dev/null +++ b/test/integrations/destinations/snapchat_custom_audience/dataDelivery/other.ts @@ -0,0 +1,204 @@ +import { ProxyV1TestData } from '../../../testTypes'; +import { generateMetadata, generateProxyV1Payload } from '../../../testUtils'; + +const expectedStatTags = { + destType: 'SNAPCHAT_CUSTOM_AUDIENCE', + errorCategory: 'network', + destinationId: 'default-destinationId', + workspaceId: 'default-workspaceId', + errorType: 'retryable', + feature: 'dataDelivery', + implementation: 'native', + module: 'destination', +}; + +export const otherScenariosV1: ProxyV1TestData[] = [ + { + id: 'snapchat_custom_audience_v1_other_scenario_1', + name: 'snapchat_custom_audience', + description: + '[Proxy v1 API] :: Scenario for testing Service Unavailable error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_service_not_available', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: + '{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}', + statusCode: 503, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + status: 503, + message: 'Service Unavailable during snapchat_custom_audience response transformation', + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v1_other_scenario_2', + name: 'snapchat_custom_audience', + description: '[Proxy v1 API] :: Scenario for testing Internal Server error from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_internal_server_error', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Internal Server Error"', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + status: 500, + message: 'undefined during snapchat_custom_audience response transformation', + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v1_other_scenario_3', + name: 'snapchat_custom_audience', + description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out error from destination', + successCriteria: 'Should return 504 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_gateway_time_out', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '"Gateway Timeout"', + statusCode: 504, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + status: 504, + message: 'undefined during snapchat_custom_audience response transformation', + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v1_other_scenario_4', + name: 'snapchat_custom_audience', + description: '[Proxy v1 API] :: Scenario for testing null response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_response', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + status: 500, + message: 'undefined during snapchat_custom_audience response transformation', + }, + }, + }, + }, + }, + { + id: 'snapchat_custom_audience_v1_other_scenario_5', + name: 'snapchat_custom_audience', + description: + '[Proxy v1 API] :: Scenario for testing null and no status response from destination', + successCriteria: 'Should return 500 status code with error message', + scenario: 'Framework', + feature: 'dataDelivery', + module: 'destination', + version: 'v1', + input: { + request: { + body: generateProxyV1Payload({ + endpoint: 'https://random_test_url/test_for_null_and_no_status', + }), + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: { + output: { + response: [ + { + error: '""', + statusCode: 500, + metadata: generateMetadata(1), + }, + ], + statTags: expectedStatTags, + status: 500, + message: 'undefined during snapchat_custom_audience response transformation', + }, + }, + }, + }, + }, +]; diff --git a/test/integrations/destinations/snapchat_custom_audience/network.ts b/test/integrations/destinations/snapchat_custom_audience/network.ts index 9be134c2026..39bd46122dd 100644 --- a/test/integrations/destinations/snapchat_custom_audience/network.ts +++ b/test/integrations/destinations/snapchat_custom_audience/network.ts @@ -81,4 +81,35 @@ export const networkCallsData = [ statusText: 'Forbidden', }, }, + { + httpReq: { + url: 'https://adsapi.snapchat.com/v1/segments/999/users', + data: { + users: [ + { + schema: ['EMAIL_SHA256'], + data: [['938758751f5af66652a118e26503af824404bc13acd1cb7642ddff99916f0e1c']], + }, + ], + }, + params: { destination: 'snapchat_custom_audience' }, + headers: { + Authorization: 'Bearer abcd123', + 'Content-Type': 'application/json', + 'User-Agent': 'RudderLabs', + }, + method: 'POST', + }, + httpRes: { + data: { + request_status: 'ERROR', + request_id: '98e2a602-3cf4-4596-a8f9-7f034161f89a', + debug_message: 'Caller does not have permission', + display_message: "We're sorry, but the requested resource is not available at this time", + error_code: 'E3002', + }, + status: 403, + statusText: 'Forbidden', + }, + }, ]; diff --git a/test/integrations/destinations/tiktok_ads/processor/data.ts b/test/integrations/destinations/tiktok_ads/processor/data.ts index 3b68426fbf6..d0447da43cf 100644 --- a/test/integrations/destinations/tiktok_ads/processor/data.ts +++ b/test/integrations/destinations/tiktok_ads/processor/data.ts @@ -1369,7 +1369,7 @@ export const data = [ body: [ { statusCode: 400, - error: 'Event name is required', + error: 'Either event name is not present or it is not a string', statTags: { errorCategory: 'dataValidation', errorType: 'instrumentation', @@ -6973,4 +6973,49 @@ export const data = [ }, }, }, + { + name: 'tiktok_ads', + description: 'Testing if the event name provided as a string or not', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 123, + }, + destination: { + Config: { + accessToken: 'dummyAccessToken', + pixelCode: '{{PIXEL-CODE}}', + hashUserProperties: false, + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + statusCode: 400, + error: 'Either event name is not present or it is not a string', + statTags: { + errorCategory: 'dataValidation', + errorType: 'instrumentation', + destType: 'TIKTOK_ADS', + module: 'destination', + implementation: 'native', + feature: 'processor', + }, + }, + ], + }, + }, + }, ]; diff --git a/test/integrations/destinations/wootric/network.ts b/test/integrations/destinations/wootric/network.ts index 2407efa62bc..1b51cc700c2 100644 --- a/test/integrations/destinations/wootric/network.ts +++ b/test/integrations/destinations/wootric/network.ts @@ -1,183 +1,182 @@ export const networkCallsData = [ - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486438462, - "created_at": "2022-08-10 11:39:50 -0700", - "updated_at": "2022-08-10 11:39:50 -0700", - "email": "dummyuser1@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1611149961, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "Dummy User 1", - "title": "SDE", - "gender": "Male", - "company": "Rudderstack" - }, - "phone_number": "+19123456789", - "external_id": "dummyId1", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId1?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', - method: 'GET', - }, - httpRes: { - status: 200, - data: { - "id": 486336190, - "created_at": "2022-08-10 07:30:50 -0700", - "updated_at": "2022-08-10 10:12:46 -0700", - "email": "excludeUser@gmail.com", - "last_surveyed": "2022-01-20 05:39:21 -0800", - "external_created_at": 1579755367, - "last_seen_at": null, - "properties": { - "city": "Mumbai", - "name": "exclude test user", - "email": "excludeUser@gmail.com", - "title": "AD", - "gender": "Male", - "company": "Rockstar" - }, - "phone_number": "+18324671283", - "external_id": "exclueFunTestId", - "last_response": null, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - }, + httpRes: { + status: 200, + data: { + id: 486438462, + created_at: '2022-08-10 11:39:50 -0700', + updated_at: '2022-08-10 11:39:50 -0700', + email: 'dummyuser1@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1611149961, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'Dummy User 1', + title: 'SDE', + gender: 'Male', + company: 'Rudderstack', + }, + phone_number: '+19123456789', + external_id: 'dummyId1', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', - method: 'POST', - - }, - httpRes: { - status: 200, - data: { - "type": "error_list", - "errors": [ - { - "status": "record_not_found", - "message": "The record could not be found", - "field": null - } - ] - } - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/exclueFunTestId?lookup_by_external_id=true', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/490635419', - method: 'GET' - }, - httpRes: { - data: { - "id": 490635419, - "created_at": "2022-08-20 00:55:26 -0700", - "updated_at": "2022-08-22 11:17:05 -0700", - "email": "firstuser@gmail.com", - "last_surveyed": "2022-08-01 00:11:44 -0700", - "external_created_at": 1661002761, - "last_seen_at": null, - "properties": { - "Department": "Marketing", - "product_plan": "Web", - "revenue amount": "5000" - }, - "phone_number": "+8859133456781", - "external_id": "firstUserId123", - "last_response": { - "id": 101013218, - "score": 9, - "text": "Good !!!", - "survey": { - "channel": "web" - } - }, - "settings": { - "email_nps": true, - "mobile_nps": true, - "web_nps": true, - "force_mobile_survey": null, - "force_web_survey": null, - "surveys_disabled_by_end_user": null - } - }, - status: 200, - }, + httpRes: { + status: 200, + data: { + id: 486336190, + created_at: '2022-08-10 07:30:50 -0700', + updated_at: '2022-08-10 10:12:46 -0700', + email: 'excludeUser@gmail.com', + last_surveyed: '2022-01-20 05:39:21 -0800', + external_created_at: 1579755367, + last_seen_at: null, + properties: { + city: 'Mumbai', + name: 'exclude test user', + email: 'excludeUser@gmail.com', + title: 'AD', + gender: 'Male', + company: 'Rockstar', + }, + phone_number: '+18324671283', + external_id: 'exclueFunTestId', + last_response: null, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', - method: 'POST' - }, - httpRes: { - data: { - "access_token": "2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c", - "token_type": "Bearer", - "expires_in": 7200, - "refresh_token": "f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52", - "scope": "delete_account admin respond export read survey invalidate_response", - "created_at": 1660292389 - }, - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/my-external-id-1234?lookup_by_external_id=true', + method: 'POST', }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', - method: 'GET' - }, - httpRes: { - status: 200, - }, + httpRes: { + status: 200, + data: { + type: 'error_list', + errors: [ + { + status: 'record_not_found', + message: 'The record could not be found', + field: null, + }, + ], + }, }, - { - httpReq: { - url: 'https://api.wootric.com/v1/end_users/12345', - method: 'GET' - }, - httpRes: { - status: 200, - }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/490635419', + method: 'GET', }, - { - httpReq: { - url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', - method: 'POST' - }, - httpRes: { - data: { - error: "Not found", - status: 404 - } - }, - } + httpRes: { + data: { + id: 490635419, + created_at: '2022-08-20 00:55:26 -0700', + updated_at: '2022-08-22 11:17:05 -0700', + email: 'firstuser@gmail.com', + last_surveyed: '2022-08-01 00:11:44 -0700', + external_created_at: 1661002761, + last_seen_at: null, + properties: { + Department: 'Marketing', + product_plan: 'Web', + 'revenue amount': '5000', + }, + phone_number: '+8859133456781', + external_id: 'firstUserId123', + last_response: { + id: 101013218, + score: 9, + text: 'Good !!!', + survey: { + channel: 'web', + }, + }, + settings: { + email_nps: true, + mobile_nps: true, + web_nps: true, + force_mobile_survey: null, + force_web_survey: null, + surveys_disabled_by_end_user: null, + }, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken', + method: 'POST', + }, + httpRes: { + data: { + access_token: '2fe581c1c72851e73d60f4191f720be93e5d3e8a6147e37c4e8e852b1a8f506c', + token_type: 'Bearer', + expires_in: 7200, + refresh_token: 'f4033a61742e84405a5ef8b2e09b82395dc041f0259fd5fb715fc196a1b9cd52', + scope: 'delete_account admin respond export read survey invalidate_response', + created_at: 1660292389, + }, + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/dummyId2?lookup_by_external_id=true', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/v1/end_users/12345', + method: 'GET', + }, + httpRes: { + status: 200, + }, + }, + { + httpReq: { + url: 'https://api.wootric.com/oauth/token?account_token=NPS-dummyToken12', + method: 'POST', + }, + httpRes: { + data: { + error: 'Not found', + status: 404, + }, + }, + }, ]; diff --git a/tsconfig.json b/tsconfig.json index 9db40dd0e10..926831b6123 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ /* Language and Environment */ "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "lib": [ - "es2019" + "es2019", ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ @@ -100,8 +100,8 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, }, "exclude": ["./src/**/*.test.js", "./src/**/*.test.ts", "./test"], - "include": ["./src", "./src/**/*.json"] + "include": ["./src", "./src/**/*.json"], }