diff --git a/.eslintrc.json b/.eslintrc.json
index d2928e50fd..7258c5c536 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/dependabot.yml b/.github/dependabot.yml
index b18fd29357..040e07f81d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,3 +4,11 @@ updates:
directory: '/'
schedule:
interval: 'weekly'
+ - package-ecosystem: 'docker'
+ directory: '/'
+ schedule:
+ interval: 'daily'
+ - package-ecosystem: 'npm'
+ directory: '/'
+ schedule:
+ interval: 'daily'
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index fe308ce8b9..cfcb1fc0d8 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -26,6 +26,8 @@ N/A
N/A
+@coderabbitai review
+
### Developer checklist
diff --git a/.github/workflows/build-push-docker-image.yml b/.github/workflows/build-push-docker-image.yml
index 9f6709a040..7ddae0a3ae 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 0000000000..a8ff39eee0
--- /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 9904004417..3d457df9ff 100644
--- a/.github/workflows/component-test-report.yml
+++ b/.github/workflows/component-test-report.yml
@@ -28,7 +28,7 @@ jobs:
fetch-depth: 1
- name: Setup Node
- uses: actions/setup-node@v4.0.1
+ uses: actions/setup-node@v4.0.2
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -40,13 +40,11 @@ jobs:
run: |
npm run test:ts -- component
-
- 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: Comment on PR with S3 Object URL
+ - name: Add Test Report Link as Comment on PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PAT }}
@@ -55,14 +53,25 @@ jobs:
// Get the pull request number
const prNumber = context.payload.pull_request.number;
const commentBody = `Test report for this run is available at: https://test-integrations-dev.s3.amazonaws.com/integrations-test-reports/rudder-transformer/${prNumber}/test-report.html`;
-
+
+ // find all the comments of the PR
+ const issueComments = await github.paginate(github.rest.issues.listComments, {
+ owner,
+ repo,
+ issue_number: prNumber,
+ });
+
+ for (const comment of issueComments) {
+ if (comment.body === commentBody) {
+ console.log('Comment already exists');
+ return;
+ }
+ }
// Comment on the pull request
- github.rest.issues.createComment({
+ await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
-
-
\ No newline at end of file
diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml
index a164c25bee..994283a9f4 100644
--- a/.github/workflows/create-hotfix-branch.yml
+++ b/.github/workflows/create-hotfix-branch.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
# Only allow these users to create new hotfix branch from 'main'
- if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'ujjwal-ab')
+ if: github.ref == 'refs/heads/main' && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'koladilip' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc')
steps:
- name: Create Branch
uses: peterjgrainger/action-create-branch@v2.4.0
diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml
index 33b0396705..95431fef8e 100644
--- a/.github/workflows/draft-new-release.yml
+++ b/.github/workflows/draft-new-release.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
# Only allow release stakeholders to initiate releases
- if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'ujjwal-ab') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'ujjwal-ab')
+ if: (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/')) && (github.actor == 'ItsSudip' || github.actor == 'krishna2020' || github.actor == 'saikumarrs' || github.actor == 'sandeepdsvs' || github.actor == 'koladilip' || github.actor == 'shrouti1507' || github.actor == 'anantjain45823' || github.actor == 'chandumlg' || github.actor == 'mihir-4116' || github.actor == 'yashasvibajpai' || github.actor == 'sanpj2292' || github.actor == 'utsabc') && (github.triggering_actor == 'ItsSudip' || github.triggering_actor == 'krishna2020' || github.triggering_actor == 'koladilip' || github.triggering_actor == 'saikumarrs' || github.triggering_actor == 'sandeepdsvs' || github.triggering_actor == 'shrouti1507' || github.triggering_actor == 'anantjain45823' || github.triggering_actor == 'chandumlg' || github.triggering_actor == 'mihir-4116' || github.triggering_actor == 'yashasvibajpai' || github.triggering_actor == 'sanpj2292' || github.triggering_actor == 'utsabc')
steps:
- name: Checkout
uses: actions/checkout@v4.1.1
@@ -16,7 +16,7 @@ jobs:
fetch-depth: 0
- name: Setup Node
- uses: actions/setup-node@v4.0.1
+ uses: actions/setup-node@v4.0.2
with:
node-version-file: '.nvmrc'
cache: 'npm'
diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml
index 4096227400..4375b3383e 100644
--- a/.github/workflows/dt-test-and-report-code-coverage.yml
+++ b/.github/workflows/dt-test-and-report-code-coverage.yml
@@ -20,7 +20,7 @@ jobs:
fetch-depth: 1
- name: Setup Node
- uses: actions/setup-node@v4.0.1
+ uses: actions/setup-node@v4.0.2
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -40,12 +40,16 @@ jobs:
npm run lint:fix
- name: Upload Coverage Reports to Codecov
- uses: codecov/codecov-action@v3.1.4
+ uses: codecov/codecov-action@v4.0.1
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
directory: ./reports/coverage
- name: Upload TS Coverage Reports to Codecov
- uses: codecov/codecov-action@v3.1.4
+ uses: codecov/codecov-action@v4.0.1
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
directory: ./reports/ts-coverage
diff --git a/.github/workflows/prepare-for-prod-dt-deploy.yml b/.github/workflows/prepare-for-prod-dt-deploy.yml
index 2af853f643..a5ca48e3f8 100644
--- a/.github/workflows/prepare-for-prod-dt-deploy.yml
+++ b/.github/workflows/prepare-for-prod-dt-deploy.yml
@@ -144,3 +144,39 @@ jobs:
git push -u origin hosted-transformer-$TAG_NAME
gh pr create --fill
+
+ - name: Update helm charts and raise pull request for enterprise customers on dedicated transformers
+ env:
+ GITHUB_TOKEN: ${{ secrets.PAT }}
+ run: |
+ cd rudder-devops
+ git checkout -b dedicated-transformer-$TAG_NAME
+
+ cd customer-objects
+
+ declare -a enabled_ut_customers=()
+ declare -a sub_directories=('enterprise-us' 'enterprise-eu')
+
+ # identify the customers enabled in sub-directories
+ for directory in "${sub_directories[@]}"; do
+ for f in "./$directory"/*; do
+ [[ -f $f ]] || continue
+
+ enabled="$(yq e '.spec.user_transformer.enabled' $f)"
+ if [ $enabled == "true" ]; then
+ enabled_ut_customers+=( $f )
+ fi
+ done
+ done
+
+ # bump up the customers version and repository information
+ for customer in "${enabled_ut_customers[@]}"; do
+ yq eval -i ".spec.user_transformer.image.version=\"$TAG_NAME\"" $customer
+ yq eval -i ".spec.user_transformer.image.repository=\"$TF_IMAGE_REPOSITORY\"" $customer
+ git add $customer
+ done
+
+ git commit -m "chore: upgrade dedicated transformers to $TAG_NAME"
+ git push -u origin dedicated-transformer-$TAG_NAME
+
+ gh pr create --fill
diff --git a/.github/workflows/prepare-for-prod-rollback.yml b/.github/workflows/prepare-for-prod-rollback.yml
index 9ac144a21e..825720efe1 100644
--- a/.github/workflows/prepare-for-prod-rollback.yml
+++ b/.github/workflows/prepare-for-prod-rollback.yml
@@ -27,11 +27,14 @@ jobs:
git config --global user.name "GitHub Actions"
git config --global user.email "noreply@github.com"
+ - name: Clone Devops Repo
+ run: |
+ git clone https://${{secrets.PAT}}@github.com/rudderlabs/rudder-devops.git
+
- name: Update Helm Charts and Raise Pull Request
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
- git clone https://${{secrets.PAT}}@github.com/rudderlabs/rudder-devops.git
cd rudder-devops
git checkout -b shared-transformer-rollback-${{ steps.target-version.outputs.tag_name }}
@@ -57,3 +60,38 @@ jobs:
git push -u origin shared-transformer-rollback-${{ steps.target-version.outputs.tag_name }}
gh pr create --fill
+
+ - name: Update helm charts and raise pull request for enterprise customers on dedicated transformers
+ env:
+ GITHUB_TOKEN: ${{ secrets.PAT }}
+ run: |
+ cd rudder-devops
+ git checkout -b dedicated-transformer-rollback-${{ steps.target-version.outputs.tag_name }}
+
+ cd customer-objects
+
+ declare -a enabled_ut_customers=()
+ declare -a sub_directories=('enterprise-us' 'enterprise-eu')
+
+ # identify the customers enabled in sub-directories
+ for directory in "${sub_directories[@]}"; do
+ for f in "./$directory"/*; do
+ [[ -f $f ]] || continue
+
+ enabled="$(yq e '.spec.user_transformer.enabled' $f)"
+ if [ $enabled == "true" ]; then
+ enabled_ut_customers+=( $f )
+ fi
+ done
+ done
+
+ # bump up the customers version and repository information
+ for customer in "${enabled_ut_customers[@]}"; do
+ yq eval -i ".spec.user_transformer.image.version=\"${{ steps.target-version.outputs.tag_name }}\"" $customer
+ git add $customer
+ done
+
+ git commit -m "chore: rollback dedicated transformers to ${{ steps.target-version.outputs.tag_name }}"
+ git push -u origin dedicated-transformer-rollback-${{ steps.target-version.outputs.tag_name }}
+
+ gh pr create --fill
diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml
index 4e8f29cffa..3e0b3aac19 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
@@ -113,9 +112,9 @@ jobs:
yq eval -i ".user-transformer.image.tag=\"$TAG_NAME\"" staging.yaml
git add staging.yaml
- cd ../../../../config-be-rudder-transformer
- yq eval -i ".config-be-rudder-transformer.image.tag=\"$TAG_NAME\"" values.staging.yaml
- yq eval -i ".config-be-user-transformer.image.tag=\"$TAG_NAME\"" values.staging.yaml
+ cd ../../../../config-be-rudder-transformer/environment/staging
+ yq eval -i ".config-be-rudder-transformer.image.tag=\"$TAG_NAME\"" base.yaml
+ yq eval -i ".config-be-user-transformer.image.tag=\"$TAG_NAME\"" base.yaml
git add values.staging.yaml
git commit -m "chore: upgrade staging env transformers to \"$TAG_NAME\""
diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml
index 9d1558d826..233e99577d 100644
--- a/.github/workflows/publish-new-release.yml
+++ b/.github/workflows/publish-new-release.yml
@@ -30,7 +30,7 @@ jobs:
fetch-depth: 0
- name: Setup Node
- uses: actions/setup-node@v4.0.1
+ uses: actions/setup-node@v4.0.2
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -89,7 +89,7 @@ jobs:
- name: Notify Slack Channel
id: slack
- uses: slackapi/slack-github-action@v1.24.0
+ uses: slackapi/slack-github-action@v1.25.0
continue-on-error: true
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
diff --git a/.github/workflows/ut-tests.yml b/.github/workflows/ut-tests.yml
index 30c29ceaee..0a2ef8a390 100644
--- a/.github/workflows/ut-tests.yml
+++ b/.github/workflows/ut-tests.yml
@@ -26,7 +26,7 @@ jobs:
fetch-depth: 1
- name: Setup Node
- uses: actions/setup-node@v4.0.1
+ uses: actions/setup-node@v4.0.2
with:
node-version-file: '.nvmrc'
cache: 'npm'
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
new file mode 100644
index 0000000000..4caef8dd91
--- /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/.gitignore b/.gitignore
index 956605f139..09c536ebb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -137,4 +137,5 @@ dist
.idea
# component test report
-test_reports/
\ No newline at end of file
+test_reports/
+temp/
diff --git a/.nvmrc b/.nvmrc
index a9d087399d..3c5535cf60 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-18.19.0
+18.19.1
diff --git a/.prettierignore b/.prettierignore
index 93eb370b0d..99747b29bb 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 0000000000..13c49c08f1
--- /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 08032253ac..5d3a37e122 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,116 @@
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.60.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.1...v1.60.0) (2024-03-20)
+
+
+### Features
+
+* ninetailed: add default value for context.location as {} ([#3197](https://github.com/rudderlabs/rudder-transformer/issues/3197)) ([91fc0fb](https://github.com/rudderlabs/rudder-transformer/commit/91fc0fb3e9eeb127298a0ce305ef6d1d7b72a39f))
+
+
+### Bug Fixes
+
+* heap: make userId as required for track and identify call ([#3198](https://github.com/rudderlabs/rudder-transformer/issues/3198)) ([6a7c534](https://github.com/rudderlabs/rudder-transformer/commit/6a7c534a7df812bb7e39c1905eadcc29d7cd1329))
+* tiktok_ads: validate message.event type ([#3203](https://github.com/rudderlabs/rudder-transformer/issues/3203)) ([a86c277](https://github.com/rudderlabs/rudder-transformer/commit/a86c2771034877cef4161cda61bcda5fdda2d89f))
+
+## [1.59.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.57.1...v1.59.0) (2024-03-18)
+
+
+### Features
+
+* add Koala destination ([#3122](https://github.com/rudderlabs/rudder-transformer/issues/3122)) ([1ca039d](https://github.com/rudderlabs/rudder-transformer/commit/1ca039d64ebb1a18a0fc6b78ed5ee08528ad6b48))
+* add support of skip_user_properties_sync on Amplitude ([#3181](https://github.com/rudderlabs/rudder-transformer/issues/3181)) ([5e4ddbd](https://github.com/rudderlabs/rudder-transformer/commit/5e4ddbd8a591341a581a5721505d6dcb010f2eec))
+* adding zod validations ([#3066](https://github.com/rudderlabs/rudder-transformer/issues/3066)) ([325433b](https://github.com/rudderlabs/rudder-transformer/commit/325433b9188c8d1dbe740c7e193cdc2e58fdd751))
+* onboard destination movable ink ([#3167](https://github.com/rudderlabs/rudder-transformer/issues/3167)) ([7018b1e](https://github.com/rudderlabs/rudder-transformer/commit/7018b1e5e7f37ae177191c5ecf3a71cfe2f3d147))
+* update proxy tests for cm360 ([#3039](https://github.com/rudderlabs/rudder-transformer/issues/3039)) ([0504ffa](https://github.com/rudderlabs/rudder-transformer/commit/0504ffa898956f5b61771fb32ecfd0e0bf15248f))
+* use dontBatch directive in algolia ([#3169](https://github.com/rudderlabs/rudder-transformer/issues/3169)) ([916aaec](https://github.com/rudderlabs/rudder-transformer/commit/916aaecb1939160620d5fd3c4c0c0e33f2a371b2))
+
+
+### Bug Fixes
+
+* api contract for v1 proxy ([#3049](https://github.com/rudderlabs/rudder-transformer/issues/3049)) ([93947db](https://github.com/rudderlabs/rudder-transformer/commit/93947db35cdaf1ca7ed87ec5f73567754af312ab))
+* email mapping for clevertap ([#3173](https://github.com/rudderlabs/rudder-transformer/issues/3173)) ([04eab92](https://github.com/rudderlabs/rudder-transformer/commit/04eab92e1c383f9e8cdd5c845530a42a0af2932a))
+* fb pixel test case refactor ([#3075](https://github.com/rudderlabs/rudder-transformer/issues/3075)) ([cff7d1c](https://github.com/rudderlabs/rudder-transformer/commit/cff7d1c4578087a37614c0ef4529058481873479))
+* fixed 500 status for algolia dontBatch ([#3178](https://github.com/rudderlabs/rudder-transformer/issues/3178)) ([6330888](https://github.com/rudderlabs/rudder-transformer/commit/6330888ad5c67e3a800037b56501fc08da09e4d1))
+* label not present in prometheus metrics ([#3176](https://github.com/rudderlabs/rudder-transformer/issues/3176)) ([01d460c](https://github.com/rudderlabs/rudder-transformer/commit/01d460c3edaf39b35c4686516c9e9140be46aa5e))
+* send proper status to server in cm360 ([#3127](https://github.com/rudderlabs/rudder-transformer/issues/3127)) ([229ce47](https://github.com/rudderlabs/rudder-transformer/commit/229ce473af1ddd62d946bea1b018c882b142a5ef))
+
+## [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)
+
+
+### Bug Fixes
+
+* amplitude fix for user operations ([7f2364e](https://github.com/rudderlabs/rudder-transformer/commit/7f2364e41167611c41003559de65cee1fece5464))
+* amplitude fix for user operations ([#3153](https://github.com/rudderlabs/rudder-transformer/issues/3153)) ([31869fb](https://github.com/rudderlabs/rudder-transformer/commit/31869fb114bb141d545de01c56f57b97e5aa54a6))
+
+## [1.57.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.1...v1.57.0) (2024-02-29)
+
+
+### Features
+
+* add event mapping support for branch destination ([#3135](https://github.com/rudderlabs/rudder-transformer/issues/3135)) ([cc94bba](https://github.com/rudderlabs/rudder-transformer/commit/cc94bba682f667877a721f63627adc6ff6a7386a))
+
+
+### Bug Fixes
+
+* marketo bulk upload zero and null value allowed ([#3134](https://github.com/rudderlabs/rudder-transformer/issues/3134)) ([4dcbf8f](https://github.com/rudderlabs/rudder-transformer/commit/4dcbf8fb189a39bb40b950742425a0b9da2d8d7c))
+
+### [1.56.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.56.0...v1.56.1) (2024-02-21)
+
+
+### Bug Fixes
+
+* update proxy data type for response handler input ([#3030](https://github.com/rudderlabs/rudder-transformer/issues/3030)) ([457a18b](https://github.com/rudderlabs/rudder-transformer/commit/457a18b2aec03aa0dfafcadc611b5f7176e97beb))
+
+## [1.56.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.55.0...v1.56.0) (2024-02-19)
+
+
+### Features
+
+* **hs:** chunking data based on batch limit ([#2907](https://github.com/rudderlabs/rudder-transformer/issues/2907)) ([a60694c](https://github.com/rudderlabs/rudder-transformer/commit/a60694cef1da31d27a5cf90264548cad793f556f))
+* onboard bluecore integration ([#3061](https://github.com/rudderlabs/rudder-transformer/issues/3061)) ([aef5f8e](https://github.com/rudderlabs/rudder-transformer/commit/aef5f8e5f267262e0f9e10229f14f2bcc8ad29e2))
+* tiktok_offline_events added support for all Standard events ([#3094](https://github.com/rudderlabs/rudder-transformer/issues/3094)) ([b5cdccb](https://github.com/rudderlabs/rudder-transformer/commit/b5cdccb75fe68150816140174087fddad677db10))
+
+
+### Bug Fixes
+
+* add support of placing properties at root in af ([#3082](https://github.com/rudderlabs/rudder-transformer/issues/3082)) ([0f01524](https://github.com/rudderlabs/rudder-transformer/commit/0f01524b6f4f2f82efc21f88f8c97cb6fdaf91ea))
+* amplitude batch output metadata ([#3077](https://github.com/rudderlabs/rudder-transformer/issues/3077)) ([69c8348](https://github.com/rudderlabs/rudder-transformer/commit/69c83489c85486c9b2aed4a1292bd9f0aae9ca44))
+* amplitude: Error handling for missing event type ([#3079](https://github.com/rudderlabs/rudder-transformer/issues/3079)) ([f7ec0a1](https://github.com/rudderlabs/rudder-transformer/commit/f7ec0a1244a7b97e6b40de5ed9881c63300866dc))
+* custify user-regulation logic ([#3076](https://github.com/rudderlabs/rudder-transformer/issues/3076)) ([9683161](https://github.com/rudderlabs/rudder-transformer/commit/9683161612c7e3b9c2be95a2728f68ec7dcf69f4))
+* error handling for auth0 source ([#3038](https://github.com/rudderlabs/rudder-transformer/issues/3038)) ([2a21274](https://github.com/rudderlabs/rudder-transformer/commit/2a21274333350c615991f7b56b81b766502d5bf4))
+* **ga4:** failures not considered with 200 status in events tab ([#3089](https://github.com/rudderlabs/rudder-transformer/issues/3089)) ([6a364fb](https://github.com/rudderlabs/rudder-transformer/commit/6a364fba34c46b15c0fe4b06ecfa6f4b81b6f436))
+* gaoc batching order ([#3064](https://github.com/rudderlabs/rudder-transformer/issues/3064)) ([a98cabd](https://github.com/rudderlabs/rudder-transformer/commit/a98cabdfe7781ada12baf742df4a3a439fc5fecd))
+* resolve bugsnag issue caused due to undefined properties ([#3086](https://github.com/rudderlabs/rudder-transformer/issues/3086)) ([d522b35](https://github.com/rudderlabs/rudder-transformer/commit/d522b35c908a9f262ba3ba27dda0ea5d9ac5bc6b))
+* tiktok ads v2 error handling ([#3084](https://github.com/rudderlabs/rudder-transformer/issues/3084)) ([b6edff4](https://github.com/rudderlabs/rudder-transformer/commit/b6edff46fa0e0e210e82206fea46a064e3fbe00f))
+
## [1.55.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.4...v1.55.0) (2024-02-05)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1cef57af73..bdd76d916c 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/Dockerfile b/Dockerfile
index 6bd03c9515..8cd4005a7b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1.4
-FROM node:18.19.0-alpine3.18 AS base
+FROM node:18.19.1-alpine3.18 AS base
ENV HUSKY 0
RUN apk update
diff --git a/README.md b/README.md
index 2b3908dcb5..4ff1cd4b34 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+
+⚠️ Docker image for rudder-transformer has been moved to new org rudderstack/rudder-transformer
+
+
+
[![codecov](https://codecov.io/gh/rudderlabs/rudder-transformer/branch/develop/graph/badge.svg?token=G24OON85SB)](https://codecov.io/gh/rudderlabs/rudder-transformer)
# RudderStack Transformer
diff --git a/github-release.config.js b/github-release.config.js
new file mode 100644
index 0000000000..df269d8a02
--- /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 91365f0bb5..a6c0141a97 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "rudder-transformer",
- "version": "1.55.0",
+ "version": "1.60.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rudder-transformer",
- "version": "1.55.0",
+ "version": "1.60.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",
@@ -62,6 +63,7 @@
"rudder-transformer-cdk": "^1.4.11",
"set-value": "^4.1.0",
"sha256": "^0.2.0",
+ "sqlstring": "^2.3.3",
"stacktrace-parser": "^0.1.10",
"statsd-client": "^0.4.7",
"truncate-utf8-bytes": "^1.0.2",
@@ -69,7 +71,8 @@
"unset-value": "^2.0.1",
"uuid": "^9.0.0",
"valid-url": "^1.0.9",
- "winston": "^3.11.0"
+ "winston": "^3.11.0",
+ "zod": "^3.22.4"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.6.3",
@@ -91,9 +94,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 +110,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 +158,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 +3238,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 +3252,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 +3260,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 +3282,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 +3296,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 +3331,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 +3344,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 +3355,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 +4342,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 +4354,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 +4362,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 +4380,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 +4471,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 +4491,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 +4532,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 +5434,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 +5521,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 +5572,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 +5605,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 +5631,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 +5647,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 +5673,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 +5685,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 +5711,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 +5736,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 +5751,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 +5774,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 +5785,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 +5984,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 +6011,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 +6029,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 +6037,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 +6055,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 +6072,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 +6089,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 +6152,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 +6566,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 +6772,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 +7503,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 +8511,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 +8683,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 +8730,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 +9102,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 +9113,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 +9342,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 +9394,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 +9407,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 +9415,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 +9498,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 +9552,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 +9570,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 +9578,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 +9604,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 +9614,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 +9622,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 +9638,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 +9646,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 +9676,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 +9684,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 +9695,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 +9712,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 +9893,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 +9905,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 +9916,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 +9931,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 +9945,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 +9960,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 +9970,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 +9987,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 +10002,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 +10010,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 +10017,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 +10034,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 +10063,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 +10074,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 +10082,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 +10093,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 +10101,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 +10109,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 +10215,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 +10240,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 +10250,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 +10300,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 +10337,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 +10399,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 +10426,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 +10472,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 +10484,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 +10521,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 +10672,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 +10689,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 +11011,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 +11266,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 +11355,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 +11369,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 +11383,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 +11479,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 +11528,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 +11865,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 +11873,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 +11888,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 +11915,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 +12076,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 +12141,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 +12159,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 +12170,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 +12222,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 +12244,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 +12273,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 +12328,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 +12359,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 +12370,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 +12378,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 +12410,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 +12446,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 +12485,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 +12507,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 +12521,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 +12547,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 +12597,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 +12639,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 +14448,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 +14493,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 +14587,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 +14836,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 +15185,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 +15261,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 +16028,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 +16046,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 +16062,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 +16548,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 +16614,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 +16817,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 +16856,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 +16873,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 +16886,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 +16902,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 +16913,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 +16983,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 +17142,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 +17203,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 +17284,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 +17327,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 +17341,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 +17835,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 +17849,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 +18069,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 +18105,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 +18343,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 +18543,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 +18640,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 +18671,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 +18687,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 +18721,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 +18835,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 +18879,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 +18890,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 +18941,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"
}
@@ -19159,6 +19089,14 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/stack-generator": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
@@ -19696,7 +19634,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 +19650,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 +19663,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 +19751,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 +19864,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 +19977,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 +20042,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 +20199,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 +20210,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 +20221,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 +20242,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 +20255,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 +20281,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 +20304,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 +20317,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 +20334,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 +20352,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 +20371,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 +20417,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 +20684,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 +20698,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 +20719,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",
@@ -21117,6 +21049,14 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.22.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
+ "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index eef5aa81c0..e2b9e0a8b8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rudder-transformer",
- "version": "1.55.0",
+ "version": "1.60.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",
@@ -107,6 +108,7 @@
"rudder-transformer-cdk": "^1.4.11",
"set-value": "^4.1.0",
"sha256": "^0.2.0",
+ "sqlstring": "^2.3.3",
"stacktrace-parser": "^0.1.10",
"statsd-client": "^0.4.7",
"truncate-utf8-bytes": "^1.0.2",
@@ -114,7 +116,8 @@
"unset-value": "^2.0.1",
"uuid": "^9.0.0",
"valid-url": "^1.0.9",
- "winston": "^3.11.0"
+ "winston": "^3.11.0",
+ "zod": "^3.22.4"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.6.3",
@@ -136,9 +139,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 +155,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 25ac73dafb..ce52e8a259 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 e8c3748d15..de80809a04 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/adapters/networkhandler/genericNetworkHandler.js b/src/adapters/networkhandler/genericNetworkHandler.js
index bcbcb21259..d9358085f4 100644
--- a/src/adapters/networkhandler/genericNetworkHandler.js
+++ b/src/adapters/networkhandler/genericNetworkHandler.js
@@ -17,13 +17,14 @@ const tags = require('../../v0/util/tags');
* will act as fall-fack for such scenarios.
*
*/
-const responseHandler = (destinationResponse, dest) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType } = responseParams;
const { status } = destinationResponse;
- const message = `[Generic Response Handler] Request for destination: ${dest} Processed Successfully`;
+ const message = `[Generic Response Handler] Request for destination: ${destType} Processed Successfully`;
// if the response from destination is not a success case build an explicit error
if (!isHttpStatusSuccess(status)) {
throw new NetworkError(
- `[Generic Response Handler] Request failed for destination ${dest} with status: ${status}`,
+ `[Generic Response Handler] Request failed for destination ${destType} with status: ${status}`,
status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
diff --git a/src/cdk/v2/destinations/algolia/procWorkflow.yaml b/src/cdk/v2/destinations/algolia/procWorkflow.yaml
index b9ce7ef7fd..f9ac8e3ae6 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/algolia/rtWorkflow.yaml b/src/cdk/v2/destinations/algolia/rtWorkflow.yaml
index 758a71bf5b..f5442f3209 100644
--- a/src/cdk/v2/destinations/algolia/rtWorkflow.yaml
+++ b/src/cdk/v2/destinations/algolia/rtWorkflow.yaml
@@ -2,7 +2,6 @@ bindings:
- path: ../../../../v0/destinations/algolia/config
- name: handleRtTfSingleEventError
path: ../../../../v0/util/index
-
steps:
- name: validateInput
template: |
@@ -28,10 +27,14 @@ steps:
$.outputs.transform#idx.error.(
$.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
)[]
+
- name: batchSuccessfulEvents
description: Batches the successfulEvents
template: |
- let batches = $.chunk($.outputs.successfulEvents, $.MAX_BATCH_SIZE);
+ const dontBatchTrueEvents = $.outputs.successfulEvents{.metadata.dontBatch}[];
+ const dontBatchFalseEvents = $.outputs.successfulEvents{!.metadata.dontBatch}[];
+
+ let batches = [...$.chunk(dontBatchFalseEvents, $.MAX_BATCH_SIZE), ...$.chunk(dontBatchTrueEvents, 1)];
batches@batch.({
"batchedRequest": {
"body": {
@@ -56,6 +59,7 @@ steps:
"statusCode": 200,
"destination": batch[0].destination
})[];
+
- name: finalPayload
template: |
[...$.outputs.failedEvents, ...$.outputs.batchSuccessfulEvents]
diff --git a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml
index 744c05a9a6..3292d66c69 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/config.js b/src/cdk/v2/destinations/bluecore/config.js
new file mode 100644
index 0000000000..9b9cde9c66
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/config.js
@@ -0,0 +1,56 @@
+const { getMappingConfig } = require('../../../../v0/util');
+
+const BASE_URL = 'https://api.bluecore.com/api/track/mobile/v1';
+
+const CONFIG_CATEGORIES = {
+ IDENTIFY: {
+ name: 'bluecoreIdentifyConfig',
+ type: 'identify',
+ },
+ TRACK: {
+ name: 'bluecoreTrackConfig',
+ type: 'track',
+ },
+ COMMON: {
+ name: 'bluecoreCommonConfig',
+ type: 'common',
+ },
+};
+
+const EVENT_NAME_MAPPING = [
+ {
+ src: ['product viewed'],
+ dest: 'viewed_product',
+ },
+ {
+ src: ['products searched'],
+ dest: 'search',
+ },
+ {
+ src: ['product added'],
+ dest: 'add_to_cart',
+ },
+ {
+ src: ['product removed'],
+ dest: 'remove_from_cart',
+ },
+ {
+ src: ['product added to wishlist'],
+ dest: 'wishlist',
+ },
+ {
+ src: ['order completed'],
+ dest: 'purchase',
+ },
+];
+
+const BLUECORE_EXCLUSION_FIELDS = ['query', 'order_id', 'total'];
+
+const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
+module.exports = {
+ CONFIG_CATEGORIES,
+ MAPPING_CONFIG,
+ EVENT_NAME_MAPPING,
+ BASE_URL,
+ BLUECORE_EXCLUSION_FIELDS,
+};
diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json
new file mode 100644
index 0000000000..be74c7c4b3
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/data/bluecoreCommonConfig.json
@@ -0,0 +1,52 @@
+[
+ {
+ "destKey": "properties.customer.name",
+ "sourceKeys": "name",
+ "required": false,
+ "sourceFromGenericMap": true
+ },
+ {
+ "destKey": "properties.customer.first_name",
+ "sourceKeys": "firstName",
+ "required": false,
+ "sourceFromGenericMap": true
+ },
+ {
+ "destKey": "properties.customer.last_name",
+ "sourceKeys": "lastName",
+ "required": false,
+ "sourceFromGenericMap": true
+ },
+ {
+ "destKey": "properties.customer.age",
+ "sourceKeys": ["context.traits.age", "traits.age"],
+ "required": false
+ },
+ {
+ "destKey": "properties.customer.sex",
+ "sourceKeys": ["traits.gender", "context.traits.gender", "traits.sex", "context.traits.sex"],
+ "required": false
+ },
+ {
+ "destKey": "properties.customer.address",
+ "sourceKeys": "address",
+ "required": false,
+ "sourceFromGenericMap": true
+ },
+ {
+ "destKey": "properties.customer.email",
+ "sourceKeys": "email",
+ "required": false,
+ "sourceFromGenericMap": true
+ },
+ {
+ "destKey": "properties.client",
+ "sourceKeys": "context.app.version",
+ "required": false
+ },
+ {
+ "destKey": "properties.device",
+ "sourceKeys": "context.device.model",
+ "required": false
+ }
+]
diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json
new file mode 100644
index 0000000000..5c3686f0ab
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/data/bluecoreIdentifyConfig.json
@@ -0,0 +1,7 @@
+[
+ {
+ "destKey": "event",
+ "sourceKeys": ["traits.action", "context.traits.action"],
+ "required": false
+ }
+]
diff --git a/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json
new file mode 100644
index 0000000000..8f6d59ec54
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/data/bluecoreTrackConfig.json
@@ -0,0 +1,22 @@
+[
+ {
+ "destKey": "properties.search_term",
+ "sourceKeys": "properties.query",
+ "required": false
+ },
+ {
+ "destKey": "properties.order_id",
+ "sourceKeys": "properties.order_id",
+ "required": false
+ },
+ {
+ "destKey": "properties.total",
+ "sourceKeys": "properties.total",
+ "required": false
+ },
+ {
+ "destKey": "properties.products",
+ "sourceKeys": ["properties.products"],
+ "required": false
+ }
+]
diff --git a/src/cdk/v2/destinations/bluecore/procWorkflow.yaml b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml
new file mode 100644
index 0000000000..480bced699
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/procWorkflow.yaml
@@ -0,0 +1,69 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedNullValuesAndEmptyObjectArray
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - path: ./utils
+ - path: lodash
+ name: cloneDeep
+
+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])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.bluecoreNamespace, "[BLUECORE] account namespace required for Authentication.");
+ - name: prepareIdentifyPayload
+ condition: $.outputs.messageType === {{$.EventType.IDENTIFY}}
+ template: |
+ const payload = $.constructProperties(.message);
+ payload.token = .destination.Config.bluecoreNamespace;
+ $.verifyPayload(payload, .message);
+ payload.event = payload.event ?? 'customer_patch';
+ payload.properties.distinct_id = $.populateAccurateDistinctId(payload, .message);
+ $.context.payloads = [$.removeUndefinedAndNullValues(payload)];
+ - name: handleTrackEvent
+ condition: $.outputs.messageType === {{$.EventType.TRACK}}
+ steps:
+ - name: validateInput
+ description: Additional validation for Track events
+ template: |
+ $.assert(.message.event, "event_name could not be mapped. Aborting.")
+ - name: deduceEventNames
+ template: |
+ $.context.deducedEventNameArray = $.deduceTrackEventName(.message.event,.destination.Config)
+ - name: preparePayload
+ template: |
+ const payload = $.constructProperties(.message);
+ $.context.payloads = $.context.deducedEventNameArray@eventName.(
+ const newPayload = $.cloneDeep(payload);
+ newPayload.properties.distinct_id = $.populateAccurateDistinctId(newPayload, ^.message);
+ const temporaryProductArray = newPayload.properties.products ?? $.createProductForStandardEcommEvent(^.message, eventName);
+ newPayload.properties.products = $.normalizeProductArray(temporaryProductArray);
+ newPayload.event = eventName;
+ newPayload.token = ^.destination.Config.bluecoreNamespace;
+ $.verifyPayload(newPayload, ^.message);
+ $.removeUndefinedNullValuesAndEmptyObjectArray(newPayload)
+ )[];
+
+ - name: buildResponse
+ template: |
+ $.context.payloads.(
+ const response = $.defaultRequestConfig();
+ response.body.JSON = .;
+ response.method = "POST";
+ response.endpoint = "https://api.bluecore.com/api/track/mobile/v1";
+ response.headers = {
+ "Content-Type": "application/json"
+ };
+ response
+ )
diff --git a/src/cdk/v2/destinations/bluecore/utils.js b/src/cdk/v2/destinations/bluecore/utils.js
new file mode 100644
index 0000000000..22ec254fe2
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/utils.js
@@ -0,0 +1,250 @@
+const lodash = require('lodash');
+
+const {
+ InstrumentationError,
+ isDefinedAndNotNullAndNotEmpty,
+ getHashFromArrayWithDuplicate,
+ isDefinedAndNotNull,
+ isDefinedNotNullNotEmpty,
+} = require('@rudderstack/integrations-lib');
+const {
+ getFieldValueFromMessage,
+ validateEventName,
+ constructPayload,
+ getDestinationExternalID,
+} = require('../../../../v0/util');
+const { CommonUtils } = require('../../../../util/common');
+const { EVENT_NAME_MAPPING } = require('./config');
+const { EventType } = require('../../../../constants');
+const { MAPPING_CONFIG, CONFIG_CATEGORIES } = require('./config');
+
+/**
+ * Verifies the correctness of payload for different events.
+ *
+ * @param {Object} payload - The payload object containing event information.
+ * @param {Object} message - The message object containing additional information.
+ * @throws {InstrumentationError} - Throws an error if required properties are missing.
+ * @returns {void}
+ */
+const verifyPayload = (payload, message) => {
+ if (
+ message.type === EventType.IDENTIFY &&
+ isDefinedNotNullNotEmpty(message.traits?.action) &&
+ message.traits?.action !== 'identify'
+ ) {
+ throw new InstrumentationError(
+ "[Bluecore] traits.action must be 'identify' for identify action",
+ );
+ }
+ switch (payload.event) {
+ case 'search':
+ if (!payload?.properties?.search_term) {
+ throw new InstrumentationError(
+ '[Bluecore] property:: search_query is required for search event',
+ );
+ }
+ break;
+ case 'purchase':
+ if (!payload?.properties?.order_id) {
+ throw new InstrumentationError(
+ '[Bluecore] property:: order_id is required for purchase event',
+ );
+ }
+ if (!payload?.properties?.total) {
+ throw new InstrumentationError(
+ '[Bluecore] property:: total is required for purchase event',
+ );
+ }
+ if (
+ !isDefinedAndNotNull(payload?.properties?.customer) ||
+ Object.keys(payload.properties.customer).length === 0
+ ) {
+ throw new InstrumentationError(
+ `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} event`,
+ );
+ }
+ break;
+ case 'identify':
+ case 'optin':
+ case 'unsubscribe':
+ if (!isDefinedAndNotNullAndNotEmpty(getFieldValueFromMessage(message, 'email'))) {
+ throw new InstrumentationError(
+ `[Bluecore] property:: email is required for ${payload.event} action`,
+ );
+ }
+ if (
+ !isDefinedAndNotNull(payload?.properties?.customer) ||
+ Object.keys(payload.properties.customer).length === 0
+ ) {
+ throw new InstrumentationError(
+ `[Bluecore] property:: No relevant trait to populate customer information, which is required for ${payload.event} action`,
+ );
+ }
+ break;
+ default:
+ break;
+ }
+};
+
+/**
+ * Deduces the track event name based on the provided track event name and configuration.
+ *
+ * @param {string} trackEventName - The track event name to deduce.
+ * @param {object} Config - The configuration object.
+ * @returns {string|array} - The deduced track event name.
+ */
+const deduceTrackEventName = (trackEventName, destConfig) => {
+ let eventName;
+ const { eventsMapping } = destConfig;
+ validateEventName(trackEventName);
+ /*
+ Step 1: Will look for the event name in the eventsMapping array if mapped to a standard bluecore event.
+ and return the corresponding event name if found.
+ */
+ if (eventsMapping.length > 0) {
+ const keyMap = getHashFromArrayWithDuplicate(eventsMapping, 'from', 'to', false);
+ eventName = keyMap[trackEventName];
+ }
+ if (isDefinedAndNotNullAndNotEmpty(eventName)) {
+ const finalEvent = typeof eventName === 'string' ? [eventName] : [...eventName];
+ return finalEvent;
+ }
+
+ /*
+ Step 2: To find if the particular event is amongst the list of standard
+ Rudderstack ecommerce events, used specifically for Bluecore API
+ mappings.
+ */
+
+ const eventMapInfo = EVENT_NAME_MAPPING.find((eventMap) =>
+ eventMap.src.includes(trackEventName.toLowerCase()),
+ );
+ if (isDefinedAndNotNull(eventMapInfo)) {
+ return [eventMapInfo.dest];
+ }
+
+ // Step 3: if nothing matches this is to be considered as a custom event
+ return [trackEventName];
+};
+
+/**
+ * Determines if the given event name is a standard Bluecore event.
+ *
+ * @param {string} eventName - The name of the event to check.
+ * @returns {boolean} - True if the event is a standard Bluecore event, false otherwise.
+ */
+const isStandardBluecoreEvent = (eventName) => {
+ // Return false immediately if eventName is an empty string or falsy
+ if (!eventName) {
+ return false;
+ }
+ // Proceed with the original check if eventName is not empty
+ return !!EVENT_NAME_MAPPING.some((item) => item.dest.includes(eventName));
+};
+
+/**
+ * Adds an array of products to a message.
+ *
+ * @param {object} message - The message object to add the products to.
+ * @param {array|object} products - The array or object of products to add.
+ * @param {string} eventName - The name of the event.
+ * @throws {InstrumentationError} - If the products array is not defined or null.
+ * @returns {array} - The updated product array.
+ */
+const normalizeProductArray = (products) => {
+ let finalProductArray = null;
+ if (isDefinedAndNotNull(products)) {
+ const productArray = CommonUtils.toArray(products);
+ const mappedProductArray = productArray.map(
+ ({ product_id, sku, id, query, order_id, total, ...rest }) => ({
+ id: product_id || sku || id,
+ ...rest,
+ }),
+ );
+ finalProductArray = mappedProductArray;
+ }
+ // if any custom event is not sent with product array, then it should be null
+ return finalProductArray;
+};
+
+/**
+ * Constructs properties based on the given message.
+ *
+ * @param {object} message - The message object.
+ * @returns {object} - The constructed properties object.
+ */
+const constructProperties = (message) => {
+ const commonCategory = CONFIG_CATEGORIES.COMMON;
+ const commonPayload = constructPayload(message, MAPPING_CONFIG[commonCategory.name]);
+ const category = CONFIG_CATEGORIES[message.type.toUpperCase()];
+ const typeSpecificPayload = constructPayload(message, MAPPING_CONFIG[category.name]);
+ const finalPayload = lodash.merge(commonPayload, typeSpecificPayload);
+ return finalPayload;
+};
+
+/**
+ * Creates a product for a standard e-commerce event.
+ *
+ * @param {Object} properties - The properties of the product.
+ * @param {string} eventName - The name of the event.
+ * @returns {Array|null} - An array containing the properties if the event is a standard Bluecore event and not 'search', otherwise null.
+ */
+const createProductForStandardEcommEvent = (message, eventName) => {
+ const { event, properties } = message;
+ if (event.toLowerCase() === 'order completed' && eventName === 'purchase') {
+ throw new InstrumentationError('[Bluecore]:: products array is required for purchase event');
+ }
+ if (eventName !== 'search' && isStandardBluecoreEvent(eventName)) {
+ return [properties];
+ }
+ return null;
+};
+/**
+ * Function: populateAccurateDistinctId
+ *
+ * Description:
+ * This function is used to populate the accurate distinct ID based on the given payload and message.
+ *
+ * Parameters:
+ * - payload (object): The payload object containing the event and other data.
+ * - message (object): The message object containing the user data.
+ *
+ * Returns:
+ * - distinctId (string): The accurate distinct ID based on the given payload and message.
+ *
+ * Throws:
+ * - InstrumentationError: If the distinct ID could not be set.
+ *
+ */
+const populateAccurateDistinctId = (payload, message) => {
+ const bluecoreExternalId = getDestinationExternalID(message, 'bluecoreExternalId');
+ if (isDefinedAndNotNullAndNotEmpty(bluecoreExternalId)) {
+ return bluecoreExternalId;
+ }
+ let distinctId;
+ if (payload.event === 'identify') {
+ distinctId = getFieldValueFromMessage(message, 'userId');
+ } else {
+ // email is always a more preferred distinct_id
+ distinctId =
+ getFieldValueFromMessage(message, 'email') || getFieldValueFromMessage(message, 'userId');
+ }
+
+ if (!isDefinedAndNotNullAndNotEmpty(distinctId)) {
+ // dev safe. AnonymouId should be always present
+ throw new InstrumentationError(
+ '[Bluecore] property:: distinct_id could not be set. Please provide either email or userId or anonymousId or externalId as distinct_id.',
+ );
+ }
+ return distinctId;
+};
+
+module.exports = {
+ verifyPayload,
+ deduceTrackEventName,
+ normalizeProductArray,
+ isStandardBluecoreEvent,
+ constructProperties,
+ createProductForStandardEcommEvent,
+ populateAccurateDistinctId,
+};
diff --git a/src/cdk/v2/destinations/bluecore/utils.test.js b/src/cdk/v2/destinations/bluecore/utils.test.js
new file mode 100644
index 0000000000..829073bbcc
--- /dev/null
+++ b/src/cdk/v2/destinations/bluecore/utils.test.js
@@ -0,0 +1,403 @@
+const {
+ normalizeProductArray,
+ verifyPayload,
+ isStandardBluecoreEvent,
+ deduceTrackEventName,
+ populateAccurateDistinctId,
+ createProductForStandardEcommEvent,
+} = require('./utils');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+
+describe('normalizeProductArray', () => {
+ // Adds an array of products to a message when products array is defined and not null.
+ it('should add an array of products to a message when products array is defined and not null', () => {
+ const products = [
+ { product_id: 1, name: 'Product 1' },
+ { product_id: 2, name: 'Product 2' },
+ ];
+ const eventName = 'purchase';
+
+ const result = normalizeProductArray(products, eventName);
+
+ expect(result).toEqual([
+ { id: 1, name: 'Product 1' },
+ { id: 2, name: 'Product 2' },
+ ]);
+ });
+
+ // Adds a single product object to a message when a single product object is passed.
+ it('should add a single product object to a message when a single product object is passed', () => {
+ const product = { product_id: 1, name: 'Product 1' };
+ const eventName = 'add_to_cart';
+
+ const result = normalizeProductArray(product, eventName);
+ expect(result).toEqual([{ id: 1, name: 'Product 1' }]);
+ });
+
+ it('should not throw an InstrumentationError for a custom event when products array is null', () => {
+ const message = {};
+ const products = null;
+ const eventName = 'custom';
+
+ expect(() => {
+ normalizeProductArray(message, products, eventName);
+ }).toBeNull;
+ });
+});
+
+describe('verifyPayload', () => {
+ // Verify payload for search event with search_term property.
+ it('should verify payload for search event with search_term property', () => {
+ const payload = {
+ event: 'search',
+ properties: {
+ search_term: 'example',
+ },
+ };
+ expect(() => verifyPayload(payload, {})).not.toThrow();
+ });
+
+ // Verify payload for purchase event with order_id and total properties.
+ it('should verify payload for purchase event with order_id and total and customer properties', () => {
+ const payload = {
+ event: 'purchase',
+ properties: {
+ order_id: '123',
+ total: 100,
+ },
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+
+ // Verify payload for identify event with email property.
+ it('should verify payload for identify event with email property', () => {
+ const payload = {
+ event: 'identify',
+ properties: {
+ customer: {
+ first_name: 'John',
+ },
+ },
+ };
+ const message = {
+ traits: {
+ email: 'test@example.com',
+ },
+ };
+ expect(() => verifyPayload(payload, message)).not.toThrow();
+ });
+
+ // Verify payload for search event without search_term property, should throw an InstrumentationError.
+ it('should throw an InstrumentationError when verifying payload for search event without search_term property', () => {
+ const payload = {
+ event: 'search',
+ properties: {},
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+
+ // Verify payload for purchase event without order_id property, should throw an InstrumentationError.
+ it('should throw an InstrumentationError when verifying payload for purchase event without order_id property', () => {
+ const payload = {
+ event: 'purchase',
+ properties: {
+ total: 100,
+ },
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+
+ // Verify payload for purchase event without total property, should throw an InstrumentationError.
+ it('should throw an InstrumentationError when verifying payload for purchase event without total property', () => {
+ const payload = {
+ event: 'purchase',
+ properties: {
+ order_id: '123',
+ },
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+
+ // Verify payload for purchase event without total property, should throw an InstrumentationError.
+ it('should throw an InstrumentationError when verifying payload for identify event with action field other than identify', () => {
+ const payload = {
+ event: 'random',
+ properties: {
+ email: 'abc@gmail.com',
+ },
+ };
+ expect(() =>
+ verifyPayload(payload, { type: 'identify', traits: { action: 'random' } }),
+ ).toThrow(InstrumentationError);
+ });
+
+ it('should throw an InstrumentationError when verifying payload for optin event without email property', () => {
+ const payload = {
+ event: 'optin',
+ properties: {
+ order_id: '123',
+ },
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+
+ it('should throw an InstrumentationError when verifying payload for unsubscribe event without email property', () => {
+ const payload = {
+ event: 'unsubscribe',
+ properties: {
+ order_id: '123',
+ },
+ };
+ expect(() => verifyPayload(payload, {})).toThrow(InstrumentationError);
+ });
+});
+
+describe('isStandardBluecoreEvent', () => {
+ // Returns true if the given event name is in the list of standard Bluecore events.
+ it('should return true when the given event name is in the list of standard Bluecore events', () => {
+ const eventName = 'search';
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(true);
+ });
+
+ // Returns false if the given event name is not in the list of standard Bluecore events.
+ it('should return false when the given event name is not in the list of standard Bluecore events', () => {
+ const eventName = 'someEvent';
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(false);
+ });
+
+ // Returns false if the given event name is null.
+ it('should return false when the given event name is null', () => {
+ const eventName = null;
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(false);
+ });
+
+ // Returns false if the given event name is undefined.
+ it('should return false when the given event name is undefined', () => {
+ const eventName = undefined;
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(false);
+ });
+
+ // Returns false if the given event name is not a string.
+ it('should return false when the given event name is not a string', () => {
+ const eventName = 123;
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(false);
+ });
+
+ // Returns false if the given event name is an empty string.
+ it('should return false when the given event name is an empty string', () => {
+ const eventName = '';
+ const result = isStandardBluecoreEvent(eventName);
+ expect(result).toBe(false);
+ });
+});
+
+describe('deduceTrackEventName', () => {
+ // The function returns the trackEventName if no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event.
+ it('should return the trackEventName when no eventsMapping is provided and the trackEventName is not a standard Rudderstack ecommerce event', () => {
+ const trackEventName = 'customEvent';
+ const Config = {
+ eventsMapping: [],
+ };
+ const result = deduceTrackEventName(trackEventName, Config);
+ expect(result).toEqual([trackEventName]);
+ });
+
+ // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event.
+ it('should return the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event', () => {
+ const trackEventName = 'customEvent';
+ const Config = {
+ eventsMapping: [{ from: 'customEvent', to: 'search' }],
+ };
+ const result = deduceTrackEventName(trackEventName, Config);
+ expect(result).toEqual(['search']);
+ });
+
+ // The function returns the corresponding event name from eventsMapping if the trackEventName is mapped to a standard bluecore event.
+ it('should return the corresponding event name array from eventsMapping if the trackEventName is mapped to more than one standard bluecore events', () => {
+ const trackEventName = 'customEvent';
+ const Config = {
+ eventsMapping: [
+ { from: 'customEvent', to: 'search' },
+ { from: 'customEvent', to: 'purchase' },
+ ],
+ };
+ const result = deduceTrackEventName(trackEventName, Config);
+ expect(result).toEqual(['search', 'purchase']);
+ });
+
+ // The function returns the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event.
+ it('should return the corresponding standard Rudderstack ecommerce event name if the trackEventName is a standard bluecore event', () => {
+ const trackEventName = 'Product Added to Wishlist';
+ const Config = {
+ eventsMapping: [],
+ };
+ const result = deduceTrackEventName(trackEventName, Config);
+ expect(result).toEqual(['wishlist']);
+ });
+
+ // The function throws an error if the trackEventName is not a string.
+ it('should throw an error if the trackEventName is not a string', () => {
+ const trackEventName = 123;
+ const Config = {
+ eventsMapping: [],
+ };
+ expect(() => deduceTrackEventName(trackEventName, Config)).toThrow();
+ });
+
+ // The function throws an error if the trackEventName is an empty string.
+ it('should throw an error if the trackEventName is an empty string', () => {
+ const trackEventName = '';
+ const Config = {
+ eventsMapping: [],
+ };
+ expect(() => deduceTrackEventName(trackEventName, Config)).toThrow();
+ });
+});
+
+describe('populateAccurateDistinctId', () => {
+ // Returns the distinctId based on the email field when it exists in the message object and the event is not an identify event.
+ it('should return the distinctId based on the email field when it exists in the message object and the event is not an identify event', () => {
+ const payload = { event: 'event' };
+ const message = { userId: '123', context: { traits: { email: 'test@example.com' } } };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('test@example.com');
+ });
+
+ // Returns the distinctId based on the userId field when it exists in the message object and the event is an identify event.
+ it('should return the distinctId based on the userId field when it exists in the message object and the event is an identify event', () => {
+ const payload = { event: 'identify' };
+ const message = { userId: '123', context: { traits: { email: 'test@example.com' } } };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('123');
+ });
+
+ // Returns the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event.
+ it('should return the distinctId based on the userId field when it exists in the message object and the email field does not exist and the event is not an identify event', () => {
+ const payload = { event: 'event' };
+ const message = { userId: '123' };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('123');
+ });
+
+ // Returns the distinctId based on the email field when it exists in the message object and the userId field is empty and the event is not an identify event.
+ it('should throw instrumenatation error as the message is malformed where email is at the root level', () => {
+ const payload = { event: 'event' };
+ const message = { email: 'test@example.com', userId: '' };
+ const testFn = () => populateAccurateDistinctId(payload, message);
+ expect(testFn).toThrow(InstrumentationError);
+ });
+
+ // Returns the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event.
+ it('should return the distinctId based on the userId field when it exists in the message object and the email field is empty and the event is not an identify event', () => {
+ const payload = { event: 'event' };
+ const message = { email: '', userId: '123' };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('123');
+ });
+
+ // Returns the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event.
+ it('should return the distinctId based on the anonymousId field when it exists in the message object and the email and userId fields are empty and the event is not an identify event', () => {
+ const payload = { event: 'event' };
+ const message = { anonymousId: 'abc' };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('abc');
+ });
+
+ it('should return the distinctId based on the externalId field when it exists in the context object and the event is not an identify event', () => {
+ const payload = { event: 'event' };
+ const message = {
+ userId: '123',
+ context: {
+ traits: { email: 'test@example.com' },
+ externalId: [{ type: 'bluecoreExternalId', id: '54321' }],
+ },
+ };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('54321');
+ });
+
+ it('should return the distinctId based on the externalId field when it exists in the context object and the event is an identify event', () => {
+ const payload = { event: 'identify' };
+ const message = {
+ userId: '123',
+ context: {
+ traits: { email: 'test@example.com' },
+ externalId: [{ type: 'bluecoreExternalId', id: '54321' }],
+ },
+ };
+ const distinctId = populateAccurateDistinctId(payload, message);
+ expect(distinctId).toBe('54321');
+ });
+});
+
+describe('createProductForStandardEcommEvent', () => {
+ // Returns an array containing the properties if the event is a standard Bluecore event and not 'search'.
+ it("should return an array containing the properties when the event is a standard Bluecore event and not 'search'", () => {
+ const message = {
+ event: 'some event',
+ properties: { name: 'product 1' },
+ };
+ const eventName = 'some event';
+ const result = createProductForStandardEcommEvent(message, eventName);
+ expect(result).toEqual(null);
+ });
+
+ // Returns null if the event is 'search'.
+ it("should return null when the event is 'search'", () => {
+ const message = {
+ event: 'search',
+ properties: { name: 'product 1' },
+ };
+ const eventName = 'search';
+ const result = createProductForStandardEcommEvent(message, eventName);
+ expect(result).toBeNull();
+ });
+
+ // Throws an InstrumentationError if the event is 'order completed' and the eventName is 'purchase'.
+ it("should throw an InstrumentationError when the event is 'order completed' and the eventName is 'purchase'", () => {
+ const message = {
+ event: 'order completed',
+ properties: { name: 'product 1' },
+ };
+ const eventName = 'purchase';
+ expect(() => {
+ createProductForStandardEcommEvent(message, eventName);
+ }).toThrow(InstrumentationError);
+ });
+
+ // Returns null if the eventName is not a standard Bluecore event.
+ it('should return null when the eventName is not a standard Bluecore event', () => {
+ const message = {
+ event: 'some event',
+ properties: { name: 'product 1', products: [{ product_id: 1, name: 'prod1' }] },
+ };
+ const eventName = 'non-standard';
+ const result = createProductForStandardEcommEvent(message, eventName);
+ expect(result).toBeNull();
+ });
+
+ // Returns null if the eventName is not provided.
+ it('should return null when the eventName is not provided', () => {
+ const message = {
+ event: 'some event',
+ properties: { name: 'product 1' },
+ };
+ const result = createProductForStandardEcommEvent(message);
+ expect(result).toBeNull();
+ });
+
+ // Returns null if the properties are not provided.
+ it('should return null when the properties are not provided', () => {
+ const message = {
+ event: 'some event',
+ };
+ const eventName = 'some event';
+ const result = createProductForStandardEcommEvent(message, eventName);
+ expect(result).toBeNull();
+ });
+});
diff --git a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml
index 50ac2a8163..1a54e8688c 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 fe8697bc31..a53a0ca8f5 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 341e5552c8..fc5d474d60 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 0191b75d18..ac12e7e02a 100644
--- a/src/cdk/v2/destinations/heap/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/heap/procWorkflow.yaml
@@ -40,7 +40,9 @@ steps:
});
.message.properties.idempotencyKey ?
($.context.payload.idempotency_key = .message.properties.idempotencyKey);
-
+ - name: validateuserId
+ template: |
+ $.assert($.context.payload.identity, "userId is required");
- name: finalPayload
description: In batchMode we return payload directly
condition: $.batchMode
@@ -58,5 +60,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 4b2ca1869e..0a8842d5e7 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 3ed1046959..edb7267b84 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 0f18029f19..ba3063c9f9 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/koala/procWorkflow.yaml b/src/cdk/v2/destinations/koala/procWorkflow.yaml
new file mode 100644
index 0000000000..9ec0202b13
--- /dev/null
+++ b/src/cdk/v2/destinations/koala/procWorkflow.yaml
@@ -0,0 +1,65 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+
+steps:
+ - name: validateInput
+ template: |
+ $.assert(.message.type, "message Type is not present. Aborting message");
+ $.assert(.message.type in {{$.EventType.([.IDENTIFY, .TRACK])}},
+ "message type " + .message.type + " is not supported");
+ $.assertConfig(.destination.Config.publicKey, "publicKey is not present. Aborting message");
+ $.context.email = .message.().({{{{$.getGenericPaths("emailOnly")}}}});
+ $.context.ko_profile_id = .message.traits.ko_profile_id ?? .message.context.traits.ko_profile_id ?? .message.properties.ko_profile_id;
+ $.assert($.context.email || $.context.ko_profile_id, "Neither email or ko_profile_id are present on traits. Aborting message");
+ - name: setMessageType
+ template: |
+ $.context.messageType = .message.type.toLowerCase();
+ - name: preparePayloadForIdentify
+ condition: $.context.messageType === {{$.EventType.IDENTIFY}}
+ template: |
+ const traits = .message.traits ?? .message.context.traits ?? {};
+ const koTraits = traits{~['ko_profile_id']}
+ const basePayload = {
+ email: $.context.email,
+ profile_id: $.context.ko_profile_id,
+ identifies: [{
+ type: $.context.messageType,
+ sent_at: .message.().({{{{$.getGenericPaths("timestamp")}}}}),
+ traits: koTraits
+ }]
+ };
+
+ $.context.payload = basePayload
+ - name: preparePayloadForTrack
+ condition: $.context.messageType === {{$.EventType.TRACK}}
+ template: |
+ const properties = .message.properties ?? {};
+ const koProperties = properties{~['ko_profile_id']}
+ const basePayload = {
+ ip: .message.context.ip ?? .message.request_ip,
+ email: $.context.email,
+ profile_id: $.context.ko_profile_id,
+ events: [{
+ type: $.context.messageType,
+ event: .message.event,
+ message_id: .message.messageId,
+ sent_at: .message.().({{{{$.getGenericPaths("timestamp")}}}}),
+ properties: koProperties,
+ context: .message.context
+ }]
+ };
+
+ $.context.payload = basePayload
+ - name: buildResponseForProcessTransformation
+ template: |
+ const response = $.defaultRequestConfig();
+ response.body.JSON = $.context.payload;
+ response.endpoint = "https://api2.getkoala.com/web/projects/" + .destination.Config.publicKey + "/batch";
+ response.headers = {
+ "content-type": "application/json"
+ };
+ response
diff --git a/src/cdk/v2/destinations/koala/rtWorkflow.yaml b/src/cdk/v2/destinations/koala/rtWorkflow.yaml
new file mode 100644
index 0000000000..335293b6db
--- /dev/null
+++ b/src/cdk/v2/destinations/koala/rtWorkflow.yaml
@@ -0,0 +1,31 @@
+bindings:
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+
+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.({
+ "batchedRequest": .,
+ "batched": false,
+ "destination": ^[idx].destination,
+ "metadata": ^[idx].metadata[],
+ "statusCode": 200
+ })[]
+ - name: failedEvents
+ template: |
+ $.outputs.transform#idx.error.(
+ $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
+ )[]
+ - name: finalPayload
+ template: |
+ [...$.outputs.failedEvents, ...$.outputs.successfulEvents]
diff --git a/src/cdk/v2/destinations/kochava/procWorkflow.yaml b/src/cdk/v2/destinations/kochava/procWorkflow.yaml
index 557b1a0b63..3e73ee1520 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 1d6177fe09..2622146221 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/movable_ink/config.js b/src/cdk/v2/destinations/movable_ink/config.js
new file mode 100644
index 0000000000..673e94620e
--- /dev/null
+++ b/src/cdk/v2/destinations/movable_ink/config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ MAX_REQUEST_SIZE_IN_BYTES: 13500,
+};
diff --git a/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml b/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml
new file mode 100644
index 0000000000..43dbb3cbce
--- /dev/null
+++ b/src/cdk/v2/destinations/movable_ink/procWorkflow.yaml
@@ -0,0 +1,73 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: toUnixTimestampInMS
+ path: ../../../../v0/util
+ - name: base64Convertor
+ 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.([.IDENTIFY,.TRACK])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.endpoint, "Movable Ink Endpoint is not present. Aborting");
+ $.assertConfig(.destination.Config.accessKey, "Access key is not present . Aborting");
+ $.assertConfig(.destination.Config.accessSecret, "Access Secret is not present. Aborting");
+ $.assert(.message.timestamp ?? .message.originalTimestamp, "Timestamp is not present. Aborting");
+
+ const userId = .message.().(
+ {{{{$.getGenericPaths("userIdOnly")}}}};
+ );
+ const email = .message.().(
+ {{{{$.getGenericPaths("email")}}}};
+ );
+
+ $.assert(userId ?? email ?? .message.anonymousId, "Either one of userId or email or anonymousId is required. Aborting");
+ $.validateEventPayload(.message);
+
+ - name: preparePayload
+ description: Prepare payload for identify and track. This payload schema needs to be configured in the Movable Ink dashboard. Movable Ink will discard any additional fields from the input payload.
+ template: |
+ const userId = .message.().(
+ {{{{$.getGenericPaths("userIdOnly")}}}};
+ );
+ const email = .message.().(
+ {{{{$.getGenericPaths("email")}}}};
+ );
+ const timestampInUnix = $.toUnixTimestampInMS(.message.().(
+ {{{{$.getGenericPaths("timestamp")}}}};
+ ));
+ $.context.payload = {
+ ...(.message),
+ userId: userId ?? email,
+ timestamp: timestampInUnix,
+ anonymousId: .message.anonymousId
+ }
+
+ - name: buildResponse
+ description: In batchMode we return payload directly
+ condition: $.batchMode
+ template: |
+ $.context.payload
+ else:
+ name: buildResponseForProcessTransformation
+ template: |
+ const response = $.defaultRequestConfig();
+ response.body.JSON = $.context.payload;
+ response.endpoint = .destination.Config.endpoint;
+ response.method = "POST";
+ response.headers = {
+ "Content-Type": "application/json",
+ "Authorization": "Basic " + $.base64Convertor(.destination.Config.accessKey + ":" + .destination.Config.accessSecret)
+ }
+ response;
diff --git a/src/cdk/v2/destinations/movable_ink/rtWorkflow.yaml b/src/cdk/v2/destinations/movable_ink/rtWorkflow.yaml
new file mode 100644
index 0000000000..46afb34d53
--- /dev/null
+++ b/src/cdk/v2/destinations/movable_ink/rtWorkflow.yaml
@@ -0,0 +1,74 @@
+bindings:
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+ - path: ./utils
+ exportAll: true
+ - name: base64Convertor
+ path: ../../../../v0/util
+ - name: BatchUtils
+ path: '@rudderstack/workflow-engine'
+ - path: ./config
+
+steps:
+ - name: validateInput
+ template: |
+ $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array")
+
+ - name: transform
+ externalWorkflow:
+ path: ./procWorkflow.yaml
+ bindings:
+ - name: batchMode
+ value: true
+ loopOverInput: true
+
+ - name: successfulEvents
+ template: |
+ $.outputs.transform#idx.output.({
+ "batchedRequest": .,
+ "batched": false,
+ "destination": ^[idx].destination,
+ "metadata": ^[idx].metadata,
+ "statusCode": 200
+ })[]
+
+ - name: failedEvents
+ template: |
+ $.outputs.transform#idx.error.(
+ $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
+ )[]
+
+ - name: batchSuccessfulEvents
+ description: Batches the successfulEvents
+ template: |
+ let batches = $.BatchUtils.chunkArrayBySizeAndLength(
+ $.outputs.successfulEvents, {maxSizeInBytes: $.MAX_REQUEST_SIZE_IN_BYTES}).items;
+
+ batches@batch.({
+ "batchedRequest": {
+ "body": {
+ "JSON": {"events": ~r batch.batchedRequest[]},
+ "JSON_ARRAY": {},
+ "XML": {},
+ "FORM": {}
+ },
+ "version": "1",
+ "type": "REST",
+ "method": "POST",
+ "endpoint": batch[0].destination.Config.().(.endpoint),
+ "headers": batch[0].destination.Config.().({
+ "Content-Type": "application/json",
+ "Authorization": "Basic " + $.base64Convertor(.accessKey + ":" + .accessSecret)
+ }),
+ "params": {},
+ "files": {}
+ },
+ "metadata": ~r batch.metadata[],
+ "batched": true,
+ "statusCode": 200,
+ "destination": batch[0].destination
+ })[];
+
+ - name: finalPayload
+ template: |
+ [...$.outputs.batchSuccessfulEvents, ...$.outputs.failedEvents]
diff --git a/src/cdk/v2/destinations/movable_ink/utils.js b/src/cdk/v2/destinations/movable_ink/utils.js
new file mode 100644
index 0000000000..04d7046b1a
--- /dev/null
+++ b/src/cdk/v2/destinations/movable_ink/utils.js
@@ -0,0 +1,21 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+
+const validateEventPayload = (message) => {
+ const { event } = message;
+ const { properties } = message;
+ if (event === 'Products Searched' && !properties?.query) {
+ throw new InstrumentationError("Missing 'query' property in properties. Aborting");
+ }
+
+ if (
+ (event === 'Product Added' ||
+ event === 'Product Removed' ||
+ event === 'Product Viewed' ||
+ event === 'Category Viewed') &&
+ !properties?.product_id
+ ) {
+ throw new InstrumentationError("Missing 'product_id' property in properties. Aborting");
+ }
+};
+
+module.exports = { validateEventPayload };
diff --git a/src/cdk/v2/destinations/ninetailed/config.js b/src/cdk/v2/destinations/ninetailed/config.js
new file mode 100644
index 0000000000..c38496a415
--- /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 0000000000..f2373b61c1
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/data/contextMapping.json
@@ -0,0 +1,46 @@
+[
+ {
+ "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": false,
+ "metadata": {
+ "defaultValue": {}
+ },
+ "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 0000000000..3ab72d1b9f
--- /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 0000000000..e8d3f7797d
--- /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 0000000000..80ec2f58f1
--- /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 0000000000..44af6dd1a3
--- /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 0000000000..6f5056ce10
--- /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 0000000000..30dd3fdd95
--- /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 0000000000..b716422a0e
--- /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 8a956e905c..1122a80404 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 227942dfea..215ead12b1 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 e04765faed..db5d36fc4d 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 fe37455a57..ef6b197db7 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 e6c05ef86d..59725c1257 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 18b46913fd..4dcebeffcd 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 b3c85e31dc..6d3328b87e 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/the_trade_desk/config.js b/src/cdk/v2/destinations/the_trade_desk/config.js
index 828bab3714..300325223b 100644
--- a/src/cdk/v2/destinations/the_trade_desk/config.js
+++ b/src/cdk/v2/destinations/the_trade_desk/config.js
@@ -1,6 +1,4 @@
-const { getMappingConfig } = require('../../../../v0/util');
-
-const SUPPORTED_EVENT_TYPE = ['record', 'track'];
+const SUPPORTED_EVENT_TYPE = ['record'];
const ACTION_TYPES = ['insert', 'delete'];
const DATA_PROVIDER_ID = 'rudderstack';
@@ -15,66 +13,10 @@ const DATA_SERVERS_BASE_ENDPOINTS_MAP = {
china: 'https://data-cn2.adsrvr.cn',
};
-// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi
-const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion';
-
-const CONVERSION_SUPPORTED_ID_TYPES = [
- 'TDID',
- 'IDFA',
- 'AAID',
- 'DAID',
- 'NAID',
- 'IDL',
- 'EUID',
- 'UID2',
-];
-
-const ECOMM_EVENT_MAP = {
- 'product added': {
- event: 'addtocart',
- rootLevelPriceSupported: true,
- },
- 'order completed': {
- event: 'purchase',
- itemsArray: true,
- revenueFieldSupported: true,
- },
- 'product viewed': {
- event: 'viewitem',
- rootLevelPriceSupported: true,
- },
- 'checkout started': {
- event: 'startcheckout',
- itemsArray: true,
- revenueFieldSupported: true,
- },
- 'cart viewed': {
- event: 'viewcart',
- itemsArray: true,
- },
- 'product added to wishlist': {
- event: 'wishlistitem',
- rootLevelPriceSupported: true,
- },
-};
-
-const CONFIG_CATEGORIES = {
- COMMON_CONFIGS: { name: 'TTDCommonConfig' },
- ITEM_CONFIGS: { name: 'TTDItemConfig' },
-};
-
-const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
-
module.exports = {
SUPPORTED_EVENT_TYPE,
ACTION_TYPES,
DATA_PROVIDER_ID,
MAX_REQUEST_SIZE_IN_BYTES: 2500000,
DATA_SERVERS_BASE_ENDPOINTS_MAP,
- CONVERSION_SUPPORTED_ID_TYPES,
- CONFIG_CATEGORIES,
- COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name],
- ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name],
- ECOMM_EVENT_MAP,
- REAL_TIME_CONVERSION_ENDPOINT,
};
diff --git a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
index ee05ecd967..5f0476dd62 100644
--- a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
+++ b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
@@ -3,20 +3,19 @@ bindings:
path: ../../../../constants
- name: processRecordInputs
path: ./transformRecord
- - name: processConversionInputs
- path: ./transformConversion
- name: handleRtTfSingleEventError
path: ../../../../v0/util/index
- name: InstrumentationError
path: '@rudderstack/integrations-lib'
steps:
- - name: validateCommonConfig
- description: |
- validate common config for first party data and realtime conversion flow
+ - name: validateConfig
template: |
const config = ^[0].destination.Config
+ $.assertConfig(config.audienceId, "Segment name/Audience ID is not present. Aborting")
$.assertConfig(config.advertiserId, "Advertiser ID is not present. Aborting")
+ $.assertConfig(config.advertiserSecretKey, "Advertiser Secret Key is not present. Aborting")
+ config.ttlInDays ? $.assertConfig(config.ttlInDays >=0 && config.ttlInDays <= 180, "TTL is out of range. Allowed values are 0 to 180 days")
- name: validateInput
template: |
@@ -26,22 +25,17 @@ steps:
template: |
$.processRecordInputs(^.{.message.type === $.EventType.RECORD}[], ^[0].destination)
- - name: processConversionEvents
- template: |
- $.processConversionInputs(^.{.message.type === $.EventType.TRACK}[])
-
- name: failOtherEvents
template: |
- const otherEvents = ^.{.message.type !== $.EventType.TRACK && .message.type !== $.EventType.RECORD}[]
+ const otherEvents = ^.{.message.type !== $.EventType.RECORD}[]
let failedEvents = otherEvents.map(
function(event) {
const error = new $.InstrumentationError("Event type " + event.message.type + " is not supported");
$.handleRtTfSingleEventError(event, error, {})
}
)
-
failedEvents ?? []
- name: finalPayload
template: |
- [...$.outputs.processRecordEvents, ...$.outputs.processConversionEvents, ...$.outputs.failOtherEvents]
+ [...$.outputs.processRecordEvents, ...$.outputs.failOtherEvents]
diff --git a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js b/src/cdk/v2/destinations/the_trade_desk/transformConversion.js
deleted file mode 100644
index b282c43151..0000000000
--- a/src/cdk/v2/destinations/the_trade_desk/transformConversion.js
+++ /dev/null
@@ -1,98 +0,0 @@
-const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib');
-const {
- defaultRequestConfig,
- simpleProcessRouterDest,
- defaultPostRequestConfig,
- removeUndefinedAndNullValues,
-} = require('../../../../v0/util');
-const { EventType } = require('../../../../constants');
-const { REAL_TIME_CONVERSION_ENDPOINT } = require('./config');
-const {
- prepareFromConfig,
- prepareCommonPayload,
- getRevenue,
- prepareItemsPayload,
- getAdvertisingId,
- prepareCustomProperties,
- populateEventName,
- getDataProcessingOptions,
- getPrivacySetting,
- enrichTrackPayload,
-} = require('./utils');
-
-const responseBuilder = (payload) => {
- const response = defaultRequestConfig();
- response.endpoint = REAL_TIME_CONVERSION_ENDPOINT;
- response.method = defaultPostRequestConfig.requestMethod;
- response.body.JSON = payload;
- return response;
-};
-
-const validateInputAndConfig = (message, destination) => {
- const { Config } = destination;
- if (!Config.trackerId) {
- throw new ConfigurationError('Tracking Tag ID is not present. Aborting');
- }
-
- if (!message.type) {
- throw new InstrumentationError('Event type is required');
- }
-
- const messageType = message.type.toLowerCase();
- if (messageType !== EventType.TRACK) {
- throw new InstrumentationError(`Event type "${messageType}" is not supported`);
- }
-
- if (!message.event) {
- throw new InstrumentationError('Event name is not present. Aborting.');
- }
-};
-
-const prepareTrackPayload = (message, destination) => {
- const configPayload = prepareFromConfig(destination);
- const commonPayload = prepareCommonPayload(message);
- // prepare items array
- const items = prepareItemsPayload(message);
- const { id, type } = getAdvertisingId(message);
- // get td1-td10 custom properties
- const customProperties = prepareCustomProperties(message, destination);
- const eventName = populateEventName(message, destination);
- const value = getRevenue(message);
- let payload = {
- ...configPayload,
- ...commonPayload,
- event_name: eventName,
- value,
- items,
- adid: id,
- adid_type: type,
- ...customProperties,
- data_processing_option: getDataProcessingOptions(message),
- privacy_settings: getPrivacySetting(message),
- };
-
- payload = enrichTrackPayload(message, payload);
- return { data: [removeUndefinedAndNullValues(payload)] };
-};
-
-const trackResponseBuilder = (message, destination) => {
- const payload = prepareTrackPayload(message, destination);
- return responseBuilder(payload);
-};
-
-const processEvent = (message, destination) => {
- validateInputAndConfig(message, destination);
- return trackResponseBuilder(message, destination);
-};
-
-const process = (event) => processEvent(event.message, event.destination);
-
-const processConversionInputs = async (inputs, reqMetadata) => {
- if (!inputs || inputs.length === 0) {
- return [];
- }
- const respList = await simpleProcessRouterDest(inputs, process, reqMetadata);
- return respList;
-};
-
-module.exports = { processConversionInputs };
diff --git a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js
index d571e11b7a..b452f8d7bc 100644
--- a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js
+++ b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js
@@ -1,4 +1,4 @@
-const { InstrumentationError, ConfigurationError } = require('@rudderstack/integrations-lib');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { BatchUtils } = require('@rudderstack/workflow-engine');
const {
defaultPostRequestConfig,
@@ -13,20 +13,6 @@ const tradeDeskConfig = require('./config');
const { DATA_PROVIDER_ID } = tradeDeskConfig;
-const validateConfig = (config) => {
- if (!config.advertiserSecretKey) {
- throw new ConfigurationError('Advertiser Secret Key is not present. Aborting');
- }
-
- if (config.ttlInDays && !(config.ttlInDays >= 0 && config.ttlInDays <= 180)) {
- throw new ConfigurationError('TTL is out of range. Allowed values are 0 to 180 days');
- }
-
- if (!config.audienceId) {
- throw new ConfigurationError('Segment name/Audience ID is not present. Aborting');
- }
-};
-
const responseBuilder = (items, config) => {
const { advertiserId, dataServer } = config;
@@ -64,8 +50,6 @@ const processRecordInputs = (inputs, destination) => {
return [];
}
- validateConfig(Config);
-
const invalidActionTypeError = new InstrumentationError(
'Invalid action type. You can only add or remove IDs from the audience/segment',
);
diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.js b/src/cdk/v2/destinations/the_trade_desk/utils.js
index f51d8dc3ff..71b479438c 100644
--- a/src/cdk/v2/destinations/the_trade_desk/utils.js
+++ b/src/cdk/v2/destinations/the_trade_desk/utils.js
@@ -1,28 +1,10 @@
-const lodash = require('lodash');
-const get = require('get-value');
const CryptoJS = require('crypto-js');
-const { InstrumentationError, AbortedError } = require('@rudderstack/integrations-lib');
-const {
- constructPayload,
- getHashFromArray,
- isDefinedAndNotNull,
- isAppleFamily,
- getIntegrationsObj,
- extractCustomFields,
- generateExclusionList,
-} = require('../../../../v0/util');
-const {
- DATA_SERVERS_BASE_ENDPOINTS_MAP,
- CONVERSION_SUPPORTED_ID_TYPES,
- COMMON_CONFIGS,
- ITEM_CONFIGS,
- ECOMM_EVENT_MAP,
-} = require('./config');
+const { AbortedError } = require('@rudderstack/integrations-lib');
+const { DATA_SERVERS_BASE_ENDPOINTS_MAP } = require('./config');
const getTTLInMin = (ttl) => parseInt(ttl, 10) * 1440;
const getBaseEndpoint = (dataServer) => DATA_SERVERS_BASE_ENDPOINTS_MAP[dataServer];
const getFirstPartyEndpoint = (dataServer) => `${getBaseEndpoint(dataServer)}/data/advertiser`;
-const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS);
/**
* Generates a signature header for a given request using a secret key.
@@ -41,300 +23,8 @@ const getSignatureHeader = (request, secretKey) => {
return base;
};
-const prepareFromConfig = (destination) => ({
- tracker_id: destination.Config?.trackerId,
- adv: destination.Config?.advertiserId,
-});
-
-/**
- * Calculates the revenue based on the given message.
- *
- * @param {Object} message - The message object containing the event and properties.
- * @returns {number} - The calculated revenue.
- * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided.
- */
-const getRevenue = (message) => {
- const { event, properties } = message;
- let revenue = properties?.value;
- const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
- if (eventsMapInfo?.rootLevelPriceSupported) {
- const { price, quantity = 1 } = properties;
- if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) {
- revenue = parseFloat(price) * parseInt(quantity, 10);
- }
- } else if (eventsMapInfo?.revenueFieldSupported) {
- revenue = properties?.revenue || revenue;
- if (event.toLowerCase() === 'order completed' && !revenue) {
- throw new InstrumentationError('value is required for `Order Completed` event');
- }
- }
-
- return revenue;
-};
-
-/**
- * Generates items from properties of a given message.
- *
- * @param {Object} message - The message object containing properties.
- * @returns {Array} - An array of items generated from the properties.
- */
-const prepareItemsFromProperties = (message) => {
- const { properties } = message;
- const items = [];
- const item = constructPayload(properties, ITEM_CONFIGS);
- items.push(item);
- return items;
-};
-
-/**
- * Generates items payload from products.
- *
- * @param {Object} message - The message object.
- * @returns {Array} - The items payload.
- */
-const prepareItemsFromProducts = (message) => {
- const products = get(message, 'properties.products');
- const items = [];
- products.forEach((product) => {
- const item = constructPayload(product, ITEM_CONFIGS);
- const itemExclusionList = generateExclusionList(ITEM_CONFIGS);
- extractCustomFields(product, item, 'root', itemExclusionList);
- items.push(item);
- });
- return items;
-};
-
-/**
- * Generates items payload from root properties or products.
- *
- * @param {Object} message - The message object containing event and properties.
- * @returns {Array} - The array of items payload.
- */
-const prepareItemsPayload = (message) => {
- const { event } = message;
- let items;
- const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
- if (eventMapInfo?.itemsArray) {
- // if event is one of the supported ecommerce events and products array is present
- items = prepareItemsFromProducts(message);
- } else if (eventMapInfo) {
- // if event is one of the supported ecommerce events and products array is not present
- items = prepareItemsFromProperties(message);
- }
- return items;
-};
-
-/**
- * Retrieves the device advertising ID and type based on the provided message.
- *
- * @param {Object} message - The message object containing the context.
- * @returns {Object} - An object containing the device advertising ID and type.
- */
-const getDeviceAdvertisingId = (message) => {
- const { context } = message;
- const deviceId = context?.device?.advertisingId;
- const osName = context?.os?.name?.toLowerCase();
-
- let type;
- switch (osName) {
- case 'android':
- type = 'AAID';
- break;
- case 'windows':
- type = 'NAID';
- break;
- default:
- type = isAppleFamily(osName) ? 'IDFA' : undefined;
- break;
- }
-
- return { deviceId, type };
-};
-
-/**
- * Retrieves the external ID object from the given message context.
- *
- * @param {Object} message - The message object containing the context.
- * @returns {Object|undefined} - The external ID object, or undefined if not found.
- */
-const getDestinationExternalIDObject = (message) => {
- const { context } = message;
- const externalIdArray = context?.externalId || [];
-
- let externalIdObj;
-
- if (Array.isArray(externalIdArray)) {
- externalIdObj = externalIdArray.find(
- (extIdObj) =>
- CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id,
- );
- }
- return externalIdObj;
-};
-
-/**
- * Retrieves the advertising ID and type from the given message.
- *
- * @param {Object} message - The message object containing the context.
- * @returns {Object} - An object containing the advertising ID and type.
- * If the advertising ID and type are found in the device context, they are returned.
- * If not, the external ID object is checked and if found, its ID and type are returned.
- * If neither the device context nor the external ID object contain the required information,
- * an object with null values for ID and type is returned.
- */
-const getAdvertisingId = (message) => {
- const { deviceId, type } = getDeviceAdvertisingId(message);
- if (deviceId && type) {
- return { id: deviceId, type };
- }
- const externalIdObj = getDestinationExternalIDObject(message);
- if (externalIdObj?.id && externalIdObj?.type) {
- return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() };
- }
-
- return { id: null, type: null };
-};
-
-/**
- * Prepares custom properties (td1-td10) for a given message and destination.
- *
- * @param {object} message - The message object.
- * @param {object} destination - The destination object.
- * @returns {object} - The prepared payload object.
- */
-const prepareCustomProperties = (message, destination) => {
- const { customProperties } = destination.Config;
- const payload = {};
- if (customProperties) {
- customProperties.forEach((customProperty) => {
- const { rudderProperty, tradeDeskProperty } = customProperty;
- const value = get(message, rudderProperty);
- if (value) {
- payload[tradeDeskProperty] = value;
- // unset the rudder property from the message, since it is already mapped to a trade desk property
- lodash.unset(message, rudderProperty);
- }
- });
- }
- return payload;
-};
-
-/**
- * Retrieves the event name based on the provided message and destination.
- *
- * @param {object} message - The message object containing the event.
- * @param {object} destination - The destination object containing the events mapping configuration.
- * @returns {string} - The event name.
- */
-const populateEventName = (message, destination) => {
- let eventName;
- const { event } = message;
- const { eventsMapping } = destination.Config;
-
- // if event is mapped on dashboard, use the mapped event name
- if (Array.isArray(eventsMapping) && eventsMapping.length > 0) {
- const keyMap = getHashFromArray(eventsMapping, 'from', 'to');
- eventName = keyMap[event.toLowerCase()];
- }
-
- if (eventName) {
- return eventName;
- }
-
- // if event is one of the supported ecommerce events, use the mapped trade desk event name
- const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
- if (isDefinedAndNotNull(eventMapInfo)) {
- return eventMapInfo.event;
- }
-
- // else return the event name as it is
- return event;
-};
-
-/**
- * Retrieves the data processing options based on the provided message.
- *
- * @param {string} message - The message to process.
- * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported.
- * @returns {Object} - The data processing options, including the policies and region.
- */
-const getDataProcessingOptions = (message) => {
- const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {};
- let { policies } = integrationObj;
- const { region } = integrationObj;
- let dataProcessingOptions;
-
- if (region && !region.toLowerCase().startsWith('us')) {
- throw new InstrumentationError('Only US states are supported');
- }
-
- if (!policies || (Array.isArray(policies) && policies.length === 0)) {
- policies = ['LDU'];
- }
-
- if (policies.length > 1) {
- throw new InstrumentationError('Only one policy is allowed');
- }
-
- if (policies[0] !== 'LDU') {
- throw new InstrumentationError('Only LDU policy is supported');
- }
-
- if (policies && region) {
- dataProcessingOptions = { policies, region };
- }
-
- return dataProcessingOptions;
-};
-
-const getPrivacySetting = (message) => {
- const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK');
- return integrationObj?.privacy_settings;
-};
-
-/**
- * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json
- *
- * @param {Object} message - The message object containing the event information.
- * @param {Object} payload - The payload object to be enriched.
- * @returns {Object} - The enriched payload object.
- */
-const enrichTrackPayload = (message, payload) => {
- let rawPayload = { ...payload };
- const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()];
- // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event
- if (eventsMapInfo && !eventsMapInfo.itemsArray) {
- const itemExclusionList = generateExclusionList(ITEM_CONFIGS);
- rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList);
- } else if (eventsMapInfo) {
- // for ecomm events with products array supports. e.g Order Completed event
- rawPayload = extractCustomFields(
- message,
- rawPayload,
- ['properties'],
- ['products', 'revenue', 'value'],
- );
- } else {
- // for custom events
- rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']);
- }
- return rawPayload;
-};
-
module.exports = {
getTTLInMin,
getFirstPartyEndpoint,
getSignatureHeader,
- prepareFromConfig,
- getRevenue,
- prepareCommonPayload,
- prepareItemsPayload,
- getDeviceAdvertisingId,
- getDestinationExternalIDObject,
- getAdvertisingId,
- prepareCustomProperties,
- populateEventName,
- getDataProcessingOptions,
- getPrivacySetting,
- enrichTrackPayload,
};
diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.test.js b/src/cdk/v2/destinations/the_trade_desk/utils.test.js
index 029c3004ae..81fd7cf17d 100644
--- a/src/cdk/v2/destinations/the_trade_desk/utils.test.js
+++ b/src/cdk/v2/destinations/the_trade_desk/utils.test.js
@@ -1,16 +1,5 @@
-const { AbortedError, InstrumentationError } = require('@rudderstack/integrations-lib');
-const {
- getSignatureHeader,
- getRevenue,
- getDeviceAdvertisingId,
- getDestinationExternalIDObject,
- getAdvertisingId,
- prepareCustomProperties,
- populateEventName,
- getDataProcessingOptions,
- getPrivacySetting,
- enrichTrackPayload,
-} = require('./utils');
+const { AbortedError } = require('@rudderstack/integrations-lib');
+const { getSignatureHeader } = require('./utils');
describe('getSignatureHeader', () => {
it('should calculate the signature header for a valid request and secret key', () => {
@@ -58,612 +47,3 @@ describe('getSignatureHeader', () => {
}).toThrow(AbortedError);
});
});
-
-describe('getRevenue', () => {
- it('should return revenue value from message properties for custom events', () => {
- const message = {
- event: 'customEvent',
- properties: {
- value: 100,
- },
- };
- const result = getRevenue(message);
- expect(result).toBe(100);
- });
-
- it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => {
- const message = {
- event: 'Product Added',
- properties: {
- price: 10,
- quantity: 5,
- },
- };
- const result = getRevenue(message);
- expect(result).toBe(50);
- });
-
- it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => {
- const message = {
- event: 'Order Completed',
- properties: {
- revenue: 200,
- },
- };
- const result = getRevenue(message);
- expect(result).toBe(200);
- });
-
- it('should return default revenue value from properties.value for ecomm events', () => {
- let message = {
- event: 'Product Added',
- properties: {
- price: '',
- value: 200,
- },
- };
- let result = getRevenue(message);
- expect(result).toBe(200);
-
- message = {
- event: 'Order Completed',
- properties: {
- value: 200,
- },
- };
- result = getRevenue(message);
- expect(result).toBe(200);
- });
-
- it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => {
- const message = {
- event: 'Order Completed',
- properties: {},
- };
- expect(() => {
- getRevenue(message);
- }).toThrow(InstrumentationError);
- });
-});
-
-describe('getDeviceAdvertisingId', () => {
- it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => {
- let message = {
- context: {
- device: {
- advertisingId: '123456789',
- },
- os: {
- name: 'android',
- },
- },
- };
- let result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: '123456789', type: 'AAID' });
-
- message = {
- context: {
- device: {
- advertisingId: '123456789',
- },
- os: {
- name: 'ios',
- },
- },
- };
- result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' });
-
- message = {
- context: {
- device: {
- advertisingId: '123456789',
- },
- os: {
- name: 'windows',
- },
- },
- };
- result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: '123456789', type: 'NAID' });
- });
-
- it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => {
- const message = {
- context: {
- device: {
- advertisingId: '123456789',
- },
- os: {
- name: 'linux',
- },
- },
- };
- const result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: '123456789', type: undefined });
- });
-
- it('should return an object with undefined deviceId and type properties when context is undefined', () => {
- let message = {};
- let result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: undefined, type: undefined });
-
- message = {
- context: {},
- };
- result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: undefined, type: undefined });
-
- message = {
- context: {
- device: {},
- },
- };
- result = getDeviceAdvertisingId(message);
- expect(result).toEqual({ deviceId: undefined, type: undefined });
- });
-});
-
-describe('getDestinationExternalIDObject', () => {
- it('should return the external ID object when it exists in the message context', () => {
- const message = {
- context: {
- externalId: [
- { id: '123', type: 'daid' },
- { id: '456', type: 'type123' },
- ],
- },
- };
- const result = getDestinationExternalIDObject(message);
- expect(result).toEqual({ id: '123', type: 'daid' });
- });
-
- it('should return undefined when no external ID object exists in the message context', () => {
- let message = {
- context: {
- externalId: [],
- },
- };
- let result = getDestinationExternalIDObject(message);
- expect(result).toBeUndefined();
-
- message = {
- context: {},
- };
- result = getDestinationExternalIDObject(message);
- expect(result).toBeUndefined();
- });
-
- it('should return the first matching external ID object in the array', () => {
- const message = {
- context: {
- externalId: [
- { id: '', type: 'daid' },
- { id: '456', type: 'tdid' },
- { id: '789', type: 'UID2' },
- ],
- },
- };
- const result = getDestinationExternalIDObject(message);
- expect(result).toEqual({ id: '456', type: 'tdid' });
- });
-});
-
-describe('getAdvertisingId', () => {
- it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => {
- const message = {
- context: {
- device: {
- advertisingId: '1234567890',
- },
- os: {
- name: 'android',
- },
- },
- };
-
- const result = getAdvertisingId(message);
- expect(result).toEqual({ id: '1234567890', type: 'AAID' });
- });
-
- it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => {
- const message = {
- context: {
- externalId: [
- {
- type: 'IDFA',
- id: 'abcdefg',
- },
- ],
- },
- };
-
- const result = getAdvertisingId(message);
- expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' });
- });
-
- it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => {
- let message = {
- context: {
- externalId: [
- {
- type: 'unsupported',
- id: '1234567890',
- },
- ],
- },
- };
-
- let result = getAdvertisingId(message);
- expect(result).toEqual({ id: null, type: null });
-
- message = {
- context: {
- device: {
- advertisingId: '1234567890',
- },
- },
- };
-
- result = getAdvertisingId(message);
- expect(result).toEqual({ id: null, type: null });
- });
-
- it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => {
- let message = {
- context: {
- externalId: [
- {
- type: 'IDFA',
- },
- ],
- },
- };
- let result = getAdvertisingId(message);
- expect(result).toEqual({ id: null, type: null });
-
- message = {
- context: {},
- };
- result = getAdvertisingId(message);
- expect(result).toEqual({ id: null, type: null });
- });
-});
-
-describe('prepareCustomProperties', () => {
- it('should return an empty object when customProperties is an empty array', () => {
- const message = {};
- let destination = { Config: { customProperties: [] } };
- let result = prepareCustomProperties(message, destination);
- expect(result).toEqual({});
-
- destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } };
- result = prepareCustomProperties(message, destination);
- expect(result).toEqual({});
-
- destination = { Config: { customProperties: undefined } };
- result = prepareCustomProperties(message, destination);
- expect(result).toEqual({});
- });
-
- it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => {
- const message = {
- rudderProperty1: 'value1',
- rudderProperty2: 'value2',
- };
- const destination = {
- Config: {
- customProperties: [
- {
- rudderProperty: 'rudderProperty1',
- tradeDeskProperty: 'tradeDeskProperty1',
- },
- {
- rudderProperty: 'rudderProperty2',
- tradeDeskProperty: 'tradeDeskProperty2',
- },
- {
- rudderProperty: 'rudderProperty3',
- tradeDeskProperty: 'tradeDeskProperty3',
- },
- ],
- },
- };
- const result = prepareCustomProperties(message, destination);
- expect(result).toEqual({
- tradeDeskProperty1: 'value1',
- tradeDeskProperty2: 'value2',
- });
- });
-});
-
-describe('populateEventName', () => {
- it('should return the eventName if it exists in the eventsMapping of destination.Config', () => {
- const message = { event: 'someEvent' };
- const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } };
- const result = populateEventName(message, destination);
- expect(result).toBe('mappedEvent');
- });
-
- it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => {
- const message = { event: 'product added' };
- let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } };
- let result = populateEventName(message, destination);
- expect(result).toBe('addtocart');
-
- destination = { Config: { eventsMapping: [] } };
- result = populateEventName(message, destination);
- expect(result).toBe('addtocart');
- });
-
- it('should return undefined if eventsMapping is an empty array', () => {
- const message = { event: 'someEvent' };
- const destination = { Config: { eventsMapping: [] } };
- const result = populateEventName(message, destination);
- expect(result).toBe('someEvent');
- });
-});
-
-describe('getDataProcessingOptions', () => {
- it('should return an object with policies and region when provided a integrationObj in message', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- policies: ['LDU'],
- region: 'US-CO',
- },
- },
- };
- const expected = {
- policies: ['LDU'],
- region: 'US-CO',
- };
- const result = getDataProcessingOptions(message);
- expect(result).toEqual(expected);
- });
-
- it('should throw an InstrumentationError if the region is not a US state', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- policies: ['LDU'],
- region: 'EU-abc',
- },
- },
- };
- expect(() => {
- getDataProcessingOptions(message);
- }).toThrow(InstrumentationError);
- });
-
- it('should throw an InstrumentationError if multiple policies are provided', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- policies: ['LDU', 'Policy2'],
- region: 'US-CO',
- },
- },
- };
-
- expect(() => {
- getDataProcessingOptions(message);
- }).toThrow(InstrumentationError);
- });
-
- it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- policies: ['Policy1'],
- region: 'US-CO',
- },
- },
- };
-
- expect(() => {
- getDataProcessingOptions(message);
- }).toThrow(InstrumentationError);
- });
-
- it('should return an object with default policy `LDU` when policies are not provided', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- policies: [],
- region: 'US-CO',
- },
- },
- };
-
- const expected = {
- policies: ['LDU'],
- region: 'US-CO',
- };
-
- expect(getDataProcessingOptions(message)).toEqual(expected);
- });
-
- it('should handle empty cases', () => {
- let message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {},
- },
- };
-
- expect(getDataProcessingOptions(message)).toBeUndefined();
-
- message = {
- integrations: {
- All: true,
- },
- };
-
- expect(getDataProcessingOptions(message)).toBeUndefined();
-
- message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: { region: 'US-CO' },
- },
- };
-
- expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' });
- });
-});
-
-describe('getPrivacySetting', () => {
- it('should return the privacy settings object when it exists in the integration object', () => {
- const message = {
- integrations: {
- All: true,
- THE_TRADE_DESK: {
- privacy_settings: [
- {
- privacy_type: 'GDPR',
- is_applicable: 1,
- consent_string: 'ok',
- },
- ],
- },
- },
- };
- const expected = [
- {
- privacy_type: 'GDPR',
- is_applicable: 1,
- consent_string: 'ok',
- },
- ];
- const result = getPrivacySetting(message);
- expect(result).toEqual(expected);
- });
-
- it('should return null when the privacy settings object does not exist in the integration object', () => {
- let message = { integrations: {} };
- expect(getPrivacySetting(message)).toBeUndefined();
-
- message = { integrations: { THE_TRADE_DESK: {} } };
- expect(getPrivacySetting(message)).toBeUndefined();
-
- message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } };
- expect(getPrivacySetting(message)).toBeNull();
- });
-});
-
-describe('enrichTrackPayload', () => {
- it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => {
- const message = {
- event: 'Product Added',
- properties: {
- product_id: 'prd123',
- sku: 'sku123',
- brand: 'brand123',
- property1: 'value1',
- property2: 'value2',
- },
- };
- const payload = {
- items: [
- {
- item_code: 'prd123',
- },
- ],
- property1: 'value1',
- property2: 'value2',
- };
- const expectedPayload = {
- items: [
- {
- item_code: 'prd123',
- },
- ],
- brand: 'brand123',
- property1: 'value1',
- property2: 'value2',
- };
-
- const result = enrichTrackPayload(message, payload);
- expect(result).toEqual(expectedPayload);
- });
-
- it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => {
- const message = {
- event: 'order completed',
- properties: {
- order_id: 'ord123',
- total: 52.0,
- subtotal: 45.0,
- revenue: 50.0,
- products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }],
- property1: 'value1',
- property2: 'value2',
- },
- };
- const payload = {
- order_id: 'ord123',
- value: 50.0,
- items: [{ item_code: 'prd123', brand: 'brand123' }],
- property1: 'value1',
- property2: 'value2',
- };
- const expectedPayload = {
- order_id: 'ord123',
- total: 52.0,
- subtotal: 45.0,
- value: 50.0,
- items: [{ item_code: 'prd123', brand: 'brand123' }],
- property1: 'value1',
- property2: 'value2',
- };
-
- const result = enrichTrackPayload(message, payload);
-
- expect(result).toEqual(expectedPayload);
- });
-
- it('should return the enriched payload for custom event', () => {
- const message = {
- event: 'someEvent',
- properties: {
- order_id: 'ord123',
- property1: 'value1',
- property2: 'value2',
- revenue: 10,
- value: 11,
- products: [
- {
- product_id: 'prd123',
- test: 'test',
- },
- ],
- },
- };
- const payload = {
- order_id: 'ord123',
- value: 11,
- };
- const expectedPayload = {
- order_id: 'ord123',
- property1: 'value1',
- property2: 'value2',
- revenue: 10,
- value: 11,
- products: [
- {
- product_id: 'prd123',
- test: 'test',
- },
- ],
- };
-
- const result = enrichTrackPayload(message, payload);
- expect(result).toEqual(expectedPayload);
- });
-});
diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js
new file mode 100644
index 0000000000..7af732185f
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/config.js
@@ -0,0 +1,63 @@
+const { getMappingConfig } = require('../../../../v0/util');
+
+const SUPPORTED_EVENT_TYPE = ['track'];
+
+// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataConversionEventsApi
+const REAL_TIME_CONVERSION_ENDPOINT = 'https://insight.adsrvr.org/track/realtimeconversion';
+
+const CONVERSION_SUPPORTED_ID_TYPES = [
+ 'TDID',
+ 'IDFA',
+ 'AAID',
+ 'DAID',
+ 'NAID',
+ 'IDL',
+ 'EUID',
+ 'UID2',
+];
+
+const ECOMM_EVENT_MAP = {
+ 'product added': {
+ event: 'addtocart',
+ rootLevelPriceSupported: true,
+ },
+ 'order completed': {
+ event: 'purchase',
+ itemsArray: true,
+ revenueFieldSupported: true,
+ },
+ 'product viewed': {
+ event: 'viewitem',
+ rootLevelPriceSupported: true,
+ },
+ 'checkout started': {
+ event: 'startcheckout',
+ itemsArray: true,
+ revenueFieldSupported: true,
+ },
+ 'cart viewed': {
+ event: 'viewcart',
+ itemsArray: true,
+ },
+ 'product added to wishlist': {
+ event: 'wishlistitem',
+ rootLevelPriceSupported: true,
+ },
+};
+
+const CONFIG_CATEGORIES = {
+ COMMON_CONFIGS: { name: 'TTDCommonConfig' },
+ ITEM_CONFIGS: { name: 'TTDItemConfig' },
+};
+
+const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
+
+module.exports = {
+ SUPPORTED_EVENT_TYPE,
+ CONFIG_CATEGORIES,
+ CONVERSION_SUPPORTED_ID_TYPES,
+ COMMON_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.COMMON_CONFIGS.name],
+ ITEM_CONFIGS: MAPPING_CONFIG[CONFIG_CATEGORIES.ITEM_CONFIGS.name],
+ ECOMM_EVENT_MAP,
+ REAL_TIME_CONVERSION_ENDPOINT,
+};
diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json
similarity index 100%
rename from src/cdk/v2/destinations/the_trade_desk/data/TTDCommonConfig.json
rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json
diff --git a/src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json
similarity index 100%
rename from src/cdk/v2/destinations/the_trade_desk/data/TTDItemConfig.json
rename to src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json
diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml
new file mode 100644
index 0000000000..5191320cdc
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/procWorkflow.yaml
@@ -0,0 +1,59 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - path: ./config
+ exportAll: true
+ - path: ./utils
+ exportAll: true
+
+steps:
+ - name: validateConfig
+ template: |
+ $.assertConfig(.destination.Config.advertiserId, "Advertiser ID is not present. Aborting")
+ $.assertConfig(.destination.Config.trackerId, "Tracking Tag ID is not present. Aborting")
+
+ - name: validateInput
+ template: |
+ let messageType = .message.type;
+ $.assert(messageType, "message Type is not present. Aborting.");
+ $.assert(messageType.toLowerCase() === $.EventType.TRACK, "Event type " + messageType + " is not supported");
+ $.assert(.message.event, "Event is not present. Aborting.");
+
+ - name: prepareTrackPayload
+ template: |
+ const configPayload = $.prepareFromConfig(.destination);
+ const commonPayload = $.prepareCommonPayload(.message);
+ const { id, type } = $.getAdvertisingId(.message);
+ const items = $.prepareItemsPayload(.message);
+ const customProperties = $.prepareCustomProperties(.message, .destination);
+ const eventName = $.populateEventName(.message, .destination);
+ const value = $.getRevenue(.message);
+ let payload = {
+ ...configPayload,
+ ...commonPayload,
+ event_name: eventName,
+ value,
+ items,
+ adid: id,
+ adid_type: type,
+ ...customProperties,
+ data_processing_option: $.getDataProcessingOptions(.message),
+ privacy_settings: $.getPrivacySetting(.message),
+ };
+ payload = $.enrichTrackPayload(.message, payload);
+ payload;
+
+ - name: buildResponseForProcessTransformation
+ template: |
+ const response = $.defaultRequestConfig();
+ response.body.JSON = {data: [$.removeUndefinedAndNullValues($.outputs.prepareTrackPayload)]};
+ response.endpoint = $.REAL_TIME_CONVERSION_ENDPOINT;
+ response.headers = {
+ "Content-Type": "application/json"
+ };
+ response;
diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js
new file mode 100644
index 0000000000..2232be61f0
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.js
@@ -0,0 +1,315 @@
+const lodash = require('lodash');
+const get = require('get-value');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const {
+ constructPayload,
+ getHashFromArray,
+ isDefinedAndNotNull,
+ isAppleFamily,
+ getIntegrationsObj,
+ extractCustomFields,
+ generateExclusionList,
+} = require('../../../../v0/util');
+const {
+ CONVERSION_SUPPORTED_ID_TYPES,
+ COMMON_CONFIGS,
+ ITEM_CONFIGS,
+ ECOMM_EVENT_MAP,
+} = require('./config');
+
+const prepareCommonPayload = (message) => constructPayload(message, COMMON_CONFIGS);
+
+const prepareFromConfig = (destination) => ({
+ tracker_id: destination.Config?.trackerId,
+ adv: destination.Config?.advertiserId,
+});
+
+/**
+ * Calculates the revenue based on the given message.
+ *
+ * @param {Object} message - The message object containing the event and properties.
+ * @returns {number} - The calculated revenue.
+ * @throws {InstrumentationError} - If the event is 'Order Completed' and revenue is not provided.
+ */
+const getRevenue = (message) => {
+ const { event, properties } = message;
+ let revenue = properties?.value;
+ const eventsMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
+ if (eventsMapInfo?.rootLevelPriceSupported) {
+ const { price, quantity = 1 } = properties;
+ if (price && !Number.isNaN(parseFloat(price)) && !Number.isNaN(parseInt(quantity, 10))) {
+ revenue = parseFloat(price) * parseInt(quantity, 10);
+ }
+ } else if (eventsMapInfo?.revenueFieldSupported) {
+ revenue = properties?.revenue || revenue;
+ if (event.toLowerCase() === 'order completed' && !revenue) {
+ throw new InstrumentationError('value is required for `Order Completed` event');
+ }
+ }
+
+ return revenue;
+};
+
+/**
+ * Generates items from properties of a given message.
+ *
+ * @param {Object} message - The message object containing properties.
+ * @returns {Array} - An array of items generated from the properties.
+ */
+const prepareItemsFromProperties = (message) => {
+ const { properties } = message;
+ const items = [];
+ const item = constructPayload(properties, ITEM_CONFIGS);
+ items.push(item);
+ return items;
+};
+
+/**
+ * Generates items payload from products.
+ *
+ * @param {Object} message - The message object.
+ * @returns {Array} - The items payload.
+ */
+const prepareItemsFromProducts = (message) => {
+ const products = get(message, 'properties.products');
+ const items = [];
+ products.forEach((product) => {
+ const item = constructPayload(product, ITEM_CONFIGS);
+ const itemExclusionList = generateExclusionList(ITEM_CONFIGS);
+ extractCustomFields(product, item, 'root', itemExclusionList);
+ items.push(item);
+ });
+ return items;
+};
+
+/**
+ * Generates items payload from root properties or products.
+ *
+ * @param {Object} message - The message object containing event and properties.
+ * @returns {Array} - The array of items payload.
+ */
+const prepareItemsPayload = (message) => {
+ const { event } = message;
+ let items;
+ const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
+ if (eventMapInfo?.itemsArray) {
+ // if event is one of the supported ecommerce events and products array is present
+ items = prepareItemsFromProducts(message);
+ } else if (eventMapInfo) {
+ // if event is one of the supported ecommerce events and products array is not present
+ items = prepareItemsFromProperties(message);
+ }
+ return items;
+};
+
+/**
+ * Retrieves the device advertising ID and type based on the provided message.
+ *
+ * @param {Object} message - The message object containing the context.
+ * @returns {Object} - An object containing the device advertising ID and type.
+ */
+const getDeviceAdvertisingId = (message) => {
+ const { context } = message;
+ const deviceId = context?.device?.advertisingId;
+ const osName = context?.os?.name?.toLowerCase();
+
+ let type;
+ switch (osName) {
+ case 'android':
+ type = 'AAID';
+ break;
+ case 'windows':
+ type = 'NAID';
+ break;
+ default:
+ type = isAppleFamily(osName) ? 'IDFA' : undefined;
+ break;
+ }
+
+ return { deviceId, type };
+};
+
+/**
+ * Retrieves the external ID object from the given message context.
+ *
+ * @param {Object} message - The message object containing the context.
+ * @returns {Object|undefined} - The external ID object, or undefined if not found.
+ */
+const getDestinationExternalIDObject = (message) => {
+ const { context } = message;
+ const externalIdArray = context?.externalId || [];
+
+ let externalIdObj;
+
+ if (Array.isArray(externalIdArray)) {
+ externalIdObj = externalIdArray.find(
+ (extIdObj) =>
+ CONVERSION_SUPPORTED_ID_TYPES.includes(extIdObj?.type?.toUpperCase()) && extIdObj?.id,
+ );
+ }
+ return externalIdObj;
+};
+
+/**
+ * Retrieves the advertising ID and type from the given message.
+ *
+ * @param {Object} message - The message object containing the context.
+ * @returns {Object} - An object containing the advertising ID and type.
+ * If the advertising ID and type are found in the device context, they are returned.
+ * If not, the external ID object is checked and if found, its ID and type are returned.
+ * If neither the device context nor the external ID object contain the required information,
+ * an object with null values for ID and type is returned.
+ */
+const getAdvertisingId = (message) => {
+ const { deviceId, type } = getDeviceAdvertisingId(message);
+ if (deviceId && type) {
+ return { id: deviceId, type };
+ }
+ const externalIdObj = getDestinationExternalIDObject(message);
+ if (externalIdObj?.id && externalIdObj?.type) {
+ return { id: externalIdObj.id, type: externalIdObj.type.toUpperCase() };
+ }
+
+ return { id: null, type: null };
+};
+
+/**
+ * Prepares custom properties (td1-td10) for a given message and destination.
+ *
+ * @param {object} message - The message object.
+ * @param {object} destination - The destination object.
+ * @returns {object} - The prepared payload object.
+ */
+const prepareCustomProperties = (message, destination) => {
+ const { customProperties } = destination.Config;
+ const payload = {};
+ if (customProperties) {
+ customProperties.forEach((customProperty) => {
+ const { rudderProperty, tradeDeskProperty } = customProperty;
+ const value = get(message, rudderProperty);
+ if (value) {
+ payload[tradeDeskProperty] = value;
+ // unset the rudder property from the message, since it is already mapped to a trade desk property
+ lodash.unset(message, rudderProperty);
+ }
+ });
+ }
+ return payload;
+};
+
+/**
+ * Retrieves the event name based on the provided message and destination.
+ *
+ * @param {object} message - The message object containing the event.
+ * @param {object} destination - The destination object containing the events mapping configuration.
+ * @returns {string} - The event name.
+ */
+const populateEventName = (message, destination) => {
+ let eventName;
+ const { event } = message;
+ const { eventsMapping } = destination.Config;
+
+ // if event is mapped on dashboard, use the mapped event name
+ if (Array.isArray(eventsMapping) && eventsMapping.length > 0) {
+ const keyMap = getHashFromArray(eventsMapping, 'from', 'to');
+ eventName = keyMap[event.toLowerCase()];
+ }
+
+ if (eventName) {
+ return eventName;
+ }
+
+ // if event is one of the supported ecommerce events, use the mapped trade desk event name
+ const eventMapInfo = ECOMM_EVENT_MAP[event.toLowerCase()];
+ if (isDefinedAndNotNull(eventMapInfo)) {
+ return eventMapInfo.event;
+ }
+
+ // else return the event name as it is
+ return event;
+};
+
+/**
+ * Retrieves the data processing options based on the provided message.
+ *
+ * @param {string} message - The message to process.
+ * @throws {InstrumentationError} - Throws an error if the region is not supported, if no policies are provided, if multiple policies are provided, or if the policy is not supported.
+ * @returns {Object} - The data processing options, including the policies and region.
+ */
+const getDataProcessingOptions = (message) => {
+ const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK') || {};
+ let { policies } = integrationObj;
+ const { region } = integrationObj;
+ let dataProcessingOptions;
+
+ if (region && !region.toLowerCase().startsWith('us')) {
+ throw new InstrumentationError('Only US states are supported');
+ }
+
+ if (!policies || (Array.isArray(policies) && policies.length === 0)) {
+ policies = ['LDU'];
+ }
+
+ if (policies.length > 1) {
+ throw new InstrumentationError('Only one policy is allowed');
+ }
+
+ if (policies[0] !== 'LDU') {
+ throw new InstrumentationError('Only LDU policy is supported');
+ }
+
+ if (policies && region) {
+ dataProcessingOptions = { policies, region };
+ }
+
+ return dataProcessingOptions;
+};
+
+const getPrivacySetting = (message) => {
+ const integrationObj = getIntegrationsObj(message, 'THE_TRADE_DESK');
+ return integrationObj?.privacy_settings;
+};
+
+/**
+ * Enriches the track payload with extra properties present in 'properties' other than the ones defined in TTDCommonConfig.json and TTDItemConfig.json
+ *
+ * @param {Object} message - The message object containing the event information.
+ * @param {Object} payload - The payload object to be enriched.
+ * @returns {Object} - The enriched payload object.
+ */
+const enrichTrackPayload = (message, payload) => {
+ let rawPayload = { ...payload };
+ const eventsMapInfo = ECOMM_EVENT_MAP[message.event.toLowerCase()];
+ // checking if event is an ecomm one and itemsArray/products support is not present. e.g Product Added event
+ if (eventsMapInfo && !eventsMapInfo.itemsArray) {
+ const itemExclusionList = generateExclusionList(ITEM_CONFIGS);
+ rawPayload = extractCustomFields(message, rawPayload, ['properties'], itemExclusionList);
+ } else if (eventsMapInfo) {
+ // for ecomm events with products array supports. e.g Order Completed event
+ rawPayload = extractCustomFields(
+ message,
+ rawPayload,
+ ['properties'],
+ ['products', 'revenue', 'value'],
+ );
+ } else {
+ // for custom events
+ rawPayload = extractCustomFields(message, rawPayload, ['properties'], ['value']);
+ }
+ return rawPayload;
+};
+
+module.exports = {
+ prepareFromConfig,
+ getRevenue,
+ prepareCommonPayload,
+ prepareItemsPayload,
+ getDeviceAdvertisingId,
+ getDestinationExternalIDObject,
+ getAdvertisingId,
+ prepareCustomProperties,
+ populateEventName,
+ getDataProcessingOptions,
+ getPrivacySetting,
+ enrichTrackPayload,
+};
diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js
new file mode 100644
index 0000000000..20b39fab93
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/utils.test.js
@@ -0,0 +1,621 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const {
+ getRevenue,
+ getDeviceAdvertisingId,
+ getDestinationExternalIDObject,
+ getAdvertisingId,
+ prepareCustomProperties,
+ populateEventName,
+ getDataProcessingOptions,
+ getPrivacySetting,
+ enrichTrackPayload,
+} = require('./utils');
+
+describe('getRevenue', () => {
+ it('should return revenue value from message properties for custom events', () => {
+ const message = {
+ event: 'customEvent',
+ properties: {
+ value: 100,
+ },
+ };
+ const result = getRevenue(message);
+ expect(result).toBe(100);
+ });
+
+ it('should calculate revenue based on price and quantity from message properties if ecomm event is supported for price calculation', () => {
+ const message = {
+ event: 'Product Added',
+ properties: {
+ price: 10,
+ quantity: 5,
+ },
+ };
+ const result = getRevenue(message);
+ expect(result).toBe(50);
+ });
+
+ it('should return revenue value from message properties if ecomm event is supported for revenue calculation', () => {
+ const message = {
+ event: 'Order Completed',
+ properties: {
+ revenue: 200,
+ },
+ };
+ const result = getRevenue(message);
+ expect(result).toBe(200);
+ });
+
+ it('should return default revenue value from properties.value for ecomm events', () => {
+ let message = {
+ event: 'Product Added',
+ properties: {
+ price: '',
+ value: 200,
+ },
+ };
+ let result = getRevenue(message);
+ expect(result).toBe(200);
+
+ message = {
+ event: 'Order Completed',
+ properties: {
+ value: 200,
+ },
+ };
+ result = getRevenue(message);
+ expect(result).toBe(200);
+ });
+
+ it('should throw an Instrumentation error if revenue is missing for `Order Completed` event', () => {
+ const message = {
+ event: 'Order Completed',
+ properties: {},
+ };
+ expect(() => {
+ getRevenue(message);
+ }).toThrow(InstrumentationError);
+ });
+});
+
+describe('getDeviceAdvertisingId', () => {
+ it('should return an object with deviceId and type properties when context.device.advertisingId and context.os.name are present', () => {
+ let message = {
+ context: {
+ device: {
+ advertisingId: '123456789',
+ },
+ os: {
+ name: 'android',
+ },
+ },
+ };
+ let result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: '123456789', type: 'AAID' });
+
+ message = {
+ context: {
+ device: {
+ advertisingId: '123456789',
+ },
+ os: {
+ name: 'ios',
+ },
+ },
+ };
+ result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: '123456789', type: 'IDFA' });
+
+ message = {
+ context: {
+ device: {
+ advertisingId: '123456789',
+ },
+ os: {
+ name: 'windows',
+ },
+ },
+ };
+ result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: '123456789', type: 'NAID' });
+ });
+
+ it('should return an object with undefined type property when osName is not "android", "windows", or an Apple OS', () => {
+ const message = {
+ context: {
+ device: {
+ advertisingId: '123456789',
+ },
+ os: {
+ name: 'linux',
+ },
+ },
+ };
+ const result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: '123456789', type: undefined });
+ });
+
+ it('should return an object with undefined deviceId and type properties when context is undefined', () => {
+ let message = {};
+ let result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: undefined, type: undefined });
+
+ message = {
+ context: {},
+ };
+ result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: undefined, type: undefined });
+
+ message = {
+ context: {
+ device: {},
+ },
+ };
+ result = getDeviceAdvertisingId(message);
+ expect(result).toEqual({ deviceId: undefined, type: undefined });
+ });
+});
+
+describe('getDestinationExternalIDObject', () => {
+ it('should return the external ID object when it exists in the message context', () => {
+ const message = {
+ context: {
+ externalId: [
+ { id: '123', type: 'daid' },
+ { id: '456', type: 'type123' },
+ ],
+ },
+ };
+ const result = getDestinationExternalIDObject(message);
+ expect(result).toEqual({ id: '123', type: 'daid' });
+ });
+
+ it('should return undefined when no external ID object exists in the message context', () => {
+ let message = {
+ context: {
+ externalId: [],
+ },
+ };
+ let result = getDestinationExternalIDObject(message);
+ expect(result).toBeUndefined();
+
+ message = {
+ context: {},
+ };
+ result = getDestinationExternalIDObject(message);
+ expect(result).toBeUndefined();
+ });
+
+ it('should return the first matching external ID object in the array', () => {
+ const message = {
+ context: {
+ externalId: [
+ { id: '', type: 'daid' },
+ { id: '456', type: 'tdid' },
+ { id: '789', type: 'UID2' },
+ ],
+ },
+ };
+ const result = getDestinationExternalIDObject(message);
+ expect(result).toEqual({ id: '456', type: 'tdid' });
+ });
+});
+
+describe('getAdvertisingId', () => {
+ it('should return an object with the ID and type when the message contains a valid device advertising ID and OS type', () => {
+ const message = {
+ context: {
+ device: {
+ advertisingId: '1234567890',
+ },
+ os: {
+ name: 'android',
+ },
+ },
+ };
+
+ const result = getAdvertisingId(message);
+ expect(result).toEqual({ id: '1234567890', type: 'AAID' });
+ });
+
+ it('should return an object with the ID and type when the message contains a valid external ID object with a supported type', () => {
+ const message = {
+ context: {
+ externalId: [
+ {
+ type: 'IDFA',
+ id: 'abcdefg',
+ },
+ ],
+ },
+ };
+
+ const result = getAdvertisingId(message);
+ expect(result).toEqual({ id: 'abcdefg', type: 'IDFA' });
+ });
+
+ it('should return an object with undefined ID and type when the message contains a valid external ID object with an unsupported type', () => {
+ let message = {
+ context: {
+ externalId: [
+ {
+ type: 'unsupported',
+ id: '1234567890',
+ },
+ ],
+ },
+ };
+
+ let result = getAdvertisingId(message);
+ expect(result).toEqual({ id: null, type: null });
+
+ message = {
+ context: {
+ device: {
+ advertisingId: '1234567890',
+ },
+ },
+ };
+
+ result = getAdvertisingId(message);
+ expect(result).toEqual({ id: null, type: null });
+ });
+
+ it('should return an object with undefined ID and type when the message contains an external ID object with a supported type but no ID or missing externalId', () => {
+ let message = {
+ context: {
+ externalId: [
+ {
+ type: 'IDFA',
+ },
+ ],
+ },
+ };
+ let result = getAdvertisingId(message);
+ expect(result).toEqual({ id: null, type: null });
+
+ message = {
+ context: {},
+ };
+ result = getAdvertisingId(message);
+ expect(result).toEqual({ id: null, type: null });
+ });
+});
+
+describe('prepareCustomProperties', () => {
+ it('should return an empty object when customProperties is an empty array', () => {
+ const message = {};
+ let destination = { Config: { customProperties: [] } };
+ let result = prepareCustomProperties(message, destination);
+ expect(result).toEqual({});
+
+ destination = { Config: { customProperties: [{ rudderProperty: '', tradeDeskProperty: '' }] } };
+ result = prepareCustomProperties(message, destination);
+ expect(result).toEqual({});
+
+ destination = { Config: { customProperties: undefined } };
+ result = prepareCustomProperties(message, destination);
+ expect(result).toEqual({});
+ });
+
+ it('should return an object with `tradeDeskProperty` as key and `rudderProperty` value as value when `rudderProperty` exists in message', () => {
+ const message = {
+ rudderProperty1: 'value1',
+ rudderProperty2: 'value2',
+ };
+ const destination = {
+ Config: {
+ customProperties: [
+ {
+ rudderProperty: 'rudderProperty1',
+ tradeDeskProperty: 'tradeDeskProperty1',
+ },
+ {
+ rudderProperty: 'rudderProperty2',
+ tradeDeskProperty: 'tradeDeskProperty2',
+ },
+ {
+ rudderProperty: 'rudderProperty3',
+ tradeDeskProperty: 'tradeDeskProperty3',
+ },
+ ],
+ },
+ };
+ const result = prepareCustomProperties(message, destination);
+ expect(result).toEqual({
+ tradeDeskProperty1: 'value1',
+ tradeDeskProperty2: 'value2',
+ });
+ });
+});
+
+describe('populateEventName', () => {
+ it('should return the eventName if it exists in the eventsMapping of destination.Config', () => {
+ const message = { event: 'someEvent' };
+ const destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } };
+ const result = populateEventName(message, destination);
+ expect(result).toBe('mappedEvent');
+ });
+
+ it('should return the eventName if it exists in the ECOMM_EVENT_MAP', () => {
+ const message = { event: 'product added' };
+ let destination = { Config: { eventsMapping: [{ from: 'someEvent', to: 'mappedEvent' }] } };
+ let result = populateEventName(message, destination);
+ expect(result).toBe('addtocart');
+
+ destination = { Config: { eventsMapping: [] } };
+ result = populateEventName(message, destination);
+ expect(result).toBe('addtocart');
+ });
+
+ it('should return undefined if eventsMapping is an empty array', () => {
+ const message = { event: 'someEvent' };
+ const destination = { Config: { eventsMapping: [] } };
+ const result = populateEventName(message, destination);
+ expect(result).toBe('someEvent');
+ });
+});
+
+describe('getDataProcessingOptions', () => {
+ it('should return an object with policies and region when provided a integrationObj in message', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ policies: ['LDU'],
+ region: 'US-CO',
+ },
+ },
+ };
+ const expected = {
+ policies: ['LDU'],
+ region: 'US-CO',
+ };
+ const result = getDataProcessingOptions(message);
+ expect(result).toEqual(expected);
+ });
+
+ it('should throw an InstrumentationError if the region is not a US state', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ policies: ['LDU'],
+ region: 'EU-abc',
+ },
+ },
+ };
+ expect(() => {
+ getDataProcessingOptions(message);
+ }).toThrow(InstrumentationError);
+ });
+
+ it('should throw an InstrumentationError if multiple policies are provided', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ policies: ['LDU', 'Policy2'],
+ region: 'US-CO',
+ },
+ },
+ };
+
+ expect(() => {
+ getDataProcessingOptions(message);
+ }).toThrow(InstrumentationError);
+ });
+
+ it('should throw an InstrumentationError if a policy other than `LDU` is provided', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ policies: ['Policy1'],
+ region: 'US-CO',
+ },
+ },
+ };
+
+ expect(() => {
+ getDataProcessingOptions(message);
+ }).toThrow(InstrumentationError);
+ });
+
+ it('should return an object with default policy `LDU` when policies are not provided', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ policies: [],
+ region: 'US-CO',
+ },
+ },
+ };
+
+ const expected = {
+ policies: ['LDU'],
+ region: 'US-CO',
+ };
+
+ expect(getDataProcessingOptions(message)).toEqual(expected);
+ });
+
+ it('should handle empty cases', () => {
+ let message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {},
+ },
+ };
+
+ expect(getDataProcessingOptions(message)).toBeUndefined();
+
+ message = {
+ integrations: {
+ All: true,
+ },
+ };
+
+ expect(getDataProcessingOptions(message)).toBeUndefined();
+
+ message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: { region: 'US-CO' },
+ },
+ };
+
+ expect(getDataProcessingOptions(message)).toEqual({ policies: ['LDU'], region: 'US-CO' });
+ });
+});
+
+describe('getPrivacySetting', () => {
+ it('should return the privacy settings object when it exists in the integration object', () => {
+ const message = {
+ integrations: {
+ All: true,
+ THE_TRADE_DESK: {
+ privacy_settings: [
+ {
+ privacy_type: 'GDPR',
+ is_applicable: 1,
+ consent_string: 'ok',
+ },
+ ],
+ },
+ },
+ };
+ const expected = [
+ {
+ privacy_type: 'GDPR',
+ is_applicable: 1,
+ consent_string: 'ok',
+ },
+ ];
+ const result = getPrivacySetting(message);
+ expect(result).toEqual(expected);
+ });
+
+ it('should return null when the privacy settings object does not exist in the integration object', () => {
+ let message = { integrations: {} };
+ expect(getPrivacySetting(message)).toBeUndefined();
+
+ message = { integrations: { THE_TRADE_DESK: {} } };
+ expect(getPrivacySetting(message)).toBeUndefined();
+
+ message = { integrations: { THE_TRADE_DESK: { privacy_settings: null } } };
+ expect(getPrivacySetting(message)).toBeNull();
+ });
+});
+
+describe('enrichTrackPayload', () => {
+ it('should correctly enrich the payload with custom fields for ecomm events where product array is not supported', () => {
+ const message = {
+ event: 'Product Added',
+ properties: {
+ product_id: 'prd123',
+ sku: 'sku123',
+ brand: 'brand123',
+ property1: 'value1',
+ property2: 'value2',
+ },
+ };
+ const payload = {
+ items: [
+ {
+ item_code: 'prd123',
+ },
+ ],
+ property1: 'value1',
+ property2: 'value2',
+ };
+ const expectedPayload = {
+ items: [
+ {
+ item_code: 'prd123',
+ },
+ ],
+ brand: 'brand123',
+ property1: 'value1',
+ property2: 'value2',
+ };
+
+ const result = enrichTrackPayload(message, payload);
+ expect(result).toEqual(expectedPayload);
+ });
+
+ it('should correctly enrich the payload with custom fields when the for ecomm events with products array support', () => {
+ const message = {
+ event: 'order completed',
+ properties: {
+ order_id: 'ord123',
+ total: 52.0,
+ subtotal: 45.0,
+ revenue: 50.0,
+ products: [{ product_id: 'prd123', sku: 'sku123', brand: 'brand123' }],
+ property1: 'value1',
+ property2: 'value2',
+ },
+ };
+ const payload = {
+ order_id: 'ord123',
+ value: 50.0,
+ items: [{ item_code: 'prd123', brand: 'brand123' }],
+ property1: 'value1',
+ property2: 'value2',
+ };
+ const expectedPayload = {
+ order_id: 'ord123',
+ total: 52.0,
+ subtotal: 45.0,
+ value: 50.0,
+ items: [{ item_code: 'prd123', brand: 'brand123' }],
+ property1: 'value1',
+ property2: 'value2',
+ };
+
+ const result = enrichTrackPayload(message, payload);
+
+ expect(result).toEqual(expectedPayload);
+ });
+
+ it('should return the enriched payload for custom event', () => {
+ const message = {
+ event: 'someEvent',
+ properties: {
+ order_id: 'ord123',
+ property1: 'value1',
+ property2: 'value2',
+ revenue: 10,
+ value: 11,
+ products: [
+ {
+ product_id: 'prd123',
+ test: 'test',
+ },
+ ],
+ },
+ };
+ const payload = {
+ order_id: 'ord123',
+ value: 11,
+ };
+ const expectedPayload = {
+ order_id: 'ord123',
+ property1: 'value1',
+ property2: 'value2',
+ revenue: 10,
+ value: 11,
+ products: [
+ {
+ product_id: 'prd123',
+ test: 'test',
+ },
+ ],
+ };
+
+ const result = enrichTrackPayload(message, payload);
+ expect(result).toEqual(expectedPayload);
+ });
+});
diff --git a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml
index cd84ecbc87..5862fbf372 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 82d15cdec0..9f1512836e 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/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js
index d1b2b24de0..b84aff1089 100644
--- a/src/constants/destinationCanonicalNames.js
+++ b/src/constants/destinationCanonicalNames.js
@@ -152,6 +152,19 @@ const DestCanonicalNames = {
'the trade desk',
],
INTERCOM: ['INTERCOM', 'intercom', 'Intercom'],
+ GOOGLE_ADWORDS_REMARKETING_LISTS: [
+ 'GOOGLE_ADWORDS_REMARKETING_LISTS',
+ 'google_adwords_remarketing_lists',
+ 'Google Adwords Remarketing Lists',
+ 'google adwords remarketing lists',
+ ],
+ GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: [
+ 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS',
+ 'google_adwords_offline_conversions',
+ 'Google Adwords Offline Conversions',
+ 'google adwords offline conversions',
+ ],
+ koala: ['Koala', 'koala', 'KOALA'],
};
module.exports = { DestHandlerMap, DestCanonicalNames };
diff --git a/src/controllers/__tests__/delivery.test.ts b/src/controllers/__tests__/delivery.test.ts
new file mode 100644
index 0000000000..0f91913f9d
--- /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 0000000000..3c49a9a0af
--- /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 0000000000..55cd8f2d37
--- /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 0000000000..565f39d559
--- /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/bulkUpload.ts b/src/controllers/bulkUpload.ts
index eb23e82b12..088acb9db8 100644
--- a/src/controllers/bulkUpload.ts
+++ b/src/controllers/bulkUpload.ts
@@ -1,14 +1,14 @@
/* eslint-disable global-require, import/no-dynamic-require, @typescript-eslint/no-unused-vars */
import logger from "@rudderstack/integrations-lib/build/structured-logger";
import { client as errNotificationClient } from '../util/errorNotifier';
+import {
+ getJobStatusHandler,
+ getPollStatusHandler,
+ getDestFileUploadHandler,
+} from '../util/fetchDestinationHandlers';
import { CatchErr, ContextBodySimple } from '../util/types';
// TODO: To be refactored and redisgned
-const getDestFileUploadHandler = (version, dest) =>
- require(`../${version}/destinations/${dest}/fileUpload`);
-const getPollStatusHandler = (version, dest) => require(`../${version}/destinations/${dest}/poll`);
-const getJobStatusHandler = (version, dest) =>
- require(`../${version}/destinations/${dest}/fetchJobStatus`);
const ERROR_MESSAGE_PROCESSOR_STRING = 'Error occurred while processing payload.';
const getCommonMetadata = (ctx) =>
diff --git a/src/controllers/delivery.ts b/src/controllers/delivery.ts
index 9ffcb2fa73..d43e1de4e1 100644
--- a/src/controllers/delivery.ts
+++ b/src/controllers/delivery.ts
@@ -2,13 +2,15 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { Context } from 'koa';
import logger from "@rudderstack/integrations-lib/build/structured-logger";
+=======
+import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib';
import { MiscService } from '../services/misc';
import {
- DeliveriesResponse,
- DeliveryResponse,
+ DeliveryV1Response,
+ DeliveryV0Response,
ProcessorTransformationOutput,
- ProxyDeliveriesRequest,
- ProxyDeliveryRequest,
+ ProxyV0Request,
+ ProxyV1Request,
} from '../types/index';
import { ServiceSelector } from '../helpers/serviceSelector';
import { DeliveryTestService } from '../services/delivertTest/deliveryTest';
@@ -21,10 +23,10 @@ const NON_DETERMINABLE = 'Non-determinable';
export class DeliveryController {
public static async deliverToDestination(ctx: Context) {
- logger.debug('Native(Delivery):: Request to transformer::', ctx.request.body);
- let deliveryResponse: DeliveryResponse;
+ logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body));
+ let deliveryResponse: DeliveryV0Response;
const requestMetadata = MiscService.getRequestMetadata(ctx);
- const deliveryRequest = ctx.request.body as ProxyDeliveryRequest;
+ const deliveryRequest = ctx.request.body as ProxyV0Request;
const { destination }: { destination: string } = ctx.params;
const integrationService = ServiceSelector.getNativeDestinationService();
try {
@@ -33,7 +35,7 @@ export class DeliveryController {
destination,
requestMetadata,
'v0',
- )) as DeliveryResponse;
+ )) as DeliveryV0Response;
} catch (error: any) {
const { metadata } = deliveryRequest;
const metaTO = integrationService.getTags(
@@ -56,10 +58,10 @@ export class DeliveryController {
}
public static async deliverToDestinationV1(ctx: Context) {
- logger.debug('Native(Delivery):: Request to transformer::', ctx.request.body);
- let deliveryResponse: DeliveriesResponse;
+ logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body));
+ let deliveryResponse: DeliveryV1Response;
const requestMetadata = MiscService.getRequestMetadata(ctx);
- const deliveryRequest = ctx.request.body as ProxyDeliveriesRequest;
+ const deliveryRequest = ctx.request.body as ProxyV1Request;
const { destination }: { destination: string } = ctx.params;
const integrationService = ServiceSelector.getNativeDestinationService();
try {
@@ -68,7 +70,7 @@ export class DeliveryController {
destination,
requestMetadata,
'v1',
- )) as DeliveriesResponse;
+ )) as DeliveryV1Response;
} catch (error: any) {
const { metadata } = deliveryRequest;
const metaTO = integrationService.getTags(
@@ -84,7 +86,11 @@ export class DeliveryController {
);
}
ctx.body = { output: deliveryResponse };
- ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status);
+ if (isDefinedAndNotNullAndNotEmpty(deliveryResponse.authErrorCategory)) {
+ ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status);
+ } else {
+ ControllerUtility.deliveryPostProcess(ctx);
+ }
logger.debug('Native(Delivery):: Response from transformer::', ctx.body);
return ctx;
diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts
index bef6b381ce..8b54627f97 100644
--- a/src/controllers/destination.ts
+++ b/src/controllers/destination.ts
@@ -15,6 +15,7 @@ import stats from '../util/stats';
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 44a6f2a4c5..dfc7b1fd79 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.errorw(response.message);
logger.errorw(err);
diff --git a/src/controllers/regulation.ts b/src/controllers/regulation.ts
index a600c9ecf0..99dfe67440 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/trackingPlan.ts b/src/controllers/trackingPlan.ts
index 74e47e0ec9..e4802cfc4d 100644
--- a/src/controllers/trackingPlan.ts
+++ b/src/controllers/trackingPlan.ts
@@ -7,7 +7,7 @@ export class TrackingPlanController {
const events = ctx.request.body;
const requestSize = Number(ctx.request.get('content-length'));
const reqParams = ctx.request.query;
- const response = await TrackingPlanservice.validateTrackingPlan(events, requestSize, reqParams);
+ const response = await TrackingPlanservice.validate(events, requestSize, reqParams);
ctx.body = response.body;
ControllerUtility.postProcess(ctx, response.status);
return ctx;
diff --git a/src/controllers/userTransform.ts b/src/controllers/userTransform.ts
index fa7f8bbb66..7a90172042 100644
--- a/src/controllers/userTransform.ts
+++ b/src/controllers/userTransform.ts
@@ -15,9 +15,10 @@ export class UserTransformController {
'(User transform - router:/customTransform ):: Request to transformer',
ctx.request.body,
);
+ const requestSize = Number(ctx.request.get('content-length'));
const events = ctx.request.body as ProcessorTransformationRequest[];
const processedRespone: UserTransformationServiceResponse =
- await UserTransformService.transformRoutine(events, ctx.state.features);
+ await UserTransformService.transformRoutine(events, ctx.state.features, requestSize);
ctx.body = processedRespone.transformedEvents;
ControllerUtility.postProcess(ctx, processedRespone.retryStatus);
logger.debug(
diff --git a/src/controllers/util/index.ts b/src/controllers/util/index.ts
index 75d3d8ffa7..c5bf7ab358 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 8709dce432..267923fdb4 100644
--- a/src/features.json
+++ b/src/features.json
@@ -65,7 +65,10 @@
"TIKTOK_AUDIENCE": true,
"REDDIT": true,
"THE_TRADE_DESK": true,
- "INTERCOM": true
+ "INTERCOM": true,
+ "NINETAILED": true,
+ "MOVABLE_INK": true,
+ "KOALA": true
},
"regulations": [
"BRAZE",
@@ -82,5 +85,5 @@
"SPRIG"
],
"supportSourceTransformV1": true,
- "supportTransformerProxyV1": false
+ "supportTransformerProxyV1": true
}
diff --git a/src/helpers/__tests__/fetchHandlers.test.ts b/src/helpers/__tests__/fetchHandlers.test.ts
new file mode 100644
index 0000000000..2135317caf
--- /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 0000000000..c48d6bbe8b
--- /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 89678e9407..faa1c58240 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/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts
index bf39024d85..4947089b5d 100644
--- a/src/interfaces/DestinationService.ts
+++ b/src/interfaces/DestinationService.ts
@@ -1,5 +1,5 @@
import {
- DeliveryResponse,
+ DeliveryV0Response,
MetaTransferObject,
ProcessorTransformationRequest,
ProcessorTransformationResponse,
@@ -8,7 +8,7 @@ import {
UserDeletionRequest,
UserDeletionResponse,
ProxyRequest,
- DeliveriesResponse,
+ DeliveryV1Response,
} from '../types/index';
export interface DestinationService {
@@ -49,7 +49,7 @@ export interface DestinationService {
destinationType: string,
requestMetadata: NonNullable,
version: string,
- ): Promise;
+ ): Promise;
processUserDeletion(
requests: UserDeletionRequest[],
diff --git a/src/legacy/router.js b/src/legacy/router.js
index ecaba9697a..f8e711361f 100644
--- a/src/legacy/router.js
+++ b/src/legacy/router.js
@@ -7,7 +7,8 @@ const Router = require('@koa/router');
const lodash = require('lodash');
const fs = require('fs');
const path = require('path');
-const { PlatformError } = require('@rudderstack/integrations-lib');
+const logger = require('@rudderstack/integrations-lib/build/structured-logger');
+const { PlatformError, getErrorRespEvents } = require('@rudderstack/integrations-lib');
const logger = require('@rudderstack/integrations-lib/build/structured-logger');
const stats = require('../util/stats');
const { SUPPORTED_VERSIONS, API_VERSION } = require('../routes/utils/constants');
@@ -18,7 +19,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 0000000000..5dcd948b34
--- /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/comparator.ts b/src/services/comparator.ts
index 5dce1eacea..3e87221c66 100644
--- a/src/services/comparator.ts
+++ b/src/services/comparator.ts
@@ -2,8 +2,8 @@
import logger from '@rudderstack/integrations-lib/build/structured-logger';
import { DestinationService } from '../interfaces/DestinationService';
import {
- DeliveriesResponse,
- DeliveryResponse,
+ DeliveryV0Response,
+ DeliveryV1Response,
Destination,
ErrorDetailer,
MetaTransferObject,
@@ -370,7 +370,7 @@ export class ComparatorService implements DestinationService {
destinationType: string,
requestMetadata: NonNullable,
version: string,
- ): Promise {
+ ): Promise {
const primaryResplist = await this.primaryService.deliver(
event,
destinationType,
diff --git a/src/services/destination/__tests__/nativeIntegration.test.ts b/src/services/destination/__tests__/nativeIntegration.test.ts
new file mode 100644
index 0000000000..59c8b41881
--- /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 0000000000..f961dcbce7
--- /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 0000000000..c10bab78ac
--- /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/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts
index 197e3162ea..c6e60f5857 100644
--- a/src/services/destination/cdkV1Integration.ts
+++ b/src/services/destination/cdkV1Integration.ts
@@ -4,7 +4,7 @@ import path from 'path';
import { TransformationError } from '@rudderstack/integrations-lib';
import { DestinationService } from '../../interfaces/DestinationService';
import {
- DeliveryResponse,
+ DeliveryV0Response,
ErrorDetailer,
MetaTransferObject,
ProcessorTransformationRequest,
@@ -14,7 +14,7 @@ import {
UserDeletionRequest,
UserDeletionResponse,
ProxyRequest,
- DeliveriesResponse,
+ DeliveryV1Response,
} from '../../types/index';
import { DestinationPostTransformationService } from './postTransformation';
import tags from '../../v0/util/tags';
@@ -121,7 +121,7 @@ export class CDKV1DestinationService implements DestinationService {
_event: ProxyRequest,
_destinationType: string,
_requestMetadata: NonNullable,
- ): Promise {
+ ): Promise {
throw new TransformationError('CDV1 Does not Implement Delivery Routine');
}
diff --git a/src/services/destination/cdkV2Integration.ts b/src/services/destination/cdkV2Integration.ts
index 34270b421c..d19af52630 100644
--- a/src/services/destination/cdkV2Integration.ts
+++ b/src/services/destination/cdkV2Integration.ts
@@ -5,7 +5,7 @@ import { TransformationError } from '@rudderstack/integrations-lib';
import { processCdkV2Workflow } from '../../cdk/v2/handler';
import { DestinationService } from '../../interfaces/DestinationService';
import {
- DeliveryResponse,
+ DeliveryV0Response,
ErrorDetailer,
MetaTransferObject,
ProcessorTransformationRequest,
@@ -16,7 +16,7 @@ import {
UserDeletionRequest,
UserDeletionResponse,
ProxyRequest,
- DeliveriesResponse,
+ DeliveryV1Response,
} from '../../types/index';
import tags from '../../v0/util/tags';
import { DestinationPostTransformationService } from './postTransformation';
@@ -179,7 +179,7 @@ export class CDKV2DestinationService implements DestinationService {
_event: ProxyRequest,
_destinationType: string,
_requestMetadata: NonNullable,
- ): Promise {
+ ): Promise {
throw new TransformationError('CDKV2 Does not Implement Delivery Routine');
}
diff --git a/src/services/destination/nativeIntegration.ts b/src/services/destination/nativeIntegration.ts
index 95fe6a7872..6a94a39c45 100644
--- a/src/services/destination/nativeIntegration.ts
+++ b/src/services/destination/nativeIntegration.ts
@@ -5,7 +5,7 @@ import groupBy from 'lodash/groupBy';
import cloneDeep from 'lodash/cloneDeep';
import { DestinationService } from '../../interfaces/DestinationService';
import {
- DeliveryResponse,
+ DeliveryV0Response,
ErrorDetailer,
MetaTransferObject,
ProcessorTransformationRequest,
@@ -16,9 +16,9 @@ import {
UserDeletionRequest,
UserDeletionResponse,
ProxyRequest,
- ProxyDeliveriesRequest,
- ProxyDeliveryRequest,
- DeliveriesResponse,
+ ProxyV0Request,
+ ProxyV1Request,
+ DeliveryV1Response,
DeliveryJobState,
} from '../../types/index';
import { DestinationPostTransformationService } from './postTransformation';
@@ -197,7 +197,7 @@ export class NativeIntegrationDestinationService implements DestinationService {
destinationType: string,
_requestMetadata: NonNullable,
version: string,
- ): Promise {
+ ): Promise {
try {
const { networkHandler, handlerVersion } = networkHandlerFactory.getNetworkHandler(
destinationType,
@@ -207,37 +207,39 @@ export class NativeIntegrationDestinationService implements DestinationService {
const processedProxyResponse = networkHandler.processAxiosResponse(rawProxyResponse);
let rudderJobMetadata =
version.toLowerCase() === 'v1'
- ? (deliveryRequest as ProxyDeliveriesRequest).metadata
- : (deliveryRequest as ProxyDeliveryRequest).metadata;
+ ? (deliveryRequest as ProxyV1Request).metadata
+ : (deliveryRequest as ProxyV0Request).metadata;
if (version.toLowerCase() === 'v1' && handlerVersion.toLowerCase() === 'v0') {
rudderJobMetadata = rudderJobMetadata[0];
}
-
- let responseProxy = networkHandler.responseHandler(
- {
- ...processedProxyResponse,
- rudderJobMetadata,
- },
- destinationType,
- );
+ const responseParams = {
+ destinationResponse: processedProxyResponse,
+ rudderJobMetadata,
+ destType: destinationType,
+ };
+ let responseProxy = networkHandler.responseHandler(responseParams);
// Adaption Logic for V0 to V1
if (handlerVersion.toLowerCase() === 'v0' && version.toLowerCase() === 'v1') {
- const v0Response = responseProxy as DeliveryResponse;
- const jobStates = (deliveryRequest as ProxyDeliveriesRequest).metadata.map(
+ const v0Response = responseProxy as DeliveryV0Response;
+ 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,
status: v0Response.status,
message: v0Response.message,
authErrorCategory: v0Response.authErrorCategory,
- } as DeliveriesResponse;
+ } as DeliveryV1Response;
}
return responseProxy;
} catch (err: any) {
@@ -252,10 +254,10 @@ export class NativeIntegrationDestinationService implements DestinationService {
);
if (version.toLowerCase() === 'v1') {
- metaTO.metadatas = (deliveryRequest as ProxyDeliveriesRequest).metadata;
+ metaTO.metadatas = (deliveryRequest as ProxyV1Request).metadata;
return DestinationPostTransformationService.handlevV1DeliveriesFailureEvents(err, metaTO);
}
- metaTO.metadata = (deliveryRequest as ProxyDeliveryRequest).metadata;
+ metaTO.metadata = (deliveryRequest as ProxyV0Request).metadata;
return DestinationPostTransformationService.handleDeliveryFailureEvents(err, metaTO);
}
}
diff --git a/src/services/destination/postTransformation.ts b/src/services/destination/postTransformation.ts
index bd4c4afd63..bbce9fdc7d 100644
--- a/src/services/destination/postTransformation.ts
+++ b/src/services/destination/postTransformation.ts
@@ -8,10 +8,10 @@ import {
ProcessorTransformationResponse,
RouterTransformationResponse,
ProcessorTransformationOutput,
- DeliveryResponse,
+ DeliveryV0Response,
MetaTransferObject,
UserDeletionResponse,
- DeliveriesResponse,
+ DeliveryV1Response,
DeliveryJobState,
} from '../../types/index';
import { generateErrorObject } from '../../v0/util';
@@ -85,7 +85,13 @@ export class DestinationPostTransformationService {
): RouterTransformationResponse[] {
const resultantPayloads: RouterTransformationResponse[] = cloneDeep(transformedPayloads);
resultantPayloads.forEach((resultantPayload) => {
- if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) {
+ if (Array.isArray(resultantPayload.batchedRequest)) {
+ resultantPayload.batchedRequest.forEach((batchedRequest) => {
+ if (batchedRequest.userId) {
+ batchedRequest.userId = `${batchedRequest.userId}`;
+ }
+ });
+ } else if (resultantPayload.batchedRequest && resultantPayload.batchedRequest.userId) {
resultantPayload.batchedRequest.userId = `${resultantPayload.batchedRequest.userId}`;
}
});
@@ -161,7 +167,7 @@ export class DestinationPostTransformationService {
public static handleDeliveryFailureEvents(
error: any,
metaTo: MetaTransferObject,
- ): DeliveryResponse {
+ ): DeliveryV0Response {
const errObj = generateErrorObject(error, metaTo.errorDetails, false);
const resp = {
status: errObj.status,
@@ -171,16 +177,16 @@ export class DestinationPostTransformationService {
...(errObj.authErrorCategory && {
authErrorCategory: errObj.authErrorCategory,
}),
- } as DeliveryResponse;
- MiscService.logError(errObj.message || defaultErrorMessages.delivery, metaTo.errorDetails);
+ } as DeliveryV0Response;
ErrorReportingService.reportError(error, metaTo.errorContext, resp);
+ MiscService.logError(errObj.message || defaultErrorMessages.delivery, metaTo.errorDetails);
return resp;
}
public static handlevV1DeliveriesFailureEvents(
error: FixMe,
metaTo: MetaTransferObject,
- ): DeliveriesResponse {
+ ): DeliveryV1Response {
const errObj = generateErrorObject(error, metaTo.errorDetails, false);
const metadataArray = metaTo.metadatas;
if (!Array.isArray(metadataArray)) {
@@ -206,12 +212,14 @@ export class DestinationPostTransformationService {
const resp = {
response: responses,
statTags: errObj.statTags,
- authErrorCategory: errObj.authErrorCategory,
message: errObj.message.toString(),
status: errObj.status,
- } as DeliveriesResponse;
- MiscService.logError(errObj.message, metaTo.errorDetails);
+ ...(errObj.authErrorCategory && {
+ authErrorCategory: errObj.authErrorCategory,
+ }),
+ } as DeliveryV1Response;
ErrorReportingService.reportError(error, metaTo.errorContext, resp);
+ MiscService.logError(errObj.message, metaTo.errorDetails);
return resp;
}
diff --git a/src/services/source/__tests__/nativeIntegration.test.ts b/src/services/source/__tests__/nativeIntegration.test.ts
new file mode 100644
index 0000000000..bb40438811
--- /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 0000000000..e5efbe8194
--- /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/trackingPlan.ts b/src/services/trackingPlan.ts
index 9f7554f133..4a713ad3fc 100644
--- a/src/services/trackingPlan.ts
+++ b/src/services/trackingPlan.ts
@@ -1,90 +1,89 @@
-import logger from "@rudderstack/integrations-lib/build/structured-logger";
+import logger from '@rudderstack/integrations-lib/build/structured-logger';
import { RetryRequestError, RespStatusError, constructValidationErrors } from '../util/utils';
-import { getMetadata } from '../v0/util';
+import { getMetadata, getTrackingPlanMetadata } from '../v0/util';
import eventValidator from '../util/eventValidation';
import stats from '../util/stats';
+import { HTTP_STATUS_CODES } from '../v0/util/constant';
export class TrackingPlanservice {
- public static async validateTrackingPlan(events, requestSize, reqParams) {
- const requestStartTime = new Date();
+ public static async validate(events, requestSize, reqParams) {
+ const startTime = Date.now();
const respList: any[] = [];
- const metaTags = events[0].metadata ? getMetadata(events[0].metadata) : {};
+ const metaTags = events.length && events[0].metadata ? getMetadata(events[0].metadata) : {};
+ const tpTags: any =
+ events.length && events[0].metadata ? getTrackingPlanMetadata(events[0].metadata) : {};
let ctxStatusCode = 200;
for (let i = 0; i < events.length; i++) {
+ let eventValidationResponse: any;
+ let exceptionOccured = false;
+ const eventStartTime = Date.now();
const event = events[i];
- const eventStartTime = new Date();
+
try {
- const parsedEvent = event;
- parsedEvent.request = { query: reqParams };
- const hv = await eventValidator.handleValidation(parsedEvent);
- if (hv['dropEvent']) {
- respList.push({
- output: event.message,
- metadata: event.metadata,
- statusCode: 400,
- validationErrors: hv['validationErrors'],
- error: JSON.stringify(constructValidationErrors(hv['validationErrors'])),
- });
- stats.counter('tp_violation_type', 1, {
- violationType: hv['violationType'],
- ...metaTags,
- });
- } else {
- respList.push({
- output: event.message,
- metadata: event.metadata,
- statusCode: 200,
- validationErrors: hv['validationErrors'],
- error: JSON.stringify(constructValidationErrors(hv['validationErrors'])),
- });
- stats.counter('tp_propagated_events', 1, {
- ...metaTags,
- });
- }
- } catch (error) {
- const errMessage = `Error occurred while validating : ${error}`;
- logger.errorw(errMessage);
- let status = 200;
+ event.request = { query: reqParams };
+ const validatedEvent = await eventValidator.handleValidation(event);
+ eventValidationResponse = {
+ output: event.message,
+ metadata: event.metadata,
+ statusCode: validatedEvent['dropEvent']
+ ? HTTP_STATUS_CODES.BAD_REQUEST
+ : HTTP_STATUS_CODES.OK,
+ validationErrors: validatedEvent['validationErrors'],
+ error: JSON.stringify(constructValidationErrors(validatedEvent['validationErrors'])),
+ };
+ } catch (error: any) {
+ logger.debug(
+ `Error occurred while validating event`,
+ 'event',
+ `${event.message?.event}::${event.message?.type}`,
+ 'trackingPlan',
+ `${tpTags?.trackingPlanId}`,
+ 'error',
+ error.message,
+ );
+
+ exceptionOccured = true;
+ // no need to process further if
+ // we have error of retry request error
if (error instanceof RetryRequestError) {
ctxStatusCode = error.statusCode;
+ break;
}
- if (error instanceof RespStatusError) {
- status = error.statusCode;
- }
- respList.push({
+
+ eventValidationResponse = {
output: event.message,
metadata: event.metadata,
- statusCode: status,
+ statusCode: error instanceof RespStatusError ? error.statusCode : HTTP_STATUS_CODES.OK,
validationErrors: [],
- error: errMessage,
- });
- stats.counter('tp_errors', 1, {
- ...metaTags,
- workspaceId: event.metadata?.workspaceId,
- trackingPlanId: event.metadata?.trackingPlanId,
- });
+ error: `Error occurred while validating: ${error}`,
+ };
} finally {
- stats.timing('tp_event_latency', eventStartTime, {
+ // finally on every event, we need to
+ // capture the information related to the validates event
+ stats.timing('tp_event_validation_latency', eventStartTime, {
...metaTags,
+ ...tpTags,
+ status: eventValidationResponse.statusCode,
+ exception: exceptionOccured,
});
}
- }
- stats.counter('tp_events_count', events.length, {
- ...metaTags,
- });
+ respList.push(eventValidationResponse);
+ }
- stats.histogram('tp_request_size', requestSize, {
+ stats.histogram('tp_batch_size', requestSize, {
...metaTags,
+ ...tpTags,
});
- stats.timing('tp_request_latency', requestStartTime, {
+ // capture overall function latency
+ // with metadata tags
+ stats.histogram('tp_batch_validation_latency', (Date.now() - startTime) / 1000, {
...metaTags,
- workspaceId: events[0]?.metadata?.workspaceId,
- trackingPlanId: events[0]?.metadata?.trackingPlanId,
+ ...tpTags,
});
return { body: respList, status: ctxStatusCode };
}
-}
+}
\ No newline at end of file
diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts
index a5d93683f4..1b066bfb97 100644
--- a/src/services/userTransform.ts
+++ b/src/services/userTransform.ts
@@ -15,7 +15,7 @@ import {
RetryRequestError,
extractStackTraceUptoLastSubstringMatch,
} from '../util/utils';
-import { getMetadata, isNonFuncObject } from '../v0/util';
+import { getMetadata, getTransformationMetadata, isNonFuncObject } from '../v0/util';
import { SUPPORTED_FUNC_NAMES } from '../util/ivmFactory';
import stats from '../util/stats';
import { CommonUtils } from '../util/common';
@@ -28,6 +28,7 @@ export class UserTransformService {
public static async transformRoutine(
events: ProcessorTransformationRequest[],
features: FeatureFlags = {},
+ requestSize = 0,
): Promise {
let retryStatus = 200;
const groupedEvents: NonNullable = groupBy(
@@ -158,20 +159,23 @@ export class UserTransformService {
statusCode: status,
metadata: e.metadata,
error: errorString,
- } as ProcessorTransformationResponse),
+ }) as ProcessorTransformationResponse,
),
);
stats.counter('user_transform_errors', eventsToProcess.length, {
- transformationId: eventsToProcess[0]?.metadata?.transformationId,
- workspaceId: eventsToProcess[0]?.metadata?.workspaceId,
status,
...metaTags,
+ ...getTransformationMetadata(eventsToProcess[0]?.metadata),
});
} finally {
stats.timing('user_transform_request_latency', userFuncStartTime, {
- workspaceId: eventsToProcess[0]?.metadata?.workspaceId,
- transformationId: eventsToProcess[0]?.metadata?.transformationId,
...metaTags,
+ ...getTransformationMetadata(eventsToProcess[0]?.metadata),
+ });
+
+ stats.histogram('user_transform_batch_size', requestSize, {
+ ...metaTags,
+ ...getTransformationMetadata(eventsToProcess[0]?.metadata),
});
}
diff --git a/src/types/index.ts b/src/types/index.ts
index f4432e5c2a..68dfe3870d 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -6,7 +6,7 @@ type ProcessorTransformationOutput = {
type: string;
method: string;
endpoint: string;
- userId: string;
+ userId?: string;
headers?: Record;
params?: Record;
body?: {
@@ -18,7 +18,7 @@ type ProcessorTransformationOutput = {
files?: Record;
};
-type ProxyDeliveryRequest = {
+type ProxyV0Request = {
version: string;
type: string;
method: string;
@@ -33,10 +33,11 @@ type ProxyDeliveryRequest = {
FORM?: Record;
};
files?: Record;
- metadata: Metadata;
+ metadata: ProxyMetdata;
+ destinationConfig: Record;
};
-type ProxyDeliveriesRequest = {
+type ProxyV1Request = {
version: string;
type: string;
method: string;
@@ -51,10 +52,24 @@ type ProxyDeliveriesRequest = {
FORM?: Record;
};
files?: Record;
- metadata: Metadata[];
+ metadata: ProxyMetdata[];
+ destinationConfig: Record;
};
-type ProxyRequest = ProxyDeliveryRequest | ProxyDeliveriesRequest;
+type ProxyRequest = ProxyV0Request | ProxyV1Request;
+
+type ProxyMetdata = {
+ jobId: number;
+ attemptNum: number;
+ userId: string;
+ sourceId: string;
+ destinationId: string;
+ workspaceId: string;
+ secret: Record;
+ destInfo?: Record;
+ omitempty?: Record;
+ dontBatch: boolean;
+};
type Metadata = {
sourceId: string;
@@ -127,7 +142,7 @@ type ProcessorTransformationRequest = {
message: object;
metadata: Metadata;
destination: Destination;
- libraries: UserTransformationLibrary[];
+ libraries?: UserTransformationLibrary[];
};
type RouterTransformationRequestData = {
@@ -147,17 +162,17 @@ type ProcessorTransformationResponse = {
metadata: Metadata;
statusCode: number;
error?: string;
- statTags: object;
+ statTags?: object;
};
type RouterTransformationResponse = {
- batchedRequest?: ProcessorTransformationOutput;
+ batchedRequest?: ProcessorTransformationOutput | ProcessorTransformationOutput[];
metadata: Metadata[];
destination: Destination;
batched: boolean;
statusCode: number;
- error: string;
- statTags: object;
+ error?: string;
+ statTags?: object;
};
type SourceTransformationOutput = {
@@ -172,7 +187,7 @@ type SourceTransformationResponse = {
statTags: object;
};
-type DeliveryResponse = {
+type DeliveryV0Response = {
status: number;
message: string;
destinationResponse: any;
@@ -183,13 +198,14 @@ type DeliveryResponse = {
type DeliveryJobState = {
error: string;
statusCode: number;
- metadata: Metadata;
+ metadata: ProxyMetdata;
};
-type DeliveriesResponse = {
- status?: number;
- message?: string;
+type DeliveryV1Response = {
+ status: number;
+ message: string;
statTags?: object;
+ destinationResponse?: any;
authErrorCategory?: string;
response: DeliveryJobState[];
};
@@ -236,13 +252,22 @@ type ErrorDetailer = {
sourceId?: string;
};
-type MetaTransferObject = {
- metadatas?: Metadata[];
- metadata?: Metadata;
+type MetaTransferObjectForProxy = {
+ metadata?: ProxyMetdata;
+ metadatas?: ProxyMetdata[];
errorDetails: ErrorDetailer;
errorContext: string;
};
+type MetaTransferObject =
+ | {
+ metadatas?: Metadata[];
+ metadata?: Metadata;
+ errorDetails: ErrorDetailer;
+ errorContext: string;
+ }
+ | MetaTransferObjectForProxy;
+
type UserTransformationResponse = {
transformedEvent: RudderMessage;
metadata: Metadata;
@@ -307,8 +332,8 @@ type SourceInput = {
export {
ComparatorInput,
DeliveryJobState,
- DeliveryResponse,
- DeliveriesResponse,
+ DeliveryV0Response,
+ DeliveryV1Response,
Destination,
ErrorDetailer,
MessageIdMetadataMap,
@@ -317,9 +342,10 @@ export {
ProcessorTransformationOutput,
ProcessorTransformationRequest,
ProcessorTransformationResponse,
- ProxyDeliveriesRequest,
- ProxyDeliveryRequest,
+ ProxyMetdata,
ProxyRequest,
+ ProxyV0Request,
+ ProxyV1Request,
RouterTransformationRequest,
RouterTransformationRequestData,
RouterTransformationResponse,
diff --git a/src/types/zodTypes.ts b/src/types/zodTypes.ts
new file mode 100644
index 0000000000..0a65a2bae2
--- /dev/null
+++ b/src/types/zodTypes.ts
@@ -0,0 +1,242 @@
+import { z } from 'zod';
+import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib';
+import { isHttpStatusSuccess } from '../v0/util';
+
+const ProcessorTransformationOutputSchema = z.object({
+ version: z.string(),
+ type: z.string(),
+ method: z.string(),
+ endpoint: z.string(),
+ userId: z.string().optional(),
+ headers: z.record(z.unknown()).optional(),
+ params: z.record(z.unknown()).optional(),
+ body: z
+ .object({
+ JSON: z.record(z.unknown()).optional(),
+ JSON_ARRAY: z.record(z.unknown()).optional(),
+ XML: z.record(z.unknown()).optional(),
+ FORM: z.record(z.unknown()).optional(),
+ })
+ .optional(),
+ files: z.record(z.unknown()).optional(),
+});
+
+export const ProcessorTransformationResponseSchema = z
+ .object({
+ output: ProcessorTransformationOutputSchema.optional(),
+ metadata: z.record(z.unknown()),
+ statusCode: z.number(),
+ error: z.string().optional(),
+ statTags: z.record(z.unknown()).optional(),
+ })
+ .refine(
+ (data) => {
+ if (!isHttpStatusSuccess(data.statusCode)) {
+ return (
+ isDefinedAndNotNullAndNotEmpty(data.statTags) ||
+ isDefinedAndNotNullAndNotEmpty(data.error)
+ );
+ }
+ return true;
+ },
+ {
+ message: "statTags and error can't be empty when status is not a 2XX",
+ path: ['statTags', 'error'], // Pointing out which field is invalid
+ },
+ )
+ .refine(
+ (data) => {
+ if (isHttpStatusSuccess(data.statusCode)) {
+ return isDefinedAndNotNullAndNotEmpty(data.output);
+ }
+ return true;
+ },
+ {
+ message: "output can't be empty when status is 2XX",
+ path: ['output'], // Pointing out which field is invalid
+ },
+ );
+
+export const ProcessorTransformationResponseListSchema = z.array(
+ ProcessorTransformationResponseSchema,
+);
+
+export const RouterTransformationResponseSchema = z
+ .object({
+ batchedRequest: z
+ .array(ProcessorTransformationOutputSchema)
+ .or(ProcessorTransformationOutputSchema)
+ .optional(),
+ metadata: z.array(z.record(z.unknown())), // array of metadata
+ destination: z.record(z.unknown()),
+ batched: z.boolean(),
+ statusCode: z.number(),
+ error: z.string().optional(),
+ statTags: z.record(z.unknown()).optional(),
+ })
+ .refine(
+ (data) => {
+ if (!isHttpStatusSuccess(data.statusCode)) {
+ return (
+ isDefinedAndNotNullAndNotEmpty(data.statTags) ||
+ isDefinedAndNotNullAndNotEmpty(data.error)
+ );
+ }
+ return true;
+ },
+ {
+ message: "statTags and error can't be empty when status is not a 2XX",
+ path: ['statTags', 'error'], // Pointing out which field is invalid
+ },
+ )
+ .refine(
+ (data) => {
+ if (isHttpStatusSuccess(data.statusCode)) {
+ return isDefinedAndNotNullAndNotEmpty(data.batchedRequest);
+ }
+ return true;
+ },
+ {
+ message: "batchedRequest can't be empty when status is 2XX",
+ path: ['batchedRequest'], // Pointing out which field is invalid
+ },
+ );
+
+export const RouterTransformationResponseListSchema = z.array(RouterTransformationResponseSchema);
+
+// Proxy related schemas
+export const ProxyMetadataSchema = z.object({
+ jobId: z.number(),
+ attemptNum: z.number(),
+ userId: z.string(),
+ sourceId: z.string(),
+ destinationId: z.string(),
+ workspaceId: z.string(),
+ secret: z.record(z.unknown()),
+ destInfo: z.record(z.unknown()).optional(),
+ omitempty: z.record(z.unknown()).optional(),
+ dontBatch: z.boolean(),
+});
+
+export const ProxyV0RequestSchema = z.object({
+ version: z.string(),
+ type: z.string(),
+ method: z.string(),
+ endpoint: z.string(),
+ userId: z.string(),
+ headers: z.record(z.unknown()).optional(),
+ params: z.record(z.unknown()).optional(),
+ body: z
+ .object({
+ JSON: z.record(z.unknown()).optional(),
+ JSON_ARRAY: z.record(z.unknown()).optional(),
+ XML: z.record(z.unknown()).optional(),
+ FORM: z.record(z.unknown()).optional(),
+ })
+ .optional(),
+ files: z.record(z.unknown()).optional(),
+ metadata: ProxyMetadataSchema,
+ destinationConfig: z.record(z.unknown()),
+});
+
+export const ProxyV1RequestSchema = z.object({
+ version: z.string(),
+ type: z.string(),
+ method: z.string(),
+ endpoint: z.string(),
+ userId: z.string(),
+ headers: z.record(z.unknown()).optional(),
+ params: z.record(z.unknown()).optional(),
+ body: z
+ .object({
+ JSON: z.record(z.unknown()).optional(),
+ JSON_ARRAY: z.record(z.unknown()).optional(),
+ XML: z.record(z.unknown()).optional(),
+ FORM: z.record(z.unknown()).optional(),
+ })
+ .optional(),
+ files: z.record(z.unknown()).optional(),
+ metadata: z.array(ProxyMetadataSchema),
+ destinationConfig: z.record(z.unknown()),
+});
+
+const validateStatTags = (data: any) => {
+ if (!isHttpStatusSuccess(data.status)) {
+ return isDefinedAndNotNullAndNotEmpty(data.statTags);
+ }
+ return true;
+};
+
+const validateAuthErrorCategory = (data: any) => {
+ if (!isHttpStatusSuccess(data.status)) {
+ return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory);
+ }
+ return true;
+};
+
+export const DeliveryV0ResponseSchema = z
+ .object({
+ status: z.number(),
+ message: z.string(),
+ destinationResponse: z.unknown(),
+ statTags: z.record(z.unknown()).optional(),
+ authErrorCategory: z.string().optional(),
+ })
+ .refine(validateStatTags, {
+ // eslint-disable-next-line sonarjs/no-duplicate-string
+ message: "statTags can't be empty when status is not a 2XX",
+ path: ['statTags'], // Pointing out which field is invalid
+ });
+
+export const DeliveryV0ResponseSchemaForOauth = z
+ .object({
+ status: z.number(),
+ message: z.string(),
+ destinationResponse: z.unknown(),
+ statTags: z.record(z.unknown()).optional(),
+ authErrorCategory: z.string().optional(),
+ })
+ .refine(validateStatTags, {
+ message: "statTags can't be empty when status is not a 2XX",
+ path: ['statTags'], // Pointing out which field is invalid
+ })
+ .refine(validateAuthErrorCategory, {
+ message: "authErrorCategory can't be empty when status is not a 2XX",
+ path: ['authErrorCategory'], // Pointing out which field is invalid
+ });
+
+const DeliveryJobStateSchema = z.object({
+ error: z.string(),
+ statusCode: z.number(),
+ metadata: ProxyMetadataSchema,
+});
+
+export const DeliveryV1ResponseSchema = z
+ .object({
+ status: z.number(),
+ message: z.string(),
+ statTags: z.record(z.unknown()).optional(),
+ authErrorCategory: z.string().optional(),
+ response: z.array(DeliveryJobStateSchema),
+ })
+ .refine(validateStatTags, {
+ message: "statTags can't be empty when status is not a 2XX",
+ path: ['statTags'], // Pointing out which field is invalid
+ });
+
+export const DeliveryV1ResponseSchemaForOauth = z
+ .object({
+ status: z.number(),
+ message: z.string(),
+ statTags: z.record(z.unknown()).optional(),
+ authErrorCategory: z.string().optional(),
+ response: z.array(DeliveryJobStateSchema),
+ })
+ .refine(validateStatTags, {
+ message: "statTags can't be empty when status is not a 2XX",
+ path: ['statTags'], // Pointing out which field is invalid
+ })
+ .refine(validateAuthErrorCategory, {
+ message: "authErrorCategory can't be empty when status is not a 2XX",
+ path: ['authErrorCategory'], // Pointing out which field is invalid
+ });
diff --git a/src/util/customTransformer-v1.js b/src/util/customTransformer-v1.js
index 192bad5dcb..6174aeaead 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 001fe3216c..a87c12dd6e 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 1bf10e5d45..ee53531946 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 ad75672af5..6ff374b869 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 {
@@ -28,22 +27,22 @@ export class ErrorDetailsExtractorBuilder {
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) {
@@ -51,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) {
@@ -68,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 {
@@ -84,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 ff7290b4ff..b93d2fafe5 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/fetchDestinationHandlers.ts b/src/util/fetchDestinationHandlers.ts
new file mode 100644
index 0000000000..2661ef2e68
--- /dev/null
+++ b/src/util/fetchDestinationHandlers.ts
@@ -0,0 +1,42 @@
+import * as V0MarketoBulkUploadFileUpload from '../v0/destinations/marketo_bulk_upload/fileUpload';
+import * as V0MarketoBulkUploadPollStatus from '../v0/destinations/marketo_bulk_upload/poll';
+import * as V0MarketoBulkUploadJobStatus from '../v0/destinations/marketo_bulk_upload/fetchJobStatus';
+
+const fileUploadHandlers = {
+ v0: {
+ marketo_bulk_upload: V0MarketoBulkUploadFileUpload,
+ },
+};
+
+const pollStatusHandlers = {
+ v0: {
+ marketo_bulk_upload: V0MarketoBulkUploadPollStatus,
+ },
+};
+
+const jobStatusHandlers = {
+ v0: {
+ marketo_bulk_upload: V0MarketoBulkUploadJobStatus,
+ },
+};
+
+export const getDestFileUploadHandler = (version, dest) => {
+ if (fileUploadHandlers[version] && fileUploadHandlers[version][dest]) {
+ return fileUploadHandlers[version][dest];
+ }
+ return undefined;
+};
+
+export const getPollStatusHandler = (version, dest) => {
+ if (pollStatusHandlers[version] && pollStatusHandlers[version][dest]) {
+ return pollStatusHandlers[version][dest];
+ }
+ return undefined;
+};
+
+export const getJobStatusHandler = (version, dest) => {
+ if (jobStatusHandlers[version] && jobStatusHandlers[version][dest]) {
+ return jobStatusHandlers[version][dest];
+ }
+ return undefined;
+};
diff --git a/src/util/ivmFactory.js b/src/util/ivmFactory.js
index c1948d0b47..97aaf07c8d 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 60039e78a8..517220eff5 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',
@@ -582,14 +590,38 @@ class Prometheus {
labelNames: ['method', 'route', 'code'],
},
{
- name: 'tp_request_size',
- help: 'tp_request_size',
+ name: 'tp_batch_size',
+ help: 'Size of batch of events for tracking plan validation',
type: 'histogram',
- labelNames: ['sourceType', 'destinationType', 'k8_namespace'],
+ buckets: [
+ 1024, 102400, 524288, 1048576, 10485760, 20971520, 52428800, 104857600, 209715200,
+ 524288000,
+ ],
+ labelNames: [
+ 'sourceType',
+ 'destinationType',
+ 'k8_namespace',
+ 'workspaceId',
+ 'trackingPlanId',
+ ],
+ },
+ {
+ name: 'tp_event_validation_latency',
+ help: 'Latency of validating tracking plan at event level',
+ type: 'histogram',
+ labelNames: [
+ 'sourceType',
+ 'destinationType',
+ 'k8_namespace',
+ 'workspaceId',
+ 'trackingPlanId',
+ 'status',
+ 'exception',
+ ],
},
{
- name: 'tp_request_latency',
- help: 'tp_request_latency',
+ name: 'tp_batch_validation_latency',
+ help: 'Latency of validating tracking plan at batch level',
type: 'histogram',
labelNames: [
'sourceType',
@@ -642,6 +674,22 @@ class Prometheus {
'k8_namespace',
],
},
+ {
+ name: 'user_transform_batch_size',
+ help: 'user_transform_batch_size',
+ type: 'histogram',
+ labelNames: [
+ 'workspaceId',
+ 'transformationId',
+ 'sourceType',
+ 'destinationType',
+ 'k8_namespace',
+ ],
+ buckets: [
+ 1024, 102400, 524288, 1048576, 10485760, 20971520, 52428800, 104857600, 209715200,
+ 524288000,
+ ], // 1KB, 100KB, 0.5MB, 1MB, 10MB, 20MB, 50MB, 100MB, 200MB, 500MB
+ },
{
name: 'source_transform_request_latency',
help: 'source_transform_request_latency',
@@ -702,7 +750,7 @@ class Prometheus {
name: 'get_libraries_code_time',
help: 'get_libraries_code_time',
type: 'histogram',
- labelNames: ['libraryVersionId', 'versionId', 'type'],
+ labelNames: ['libraryVersionId', 'versionId', 'type', 'version'],
},
{
name: 'isolate_cpu_time',
diff --git a/src/util/redis/redisConnector.test.js b/src/util/redis/redisConnector.test.js
index 840f222e37..e0491132ff 100644
--- a/src/util/redis/redisConnector.test.js
+++ b/src/util/redis/redisConnector.test.js
@@ -70,7 +70,7 @@ describe(`Redis Class Get Tests`, () => {
data.forEach((dataPoint, index) => {
it(`${index}. Redis Get- ${dataPoint.description}`, async () => {
try {
- const output = await RedisDB.getVal(dataPoint.input.value, (isObjExpected = false));
+ const output = await RedisDB.getVal(dataPoint.input.value, false);
expect(output).toEqual(dataPoint.output);
} catch (error) {
expect(error.message).toEqual(dataPoint.output.error);
diff --git a/src/util/redis/testData/shopify_source.json b/src/util/redis/testData/shopify_source.json
index 53c6047298..04b80b8fc9 100644
--- a/src/util/redis/testData/shopify_source.json
+++ b/src/util/redis/testData/shopify_source.json
@@ -5,7 +5,8 @@
"user_id": "rudder01",
"id": "shopify_test_get_items_fail",
"query_parameters": {
- "topic": ["carts_update"]
+ "topic": ["carts_update"],
+ "writeKey": ["wr"]
},
"token": "shopify_test_get_items_fail",
"email": "test@rudderstack.com",
@@ -115,7 +116,8 @@
"input": {
"cart_token": "shopifyGetAnonymousId",
"query_parameters": {
- "topic": ["checkouts_delete"]
+ "topic": ["checkouts_delete"],
+ "writeKey": ["wr"]
},
"line_items": [],
"note": null,
@@ -154,7 +156,8 @@
"input": {
"id": "shopify_test3",
"query_parameters": {
- "topic": ["carts_update"]
+ "topic": ["carts_update"],
+ "writeKey": ["wr"]
},
"token": "shopify_test3",
"line_items": [],
@@ -252,7 +255,8 @@
"user_id": "rudder01",
"id": "shopify_test_cart",
"query_parameters": {
- "topic": ["carts_update"]
+ "topic": ["carts_update"],
+ "writeKey": ["wr"]
},
"token": "shopify_test_cart",
"email": "test@rudderstack.com",
@@ -1256,7 +1260,8 @@
"input": {
"id": "shopify_test4",
"query_parameters": {
- "topic": ["carts_update"]
+ "topic": ["carts_update"],
+ "writeKey": ["wr"]
},
"token": "shopify_test4",
"line_items": [],
diff --git a/src/util/stats.js b/src/util/stats.js
index e7b21748d3..35248637a0 100644
--- a/src/util/stats.js
+++ b/src/util/stats.js
@@ -14,17 +14,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 981dbd7520..3978f868b1 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/networkHandler.js b/src/v0/destinations/adobe_analytics/networkHandler.js
index 0ec1fad286..8715721f85 100644
--- a/src/v0/destinations/adobe_analytics/networkHandler.js
+++ b/src/v0/destinations/adobe_analytics/networkHandler.js
@@ -15,7 +15,9 @@ function extractContent(xmlPayload, tagName) {
return match ? match[1] : null;
}
-const responseHandler = (destinationResponse, dest) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType } = responseParams;
+
const message = `[${DESTINATION}] - Request Processed Successfully`;
const { response, status } = destinationResponse;
@@ -27,11 +29,11 @@ const responseHandler = (destinationResponse, dest) => {
if (responseStatus === 'FAILURE') {
if (reason) {
throw new InstrumentationError(
- `[${DESTINATION} Response Handler] Request failed for destination ${dest} : ${reason}`,
+ `[${DESTINATION} Response Handler] Request failed for destination ${destType} : ${reason}`,
);
} else {
throw new InstrumentationError(
- `[${DESTINATION} Response Handler] Request failed for destination ${dest} with a general error`,
+ `[${DESTINATION} Response Handler] Request failed for destination ${destType} with a general error`,
);
}
}
diff --git a/src/v0/destinations/adobe_analytics/transform.js b/src/v0/destinations/adobe_analytics/transform.js
index b428138724..5d3d6e7d00 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 97dc6e90bb..ceba177ff1 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 bb711292c0..ab515642aa 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 4d7ed7e635..d6c41937a1 100644
--- a/src/v0/destinations/af/transform.js
+++ b/src/v0/destinations/af/transform.js
@@ -113,9 +113,20 @@ function getEventValueForUnIdentifiedTrackEvent(message) {
return { eventValue };
}
-function getEventValueMapFromMappingJson(message, mappingJson, isMultiSupport) {
+function getEventValueMapFromMappingJson(
+ message,
+ mappingJson,
+ isMultiSupport,
+ addPropertiesAtRoot,
+) {
let eventValue = {};
- set(eventValue, 'properties', message.properties);
+
+ if (addPropertiesAtRoot) {
+ eventValue = message.properties;
+ } else {
+ set(eventValue, 'properties', message.properties);
+ }
+
const sourceKeys = Object.keys(mappingJson);
sourceKeys.forEach((sourceKey) => {
set(eventValue, mappingJson[sourceKey], get(message, sourceKey));
@@ -160,7 +171,7 @@ function processNonTrackEvents(message, eventName) {
return payload;
}
-function processEventTypeTrack(message) {
+function processEventTypeTrack(message, addPropertiesAtRoot) {
let isMultiSupport = true;
const evType = message.event && message.event.toLowerCase();
let category = ConfigCategory.DEFAULT;
@@ -184,6 +195,7 @@ function processEventTypeTrack(message) {
message,
mappingConfig[category.name],
isMultiSupport,
+ addPropertiesAtRoot,
);
payload.eventName = message.event;
payload.eventCurrency = message?.properties?.currency;
@@ -196,7 +208,7 @@ function processSingleMessage(message, destination) {
let payload;
switch (messageType) {
case EventType.TRACK: {
- payload = processEventTypeTrack(message);
+ payload = processEventTypeTrack(message, destination.Config.addPropertiesAtRoot);
break;
}
case EventType.SCREEN: {
diff --git a/src/v0/destinations/am/config.js b/src/v0/destinations/am/config.js
index 3e51a67137..78f8d43e94 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 6de9cf64a1..96c4f7b19c 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 68a9eae513..3f96ddb767 100644
--- a/src/v0/destinations/am/transform.js
+++ b/src/v0/destinations/am/transform.js
@@ -268,7 +268,7 @@ const updateConfigProperty = (message, payload, mappingJson, validatePayload, Co
}
});
};
-const identifyBuilder = (message, destination, rawPayload) => {
+const userPropertiesHandler = (message, destination, rawPayload) => {
// update payload user_properties from userProperties/traits/context.traits/nested traits of Rudder message
// traits like address converted to top level user properties (think we can skip this extra processing as AM supports nesting upto 40 levels)
let traits = getFieldValueFromMessage(message, 'traits');
@@ -335,6 +335,7 @@ const getDefaultResponseData = (message, rawPayload, evType, groupInfo) => {
const groups = groupInfo && cloneDeep(groupInfo);
return { groups, rawPayload };
};
+
const getResponseData = (evType, destination, rawPayload, message, groupInfo) => {
let groups;
@@ -342,7 +343,7 @@ const getResponseData = (evType, destination, rawPayload, message, groupInfo) =>
case EventType.IDENTIFY:
// event_type for identify event is $identify
rawPayload.event_type = IDENTIFY_AM;
- rawPayload = identifyBuilder(message, destination, rawPayload);
+ rawPayload = userPropertiesHandler(message, destination, rawPayload);
break;
case EventType.GROUP:
// event_type for identify event is $identify
@@ -357,8 +358,15 @@ const getResponseData = (evType, destination, rawPayload, message, groupInfo) =>
case EventType.ALIAS:
break;
default:
+ if (destination.Config.enableEnhancedUserOperations) {
+ // handle all other events like track, page, screen for user properties
+ rawPayload = userPropertiesHandler(message, destination, rawPayload);
+ }
({ groups, rawPayload } = getDefaultResponseData(message, rawPayload, evType, groupInfo));
}
+ if (destination.Config.enableEnhancedUserOperations) {
+ rawPayload = AMUtils.userPropertiesPostProcess(rawPayload);
+ }
return { rawPayload, groups };
};
@@ -517,6 +525,9 @@ const responseBuilderSimple = (
...campaign,
};
+ // we are updating the payload with skip_user_properties_sync
+ AMUtils.updateWithSkipAttribute(message, rawPayload);
+
const respData = getResponseData(evType, destination, rawPayload, message, groupInfo);
const { groups, rawPayload: updatedRawPayload } = respData;
@@ -614,16 +625,16 @@ const processSingleMessage = (message, destination) => {
case EventType.PAGE:
if (useUserDefinedPageEventName) {
const getMessagePath = userProvidedPageEventString
- .substring(
+ ?.substring(
userProvidedPageEventString.indexOf('{') + 2,
userProvidedPageEventString.indexOf('}'),
)
.trim();
evType =
- userProvidedPageEventString.trim() === ''
+ userProvidedPageEventString?.trim() === ''
? name
: userProvidedPageEventString
- .trim()
+ ?.trim()
.replaceAll(/{{([^{}]+)}}/g, get(message, getMessagePath));
} else {
evType = `Viewed ${name || get(message, CATEGORY_KEY) || ''} Page`;
@@ -701,6 +712,7 @@ const processSingleMessage = (message, destination) => {
logger.debugw('could not determine type');
throw new InstrumentationError('message type not supported');
}
+ AMUtils.validateEventType(evType);
return responseBuilderSimple(
groupInfo,
payloadObjectName,
diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js
index faaa9170f0..498980d182 100644
--- a/src/v0/destinations/am/util.test.js
+++ b/src/v0/destinations/am/util.test.js
@@ -1,4 +1,9 @@
-const { getUnsetObj } = require('./utils');
+const {
+ getUnsetObj,
+ validateEventType,
+ userPropertiesPostProcess,
+ updateWithSkipAttribute,
+} = require('./utils');
describe('getUnsetObj', () => {
it("should return undefined when 'message.integrations.Amplitude.fieldsToUnset' is not array", () => {
@@ -64,3 +69,131 @@ describe('getUnsetObj', () => {
expect(result).toBeUndefined();
});
});
+
+describe('validateEventType', () => {
+ it('should validate event type when it is valid with only page name given', () => {
+ expect(() => {
+ validateEventType('Home Page');
+ }).not.toThrow();
+ });
+
+ it('should throw an error when event type is null', () => {
+ expect(() => {
+ validateEventType(null);
+ }).toThrow(
+ 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`',
+ );
+ });
+
+ it('should throw an error when event type is undefined', () => {
+ expect(() => {
+ validateEventType(undefined);
+ }).toThrow(
+ 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`',
+ );
+ });
+
+ // Function receives an empty string as event type
+ it('should throw an error when event type is an empty string', () => {
+ expect(() => {
+ validateEventType('');
+ }).toThrow(
+ 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`',
+ );
+ });
+});
+
+describe('userPropertiesPostProcess', () => {
+ // The function correctly removes duplicate keys found in both operation keys and root level keys.
+ it('should remove duplicate keys from user_properties', () => {
+ // Arrange
+ const rawPayload = {
+ user_properties: {
+ $setOnce: {
+ key1: 'value1',
+ },
+ $add: {
+ key2: 'value2',
+ },
+ key3: 'value3',
+ key1: 'value4',
+ },
+ };
+
+ // Act
+ const result = userPropertiesPostProcess(rawPayload);
+
+ // Assert
+ expect(result.user_properties).toEqual({
+ $setOnce: {
+ key1: 'value1',
+ },
+ $add: {
+ key2: 'value2',
+ },
+ $set: {
+ key3: 'value3',
+ },
+ });
+ });
+
+ // The function correctly moves root level properties to $set operation.
+ it('should move root level properties to $set operation when they dont belong to any other operation', () => {
+ // Arrange
+ const rawPayload = {
+ user_properties: {
+ $setOnce: {
+ key1: 'value1',
+ },
+ $add: {
+ key2: 'value2',
+ },
+ key3: 'value3',
+ },
+ };
+
+ // Act
+ const result = userPropertiesPostProcess(rawPayload);
+
+ // Assert
+ expect(result.user_properties).toEqual({
+ $set: {
+ key3: 'value3',
+ },
+ $setOnce: {
+ key1: 'value1',
+ },
+ $add: {
+ key2: 'value2',
+ },
+ });
+ });
+});
+
+describe('updateWithSkipAttribute', () => {
+ // when 'skipUserPropertiesSync ' is present in 'integrations.Amplitude', return the original payload.
+ it("should return the original payload when 'skipUserPropertiesSync' is present", () => {
+ const message = { integrations: { Amplitude: { skipUserPropertiesSync: true } } };
+ const payload = { key: 'value' };
+ const expectedPayload = { key: 'value', $skip_user_properties_sync: true };
+ updateWithSkipAttribute(message, payload);
+ expect(expectedPayload).toEqual(payload);
+ });
+
+ // When 'skipUserPropertiesSync' is not present in 'integrations.Amplitude', return the original payload.
+ it("should return the original payload when 'skipUserPropertiesSync' is not present", () => {
+ const message = { integrations: { Amplitude: {} } };
+ const payload = { key: 'value' };
+ const expectedPayload = { key: 'value' };
+ updateWithSkipAttribute(message, payload);
+ expect(payload).toEqual(expectedPayload);
+ });
+ // When 'message' is null, return null.
+ it("should return null when 'message' is null", () => {
+ const message = null;
+ const payload = { key: 'value' };
+ const expectedPayload = { key: 'value' };
+ updateWithSkipAttribute(message, payload);
+ expect(payload).toEqual(expectedPayload);
+ });
+});
diff --git a/src/v0/destinations/am/utils.js b/src/v0/destinations/am/utils.js
index 22b2784ef3..8bfd70aec2 100644
--- a/src/v0/destinations/am/utils.js
+++ b/src/v0/destinations/am/utils.js
@@ -10,6 +10,8 @@
// populate these dest keys
const get = require('get-value');
const uaParser = require('@amplitude/ua-parser-js');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const set = require('set-value');
const logger = require('../../../logger');
const { isDefinedAndNotNull } = require('../../util');
@@ -108,6 +110,80 @@ const getUnsetObj = (message) => {
return unsetObject;
};
+
+const updateWithSkipAttribute = (message, payload) => {
+ const skipAttribute = get(message, 'integrations.Amplitude.skipUserPropertiesSync');
+ if (skipAttribute) {
+ set(payload, '$skip_user_properties_sync', true);
+ }
+};
+
+/**
+ * Check for evType as in some cases, like when the page name is absent,
+ * either the template depends only on the event.name or there is no template provided by user
+ * @param {*} evType
+ */
+const validateEventType = (evType) => {
+ if (!isDefinedAndNotNull(evType) || (typeof evType === 'string' && evType.length === 0)) {
+ throw new InstrumentationError(
+ 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`',
+ );
+ }
+};
+
+const userPropertiesPostProcess = (rawPayload) => {
+ const operationList = [
+ '$setOnce',
+ '$add',
+ '$unset',
+ '$append',
+ '$prepend',
+ '$preInsert',
+ '$postInsert',
+ '$remove',
+ ];
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const { user_properties } = rawPayload;
+ const userPropertiesKeys = Object.keys(user_properties).filter(
+ (key) => !operationList.includes(key),
+ );
+ const duplicatekeys = new Set();
+ // eslint-disable-next-line no-restricted-syntax, guard-for-in
+ for (const key of userPropertiesKeys) {
+ // check if any of the keys are present in the user_properties $setOnce, $add, $unset, $append, $prepend, $preInsert, $postInsert, $remove keys as well as root level
+
+ if (
+ operationList.some(
+ (operation) => user_properties[operation] && user_properties[operation][key],
+ )
+ ) {
+ duplicatekeys.add(key);
+ }
+ }
+ // eslint-disable-next-line no-restricted-syntax, guard-for-in
+ for (const key of duplicatekeys) {
+ delete user_properties[key];
+ }
+
+ // Moving root level properties that doesn't belong to any operation under $set
+ const setProps = {};
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [key, value] of Object.entries(user_properties)) {
+ if (!operationList.includes(key)) {
+ setProps[key] = value;
+ delete user_properties[key];
+ }
+ }
+
+ if (Object.keys(setProps).length > 0) {
+ user_properties.$set = setProps;
+ }
+
+ // eslint-disable-next-line no-param-reassign
+ rawPayload.user_properties = user_properties;
+ return rawPayload;
+};
+
module.exports = {
getOSName,
getOSVersion,
@@ -117,4 +193,7 @@ module.exports = {
getBrand,
getEventId,
getUnsetObj,
+ validateEventType,
+ userPropertiesPostProcess,
+ updateWithSkipAttribute,
};
diff --git a/src/v0/destinations/bqstream/transform.js b/src/v0/destinations/bqstream/transform.js
index 598a97946d..8ee96aecf1 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/branch/transform.js b/src/v0/destinations/branch/transform.js
index 23dcd6c8db..2626d8aa81 100644
--- a/src/v0/destinations/branch/transform.js
+++ b/src/v0/destinations/branch/transform.js
@@ -13,6 +13,7 @@ const {
simpleProcessRouterDest,
} = require('../../util');
const { JSON_MIME_TYPE } = require('../../util/constant');
+const { getMappedEventNameFromConfig } = require('./utils');
function responseBuilder(payload, message, destination, category) {
const response = defaultRequestConfig();
@@ -213,24 +214,21 @@ function getCommonPayload(message, category, evName) {
return rawPayload;
}
-// function getTrackPayload(message) {
-// const rawPayload = {};
-// const { name, category } = getCategoryAndName(message.event);
-// rawPayload.name = name;
-//
-// return commonPayload(message, rawPayload, category);
-// }
-
function processMessage(message, destination) {
let evName;
let category;
switch (message.type) {
- case EventType.TRACK:
+ case EventType.TRACK: {
if (!message.event) {
throw new InstrumentationError('Event name is required');
}
({ evName, category } = getCategoryAndName(message.event));
+ const eventNameFromConfig = getMappedEventNameFromConfig(message, destination);
+ if (eventNameFromConfig) {
+ evName = eventNameFromConfig;
+ }
break;
+ }
case EventType.IDENTIFY:
({ evName, category } = getCategoryAndName(message.userId));
break;
diff --git a/src/v0/destinations/branch/utils.js b/src/v0/destinations/branch/utils.js
new file mode 100644
index 0000000000..1415f0de39
--- /dev/null
+++ b/src/v0/destinations/branch/utils.js
@@ -0,0 +1,24 @@
+const { getHashFromArray } = require('../../util');
+
+/**
+ * Retrieves the mapped event name from the given config.
+ *
+ * @param {object} message - The message object containing the event.
+ * @param {object} destination - The destination object containing the events mapping configuration.
+ * @returns {string} - The mapped event name, or undefined if not found.
+ */
+const getMappedEventNameFromConfig = (message, destination) => {
+ let eventName;
+ const { event } = message;
+ const { eventsMapping } = destination.Config;
+
+ // if event is mapped on dashboard, use the mapped event name
+ if (Array.isArray(eventsMapping) && eventsMapping.length > 0) {
+ const keyMap = getHashFromArray(eventsMapping, 'from', 'to', false);
+ eventName = keyMap[event];
+ }
+
+ return eventName;
+};
+
+module.exports = { getMappedEventNameFromConfig };
diff --git a/src/v0/destinations/branch/utils.test.js b/src/v0/destinations/branch/utils.test.js
new file mode 100644
index 0000000000..e7da007d89
--- /dev/null
+++ b/src/v0/destinations/branch/utils.test.js
@@ -0,0 +1,18 @@
+const { getMappedEventNameFromConfig } = require('./utils');
+describe('getMappedEventNameFromConfig', () => {
+ it('should return the mapped event name when it exists in the events mapping configuration', () => {
+ const message = { event: 'Order Completed' };
+ const destination = {
+ Config: { eventsMapping: [{ from: 'Order Completed', to: 'PURCHASE' }] },
+ };
+ const result = getMappedEventNameFromConfig(message, destination);
+ expect(result).toBe('PURCHASE');
+ });
+
+ it('should return undefined when the event mapping is not created', () => {
+ const message = { event: 'Order Completed' };
+ const destination = { Config: {} };
+ const result = getMappedEventNameFromConfig(message, destination);
+ expect(result).toBeUndefined();
+ });
+});
diff --git a/src/v0/destinations/braze/braze.util.test.js b/src/v0/destinations/braze/braze.util.test.js
index 9e82a235f1..7b6a93d359 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 b94d901138..33c0f2ef7f 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/networkHandler.js b/src/v0/destinations/braze/networkHandler.js
index c6cf7222ea..b1363419b3 100644
--- a/src/v0/destinations/braze/networkHandler.js
+++ b/src/v0/destinations/braze/networkHandler.js
@@ -11,7 +11,8 @@ const tags = require('../../util/tags');
const stats = require('../../../util/stats');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const responseHandler = (destinationResponse, _dest) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `Request for ${DESTINATION} Processed Successfully`;
const { response, status } = destinationResponse;
// if the response from destination is not a success case build an explicit error
diff --git a/src/v0/destinations/braze/transform.js b/src/v0/destinations/braze/transform.js
index 87465e2131..111e154579 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 40b9a7eada..5f1f1e6205 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/networkHandler.js b/src/v0/destinations/campaign_manager/networkHandler.js
index a1fa24835c..df13b72adc 100644
--- a/src/v0/destinations/campaign_manager/networkHandler.js
+++ b/src/v0/destinations/campaign_manager/networkHandler.js
@@ -44,7 +44,8 @@ function checkIfFailuresAreRetryable(response) {
}
}
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully`;
const { response, status } = destinationResponse;
diff --git a/src/v0/destinations/campaign_manager/transform.js b/src/v0/destinations/campaign_manager/transform.js
index d0d0eb7ec1..2eb8aee5a7 100644
--- a/src/v0/destinations/campaign_manager/transform.js
+++ b/src/v0/destinations/campaign_manager/transform.js
@@ -11,7 +11,6 @@ const {
removeUndefinedAndNullValues,
getSuccessRespEvents,
isDefinedAndNotNull,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
getAccessToken,
} = require('../../util');
@@ -252,7 +251,6 @@ const processRouterDest = async (inputs, reqMetadata) => {
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 3b3f0bf5b5..dafd85bd9e 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.debugw(response);
diff --git a/src/v0/destinations/clevertap/data/CleverTapIdentify.json b/src/v0/destinations/clevertap/data/CleverTapIdentify.json
index 577e13c339..cdc4b28d93 100644
--- a/src/v0/destinations/clevertap/data/CleverTapIdentify.json
+++ b/src/v0/destinations/clevertap/data/CleverTapIdentify.json
@@ -1,7 +1,7 @@
[
{
"destKey": "Email",
- "sourceKeys": "email",
+ "sourceKeys": "emailOnly",
"required": false,
"sourceFromGenericMap": true
},
diff --git a/src/v0/destinations/clevertap/deleteUsers.js b/src/v0/destinations/clevertap/deleteUsers.js
index 3c07a63d93..52119bf0f1 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/networkHandler.js b/src/v0/destinations/clevertap/networkHandler.js
index e17afb57d1..02b523f3fc 100644
--- a/src/v0/destinations/clevertap/networkHandler.js
+++ b/src/v0/destinations/clevertap/networkHandler.js
@@ -7,7 +7,8 @@ const {
} = require('../../../adapters/utils/networkUtils');
const tags = require('../../util/tags');
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = 'Request Processed Successfully';
const { response, status } = destinationResponse;
diff --git a/src/v0/destinations/clevertap/transform.js b/src/v0/destinations/clevertap/transform.js
index efcd101668..51ed5c851e 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 148fe1bd07..74e961906c 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/criteo_audience/networkHandler.js b/src/v0/destinations/criteo_audience/networkHandler.js
index 18bd9a93a0..6032aabcdd 100644
--- a/src/v0/destinations/criteo_audience/networkHandler.js
+++ b/src/v0/destinations/criteo_audience/networkHandler.js
@@ -67,7 +67,8 @@ const criteoAudienceRespHandler = (destResponse, stageMsg) => {
);
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `Request Processed Successfully`;
const { status } = destinationResponse;
if (!isHttpStatusSuccess(status)) {
diff --git a/src/v0/destinations/custify/deleteUsers.js b/src/v0/destinations/custify/deleteUsers.js
index 147fcc602c..690768a170 100644
--- a/src/v0/destinations/custify/deleteUsers.js
+++ b/src/v0/destinations/custify/deleteUsers.js
@@ -17,36 +17,44 @@ const userDeletionHandler = async (userAttributes, config) => {
}
const { apiKey } = config;
- const { userId } = userAttributes;
if (!apiKey) {
throw new ConfigurationError('api key for deletion not present', 400);
}
- if (!userId) {
- throw new InstrumentationError('User id for deletion not present', 400);
- }
- const requestUrl = `https://api.custify.com/people?user_id=${userId}`;
- const requestOptions = {
- headers: {
- Authorization: `Bearer ${apiKey}`,
- },
- };
+ await Promise.all(
+ userAttributes.map(async (userAttr) => {
+ const { userId } = userAttr;
+ if (!userId) {
+ throw new InstrumentationError('User id for deletion not present', 400);
+ }
+ // Reference: https://docs.custify.com/#tag/People/paths/~1people/delete
+ const requestUrl = `https://api.custify.com/people?user_id=${userId}`;
+ const requestOptions = {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ },
+ };
- const deletionResponse = await httpDELETE(requestUrl, requestOptions, {
- destType: 'custify',
- feature: 'deleteUsers',
- });
- const processedDeletionRequest = processAxiosResponse(deletionResponse);
- if (processedDeletionRequest.status !== 200 && processedDeletionRequest.status !== 404) {
- throw new NetworkError(
- JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user',
- processedDeletionRequest.status,
- {
- [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status),
- },
- deletionResponse,
- );
- }
+ 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) {
+ throw new NetworkError(
+ JSON.stringify(processedDeletionRequest.response) || 'Error while deleting user',
+ processedDeletionRequest.status,
+ {
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedDeletionRequest.status),
+ },
+ deletionResponse,
+ );
+ }
+ }),
+ );
return { statusCode: 200, status: 'successful' };
};
diff --git a/src/v0/destinations/custify/util.js b/src/v0/destinations/custify/util.js
index 8ecabccd2e..b6f3446503 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 f092420cfa..14dbd35c01 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 2c92685fd7..c690bf5f5c 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 6869dd971c..416d0ae85a 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 a3c3055c7d..3616d2408d 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 26204ec61a..c6e3993e33 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/transform.test.js b/src/v0/destinations/facebook_pixel/transform.test.js
index 25332d770c..d3c5baa070 100644
--- a/src/v0/destinations/facebook_pixel/transform.test.js
+++ b/src/v0/destinations/facebook_pixel/transform.test.js
@@ -24,6 +24,23 @@ const getTestMessage = () => {
return message;
};
+const getTestMessageWithoutProductIdAndCategory = () => {
+ let message = {
+ properties: {
+ currency: 'CAD',
+ quantity: 1,
+ price: 24.75,
+ value: 30,
+ name: 'my product 1',
+ testDimension: true,
+ testMetric: true,
+ position: 4.5,
+ query: 'HDMI Cable',
+ },
+ };
+ return message;
+};
+
const getTestCategoryToContent = () => {
let categoryToContent = [
{
@@ -52,6 +69,17 @@ describe('Unit test cases for facebook_pixel handle search', () => {
expect(handleSearch(getTestMessage())).toEqual(expectedOutput);
});
+ it('should return content with content_ids and content fields as empty array', async () => {
+ const expectedOutput = {
+ content_ids: [],
+ content_category: '',
+ value: 30,
+ search_string: 'HDMI Cable',
+ contents: [],
+ };
+ expect(handleSearch(getTestMessageWithoutProductIdAndCategory())).toEqual(expectedOutput);
+ });
+
it("mapping 'product_id' with contentId", async () => {
let message = getTestMessage();
message.properties.product_id = 'prd-123';
diff --git a/src/v0/destinations/facebook_pixel/utils.js b/src/v0/destinations/facebook_pixel/utils.js
index 8a63a0b0fe..cfa625ee3d 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/facebook_pixel/utils.test.js b/src/v0/destinations/facebook_pixel/utils.test.js
new file mode 100644
index 0000000000..f32d7d7024
--- /dev/null
+++ b/src/v0/destinations/facebook_pixel/utils.test.js
@@ -0,0 +1,154 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const { getActionSource, formatRevenue, getCategoryFromEvent } = require('./utils');
+const { CONFIG_CATEGORIES, OTHER_STANDARD_EVENTS } = require('./config');
+
+describe('Test Facebook Pixel Utils', () => {
+ describe('getActionSource', () => {
+ // Returns 'other' if payload.action_source is not defined and channel is neither 'web' nor 'mobile'
+ it('should return "other" when payload.action_source is not defined and channel is neither "web" nor "mobile"', () => {
+ const payload = {};
+ const channel = 'email';
+ const result = getActionSource(payload, channel);
+ expect(result).toBe('other');
+ });
+
+ // Returns payload.action_source if it is defined and is a valid value from ACTION_SOURCES_VALUES
+ it('should return payload.action_source when it is defined and is a valid value from ACTION_SOURCES_VALUES', () => {
+ const payload = { action_source: 'website' };
+ const channel = 'email';
+ const result = getActionSource(payload, channel);
+ expect(result).toBe('website');
+ });
+
+ // Returns 'website' if channel is 'web' and payload.action_source is not defined
+ it('should return "website" when channel is "web" and payload.action_source is not defined', () => {
+ const payload = {};
+ const channel = 'web';
+ const result = getActionSource(payload, channel);
+ expect(result).toBe('website');
+ });
+
+ // Throws an InstrumentationError if payload.action_source is defined but not a valid value from ACTION_SOURCES_VALUES
+ it('should throw an InstrumentationError when payload.action_source is defined but not a valid value from ACTION_SOURCES_VALUES', () => {
+ const payload = { action_source: 'invalid' };
+ const channel = 'email';
+ expect(() => {
+ getActionSource(payload, channel);
+ }).toThrow(InstrumentationError);
+ });
+ });
+
+ describe('formatRevenue', () => {
+ // Returns a number with two decimal places when passed a valid revenue value.
+ it('should return a number with two decimal places when passed a valid revenue value', () => {
+ const revenue = '100.50';
+ const formattedRevenue = formatRevenue(revenue);
+ expect(formattedRevenue).toBe(100.5);
+ });
+
+ // Returns 0 when passed a null revenue value.
+ it('should return 0 when passed a null revenue value', () => {
+ const revenue = null;
+ const formattedRevenue = formatRevenue(revenue);
+ expect(formattedRevenue).toBe(0);
+ });
+
+ // Returns 0 when passed an undefined revenue value.
+ it('should return 0 when passed an undefined revenue value', () => {
+ const revenue = undefined;
+ const formattedRevenue = formatRevenue(revenue);
+ expect(formattedRevenue).toBe(0);
+ });
+
+ // Throws an InstrumentationError when passed a non-numeric string revenue value.
+ it('should throw an InstrumentationError when passed a non-numeric string revenue value', () => {
+ const revenue = 'abc';
+ expect(() => {
+ formatRevenue(revenue);
+ }).toThrow(InstrumentationError);
+ });
+
+ // Returns a number with two decimal places when passed a numeric string revenue value with more than two decimal places.
+ it('should return a number with two decimal places when passed a numeric string revenue value with more than two decimal places', () => {
+ const revenue = '100.555';
+ const formattedRevenue = formatRevenue(revenue);
+ expect(formattedRevenue).toBe(100.56);
+ });
+
+ // Returns a number with two decimal places when passed a numeric value with more than two decimal places.
+ it('should return a number with two decimal places when passed a numeric value with more than two decimal places', () => {
+ const revenue = 100.555;
+ const formattedRevenue = formatRevenue(revenue);
+ expect(formattedRevenue).toBe(100.56);
+ });
+ });
+
+ describe('getCategoryFromEvent', () => {
+ // The function correctly maps the eventName to its corresponding category.
+ it('should correctly map the eventName to its corresponding category', () => {
+ const eventName = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type;
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
+ });
+
+ // The function returns the correct category for a given eventName.
+ it('should return the correct category for a given eventName', () => {
+ const eventName = CONFIG_CATEGORIES.PRODUCT_VIEWED.type;
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.PRODUCT_VIEWED);
+ });
+
+ // The function returns the default category if the eventName is not recognized.
+ it('should return the default category if the eventName is not recognized', () => {
+ const eventName = 'unknownEvent';
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
+ });
+
+ // The function handles null or undefined eventName inputs.
+ it('should handle null or undefined eventName inputs', () => {
+ const eventName = null;
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
+ });
+
+ // The function handles empty string eventName inputs.
+ it('should handle empty string eventName inputs', () => {
+ const eventName = '';
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
+ });
+
+ // The function handles eventName inputs that are not strings.
+ it('should handle eventName inputs that are not strings', () => {
+ const eventName = 123;
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
+ });
+
+ // The function handles multiple eventNames that map to the same category.
+ it('should correctly map multiple eventNames to the same category', () => {
+ const eventName1 = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type;
+ const eventName2 = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.eventName;
+ const result1 = getCategoryFromEvent(eventName1);
+ const result2 = getCategoryFromEvent(eventName2);
+ expect(result1).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
+ expect(result2).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
+ });
+
+ // The function handles eventNames that are included in the OTHER_STANDARD_EVENTS list.
+ it('should correctly handle eventNames included in the OTHER_STANDARD_EVENTS list', () => {
+ const eventName = OTHER_STANDARD_EVENTS[0];
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.OTHER_STANDARD);
+ expect(result.eventName).toEqual(eventName);
+ });
+
+ // The function handles eventNames that are not recognized and not in the OTHER_STANDARD_EVENTS list.
+ it('should correctly handle unrecognized eventNames', () => {
+ const eventName = 'unrecognizedEvent';
+ const result = getCategoryFromEvent(eventName);
+ expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
+ });
+ });
+});
diff --git a/src/v0/destinations/fb/networkHandler.js b/src/v0/destinations/fb/networkHandler.js
index 06235fab40..7ba5b88adc 100644
--- a/src/v0/destinations/fb/networkHandler.js
+++ b/src/v0/destinations/fb/networkHandler.js
@@ -2,7 +2,8 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils')
const { errorResponseHandler } = require('../facebook_pixel/networkHandler');
const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network');
-const destResponseHandler = (destinationResponse) => {
+const destResponseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
errorResponseHandler(destinationResponse);
return {
destinationResponse: destinationResponse.response,
diff --git a/src/v0/destinations/freshmarketer/utils.js b/src/v0/destinations/freshmarketer/utils.js
index 61eed6f6d9..a990c6b4a5 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 5008fedc2d..977bde0abb 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 06e674048a..524e2c14b4 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/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js
index b62fcc8d3b..da8ae2ea30 100644
--- a/src/v0/destinations/ga4/networkHandler.js
+++ b/src/v0/destinations/ga4/networkHandler.js
@@ -8,7 +8,8 @@ const { isDefinedAndNotNull, isDefined, isHttpStatusSuccess } = require('../../u
const tags = require('../../util/tags');
-const responseHandler = (destinationResponse, dest) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType } = responseParams;
const message = `[GA4 Response Handler] - Request Processed Successfully`;
let { status } = destinationResponse;
const { response } = destinationResponse;
@@ -29,10 +30,10 @@ const responseHandler = (destinationResponse, dest) => {
// Build the error in case the validationMessages[] is non-empty
const { description, validationCode, fieldPath } = response.validationMessages[0];
throw new NetworkError(
- `Validation Server Response Handler:: Validation Error for ${dest} of field path :${fieldPath} | ${validationCode}-${description}`,
- status,
+ `Validation Server Response Handler:: Validation Error for ${destType} of field path :${fieldPath} | ${validationCode}-${description}`,
+ 400,
{
- [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(400),
},
response?.validationMessages[0]?.description,
);
@@ -42,7 +43,7 @@ const responseHandler = (destinationResponse, dest) => {
// if the response from destination is not a success case build an explicit error
if (!isHttpStatusSuccess(status)) {
throw new NetworkError(
- `[GA4 Response Handler] Request failed for destination ${dest} with status: ${status}`,
+ `[GA4 Response Handler] Request failed for destination ${destType} with status: ${status}`,
status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
diff --git a/src/v0/destinations/gainsight/util.js b/src/v0/destinations/gainsight/util.js
index 0b3942ae77..3a406b7087 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 5109286b3f..83d23566dd 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 7266154a09..f7ac660f53 100644
--- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js
+++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js
@@ -1,6 +1,7 @@
const { get, set } = require('lodash');
const sha256 = require('sha256');
const { NetworkError, NetworkInstrumentationError } = require('@rudderstack/integrations-lib');
+const SqlString = require('sqlstring');
const { prepareProxyRequest, handleHttpRequest } = require('../../../adapters/network');
const { isHttpStatusSuccess, getAuthErrCategoryFromStCode } = require('../../util/index');
const { CONVERSION_ACTION_ID_CACHE_TTL } = require('./config');
@@ -29,8 +30,12 @@ const ERROR_MSG_PATH = 'response[0].error.message';
const getConversionActionId = async (method, headers, params) => {
const conversionActionIdKey = sha256(params.event + params.customerId).toString();
return conversionActionIdCache.get(conversionActionIdKey, async () => {
+ const queryString = SqlString.format(
+ 'SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = ?',
+ [params.event],
+ );
const data = {
- query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = '${params.event}'`,
+ query: queryString,
};
const requestBody = {
url: `${BASE_ENDPOINT}/${params.customerId}/googleAds:searchStream`,
@@ -45,6 +50,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,11 +105,14 @@ const ProxyRequest = async (request) => {
destType: 'google_adwords_enhanced_conversions',
feature: 'proxy',
endpointPath: `/googleAds:uploadOfflineUserData`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
return response;
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = 'Request Processed Successfully';
const { status } = destinationResponse;
if (isHttpStatusSuccess(status)) {
@@ -112,7 +122,7 @@ const responseHandler = (destinationResponse) => {
// Ref - https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
if (partialFailureError && partialFailureError.code !== 0) {
throw new NetworkError(
- `[Google Ads Offline Conversions]:: partialFailureError - ${JSON.stringify(
+ `[Google Adwords Enhanced Conversions]:: partialFailureError - ${JSON.stringify(
partialFailureError,
)}`,
400,
diff --git a/src/v0/destinations/google_adwords_offline_conversions/config.js b/src/v0/destinations/google_adwords_offline_conversions/config.js
index a02732894f..f065be946c 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/config.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/config.js
@@ -1,6 +1,6 @@
const { getMappingConfig } = require('../../util');
-const API_VERSION = 'v14';
+const API_VERSION = 'v16';
const BASE_ENDPOINT = `https://googleads.googleapis.com/${API_VERSION}/customers/:customerId`;
@@ -42,6 +42,11 @@ const CONVERSION_CUSTOM_VARIABLE_CACHE_TTL = process.env.CONVERSION_CUSTOM_VARIA
const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
+const consentConfigMap = {
+ personalizationConsent: 'adPersonalization',
+ userDataConsent: 'adUserData',
+};
+
module.exports = {
trackClickConversionsMapping:
MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_CLICK_CONVERSIONS_CONFIG.name],
@@ -58,4 +63,5 @@ module.exports = {
MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_CONVERSION_CONFIG_ADD_CONVERSION.name],
trackAddStoreAddressConversionsMapping:
MAPPING_CONFIG[CONFIG_CATEGORIES.TRACK_STORE_ADDRESS_IDENTIFIER.name],
+ consentConfigMap,
};
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 aeecc3e01b..9c88e59ddb 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 6922cde8c8..5541fd6e1e 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,11 +255,14 @@ const ProxyRequest = async (request) => {
feature: 'proxy',
destType: 'gogole_adwords_offline_conversions',
endpointPath: `/proxy`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
return response;
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `[Google Ads Offline Conversions Response Handler] - Request processed successfully`;
const { status } = destinationResponse;
if (isHttpStatusSuccess(status)) {
diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js
index 46cde72771..c3be0f7cab 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/transform.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js
@@ -3,25 +3,21 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ
const { EventType } = require('../../../constants');
const {
getHashFromArrayWithDuplicate,
- constructPayload,
removeHyphens,
getHashFromArray,
handleRtTfSingleEventError,
defaultBatchRequestConfig,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
combineBatchRequestsWithSameJobIds,
} = require('../../util');
-const {
- CALL_CONVERSION,
- trackCallConversionsMapping,
- STORE_CONVERSION_CONFIG,
-} = require('./config');
+const { CALL_CONVERSION, STORE_CONVERSION_CONFIG } = require('./config');
const {
validateDestinationConfig,
getStoreConversionPayload,
requestBuilder,
getClickConversionPayloadAndEndpoint,
+ getConsentsDataFromIntegrationObj,
+ getCallConversionPayload,
} = require('./utils');
const helper = require('./helper');
@@ -42,12 +38,15 @@ const getConversions = (message, metadata, { Config }, event, conversionType) =>
const { properties, timestamp, originalTimestamp } = message;
const filteredCustomerId = removeHyphens(customerId);
+ const eventLevelConsentsData = getConsentsDataFromIntegrationObj(message);
+
if (conversionType === 'click') {
// click conversion
const convertedPayload = getClickConversionPayloadAndEndpoint(
message,
Config,
filteredCustomerId,
+ eventLevelConsentsData,
);
payload = convertedPayload.payload;
endpoint = convertedPayload.endpoint;
@@ -56,7 +55,7 @@ const getConversions = (message, metadata, { Config }, event, conversionType) =>
endpoint = STORE_CONVERSION_CONFIG.replace(':customerId', filteredCustomerId);
} else {
// call conversions
- payload = constructPayload(message, trackCallConversionsMapping);
+ payload = getCallConversionPayload(message, Config, eventLevelConsentsData);
endpoint = CALL_CONVERSION.replace(':customerId', filteredCustomerId);
}
@@ -120,7 +119,6 @@ const trackResponseBuilder = (message, metadata, destination) => {
const process = async (event) => {
const { message, metadata, destination } = event;
-
if (!message.type) {
throw new InstrumentationError('Message type is not present. Aborting message.');
}
@@ -186,11 +184,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 599a163c54..70b42e2157 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/utils.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js
@@ -1,4 +1,5 @@
const sha256 = require('sha256');
+const SqlString = require('sqlstring');
const { get, set, cloneDeep } = require('lodash');
const {
AbortedError,
@@ -17,6 +18,7 @@ const {
isDefinedAndNotNull,
getAuthErrCategoryFromStCode,
getAccessToken,
+ getIntegrationsObj,
} = require('../../util');
const {
SEARCH_STREAM,
@@ -26,10 +28,13 @@ const {
trackAddStoreAddressConversionsMapping,
trackClickConversionsMapping,
CLICK_CONVERSION,
+ trackCallConversionsMapping,
+ consentConfigMap,
} = require('./config');
const { processAxiosResponse } = require('../../../adapters/utils/networkUtils');
const Cache = require('../../util/cache');
const helper = require('./helper');
+const { finaliseConsent } = require('../../util/googleUtils');
const conversionActionIdCache = new Cache(CONVERSION_ACTION_ID_CACHE_TTL);
@@ -53,8 +58,12 @@ const validateDestinationConfig = ({ Config }) => {
const getConversionActionId = async (headers, params) => {
const conversionActionIdKey = sha256(params.event + params.customerId).toString();
return conversionActionIdCache.get(conversionActionIdKey, async () => {
+ const queryString = SqlString.format(
+ 'SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = ?',
+ [params.event],
+ );
const data = {
- query: `SELECT conversion_action.id FROM conversion_action WHERE conversion_action.name = '${params.event}'`,
+ query: queryString,
};
const endpoint = SEARCH_STREAM.replace(':customerId', params.customerId);
const requestOptions = {
@@ -64,6 +73,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)) {
@@ -214,6 +225,17 @@ function getExisitingUserIdentifier(userIdentifierInfo, defaultUserIdentifier) {
return result;
}
+const getCallConversionPayload = (message, Config, eventLevelConsentsData) => {
+ const payload = constructPayload(message, trackCallConversionsMapping);
+ // here conversions[0] should be present because there are some mandatory properties mapped in the mapping json.
+ payload.conversions[0].consent = finaliseConsent(
+ consentConfigMap,
+ eventLevelConsentsData,
+ Config,
+ );
+ return payload;
+};
+
/**
* This Function create the add conversion payload
* and returns the payload
@@ -270,6 +292,10 @@ const getAddConversionPayload = (message, Config) => {
set(payload, 'operations.create.userIdentifiers[0]', {});
}
}
+ // add consent support for store conversions. Note: No event level consent supported.
+ const consentObject = finaliseConsent(consentConfigMap, {}, Config);
+ // create property should be present because there are some mandatory properties mapped in the mapping json.
+ set(payload, 'operations.create.consent', consentObject);
return payload;
};
@@ -285,7 +311,12 @@ const getStoreConversionPayload = (message, Config, event) => {
return payload;
};
-const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerId) => {
+const getClickConversionPayloadAndEndpoint = (
+ message,
+ Config,
+ filteredCustomerId,
+ eventLevelConsent,
+) => {
const email = getFieldValueFromMessage(message, 'emailOnly');
const phone = getFieldValueFromMessage(message, 'phone');
const { hashUserIdentifier, defaultUserIdentifier, UserIdentifierSource, conversionEnvironment } =
@@ -357,9 +388,19 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI
if (!properties.conversionEnvironment && conversionEnvironment !== 'none') {
set(payload, 'conversions[0].conversionEnvironment', conversionEnvironment);
}
+
+ // add consent support for click conversions
+ const consentObject = finaliseConsent(consentConfigMap, eventLevelConsent, Config);
+ // here conversions[0] is expected to be present there are some mandatory properties mapped in the mapping json.
+ set(payload, 'conversions[0].consent', consentObject);
return { payload, endpoint };
};
+const getConsentsDataFromIntegrationObj = (message) => {
+ const integrationObj = getIntegrationsObj(message, 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS') || {};
+ return integrationObj?.consents || {};
+};
+
module.exports = {
validateDestinationConfig,
generateItemListFromProducts,
@@ -370,4 +411,6 @@ module.exports = {
buildAndGetAddress,
getClickConversionPayloadAndEndpoint,
getExisitingUserIdentifier,
+ getConsentsDataFromIntegrationObj,
+ getCallConversionPayload,
};
diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js
index 8deaa3ab0a..2d1863413c 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js
@@ -2,6 +2,8 @@ const {
getClickConversionPayloadAndEndpoint,
buildAndGetAddress,
getExisitingUserIdentifier,
+ getConsentsDataFromIntegrationObj,
+ getCallConversionPayload,
} = require('./utils');
const getTestMessage = () => {
@@ -161,12 +163,16 @@ describe('getExisitingUserIdentifier util tests', () => {
describe('getClickConversionPayloadAndEndpoint util tests', () => {
it('getClickConversionPayloadAndEndpoint flow check when default field identifier is present', () => {
let expectedOutput = {
- endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions',
+ endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions',
payload: {
conversions: [
{
conversionDateTime: '2022-01-01 12:32:45-08:00',
conversionEnvironment: 'WEB',
+ consent: {
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ },
userIdentifiers: [
{
hashedEmail: 'fa922cb41ff930664d4c9ced3c472ce7ecf29a0f8248b7018456e990177fff75',
@@ -187,11 +193,15 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => {
delete fittingPayload.traits.email;
delete fittingPayload.properties.email;
let expectedOutput = {
- endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions',
+ endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions',
payload: {
conversions: [
{
conversionDateTime: '2022-01-01 12:32:45-08:00',
+ consent: {
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ },
conversionEnvironment: 'WEB',
userIdentifiers: [
{
@@ -215,7 +225,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => {
delete fittingPayload.traits.phone;
delete fittingPayload.properties.email;
let expectedOutput = {
- endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions',
+ endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions',
payload: {
conversions: [
{
@@ -237,7 +247,7 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => {
).toThrow('Either of email or phone is required for user identifier');
});
- it('getClickConversionPayloadAndEndpoint flow check when default field identifier is present and product list present', () => {
+ it('finaliseConsent', () => {
let fittingPayload = { ...getTestMessage() };
fittingPayload.properties.products = [
{
@@ -251,13 +261,17 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => {
},
];
let expectedOutput = {
- endpoint: 'https://googleads.googleapis.com/v14/customers/9625812972:uploadClickConversions',
+ endpoint: 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions',
payload: {
conversions: [
{
cartData: { items: [{ productId: 1234, quantity: 2, unitPrice: 10 }] },
conversionDateTime: '2022-01-01 12:32:45-08:00',
conversionEnvironment: 'WEB',
+ consent: {
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ },
userIdentifiers: [
{
hashedEmail: 'fa922cb41ff930664d4c9ced3c472ce7ecf29a0f8248b7018456e990177fff75',
@@ -273,3 +287,118 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => {
);
});
});
+
+describe('getConsentsDataFromIntegrationObj', () => {
+ it('should return an empty object when conversionType is "store"', () => {
+ const message = {};
+ const result = getConsentsDataFromIntegrationObj(message);
+ expect(result).toEqual({});
+ });
+ it('should return the consent object when conversion type is call', () => {
+ const message = {
+ integrations: {
+ GOOGLE_ADWORDS_OFFLINE_CONVERSIONS: {
+ consents: {
+ adUserData: 'GRANTED',
+ adPersonalization: 'DENIED',
+ },
+ },
+ },
+ };
+ const conversionType = 'call';
+ const result = getConsentsDataFromIntegrationObj(message, conversionType);
+ expect(result).toEqual({
+ adPersonalization: 'DENIED',
+ adUserData: 'GRANTED',
+ });
+ });
+});
+
+describe('getCallConversionPayload', () => {
+ it('should call conversion payload with consent object', () => {
+ const message = {
+ properties: {
+ callerId: '1234',
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ };
+ const result = getCallConversionPayload(
+ message,
+ {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'DENIED',
+ },
+ {
+ adUserData: 'GRANTED',
+ adPersonalization: 'GRANTED',
+ },
+ );
+ expect(result).toEqual({
+ conversions: [
+ {
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ callerId: '1234',
+ consent: {
+ adPersonalization: 'GRANTED',
+ adUserData: 'GRANTED',
+ },
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ ],
+ });
+ });
+ it('should call conversion payload with consent object', () => {
+ const message = {
+ properties: {
+ callerId: '1234',
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ };
+ const result = getCallConversionPayload(
+ message,
+ {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'DENIED',
+ },
+ {},
+ );
+ expect(result).toEqual({
+ conversions: [
+ {
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ callerId: '1234',
+ consent: {
+ adPersonalization: 'DENIED',
+ adUserData: 'GRANTED',
+ },
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ ],
+ });
+ });
+ it('should call conversion payload with consent object even if no consent input from UI as well as event level', () => {
+ const message = {
+ properties: {
+ callerId: '1234',
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ };
+ const result = getCallConversionPayload(message, {}, {});
+ expect(result).toEqual({
+ conversions: [
+ {
+ callStartDateTime: '2022-01-01 12:32:45-08:00',
+ callerId: '1234',
+ consent: {
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ },
+ conversionDateTime: '2022-01-01 12:32:45-08:00',
+ },
+ ],
+ });
+ });
+});
diff --git a/src/v0/destinations/google_adwords_remarketing_lists/config.js b/src/v0/destinations/google_adwords_remarketing_lists/config.js
index 5bf0d8a299..0f08b3866d 100644
--- a/src/v0/destinations/google_adwords_remarketing_lists/config.js
+++ b/src/v0/destinations/google_adwords_remarketing_lists/config.js
@@ -16,6 +16,11 @@ const TYPEOFLIST = Object.freeze({
mobileDeviceID: 'mobileId',
});
+const consentConfigMap = {
+ personalizationConsent: 'adPersonalization',
+ userDataConsent: 'adUserData',
+};
+
module.exports = {
BASE_ENDPOINT,
TYPEOFLIST,
@@ -23,4 +28,5 @@ module.exports = {
hashAttributes,
offlineDataJobsMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.AUDIENCE_LIST.name],
addressInfoMapping: MAPPING_CONFIG[CONFIG_CATEGORIES.ADDRESSINFO.name],
+ consentConfigMap,
};
diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js
index bf703ccb1b..3045c1713f 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;
};
@@ -153,7 +159,8 @@ const gaAudienceRespHandler = (destResponse, stageMsg) => {
);
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `Request Processed Successfully`;
const { status, response } = destinationResponse;
if (isHttpStatusSuccess(status)) {
diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js
index 7a367fd086..0e53e6c092 100644
--- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js
+++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js
@@ -15,7 +15,7 @@ const {
getAccessToken,
} = require('../../util');
-const { populateConsentForGoogleDestinations } = require('../../util/googleUtils');
+const { populateConsentFromConfig } = require('../../util/googleUtils');
const {
offlineDataJobsMapping,
@@ -24,6 +24,7 @@ const {
attributeMapping,
hashAttributes,
TYPEOFLIST,
+ consentConfigMap,
} = require('./config');
const { JSON_MIME_TYPE } = require('../../util/constant');
const { MappedToDestinationKey } = require('../../../constants');
@@ -218,7 +219,7 @@ const processEvent = async (metadata, message, destination) => {
}
Object.values(createdPayload).forEach((data) => {
- const consentObj = populateConsentForGoogleDestinations(message.properties);
+ const consentObj = populateConsentFromConfig(destination.Config, consentConfigMap);
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 b218615b44..5e870b9581 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 6e27f6192c..79dcf1bdf2 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 c26e024a6c..9eed244af4 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 e905ee63c4..359c93dc1a 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 b91f520ade..2c35f29e53 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/intercom/networkHandler.js b/src/v0/destinations/intercom/networkHandler.js
index a4106257b3..8485dac52e 100644
--- a/src/v0/destinations/intercom/networkHandler.js
+++ b/src/v0/destinations/intercom/networkHandler.js
@@ -13,8 +13,9 @@ const errorResponseHandler = (destinationResponse, dest) => {
}
};
-const destResponseHandler = (destinationResponse, dest) => {
- errorResponseHandler(destinationResponse, dest);
+const destResponseHandler = (responseParams) => {
+ const { destinationResponse, destType } = responseParams;
+ errorResponseHandler(destinationResponse, destType);
return {
destinationResponse: destinationResponse.response,
message: 'Request Processed Successfully',
diff --git a/src/v0/destinations/iterable/deleteUsers.js b/src/v0/destinations/iterable/deleteUsers.js
index a179a8930f..015a9de9a0 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 64bdcfcfa4..207a8d1186 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 b08c717475..78f278575a 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 e128f2666c..b358919bc1 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 e2a8d86085..329ecd978f 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 3c2f8137f2..a0fe3e81a7 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 60b334f3a2..df2dbb4712 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 571a03f139..530983bb26 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 1570a69ec3..efc68b89d6 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 894f70672a..87f547c124 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 111e6ed3de..e986157b64 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 9156bf45e9..78b4f766d1 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 756522939d..a61ca8a73d 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 7abcc65c02..ac555accfe 100644
--- a/src/v0/destinations/marketo/networkHandler.js
+++ b/src/v0/destinations/marketo/networkHandler.js
@@ -3,9 +3,10 @@ const { marketoResponseHandler } = require('./util');
const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network');
const { processAxiosResponse } = require('../../../adapters/utils/networkUtils');
-const responseHandler = (destinationResponse, destType) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = 'Request Processed Successfully';
- const { status, rudderJobMetadata } = destinationResponse;
+ const { status } = destinationResponse;
const authCache = v0Utils.getDestAuthCacheInstance(destType);
// check for marketo application level failures
marketoResponseHandler(
diff --git a/src/v0/destinations/marketo/transform.js b/src/v0/destinations/marketo/transform.js
index ea867c7e4b..b74d632f56 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 967c7a53b1..be45d91ac3 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 e6f5662000..db3b13eeb8 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 9c42fdc98d..b49a265fd5 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 875b0d8280..aa4b3aacc4 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 db7a634774..f53347d6e5 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/transform.js b/src/v0/destinations/marketo_bulk_upload/transform.js
index 5431e67d38..1943d817cd 100644
--- a/src/v0/destinations/marketo_bulk_upload/transform.js
+++ b/src/v0/destinations/marketo_bulk_upload/transform.js
@@ -1,4 +1,4 @@
-const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const { InstrumentationError, isDefined } = require('@rudderstack/integrations-lib');
const {
getHashFromArray,
getFieldValueFromMessage,
@@ -34,7 +34,7 @@ function responseBuilderSimple(message, destination) {
// columnNames with trait's values from rudder payload
Object.keys(fieldHashmap).forEach((key) => {
const val = traits[fieldHashmap[key]];
- if (val) {
+ if (isDefined(val)) {
payload[key] = val;
}
});
diff --git a/src/v0/destinations/marketo_bulk_upload/util.js b/src/v0/destinations/marketo_bulk_upload/util.js
index 8e515c6137..833780d4c1 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 30b053b9d3..086378cf6a 100644
--- a/src/v0/destinations/marketo_static_list/networkHandler.js
+++ b/src/v0/destinations/marketo_static_list/networkHandler.js
@@ -4,9 +4,10 @@ const v0Utils = require('../../util');
const { processAxiosResponse } = require('../../../adapters/utils/networkUtils');
const { DESTINATION } = require('./config');
-const responseHandler = (destinationResponse, destType) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = 'Request Processed Successfully';
- const { status, rudderJobMetadata } = 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 294e34f91b..92c137c614 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 912d548d09..73d4bec8f8 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 d8ad8dbffc..7a1827e769 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 736f0133fd..872fad42a7 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 f01475ef2b..e1240c609d 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 24890c0eb1..10271bebef 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 8e943f41dd..f56242d88b 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 866119a336..40cdb34649 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 55038e10b8..5eff77bd15 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 a072aef0e4..b025660fa4 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/networkHandler.js b/src/v0/destinations/pardot/networkHandler.js
index 12b4abbc53..60d2f7ee23 100644
--- a/src/v0/destinations/pardot/networkHandler.js
+++ b/src/v0/destinations/pardot/networkHandler.js
@@ -46,6 +46,19 @@ const getStatus = (code) => {
const pardotRespHandler = (destResponse, stageMsg) => {
const { status, response } = destResponse;
const respAttributes = response['@attributes'];
+
+ // to handle errors like service unavilable, wrong url, no response
+ if (!respAttributes) {
+ throw new NetworkError(
+ `${JSON.stringify(response)} ${stageMsg}`,
+ status,
+ {
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ response,
+ );
+ }
+
const { stat, err_code: errorCode } = respAttributes;
if (isHttpStatusSuccess(status) && stat !== 'fail') {
@@ -65,7 +78,8 @@ const pardotRespHandler = (destResponse, stageMsg) => {
);
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = 'Request Processed Successfully';
const { status } = destinationResponse;
// else successfully return status, message and original destination response
diff --git a/src/v0/destinations/pardot/transform.js b/src/v0/destinations/pardot/transform.js
index 532aa9ba1a..f8c9bbbd89 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 ee7e2e5b19..f8ccfd48ea 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 acc4db2035..1b23561721 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 1b16bd5538..4c97a23e51 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) => {
@@ -27,7 +33,8 @@ const extractContent = (xmlPayload, tagName) => {
return match ? match[1] : null;
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`;
const { response, status } = destinationResponse;
if (status === 400) {
@@ -99,5 +106,5 @@ class networkHandler {
module.exports = {
networkHandler,
- responseHandler
+ responseHandler,
};
diff --git a/src/v0/destinations/rakuten/networkHandler.test.js b/src/v0/destinations/rakuten/networkHandler.test.js
index 70461c86c1..da74e05cb3 100644
--- a/src/v0/destinations/rakuten/networkHandler.test.js
+++ b/src/v0/destinations/rakuten/networkHandler.test.js
@@ -8,7 +8,7 @@ describe('responseHandler', () => {
status: 200,
};
- const result = responseHandler(destinationResponse);
+ const result = responseHandler({ destinationResponse });
expect(result.status).toBe(200);
expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
@@ -21,7 +21,7 @@ describe('responseHandler', () => {
status: 400,
};
expect(() => {
- responseHandler(destinationResponse);
+ responseHandler({ destinationResponse });
}).toThrow('Request failed with status: 400 due to invalid Marketing Id');
});
@@ -31,7 +31,7 @@ describe('responseHandler', () => {
status: 200,
};
expect(() => {
- responseHandler(destinationResponse);
+ responseHandler({ destinationResponse });
}).toThrow(
'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.',
);
@@ -43,7 +43,7 @@ describe('responseHandler', () => {
status: 200,
};
- const result = responseHandler(destinationResponse);
+ const result = responseHandler({ destinationResponse });
expect(result.status).toBe(200);
expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
@@ -57,8 +57,7 @@ describe('responseHandler', () => {
};
expect(() => {
- responseHandler(destinationResponse);
+ responseHandler({ destinationResponse });
}).toThrow('Request failed with status: 200 with number of bad records 1');
-
});
});
diff --git a/src/v0/destinations/reddit/networkHandler.js b/src/v0/destinations/reddit/networkHandler.js
index 836c015859..55087b52ac 100644
--- a/src/v0/destinations/reddit/networkHandler.js
+++ b/src/v0/destinations/reddit/networkHandler.js
@@ -18,7 +18,8 @@ const redditRespHandler = (destResponse) => {
);
}
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `Request Processed Successfully`;
const { status } = destinationResponse;
if (!isHttpStatusSuccess(status)) {
diff --git a/src/v0/destinations/salesforce/networkHandler.js b/src/v0/destinations/salesforce/networkHandler.js
index 918084cc89..ac31241775 100644
--- a/src/v0/destinations/salesforce/networkHandler.js
+++ b/src/v0/destinations/salesforce/networkHandler.js
@@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils')
const { LEGACY } = require('./config');
const { salesforceResponseHandler } = require('./utils');
-const responseHandler = (destinationResponse, destType) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = `Request for destination: ${destType} Processed Successfully`;
salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
- destinationResponse?.rudderJobMetadata?.destInfo?.authKey,
+ rudderJobMetadata?.destInfo?.authKey,
LEGACY,
);
diff --git a/src/v0/destinations/salesforce/transform.js b/src/v0/destinations/salesforce/transform.js
index 5ada9dfaa0..b8f032c5bf 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 96735ecc17..85061ce2b2 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/salesforce_oauth/networkHandler.js b/src/v0/destinations/salesforce_oauth/networkHandler.js
index 2bcace31c9..b6cbed77f9 100644
--- a/src/v0/destinations/salesforce_oauth/networkHandler.js
+++ b/src/v0/destinations/salesforce_oauth/networkHandler.js
@@ -3,13 +3,14 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils')
const { OAUTH } = require('../salesforce/config');
const { salesforceResponseHandler } = require('../salesforce/utils');
-const responseHandler = (destinationResponse, destType) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = `Request for destination: ${destType} Processed Successfully`;
salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
- destinationResponse?.rudderJobMetadata?.destInfo?.authKey,
+ rudderJobMetadata?.destInfo?.authKey,
OAUTH,
);
diff --git a/src/v0/destinations/sendgrid/deleteUsers.js b/src/v0/destinations/sendgrid/deleteUsers.js
index 8410f41296..ccd277d90d 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 5038fedf7b..c32e34c489 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 b6c257ee0c..e76b124968 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 9ad37fc9b7..9fded8e493 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 f856c44d6b..1b1f5c323b 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 7623d751f1..53925bc7ed 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 c49c49017c..8d382ef649 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 37d321a468..6fec6313a4 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 db36f6f518..6044216293 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;
};
@@ -80,7 +83,8 @@ const scaAudienceRespHandler = (destResponse, stageMsg) => {
);
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = `Request Processed Successfully`;
const { status } = destinationResponse;
if (isHttpStatusSuccess(status)) {
diff --git a/src/v0/destinations/sprig/deleteUsers.js b/src/v0/destinations/sprig/deleteUsers.js
index a886bbbafc..01044adcd1 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 e9693e8132..aebbfc0785 100644
--- a/src/v0/destinations/the_trade_desk/networkHandler.js
+++ b/src/v0/destinations/the_trade_desk/networkHandler.js
@@ -8,37 +8,28 @@ const { getSignatureHeader } = require('../../../cdk/v2/destinations/the_trade_d
const { isHttpStatusSuccess } = require('../../util/index');
const tags = require('../../util/tags');
const { JSON_MIME_TYPE } = require('../../util/constant');
-const {
- REAL_TIME_CONVERSION_ENDPOINT,
-} = require('../../../cdk/v2/destinations/the_trade_desk/config');
const proxyRequest = async (request) => {
const { endpoint, data, method, params, headers, config } = prepareProxyRequest(request);
- let ProxyHeaders = {
- ...headers,
- 'Content-Type': JSON_MIME_TYPE,
- };
-
- // For first party data flow
- if (endpoint !== REAL_TIME_CONVERSION_ENDPOINT) {
- if (!config?.advertiserSecretKey) {
- throw new PlatformError('Advertiser secret key is missing in destination config. Aborting');
- }
- if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) {
- throw new PlatformError('Data provider secret key is missing. Aborting');
- }
+ if (!config?.advertiserSecretKey) {
+ throw new PlatformError('Advertiser secret key is missing in destination config. Aborting');
+ }
- ProxyHeaders = {
- ...ProxyHeaders,
- TtdSignature: getSignatureHeader(data, config.advertiserSecretKey),
- 'TtdSignature-dp': getSignatureHeader(
- data,
- process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY,
- ),
- };
+ if (!process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY) {
+ throw new PlatformError('Data provider secret key is missing. Aborting');
}
+ const ProxyHeaders = {
+ ...headers,
+ 'Content-Type': JSON_MIME_TYPE,
+ TtdSignature: getSignatureHeader(data, config.advertiserSecretKey),
+ 'TtdSignature-dp': getSignatureHeader(
+ data,
+ process.env.THE_TRADE_DESK_DATA_PROVIDER_SECRET_KEY,
+ ),
+ };
+
const requestOptions = {
url: endpoint,
data,
@@ -46,11 +37,18 @@ 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;
};
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const message = 'Request Processed Successfully';
const { response, status } = destinationResponse;
@@ -69,7 +67,6 @@ const responseHandler = (destinationResponse) => {
// Trade desk first party data api returns 200 with an error in case of "Failed to parse TDID, DAID, UID2, IDL, EUID, or failed to decrypt UID2Token or EUIDToken"
// https://partner.thetradedesk.com/v3/portal/data/doc/post-data-advertiser-external
// {"FailedLines":[{"ErrorCode":"MissingUserId","Message":"Invalid DAID, item #1"}]}
- // For real time conversion api we don't have separate response handling, trade desk always return 400 for bad events.
if ('FailedLines' in response && response.FailedLines.length > 0) {
throw new AbortedError(
`Request failed with status: ${status} due to ${JSON.stringify(response)}`,
diff --git a/src/v0/destinations/tiktok_ads/networkHandler.js b/src/v0/destinations/tiktok_ads/networkHandler.js
index ae93b1ec15..5d4b7fd4e0 100644
--- a/src/v0/destinations/tiktok_ads/networkHandler.js
+++ b/src/v0/destinations/tiktok_ads/networkHandler.js
@@ -8,7 +8,8 @@ const { DESTINATION } = require('./config');
const { TAG_NAMES } = require('../../util/tags');
const { HTTP_STATUS_CODES } = require('../../util/constant');
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`;
const {
response: { code },
diff --git a/src/v0/destinations/tiktok_ads/transform.js b/src/v0/destinations/tiktok_ads/transform.js
index bdf3a0defe..17a37984c3 100644
--- a/src/v0/destinations/tiktok_ads/transform.js
+++ b/src/v0/destinations/tiktok_ads/transform.js
@@ -17,9 +17,9 @@ const {
getDestinationExternalID,
getFieldValueFromMessage,
getHashFromArrayWithDuplicate,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
batchMultiplexedEvents,
+ validateEventName,
} = require('../../util');
const { process: processV2, processRouterDest: processRouterDestV2 } = require('./transformV2');
const { getContents } = require('./util');
@@ -130,14 +130,9 @@ 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');
- }
-
+ validateEventName(message.event);
+ let event = message.event.toLowerCase().trim();
const standardEventsMap = getHashFromArrayWithDuplicate(eventsToStandard);
-
if (!sendCustomEvents && eventNameMapping[event] === undefined && !standardEventsMap[event]) {
throw new InstrumentationError(
`Event name (${event}) is not valid, must be mapped to one of standard events`,
@@ -248,10 +243,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 91078dfe65..3bd8699e3a 100644
--- a/src/v0/destinations/tiktok_ads/transformV2.js
+++ b/src/v0/destinations/tiktok_ads/transformV2.js
@@ -13,8 +13,8 @@ const {
isDefinedAndNotNullAndNotEmpty,
getDestinationExternalID,
getHashFromArrayWithDuplicate,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
+ validateEventName,
} = require('../../util');
const { getContents, hashUserField } = require('./util');
const config = require('./config');
@@ -40,7 +40,7 @@ const getTrackResponsePayload = (message, destConfig, event) => {
}
// if contents is not present but we have properties.products present which has fields with superset of contents fields
- if (payload.properties && !payload.properties.contents && message.properties.products) {
+ if (!payload.properties?.contents && message.properties?.products) {
// retreiving data from products only when contents is not present
payload.properties.contents = getContents(message, false);
}
@@ -60,7 +60,7 @@ const getTrackResponsePayload = (message, destConfig, event) => {
const trackResponseBuilder = async (message, { Config }) => {
const { eventsToStandard, sendCustomEvents, accessToken, pixelCode } = Config;
-
+ validateEventName(message.event);
let event = message.event?.toLowerCase().trim();
if (!event) {
throw new InstrumentationError('Event name is required');
@@ -690,10 +690,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 5d5e80c716..4bb3bda850 100644
--- a/src/v0/destinations/tiktok_ads_offline_events/config.js
+++ b/src/v0/destinations/tiktok_ads_offline_events/config.js
@@ -19,12 +19,24 @@ const CONFIG_CATEGORIES = {
const PARTNER_NAME = 'RudderStack';
const EVENT_NAME_MAPPING = {
+ addpaymentinfo: 'AddPaymentInfo',
+ addtocart: 'AddToCart',
+ addtowishlist: 'AddToWishlist',
+ 'checkout started': 'InitiateCheckout',
'checkout step completed': 'CompletePayment',
+ 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',
};
-
const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname);
module.exports = {
diff --git a/src/v0/destinations/tiktok_ads_offline_events/transform.js b/src/v0/destinations/tiktok_ads_offline_events/transform.js
index 945c31ea63..dbe9a06dd6 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 06e5496a1e..01c5cfeb25 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 601675fc2f..6b0db0622c 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 2f237b1dd8..ad59a81267 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 52fba2167e..f332d7a4a7 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 eb61a472cf..c2505c635b 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 d41716935f..255f84d1c9 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 54566c71bb..7690e9f94a 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.debugw(`${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.debugw(`${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 18d7b8fc0e..dd37482bc4 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 9b0d814d6a..e3d686fcef 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 9c570dde41..d058db36b5 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/sources/shopify/shopify_redis.util.test.js b/src/v0/sources/shopify/shopify_redis.util.test.js
index db596e1dfb..fb99837932 100644
--- a/src/v0/sources/shopify/shopify_redis.util.test.js
+++ b/src/v0/sources/shopify/shopify_redis.util.test.js
@@ -1,5 +1,9 @@
const { getAnonymousIdAndSessionId, checkAndUpdateCartItems } = require('./util');
jest.mock('ioredis', () => require('../../../../test/__mocks__/redis'));
+const metricMetadata = {
+ writeKey: 'dummyKey',
+ source: 'src',
+};
describe('Shopify Utils Test', () => {
describe('Check for valid cart update event test cases', () => {
it('Event containing token and nothing is retreived from redis and less than req. time difference between created_at and uadated_at', async () => {
@@ -14,7 +18,7 @@ describe('Shopify Utils Test', () => {
created_at: '2023-02-10T12:05:04.402Z',
};
const expectedOutput = false;
- const output = await checkAndUpdateCartItems(input);
+ const output = await checkAndUpdateCartItems(input, null, metricMetadata);
expect(output).toEqual(expectedOutput);
});
it('Event containing token and nothing is retreived from redis', async () => {
@@ -28,7 +32,7 @@ describe('Shopify Utils Test', () => {
],
};
const expectedOutput = true;
- const output = await checkAndUpdateCartItems(input);
+ const output = await checkAndUpdateCartItems(input, null, metricMetadata);
expect(output).toEqual(expectedOutput);
});
@@ -44,7 +48,7 @@ describe('Shopify Utils Test', () => {
};
const expectedOutput = true;
- const output = await checkAndUpdateCartItems(input);
+ const output = await checkAndUpdateCartItems(input, null, metricMetadata);
expect(output).toEqual(expectedOutput);
});
@@ -60,7 +64,7 @@ describe('Shopify Utils Test', () => {
};
const expectedOutput = false;
- const output = await checkAndUpdateCartItems(input);
+ const output = await checkAndUpdateCartItems(input, null, metricMetadata);
expect(output).toEqual(expectedOutput);
});
@@ -76,7 +80,7 @@ describe('Shopify Utils Test', () => {
};
const expectedOutput = true;
- const output = await checkAndUpdateCartItems(input);
+ const output = await checkAndUpdateCartItems(input, null, metricMetadata);
expect(output).toEqual(expectedOutput);
});
});
diff --git a/src/v0/sources/shopify/transform.js b/src/v0/sources/shopify/transform.js
index 17d31c10b4..52c16b7a98 100644
--- a/src/v0/sources/shopify/transform.js
+++ b/src/v0/sources/shopify/transform.js
@@ -143,7 +143,7 @@ const processEvent = async (inputEvent, metricMetadata) => {
break;
case 'carts_update':
if (useRedisDatabase) {
- redisData = await getDataFromRedis(event.id || event.token);
+ redisData = await getDataFromRedis(event.id || event.token, metricMetadata);
const isValidEvent = await checkAndUpdateCartItems(inputEvent, redisData, metricMetadata);
if (!isValidEvent) {
return NO_OPERATION_SUCCESS;
@@ -154,8 +154,9 @@ const processEvent = async (inputEvent, metricMetadata) => {
default:
if (!SUPPORTED_TRACK_EVENTS.includes(shopifyTopic)) {
stats.increment('invalid_shopify_event', {
- event: shopifyTopic,
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
+ shopifyTopic: metricMetadata.shopifyTopic,
});
return NO_OPERATION_SUCCESS;
}
@@ -215,7 +216,8 @@ const processIdentifierEvent = async (event, metricMetadata) => {
stats.increment('shopify_redis_calls', {
type: 'set',
field: 'itemsHash',
- ...metricMetadata,
+ source: metricMetadata.source,
+ writeKey: metricMetadata.writeKey,
});
/* cart_token: {
anonymousId: 'anon_id1',
@@ -236,14 +238,16 @@ const processIdentifierEvent = async (event, metricMetadata) => {
stats.increment('shopify_redis_calls', {
type: 'set',
field,
- ...metricMetadata,
+ source: metricMetadata.source,
+ writeKey: metricMetadata.writeKey,
});
await RedisDB.setVal(`${event.cartToken}`, value);
} catch (e) {
logger.debugw(`{{SHOPIFY::}} cartToken map set call Failed due redis error ${e}`);
stats.increment('shopify_redis_failures', {
type: 'set',
- ...metricMetadata,
+ source: metricMetadata.source,
+ writeKey: metricMetadata.writeKey,
});
}
}
diff --git a/src/v0/sources/shopify/util.js b/src/v0/sources/shopify/util.js
index 5a0cc0e9ad..b5ed2d0b19 100644
--- a/src/v0/sources/shopify/util.js
+++ b/src/v0/sources/shopify/util.js
@@ -29,7 +29,8 @@ const getDataFromRedis = async (key, metricMetadata) => {
stats.increment('shopify_redis_calls', {
type: 'get',
field: 'all',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
});
const redisData = await RedisDB.getVal(key);
if (
@@ -37,7 +38,8 @@ const getDataFromRedis = async (key, metricMetadata) => {
(typeof redisData === 'object' && Object.keys(redisData).length === 0)
) {
stats.increment('shopify_redis_no_val', {
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
});
}
return redisData;
@@ -45,7 +47,8 @@ const getDataFromRedis = async (key, metricMetadata) => {
logger.debugw(`{{SHOPIFY::}} Get call Failed due redis error ${e}`);
stats.increment('shopify_redis_failures', {
type: 'get',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
});
}
return null;
@@ -166,7 +169,9 @@ const getAnonymousIdAndSessionId = async (message, metricMetadata, redisData = n
if (isDefinedAndNotNull(anonymousId) && isDefinedAndNotNull(sessionId)) {
stats.increment('shopify_anon_id_resolve', {
method: 'note_attributes',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
+ shopifyTopic: metricMetadata.shopifyTopic,
});
return { anonymousId, sessionId };
}
@@ -198,7 +203,9 @@ const getAnonymousIdAndSessionId = async (message, metricMetadata, redisData = n
// and for how many
stats.increment('shopify_anon_id_resolve', {
method: 'database',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
+ shopifyTopic: metricMetadata.shopifyTopic,
});
}
return { anonymousId, sessionId };
@@ -215,14 +222,16 @@ const updateCartItemsInRedis = async (cartToken, newCartItemsHash, metricMetadat
stats.increment('shopify_redis_calls', {
type: 'set',
field: 'itemsHash',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
});
await RedisDB.setVal(`${cartToken}`, value);
} catch (e) {
logger.debugw(`{{SHOPIFY::}} itemsHash set call Failed due redis error ${e}`);
stats.increment('shopify_redis_failures', {
type: 'set',
- ...metricMetadata,
+ writeKey: metricMetadata.writeKey,
+ source: metricMetadata.source,
});
}
};
diff --git a/src/v0/util/facebookUtils/index.js b/src/v0/util/facebookUtils/index.js
index 7fa1e898fe..c7753d255f 100644
--- a/src/v0/util/facebookUtils/index.js
+++ b/src/v0/util/facebookUtils/index.js
@@ -298,4 +298,5 @@ module.exports = {
transformedPayloadData,
formingFinalResponse,
fetchUserData,
+ deduceFbcParam,
};
diff --git a/src/v0/util/facebookUtils/index.test.js b/src/v0/util/facebookUtils/index.test.js
index 98e4ccec40..20c4ee59f2 100644
--- a/src/v0/util/facebookUtils/index.test.js
+++ b/src/v0/util/facebookUtils/index.test.js
@@ -1,5 +1,11 @@
-const { transformedPayloadData } = require('./index');
+const {
+ transformedPayloadData,
+ fetchUserData,
+ deduceFbcParam,
+ getContentType,
+} = require('./index');
const sha256 = require('sha256');
+const { MAPPING_CONFIG, CONFIG_CATEGORIES } = require('../../destinations/facebook_pixel/config');
describe('transformedPayloadData_function', () => {
// Tests with default values for all parameters
@@ -301,3 +307,335 @@ describe('transformedPayloadData_function', () => {
expect(result).toEqual({});
});
});
+
+describe('deduceFbcParam', () => {
+ // Should return undefined if message.context.page.url is undefined
+ it('should return undefined when message.context.page.url is undefined', () => {
+ const message = {};
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should return undefined if URL constructor throws an error
+ it('should return undefined when URL constructor throws an error', () => {
+ const message = {
+ context: {
+ page: {
+ url: 'invalid-url',
+ },
+ },
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should return undefined if fbclid is undefined
+ it('should return undefined when fbclid is undefined', () => {
+ const message = {
+ context: {
+ page: {
+ url: 'https://example.com',
+ },
+ },
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should handle message with empty context object
+ it('should handle message with empty context object', () => {
+ const message = {
+ context: {},
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should handle message with empty page object
+ it('should handle message with empty page object', () => {
+ const message = {
+ context: {
+ page: {},
+ },
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should handle message with empty url string
+ it('should handle message with empty url string', () => {
+ const message = {
+ context: {
+ page: {
+ url: '',
+ },
+ },
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toBeUndefined();
+ });
+
+ // Should return fbc parameter when all conditions are met
+ it('should return fbc parameter when all conditions are met', () => {
+ const message = {
+ context: {
+ page: {
+ url: 'https://example.com?fbclid=123456',
+ },
+ },
+ };
+ const result = deduceFbcParam(message);
+ expect(result).toEqual(expect.stringContaining('fb.1.'));
+ });
+});
+
+describe('fetchUserData', () => {
+ const message = {
+ channel: 'web',
+ context: {
+ traits: {
+ name: 'Rudder Test',
+ email: 'abc@gmail.com',
+ firstname: 'Rudder',
+ lastname: 'Test',
+ phone: 9000000000,
+ gender: 'female',
+ },
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.0',
+ },
+ 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,
+ },
+ },
+ properties: {
+ plan: 'standard plan',
+ name: 'rudder test',
+ },
+ type: 'identify',
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
+ anonymousId: '00000000000000000000000000',
+ userId: '123456',
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ };
+
+ const Config = {
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: '',
+ blacklistPiiHash: false,
+ },
+ ],
+ accessToken: '09876',
+ pixelId: 'dummyPixelId',
+ eventsToEvents: [
+ {
+ from: '',
+ to: '',
+ },
+ ],
+ eventCustomProperties: [
+ {
+ eventCustomProperties: '',
+ },
+ ],
+ valueFieldIdentifier: '',
+ advancedMapping: true,
+ whitelistPiiProperties: [
+ {
+ whitelistPiiProperties: '',
+ },
+ ],
+ };
+
+ // Returns a valid user data object when given valid inputs.
+ it('should return a valid user data object when given valid inputs without integrations object', () => {
+ const mappingJson = MAPPING_CONFIG[CONFIG_CATEGORIES.USERDATA.name];
+ const destinationName = 'fb_pixel';
+
+ const result = fetchUserData(message, Config, mappingJson, destinationName);
+
+ expect(result).toEqual({
+ external_id: '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ ph: '593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579',
+ ge: '252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111',
+ ln: '532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25',
+ fn: '2c2ccf28d806f6f9a34b67aa874d2113b7ac1444f1a4092541b8b75b84771747',
+ client_ip_address: '0.0.0.0',
+ client_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',
+ fbc: undefined,
+ });
+ });
+
+ it('should return a valid user data object when given valid inputs with integrations object', () => {
+ const mappingJson = MAPPING_CONFIG[CONFIG_CATEGORIES.USERDATA.name];
+ const destinationName = 'fb_pixel';
+ message.integrations.FacebookPixel = { hashed: true };
+
+ const result = fetchUserData(message, Config, mappingJson, destinationName);
+
+ expect(result).toEqual({
+ em: 'abc@gmail.com',
+ external_id: '123456',
+ ph: '9000000000',
+ ge: '252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111',
+ ln: 'Test',
+ fn: 'Rudder',
+ client_ip_address: '0.0.0.0',
+ client_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',
+ fbc: undefined,
+ });
+ });
+
+ it('should return null when mappingJson is undefined', () => {
+ const mappingJson = undefined;
+ const destinationName = 'fb_pixel';
+ const result = fetchUserData(message, Config, mappingJson, destinationName);
+
+ expect(result).toBeNull();
+ });
+
+ it('should return hashed data when destinationName is undefined', () => {
+ const mappingJson = MAPPING_CONFIG[CONFIG_CATEGORIES.USERDATA.name];
+ const destinationName = undefined;
+
+ const result = fetchUserData(message, Config, mappingJson, destinationName);
+
+ expect(result).toEqual({
+ client_ip_address: '0.0.0.0',
+ client_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',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ external_id: '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92',
+ fbc: undefined,
+ fn: '2c2ccf28d806f6f9a34b67aa874d2113b7ac1444f1a4092541b8b75b84771747',
+ ge: '252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111',
+ ln: '532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25',
+ ph: '593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579',
+ });
+ });
+});
+
+describe('getContentType', () => {
+ // Returns default value when no category or categoryToContent is provided
+ it('should return default value when no category or categoryToContent is provided', () => {
+ const message = {
+ properties: {
+ produtcs: [
+ {
+ product_id: '123',
+ },
+ ],
+ },
+ };
+ const defaultValue = 'product';
+ const categoryToContent = [];
+ const destinationName = 'fb_pixel';
+
+ const result = getContentType(message, defaultValue, categoryToContent, destinationName);
+
+ expect(result).toBe(defaultValue);
+ });
+
+ // Returns default value when categoryToContent is not an array
+ it('should return default value when categoryToContent is not an array', () => {
+ const message = {
+ properties: {
+ products: [
+ {
+ product_id: '123',
+ },
+ ],
+ },
+ };
+ const defaultValue = 'product';
+ const categoryToContent = 'not an array';
+ const destinationName = 'fb_pixel';
+
+ const result = getContentType(message, defaultValue, categoryToContent, destinationName);
+
+ expect(result).toBe(defaultValue);
+ });
+
+ // Returns categoryToContent value when category is provided and matches with categoryToContent
+ it('should return categoryToContent value when category is provided and matches with categoryToContent', () => {
+ const message = {
+ properties: {
+ category: 'clothing',
+ },
+ };
+ const defaultValue = 'product';
+ const categoryToContent = [{ from: 'clothing', to: 'garments' }];
+ const destinationName = 'fb_pixel';
+
+ const result = getContentType(message, defaultValue, categoryToContent, destinationName);
+
+ expect(result).toBe(categoryToContent[0].to);
+ });
+
+ // Returns integrationsObj.contentType when it exists
+ it('should return integrationsObj.contentType when it exists', () => {
+ const message = {
+ properties: {
+ products: [
+ {
+ product_id: '123',
+ },
+ ],
+ },
+ integrations: {
+ fb_pixel: {
+ contentType: 'content_type_value',
+ },
+ },
+ };
+ const defaultValue = 'product';
+ const categoryToContent = [];
+ const destinationName = 'fb_pixel';
+ const integrationsObj = {
+ contentType: 'content_type_value',
+ };
+
+ const result = getContentType(message, defaultValue, categoryToContent, destinationName);
+
+ expect(result).toBe(integrationsObj.contentType);
+ });
+
+ // Returns 'product' when category is 'clothing' and categoryToContent is not provided
+ it("should return 'product' when category is 'clothing' and categoryToContent is not provided", () => {
+ const message = {
+ properties: {
+ category: 'clothing',
+ },
+ };
+ const defaultValue = 'product';
+ const categoryToContent = [];
+ const destinationName = 'fb_pixel';
+
+ const result = getContentType(message, defaultValue, categoryToContent, destinationName);
+
+ expect(result).toBe(defaultValue);
+ });
+});
diff --git a/src/v0/util/facebookUtils/networkHandler.js b/src/v0/util/facebookUtils/networkHandler.js
index 7219120dd7..d5a731067f 100644
--- a/src/v0/util/facebookUtils/networkHandler.js
+++ b/src/v0/util/facebookUtils/networkHandler.js
@@ -275,7 +275,8 @@ const errorResponseHandler = (destResponse) => {
);
};
-const destResponseHandler = (destinationResponse) => {
+const destResponseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
errorResponseHandler(destinationResponse);
return {
destinationResponse: destinationResponse.response,
diff --git a/src/v0/util/googleUtils/index.js b/src/v0/util/googleUtils/index.js
index c8d872e90e..c153731e73 100644
--- a/src/v0/util/googleUtils/index.js
+++ b/src/v0/util/googleUtils/index.js
@@ -1,30 +1,91 @@
const GOOGLE_ALLOWED_CONSENT_STATUS = ['UNSPECIFIED', 'UNKNOWN', 'GRANTED', 'DENIED'];
+const UNSPECIFIED_CONSENT = 'UNSPECIFIED';
+const UNKNOWN_CONSENT = 'UNKNOWN';
+
/**
- * Populates the consent object based on the provided properties.
+ * Populates the consent object based on the provided configuration and consent mapping.
*
- * @param {object} properties - message.properties containing properties related to consent.
- * @returns {object} - An object containing consent information.
- * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v15/Consent
+ * @param {Object} config - The configuration object containing consent values.
+ * @param {Object} consentConfigMap - The mapping of consent keys to consent types.
+ * @returns {Object} - The consent object populated with consent values based on the configuration.
+ * * ref : https://developers.google.com/google-ads/api/rest/reference/rest/v16/Consent
*/
-
-const populateConsentForGoogleDestinations = (properties) => {
+const populateConsentFromConfig = (config, consentConfigMap) => {
const consent = {};
- if (
- properties?.userDataConsent &&
- GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.userDataConsent)
- ) {
- consent.adUserData = properties.userDataConsent;
- }
+ Object.keys(consentConfigMap).forEach((key) => {
+ const consentType = consentConfigMap[key];
+ if (config?.[key]) {
+ if (GOOGLE_ALLOWED_CONSENT_STATUS.includes(config[key])) {
+ consent[consentType] = config[key];
+ } else {
+ consent[consentType] = UNKNOWN_CONSENT;
+ }
+ } else {
+ consent[consentType] = UNSPECIFIED_CONSENT;
+ }
+ });
- if (
- properties?.personalizationConsent &&
- GOOGLE_ALLOWED_CONSENT_STATUS.includes(properties.personalizationConsent)
- ) {
- consent.adPersonalization = properties.personalizationConsent;
- }
return consent;
};
-module.exports = { populateConsentForGoogleDestinations };
+/**
+ * Generates the final consent object based on the provided consent configuration map, event-level consent, and destination configuration.
+ *
+ * @param {Object} consentConfigMap - The map of consent configuration keys and their corresponding consent types.
+ * @param {Object} [eventLevelConsent={}] - The event-level consent object.
+ * @param {Object} [destConfig={}] - The destination configuration object.
+ * @returns {Object} The final consent object.
+ * ref :
+ * 1) For click conversion :
+ * a) https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/uploadClickConversions#ClickConversion
+ * b) https://developers.google.com/google-ads/api/reference/rpc/v16/ClickConversion#consent
+ * 2) For Call conversion :
+ * a) https://developers.google.com/google-ads/api/rest/reference/rest/v16/customers/uploadCallConversions#CallConversion
+ * b) https://developers.google.com/google-ads/api/reference/rpc/v16/CallConversion#consent
+ * 3) For Store sales conversion :
+ * a) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData
+ * b) https://developers.google.com/google-ads/api/reference/rpc/v16/UserData#consent
+ */
+const finaliseConsent = (consentConfigMap, eventLevelConsent = {}, destConfig = {}) => {
+ // Initialize defaultConsentBlock with unspecified consent for all keys defined in consentConfigMap
+ const defaultConsentBlock = Object.keys(consentConfigMap).reduce((acc, key) => {
+ const consentType = consentConfigMap[key];
+ acc[consentType] = UNSPECIFIED_CONSENT;
+ return acc;
+ }, {});
+
+ // If destConfig is provided, update defaultConsentBlock based on it using populateConsentFromConfig
+ if (Object.keys(destConfig).length > 0) {
+ const populatedConsent = populateConsentFromConfig(destConfig, consentConfigMap);
+ Object.assign(defaultConsentBlock, populatedConsent);
+ }
+
+ const consentObj = {};
+
+ // Iterate through each key in consentConfigMap to determine the final consent
+ Object.keys(consentConfigMap).forEach((configKey) => {
+ const consentKey = consentConfigMap[configKey]; // e.g., 'adUserData'
+
+ // Prioritize event-level consent if available
+ if (eventLevelConsent && eventLevelConsent.hasOwnProperty(consentKey)) {
+ consentObj[consentKey] = GOOGLE_ALLOWED_CONSENT_STATUS.includes(eventLevelConsent[consentKey])
+ ? eventLevelConsent[consentKey]
+ : UNKNOWN_CONSENT;
+ } else {
+ // Fallback to default consent block
+ consentObj[consentKey] = defaultConsentBlock[consentKey];
+ }
+ });
+
+ return consentObj;
+};
+
+module.exports = {
+ populateConsentFromConfig,
+ UNSPECIFIED_CONSENT,
+ UNKNOWN_CONSENT,
+ GOOGLE_ALLOWED_CONSENT_STATUS,
+ finaliseConsent,
+};
diff --git a/src/v0/util/googleUtils/index.test.js b/src/v0/util/googleUtils/index.test.js
index 27eff2a793..28e0fa9ac8 100644
--- a/src/v0/util/googleUtils/index.test.js
+++ b/src/v0/util/googleUtils/index.test.js
@@ -1,50 +1,245 @@
-const { populateConsentForGoogleDestinations } = require('./index');
+const { populateConsentFromConfig, finaliseConsent } = 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', () => {
- const result = populateConsentForGoogleDestinations({});
- expect(result).toEqual({});
+describe('unit test for populateConsentFromConfig', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'adPersonalization',
+ userDataConsent: 'adUserData',
+ };
+ it('should return an UNSPECIFIED object when no properties are provided', () => {
+ const result = populateConsentFromConfig({}, consentConfigMap);
+ 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' });
+ const result = populateConsentFromConfig(properties, consentConfigMap);
+ 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' });
+ const result = populateConsentFromConfig(properties, consentConfigMap);
+ expect(result).toEqual({ adPersonalization: 'DENIED', adUserData: 'UNSPECIFIED' });
+ });
+
+ it('should return an UNSPECIFIED object when properties parameter is not provided', () => {
+ const result = populateConsentFromConfig(undefined, consentConfigMap);
+ expect(result).toEqual({
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ });
+ });
+
+ it('should return an UNSPECIFIED object when properties parameter is null', () => {
+ const result = populateConsentFromConfig(null, consentConfigMap);
+ expect(result).toEqual({
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ });
+ });
+
+ it('should return an UNSPECIFIED object when properties parameter is an UNSPECIFIED object', () => {
+ const result = populateConsentFromConfig({}, consentConfigMap);
+ expect(result).toEqual({
+ adPersonalization: 'UNSPECIFIED',
+ adUserData: 'UNSPECIFIED',
+ });
+ });
+
+ it('should return UNKNOWN when properties parameter contains adUserData and adPersonalization with non-allowed values', () => {
+ const result = populateConsentFromConfig(
+ {
+ userDataConsent: 'RANDOM',
+ personalizationConsent: 'RANDOM',
+ },
+ consentConfigMap,
+ );
+ expect(result).toEqual({
+ adPersonalization: 'UNKNOWN',
+ adUserData: 'UNKNOWN',
+ });
+ });
+});
+
+describe('finaliseConsent', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'adPersonalization',
+ userDataConsent: 'adUserData',
+ };
+ // Returns an object containing consent information.
+ it('should return an object containing consent information when eventLevelConsent, destConfig, and destinationAllowedConsentKeys are provided', () => {
+ const eventLevelConsent = {
+ adUserData: 'GRANTED',
+ adPersonalization: 'DENIED',
+ };
+ const destConfig = {
+ userDataConsent: 'UNKNOWN',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig);
+
+ expect(result).toEqual({
+ adUserData: 'GRANTED',
+ adPersonalization: 'DENIED',
+ });
+ });
+
+ it('should return an object containing consent information from destConfig when evenLevelConsent is empty object', () => {
+ const eventLevelConsent = {}; // for store conversion we will use this
+ const destConfig = {
+ userDataConsent: 'UNKNOWN',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig);
+
+ expect(result).toEqual({
+ adUserData: 'UNKNOWN',
+ adPersonalization: 'GRANTED',
+ });
+ });
+
+ // If destConfig is not provided, it does not return UNSPECIFIED_CONSENT.
+ it('should not return UNSPECIFIED_CONSENT when destConfig is not provided but event level consent is provided', () => {
+ const eventLevelConsent = {
+ adUserData: 'GRANTED',
+ adPersonalization: 'DENIED',
+ };
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, undefined);
+
+ // Assert
+ expect(result).toEqual({
+ adUserData: 'GRANTED',
+ adPersonalization: 'DENIED',
+ });
});
- // Returns an empty object when properties parameter is not provided.
- it('should return an empty object when properties parameter is not provided', () => {
- const result = populateConsentForGoogleDestinations();
- expect(result).toEqual({});
+ it('should return UNSPECIFIED_CONSENT when both destConfig and event level consent is not provided', () => {
+ const result = finaliseConsent(consentConfigMap, undefined, undefined);
+
+ // Assert
+ expect(result).toEqual({
+ adUserData: 'UNSPECIFIED',
+ adPersonalization: 'UNSPECIFIED',
+ });
+ });
+
+ it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => {
+ const destConfig = {
+ userDataConsent: 'UNKNOWN',
+ personalizationConsent: 'WRONG CONSENT',
+ };
+
+ const result = finaliseConsent(consentConfigMap, undefined, destConfig);
+
+ expect(result).toEqual({
+ adUserData: 'UNKNOWN',
+ adPersonalization: 'UNKNOWN',
+ });
+ });
+
+ it('should return UNKWOWN_CONSENT when destConfig is provided with wrong consent value', () => {
+ const destConfig = {
+ userDataConsent: 'UNKNOWN',
+ personalizationConsent: 'WRONG CONSENT',
+ };
+
+ const result = finaliseConsent(consentConfigMap, undefined, destConfig);
+
+ expect(result).toEqual({
+ adPersonalization: 'UNKNOWN',
+ adUserData: 'UNKNOWN',
+ });
+ });
+
+ it('should return consent block with appropriate fields and values from destConfig', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'newKey1',
+ userDataConsent: 'newKey2',
+ };
+ const destConfig = {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const result = finaliseConsent(consentConfigMap, undefined, destConfig);
+
+ expect(result).toEqual({
+ newKey1: 'GRANTED',
+ newKey2: 'GRANTED',
+ });
});
- // Returns an empty object when properties parameter is null.
- it('should return an empty object when properties parameter is null', () => {
- const result = populateConsentForGoogleDestinations(null);
- expect(result).toEqual({});
+ it('should return consent block with appropriate fields from consentConfigMap and values from eventLevel consent', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'newKey1',
+ userDataConsent: 'newKey2',
+ };
+ const destConfig = {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const eventLevelConsent = {
+ newKey1: 'UNKNOWN',
+ newKey2: 'UNSPECIFIED',
+ };
+
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig);
+
+ expect(result).toEqual({
+ newKey1: 'UNKNOWN',
+ newKey2: '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', () => {
- const result = populateConsentForGoogleDestinations({});
- expect(result).toEqual({});
+ it('consentConfig and eventLevelConsent should have parity, also the values should be within allowed values otherwise UNKNOWN is returned ', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'newKey1',
+ userDataConsent: 'newKey2',
+ };
+ const destConfig = {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const eventLevelConsent = {
+ adUserData: 'UNKNOWN',
+ adPersonalization: 'UNSPECIFIED',
+ };
+
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig);
+
+ expect(result).toEqual({
+ newKey1: 'GRANTED',
+ newKey2: 'GRANTED',
+ });
});
- // 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', () => {
- const result = populateConsentForGoogleDestinations({
- adUserData: 'RANDOM',
- personalizationConsent: 'RANDOM',
+ it('consentConfig and eventLevelConsent should have parity, otherwise it will take values from destConfig ', () => {
+ const consentConfigMap = {
+ personalizationConsent: 'newKey1',
+ userDataConsent: 'newKey2',
+ };
+ const destConfig = {
+ userDataConsent: 'GRANTED',
+ personalizationConsent: 'GRANTED',
+ };
+
+ const eventLevelConsent = {
+ newKey1: 'DENIED',
+ newKey2: 'RANDOM',
+ };
+
+ const result = finaliseConsent(consentConfigMap, eventLevelConsent, destConfig);
+
+ expect(result).toEqual({
+ newKey1: 'DENIED',
+ newKey2: 'UNKNOWN',
});
- expect(result).toEqual({});
});
});
diff --git a/src/v0/util/index.js b/src/v0/util/index.js
index 3246d5b1aa..1dade53c57 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
// ========================================================================
@@ -1427,6 +1419,11 @@ function getStringValueOfJSON(json) {
return output;
}
+const getTrackingPlanMetadata = (metadata) => ({
+ trackingPlanId: metadata.trackingPlanId,
+ workspaceId: metadata.workspaceId,
+});
+
const getMetadata = (metadata) => ({
sourceType: metadata.sourceType,
destinationType: metadata.destinationType,
@@ -1661,7 +1658,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 +1720,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 +1747,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 +2206,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 +2261,6 @@ module.exports = {
getDestinationExternalIDInfoForRetl,
getDestinationExternalIDObjectForRetl,
getDeviceModel,
- getErrorRespEvents,
getEventTime,
getFieldValueFromMessage,
getFirstAndLastName,
@@ -2266,6 +2272,7 @@ module.exports = {
getMappingConfig,
getMetadata,
getTransformationMetadata,
+ getTrackingPlanMetadata,
getParsedIP,
getStringValueOfJSON,
getSuccessRespEvents,
@@ -2285,6 +2292,7 @@ module.exports = {
isDefinedAndNotNullAndNotEmpty,
isEmpty,
isNotEmpty,
+ isNull,
isEmptyObject,
isHttpStatusRetryable,
isHttpStatusSuccess,
@@ -2334,4 +2342,5 @@ module.exports = {
findExistingBatch,
removeDuplicateMetadata,
combineBatchRequestsWithSameJobIds,
+ validateEventAndLowerCaseConversion,
};
diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js
index 4dc6255691..810eb5a9d4 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 18f00f963f..dce8c0a338 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/v1/destinations/algolia/networkHandler.js b/src/v1/destinations/algolia/networkHandler.js
new file mode 100644
index 0000000000..de25993fb1
--- /dev/null
+++ b/src/v1/destinations/algolia/networkHandler.js
@@ -0,0 +1,81 @@
+/* eslint-disable no-restricted-syntax */
+const { TransformerProxyError } = require('../../../v0/util/errorTypes');
+const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network');
+const { isHttpStatusSuccess } = require('../../../v0/util/index');
+
+const {
+ processAxiosResponse,
+ getDynamicErrorType,
+} = require('../../../adapters/utils/networkUtils');
+const tags = require('../../../v0/util/tags');
+
+const responseHandler = (responseParams) => {
+ const { destinationResponse, rudderJobMetadata } = responseParams;
+ const message = `[ALGOLIA Response V1 Handler] - Request Processed Successfully`;
+ const responseWithIndividualEvents = [];
+ const { response, status } = destinationResponse;
+
+ if (isHttpStatusSuccess(status)) {
+ for (const mData of rudderJobMetadata) {
+ const proxyOutputObj = {
+ statusCode: 200,
+ metadata: mData,
+ error: 'success',
+ };
+ responseWithIndividualEvents.push(proxyOutputObj);
+ }
+
+ return {
+ status,
+ message,
+ destinationResponse,
+ response: responseWithIndividualEvents,
+ };
+ }
+
+ // in case of non 2xx status sending 500 for every event, populate response and update dontBatch to true
+ const errorMessage = response?.error?.message || response?.message || 'unknown error format';
+ for (const metadata of rudderJobMetadata) {
+ metadata.dontBatch = true;
+ responseWithIndividualEvents.push({
+ statusCode: 500,
+ metadata,
+ error: errorMessage,
+ });
+ }
+
+ // At least one event in the batch is invalid.
+ if (status === 422) {
+ // sending back 500 for retry
+ throw new TransformerProxyError(
+ `ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation`,
+ 500,
+ {
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(500),
+ },
+ destinationResponse,
+ '',
+ responseWithIndividualEvents,
+ );
+ }
+
+ throw new TransformerProxyError(
+ `ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation`,
+ status,
+ {
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ '',
+ responseWithIndividualEvents,
+ );
+};
+
+function networkHandler() {
+ this.prepareProxy = prepareProxyRequest;
+ this.proxy = proxyRequest;
+ this.processAxiosResponse = processAxiosResponse;
+ this.responseHandler = responseHandler;
+}
+
+module.exports = { networkHandler };
diff --git a/src/v1/destinations/campaign_manager/networkHandler.js b/src/v1/destinations/campaign_manager/networkHandler.js
index 431cbd6966..300b5f9676 100644
--- a/src/v1/destinations/campaign_manager/networkHandler.js
+++ b/src/v1/destinations/campaign_manager/networkHandler.js
@@ -34,10 +34,11 @@ function isEventAbortableAndExtractErrMsg(element, proxyOutputObj) {
return isAbortable;
}
-const responseHandler = (destinationResponse) => {
+const responseHandler = (responseParams) => {
+ const { destinationResponse, rudderJobMetadata } = responseParams;
const message = `[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully`;
const responseWithIndividualEvents = [];
- const { response, status, rudderJobMetadata } = destinationResponse;
+ const { response, status } = destinationResponse;
if (isHttpStatusSuccess(status)) {
// check for Partial Event failures and Successes
@@ -68,7 +69,7 @@ const responseHandler = (destinationResponse) => {
const errorMessage = response.error?.message || 'unknown error format';
for (const metadata of rudderJobMetadata) {
responseWithIndividualEvents.push({
- statusCode: 500,
+ statusCode: status,
metadata,
error: errorMessage,
});
@@ -76,7 +77,7 @@ const responseHandler = (destinationResponse) => {
throw new TransformerProxyError(
`Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation`,
- 500,
+ status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
},
diff --git a/src/warehouse/index.js b/src/warehouse/index.js
index 3305a52762..b3d1c5e4bc 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 11d72bfbfd..b4b22721fd 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 ead25067e6..4eef747b94 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 5b825d6a00..769c1b7fd3 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 24b964d01b..52df88e833 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 a12d84a98a..d94cfd677b 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 a647238f9f..0d7ff24ab7 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 0153df4d26..48ca8c75a0 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 cbc2abb3b2..266619b6ac 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/common/criteo/network.ts b/test/integrations/common/criteo/network.ts
new file mode 100644
index 0000000000..cd5e1ca1e8
--- /dev/null
+++ b/test/integrations/common/criteo/network.ts
@@ -0,0 +1,72 @@
+const headers = {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ 'User-Agent': 'RudderLabs',
+};
+const params = { destination: 'criteo_audience' };
+const method = 'PATCH';
+const commonData = {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+};
+
+export const networkCallsData = [
+ {
+ description: 'Mock response depicting expired access token error',
+ httpReq: {
+ url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken',
+ data: commonData,
+ params,
+ headers,
+ method,
+ },
+ httpRes: {
+ code: '400',
+ data: {
+ errors: [
+ {
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ code: 'authorization-token-expired',
+ instance: '/2022-10/audiences/123/contactlist',
+ title: 'The authorization token has expired',
+ },
+ ],
+ },
+ status: 401,
+ },
+ },
+ {
+ description: 'Mock response depicting invalid access token error',
+ httpReq: {
+ url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken',
+ data: commonData,
+ params,
+ headers,
+ method,
+ },
+ httpRes: {
+ code: '400',
+ data: {
+ errors: [
+ {
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ code: 'authorization-token-invalid',
+ instance: '/2022-10/audiences/123/contactlist',
+ title: 'The authorization header is invalid',
+ },
+ ],
+ },
+ status: 401,
+ },
+ },
+];
diff --git a/test/integrations/common/google/network.ts b/test/integrations/common/google/network.ts
new file mode 100644
index 0000000000..95b76f8da8
--- /dev/null
+++ b/test/integrations/common/google/network.ts
@@ -0,0 +1,109 @@
+// Ads API
+// Ref: https://developers.google.com/google-ads/api/docs/get-started/common-errors
+
+export const networkCallsData = [
+ {
+ description: 'Mock response depicting CREDENTIALS_MISSING error',
+ httpReq: {
+ method: 'post',
+ url: 'https://googleapis.com/test_url_for_credentials_missing',
+ },
+ httpRes: {
+ data: {
+ error: {
+ code: 401,
+ message:
+ 'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
+ errors: [
+ {
+ message: 'Login Required.',
+ domain: 'global',
+ reason: 'required',
+ location: 'Authorization',
+ locationType: 'header',
+ },
+ ],
+ status: 'UNAUTHENTICATED',
+ details: [
+ {
+ '@type': 'type.googleapis.com/google.rpc.ErrorInfo',
+ reason: 'CREDENTIALS_MISSING',
+ domain: 'googleapis.com',
+ metadata: {
+ method: 'google.ads.xfa.op.v4.DfareportingConversions.Batchinsert',
+ service: 'googleapis.com',
+ },
+ },
+ ],
+ },
+ },
+ status: 401,
+ },
+ },
+ {
+ description: 'Mock response depicting ACCESS_TOKEN_SCOPE_INSUFFICIENT error',
+ httpReq: {
+ method: 'post',
+ url: 'https://googleapis.com/test_url_for_access_token_scope_insufficient',
+ },
+ httpRes: {
+ data: {
+ error: {
+ code: 403,
+ message: 'Request had insufficient authentication scopes.',
+ errors: [
+ {
+ message: 'Insufficient Permission',
+ domain: 'global',
+ reason: 'insufficientPermissions',
+ },
+ ],
+ status: 'PERMISSION_DENIED',
+ details: [
+ {
+ '@type': 'type.googleapis.com/google.rpc.ErrorInfo',
+ reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
+ domain: 'googleapis.com',
+ metadata: {
+ service: 'gmail.googleapis.com',
+ method: 'caribou.api.proto.MailboxService.GetProfile',
+ },
+ },
+ ],
+ },
+ },
+ status: 403,
+ },
+ },
+ {
+ description: 'Mock response for google.auth.exceptions.RefreshError invalid_grant error',
+ httpReq: {
+ method: 'post',
+ url: 'https://googleapis.com/test_url_for_invalid_grant',
+ },
+ httpRes: {
+ data: {
+ error: {
+ code: 403,
+ message: 'invalid_grant',
+ error_description: 'Bad accesss',
+ },
+ },
+ status: 403,
+ },
+ },
+ {
+ description: 'Mock response for google.auth.exceptions.RefreshError refresh_token error',
+ httpReq: {
+ method: 'post',
+ url: 'https://googleapis.com/test_url_for_refresh_error',
+ },
+ httpRes: {
+ data: {
+ error: 'unauthorized',
+ error_description: 'Access token expired: 2020-10-20T12:00:00.000Z',
+ },
+ status: 401,
+ },
+ },
+];
diff --git a/test/integrations/common/network.ts b/test/integrations/common/network.ts
new file mode 100644
index 0000000000..a6ab202a4e
--- /dev/null
+++ b/test/integrations/common/network.ts
@@ -0,0 +1,95 @@
+export const networkCallsData = [
+ {
+ description: 'Mock response depicting SERVICE NOT AVAILABLE error',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_service_not_available',
+ },
+ httpRes: {
+ data: {
+ 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.',
+ },
+ },
+ status: 503,
+ },
+ },
+ {
+ description: 'Mock response depicting INTERNAL SERVER ERROR error with post method',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_internal_server_error',
+ },
+ httpRes: {
+ data: 'Internal Server Error',
+ status: 500,
+ },
+ },
+ {
+ description: 'Mock response depicting INTERNAL SERVER ERROR error with patch method',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_internal_server_error',
+ },
+ httpRes: {
+ data: 'Internal Server Error',
+ status: 500,
+ },
+ },
+ {
+ description: 'Mock response depicting GATEWAY TIME OUT error',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_gateway_time_out',
+ },
+ httpRes: {
+ data: 'Gateway Timeout',
+ status: 504,
+ },
+ },
+ {
+ description: 'Mock response depicting null response',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_null_response',
+ },
+ httpRes: {
+ data: null,
+ status: 500,
+ },
+ },
+ {
+ description: 'Mock response depicting null and no status',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/test_for_null_and_no_status',
+ },
+ httpRes: {
+ data: null,
+ },
+ },
+ {
+ description: 'Mock response depicting TOO MANY REQUESTS error with patch method',
+ httpReq: {
+ method: 'patch',
+ url: 'https://random_test_url/test_for_too_many_requests',
+ },
+ httpRes: {
+ data: {},
+ status: 429,
+ },
+ },
+ {
+ description: 'Mock response depicting DNS lookup failure error',
+ httpReq: {
+ method: 'post',
+ url: 'https://random_test_url/dns_lookup_failure',
+ },
+ httpRes: {
+ data: {},
+ status: 400,
+ },
+ },
+];
diff --git a/test/integrations/component.test.ts b/test/integrations/component.test.ts
index ec4fb02dc1..388c283c61 100644
--- a/test/integrations/component.test.ts
+++ b/test/integrations/component.test.ts
@@ -16,6 +16,7 @@ import {
getMockHttpCallsData,
getAllTestMockDataFilePaths,
addMock,
+ validateTestWithZOD,
} from './testUtils';
import tags from '../../src/v0/util/tags';
import { Server } from 'http';
@@ -53,7 +54,7 @@ if (opts.generate === 'true') {
let server: Server;
-const REPORT_COMPATIBLE_INTEGRATION = ['klaviyo'];
+const INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE = ['klaviyo', 'campaign_manager', 'criteo_audience'];
beforeAll(async () => {
initaliseReport();
@@ -147,7 +148,8 @@ const testRoute = async (route, tcData: TestCaseData) => {
expect(response.status).toEqual(outputResp.status);
- if (REPORT_COMPATIBLE_INTEGRATION.includes(tcData.name?.toLocaleLowerCase())) {
+ if (INTEGRATIONS_WITH_UPDATED_TEST_STRUCTURE.includes(tcData.name?.toLocaleLowerCase())) {
+ expect(validateTestWithZOD(tcData, response)).toEqual(true);
const bodyMatched = _.isEqual(response.body, outputResp.body);
const statusMatched = response.status === outputResp.status;
if (bodyMatched && statusMatched) {
diff --git a/test/integrations/destinations/adj/processor/data.ts b/test/integrations/destinations/adj/processor/data.ts
index 2c208d0d08..e28a25cf59 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/adobe_analytics/dataDelivery/business.ts b/test/integrations/destinations/adobe_analytics/dataDelivery/business.ts
new file mode 100644
index 0000000000..76e07690cf
--- /dev/null
+++ b/test/integrations/destinations/adobe_analytics/dataDelivery/business.ts
@@ -0,0 +1,181 @@
+import { ProxyMetdata } from '../../../../../src/types';
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV1Payload } from '../../../testUtils';
+
+const statTags = {
+ aborted: {
+ destType: 'ADOBE_ANALYTICS',
+ destinationId: 'dummyDestinationId',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ workspaceId: 'dummyWorkspaceId',
+ },
+};
+
+export const proxyMetdata: ProxyMetdata = {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+const headers = {
+ 'Content-Type': 'application/xml',
+};
+
+export const reqMetadataArray = [proxyMetdata];
+
+const failureRequestParameters = {
+ XML: {
+ payload:
+ '17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerprodViewGames;;11;148.39failureReport',
+ },
+ params: {},
+};
+
+const successRequestParameters = {
+ XML: {
+ payload:
+ '127.0.1.0www.google.co.inGoogleid1110011prodViewGames;Monopoly;1;14.00,Games;UNO;2;6.90successreport',
+ },
+ params: {},
+};
+
+export const testScenariosForV1API: ProxyV1TestData[] = [
+ {
+ id: 'adobe_analytics_v1_scenario_1',
+ name: 'adobe_analytics',
+ description: '[Proxy v1 API] :: Test for Failure response from Adobe Analytics with reason',
+ successCriteria: 'Should return a 400 status code with reason',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...failureRequestParameters,
+ headers,
+ endpoint: 'https://adobe.failure.omtrdc.net/b/ss//6',
+ },
+ reqMetadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ statTags: statTags.aborted,
+ message:
+ '[ADOBE_ANALYTICS Response Handler] Request failed for destination adobe_analytics : NO pagename OR pageurl',
+ response: [
+ {
+ error:
+ '[ADOBE_ANALYTICS Response Handler] Request failed for destination adobe_analytics : NO pagename OR pageurl',
+ metadata: proxyMetdata,
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'adobe_analytics_v1_scenario_2',
+ name: 'adobe_analytics',
+ description:
+ '[Proxy v1 API] :: Test for Failure response from Adobe Analytics without reason (Generic error)',
+ successCriteria: 'Should return a 400 status code with a general error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...failureRequestParameters,
+ headers,
+ endpoint: 'https://adobe.failure2.omtrdc.net/b/ss//6',
+ },
+ reqMetadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ statTags: statTags.aborted,
+ message:
+ '[ADOBE_ANALYTICS Response Handler] Request failed for destination adobe_analytics with a general error',
+ response: [
+ {
+ error:
+ '[ADOBE_ANALYTICS Response Handler] Request failed for destination adobe_analytics with a general error',
+ metadata: proxyMetdata,
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'adobe_analytics_v1_scenario_3',
+ name: 'adobe_analytics',
+ description: '[Proxy v1 API] :: Test for Success response from Adobe Analytics',
+ successCriteria: 'Should return a 200 status code with status SUCCESS',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...successRequestParameters,
+ headers,
+ endpoint: 'https://adobe.success.omtrdc.net/b/ss//6',
+ },
+ reqMetadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[ADOBE_ANALYTICS] - Request Processed Successfully',
+ response: [
+ {
+ error: '"SUCCESS"',
+ metadata: proxyMetdata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/adobe_analytics/dataDelivery/data.ts b/test/integrations/destinations/adobe_analytics/dataDelivery/data.ts
index 182969da73..2535a0639e 100644
--- a/test/integrations/destinations/adobe_analytics/dataDelivery/data.ts
+++ b/test/integrations/destinations/adobe_analytics/dataDelivery/data.ts
@@ -1,4 +1,6 @@
-export const data = [
+import { testScenariosForV1API } from './business';
+
+const legacyTests = [
{
name: 'adobe_analytics',
description: 'Test 0: Failure response from Adobe Analytics with reason',
@@ -72,7 +74,7 @@ export const data = [
JSON_ARRAY: {},
XML: {
payload:
- '17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerprodViewGames;;11;148.39failureReportgeneric',
+ '17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerprodViewGames;;11;148.39failureReport',
},
FORM: {},
},
@@ -140,3 +142,5 @@ export const data = [
},
},
];
+
+export const data = [...testScenariosForV1API, ...legacyTests];
diff --git a/test/integrations/destinations/adobe_analytics/network.ts b/test/integrations/destinations/adobe_analytics/network.ts
index 2fe4f0204e..7e32c5f10b 100644
--- a/test/integrations/destinations/adobe_analytics/network.ts
+++ b/test/integrations/destinations/adobe_analytics/network.ts
@@ -17,7 +17,7 @@ export const networkCallsData = [
{
httpReq: {
url: 'https://adobe.failure2.omtrdc.net/b/ss//6',
- data: '17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerprodViewGames;;11;148.39failureReportgeneric',
+ data: '17941080sales campaignwebUSD127.0.0.1en-USDalvik/2.1.0 (Linux; U; Android 9; Android SDK built for x86 Build/PSR1.180720.075)https://www.google.com/search?q=estore+bestsellerprodViewGames;;11;148.39failureReport',
params: {},
headers: {
'Content-Type': 'application/xml',
diff --git a/test/integrations/destinations/af/processor/data.ts b/test/integrations/destinations/af/processor/data.ts
index 8b639f45c0..d0fd29b089 100644
--- a/test/integrations/destinations/af/processor/data.ts
+++ b/test/integrations/destinations/af/processor/data.ts
@@ -45,6 +45,7 @@ export const data = [
destination: {
Config: { devKey: 'ef1d42390426e3f7c90ac78272e74344', androidAppId: 'appId' },
Enabled: true,
+ addPropertiesAtRoot: false,
},
},
],
@@ -118,6 +119,7 @@ export const data = [
Config: {
devKey: 'ef1d42390426e3f7c90ac78272e74344',
androidAppId: 'com.rudderlabs.javascript',
+ addPropertiesAtRoot: false,
},
Enabled: true,
},
@@ -305,6 +307,7 @@ export const data = [
Config: {
devKey: 'ef1d42390426e3f7c90ac78272e74344',
androidAppId: 'com.rudderlabs.javascript',
+ addPropertiesAtRoot: false,
},
Enabled: true,
},
@@ -1532,4 +1535,306 @@ export const data = [
},
},
},
+ {
+ name: 'af',
+ description: 'Place Properties at root level Page Call',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ channel: 'web',
+ context: {
+ externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }],
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.0',
+ },
+ traits: {
+ email: 'testhubspot2@email.com',
+ name: 'Test Hubspot',
+ anonymousId: '12345',
+ },
+ 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',
+ os: { name: 'android', version: '' },
+ screen: { density: 2 },
+ },
+ type: 'page',
+ messageId: 'e8585d9a-7137-4223-b295-68ab1b17dad7',
+ originalTimestamp: '2019-10-15T09:35:31.289Z',
+ anonymousId: '00000000000000000000000000',
+ userId: '12345',
+ properties: { path: '', referrer: '', search: '', title: '', url: '' },
+ name: 'ApplicationLoaded',
+ sentAt: '2019-10-14T11:15:53.296Z',
+ integrations: { AF: { af_uid: 'afUid' } },
+ },
+ destination: {
+ Config: {
+ devKey: 'ef1d42390426e3f7c90ac78272e74344',
+ androidAppId: 'com.rudderlabs.javascript',
+ sharingFilter: 'all',
+ addPropertiesAtRoot: true,
+ },
+ Enabled: true,
+ },
+ },
+ ],
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript',
+ headers: {
+ 'Content-Type': 'application/json',
+ authentication: 'ef1d42390426e3f7c90ac78272e74344',
+ },
+ method: 'POST',
+ params: {},
+ body: {
+ JSON: {
+ app_version_name: '1.0.0',
+ bundleIdentifier: 'com.rudderlabs.javascript',
+ customer_user_id: '12345',
+ eventValue: '{"path":"","referrer":"","search":"","title":"","url":""}',
+ eventName: 'page',
+ appsflyer_id: 'afUid',
+ os: '',
+ ip: '0.0.0.0',
+ sharing_filter: 'all',
+ },
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'af',
+ description: 'Place properties at root level track call',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ channel: 'web',
+ context: {
+ externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }],
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.0',
+ },
+ traits: { email: 'testhubspot2@email.com', name: 'Test Hubspot' },
+ 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',
+ os: { name: 'android', version: '' },
+ screen: { density: 2 },
+ },
+ type: 'track',
+ messageId: '08829772-d991-427c-b976-b4c4f4430b4e',
+ originalTimestamp: '2019-10-15T09:35:31.291Z',
+ anonymousId: '00000000000000000000000000',
+ userId: '12345',
+ event: 'test track event HS',
+ properties: { user_actual_role: 'system_admin, system_user', user_actual_id: 12345 },
+ sentAt: '2019-10-14T11:15:53.296Z',
+ integrations: { AF: { af_uid: 'afUid' } },
+ },
+ destination: {
+ Config: {
+ devKey: 'ef1d42390426e3f7c90ac78272e74344',
+ androidAppId: 'com.rudderlabs.javascript',
+ addPropertiesAtRoot: true,
+ },
+ Enabled: true,
+ },
+ },
+ ],
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript',
+ headers: {
+ 'Content-Type': 'application/json',
+ authentication: 'ef1d42390426e3f7c90ac78272e74344',
+ },
+ params: {},
+ body: {
+ JSON: {
+ eventValue:
+ '{"user_actual_role":"system_admin, system_user","user_actual_id":12345}',
+ eventName: 'test track event HS',
+ customer_user_id: '12345',
+ ip: '0.0.0.0',
+ os: '',
+ appsflyer_id: 'afUid',
+ app_version_name: '1.0.0',
+ bundleIdentifier: 'com.rudderlabs.javascript',
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'af',
+ description: 'Place properties at root track call with af data',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ type: 'track',
+ event: 'Order Completed',
+ sentAt: '2020-08-14T05:30:30.118Z',
+ context: {
+ externalId: [{ type: 'appsflyerExternalId', id: 'afUid' }],
+ source: 'test',
+ app: { namespace: 'com.rudderlabs.javascript' },
+ os: { name: 'android' },
+ traits: { anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1' },
+ library: { name: 'rudder-sdk-ruby-sync', version: '1.0.6' },
+ },
+ messageId: '7208bbb6-2c4e-45bb-bf5b-ad426f3593e9',
+ timestamp: '2020-08-14T05:30:30.118Z',
+ properties: {
+ tax: 2,
+ total: 27.5,
+ coupon: 'hasbros',
+ revenue: 48,
+ price: 25,
+ quantity: 2,
+ currency: 'ZAR',
+ discount: 2.5,
+ order_id: '50314b8e9bcf000000000000',
+ products: [
+ {
+ sku: '45790-32',
+ url: 'https://www.example.com/product/path',
+ name: 'Monopoly: 3rd Edition',
+ price: 19,
+ category: 'Games',
+ quantity: 1,
+ image_url: 'https:///www.example.com/product/path.jpg',
+ product_id: '507f1f77bcf86cd799439011',
+ },
+ {
+ sku: '46493-32',
+ name: 'Uno Card Game',
+ price: 3,
+ category: 'Games',
+ quantity: 2,
+ product_id: '505bd76785ebb509fc183733',
+ },
+ ],
+ shipping: 3,
+ subtotal: 22.5,
+ affiliation: 'Google Store',
+ checkout_id: 'fksdjfsdjfisjf9sdfjsd9f',
+ },
+ anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1',
+ integrations: { AF: { af_uid: 'afUid' } },
+ },
+ destination: {
+ Config: {
+ devKey: 'abcde',
+ androidAppId: 'com.rudderlabs.javascript',
+ groupTypeTrait: 'email',
+ groupValueTrait: 'age',
+ trackProductsOnce: false,
+ trackRevenuePerProduct: false,
+ addPropertiesAtRoot: true,
+ },
+ },
+ },
+ ],
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api2.appsflyer.com/inappevent/com.rudderlabs.javascript',
+ headers: { 'Content-Type': 'application/json', authentication: 'abcde' },
+ params: {},
+ body: {
+ JSON: {
+ bundleIdentifier: 'com.rudderlabs.javascript',
+ eventValue:
+ '{"tax":2,"total":27.5,"coupon":"hasbros","revenue":48,"price":25,"quantity":2,"currency":"ZAR","discount":2.5,"order_id":"50314b8e9bcf000000000000","products":[{"sku":"45790-32","url":"https://www.example.com/product/path","name":"Monopoly: 3rd Edition","price":19,"category":"Games","quantity":1,"image_url":"https:///www.example.com/product/path.jpg","product_id":"507f1f77bcf86cd799439011"},{"sku":"46493-32","name":"Uno Card Game","price":3,"category":"Games","quantity":2,"product_id":"505bd76785ebb509fc183733"}],"shipping":3,"subtotal":22.5,"affiliation":"Google Store","checkout_id":"fksdjfsdjfisjf9sdfjsd9f","af_revenue":48,"af_price":[19,3],"af_quantity":[1,2],"af_order_id":"50314b8e9bcf000000000000","af_content_id":["507f1f77bcf86cd799439011","505bd76785ebb509fc183733"]}',
+ eventName: 'Order Completed',
+ eventCurrency: 'ZAR',
+ eventTime: '2020-08-14T05:30:30.118Z',
+ appsflyer_id: 'afUid',
+ },
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
];
diff --git a/test/integrations/destinations/algolia/dataDelivery/business.ts b/test/integrations/destinations/algolia/dataDelivery/business.ts
new file mode 100644
index 0000000000..8ba964e2dd
--- /dev/null
+++ b/test/integrations/destinations/algolia/dataDelivery/business.ts
@@ -0,0 +1,331 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils';
+import { abortStatTags, commonRequestProperties, metadataArray, retryStatTags } from './constant';
+const proxyMetdata3 = {
+ jobId: 3,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+export const testScenariosForV0API = [
+ {
+ id: 'algolia_v0_bussiness_scenario_1',
+ name: 'algolia',
+ description: '[Proxy v0 API] :: algolia all valid events',
+ successCriteria: 'Proper response from destination is received',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message:
+ '[Generic Response Handler] Request for destination: algolia Processed Successfully',
+ destinationResponse: {
+ response: {
+ message: 'OK',
+ status: 200,
+ },
+ status: 200,
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_bussiness_scenario_2',
+ name: 'algolia',
+ description: '[Proxy v0 API] :: algolia with invalid event',
+ successCriteria: 'Error Response from destination is received',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ JSON: commonRequestProperties.singleInValidEvent,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 422,
+ body: {
+ output: {
+ status: 422,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 422',
+ destinationResponse: {
+ response: {
+ status: 422,
+ message: 'EventType must be one of "click", "conversion" or "view"',
+ },
+ status: 422,
+ },
+ statTags: abortStatTags,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_bussiness_scenario_3',
+ name: 'algolia',
+ description: '[Proxy v0 API] :: algolia with invalid events in batch',
+ successCriteria: 'Error Response from destination is received',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ JSON: commonRequestProperties.combinedValidInvalidEvents,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 422,
+ body: {
+ output: {
+ status: 422,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 422',
+ destinationResponse: {
+ response: {
+ status: 422,
+ message: 'EventType must be one of "click", "conversion" or "view"',
+ },
+ status: 422,
+ },
+ statTags: abortStatTags,
+ },
+ },
+ },
+ },
+ },
+];
+
+export const testScenariosForV1API: ProxyV1TestData[] = [
+ {
+ id: 'algolia_v1_bussiness_scenario_1',
+ name: 'algolia',
+ description: '[Proxy v1 API] :: algolia all valid events in batch',
+ successCriteria: 'Success response from destination is received',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ JSON: commonRequestProperties.multipleValidEvent,
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[ALGOLIA Response V1 Handler] - Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ message: 'OK',
+ status: 200,
+ },
+ status: 200,
+ },
+ response: [
+ {
+ error: 'success',
+ metadata: metadataArray[0],
+ statusCode: 200,
+ },
+ {
+ error: 'success',
+ metadata: metadataArray[1],
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_bussiness_scenario_2',
+ name: 'algolia',
+ description: '[Proxy v1 API] :: algolia all invalid events in batch',
+ successCriteria: 'Send response with dontBatch as true',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ JSON: commonRequestProperties.singleInValidEvent,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 500,
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ response: [
+ {
+ error:
+ '{"status":422,"message":"EventType must be one of \\"click\\", \\"conversion\\" or \\"view\\""}',
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ statusCode: 500,
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_bussiness_scenario_3',
+ name: 'algolia',
+ description: '[Proxy v1 API] :: algolia combination of valid and invalid events in batch',
+ successCriteria: 'Should use dontBatch true and proper response returned',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...commonRequestProperties.commonHeaders,
+ endpoint: 'https://insights.algolia.io/1/events',
+ JSON: commonRequestProperties.combinedValidInvalidEvents,
+ },
+ [...metadataArray, proxyMetdata3],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 500,
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ response: [
+ {
+ error:
+ '{"status":422,"message":"EventType must be one of \\"click\\", \\"conversion\\" or \\"view\\""}',
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: true,
+ },
+ statusCode: 500,
+ },
+ {
+ error:
+ '{"status":422,"message":"EventType must be one of \\"click\\", \\"conversion\\" or \\"view\\""}',
+ metadata: {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: true,
+ },
+ statusCode: 500,
+ },
+ {
+ error:
+ '{"status":422,"message":"EventType must be one of \\"click\\", \\"conversion\\" or \\"view\\""}',
+ metadata: {
+ jobId: 3,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: true,
+ },
+ statusCode: 500,
+ },
+ ],
+ statTags: retryStatTags,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/algolia/dataDelivery/constant.ts b/test/integrations/destinations/algolia/dataDelivery/constant.ts
new file mode 100644
index 0000000000..e8d0817a7f
--- /dev/null
+++ b/test/integrations/destinations/algolia/dataDelivery/constant.ts
@@ -0,0 +1,118 @@
+const proxyMetdata1 = {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+
+const proxyMetdata2 = {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+
+export const metadataArray = [proxyMetdata1, proxyMetdata2];
+
+export const abortStatTags = {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+};
+
+export const commonRequestProperties = {
+ commonHeaders: {
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'User-Agent': 'RudderLabs',
+ },
+ singleValidEvent: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'click',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ singleInValidEvent: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'abc',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ multipleValidEvent: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'click',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'view',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ combinedValidInvalidEvents: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'click',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'view',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'abc',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+};
+
+export const retryStatTags = {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+};
diff --git a/test/integrations/destinations/algolia/dataDelivery/data.ts b/test/integrations/destinations/algolia/dataDelivery/data.ts
new file mode 100644
index 0000000000..feb4eb46c5
--- /dev/null
+++ b/test/integrations/destinations/algolia/dataDelivery/data.ts
@@ -0,0 +1,9 @@
+import { testScenariosForV0API, testScenariosForV1API } from './business';
+import { otherScenariosV0, otherScenariosV1 } from './other';
+
+export const data = [
+ ...testScenariosForV0API,
+ ...testScenariosForV1API,
+ ...otherScenariosV0,
+ ...otherScenariosV1,
+];
diff --git a/test/integrations/destinations/algolia/dataDelivery/other.ts b/test/integrations/destinations/algolia/dataDelivery/other.ts
new file mode 100644
index 0000000000..f5ccc70337
--- /dev/null
+++ b/test/integrations/destinations/algolia/dataDelivery/other.ts
@@ -0,0 +1,524 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils';
+
+export const otherScenariosV0 = [
+ {
+ id: 'algolia_v0_other_scenario_1',
+ name: 'algolia',
+ description:
+ '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_service_not_available',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 503,
+ body: {
+ output: {
+ status: 503,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 503',
+ destinationResponse: {
+ response: {
+ 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.',
+ },
+ },
+ status: 503,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_other_scenario_2',
+ name: 'algolia',
+ description: '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_internal_server_error',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 500',
+ destinationResponse: {
+ response: 'Internal Server Error',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_other_scenario_3',
+ name: 'algolia',
+ description: '[Proxy v0 API] :: Scenario for testing Gateway Time Out error from destination',
+ successCriteria: 'Should return 500 status code with error message',
+ scenario: 'Framework',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_gateway_time_out',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 504,
+ body: {
+ output: {
+ status: 504,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 504',
+ destinationResponse: {
+ response: 'Gateway Timeout',
+ status: 504,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_other_scenario_4',
+ name: 'algolia',
+ description: '[Proxy v0 API] :: Scenario for testing null response from destination',
+ successCriteria: 'Should return 500 status code with error message',
+ scenario: 'Framework',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_null_response',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 500',
+ destinationResponse: {
+ response: '',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v0_other_scenario_5',
+ name: 'algolia',
+ description:
+ '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_null_and_no_status',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ '[Generic Response Handler] Request failed for destination algolia with status: 500',
+ destinationResponse: {
+ response: '',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+];
+
+export const otherScenariosV1: ProxyV1TestData[] = [
+ {
+ id: 'algolia_v1_other_scenario_1',
+ name: 'algolia',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ status: 503,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_other_scenario_2',
+ name: 'algolia',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_other_scenario_3',
+ name: 'algolia',
+ description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out 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_gateway_time_out',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: '"Gateway Timeout"',
+ statusCode: 504,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ status: 504,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_other_scenario_4',
+ name: 'algolia',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'algolia_v1_other_scenario_5',
+ name: 'algolia',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: true,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'ALGOLIA',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message: 'ALGOLIA: Error transformer proxy v1 during ALGOLIA response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/algolia/network.ts b/test/integrations/destinations/algolia/network.ts
new file mode 100644
index 0000000000..84e932bbdb
--- /dev/null
+++ b/test/integrations/destinations/algolia/network.ts
@@ -0,0 +1,117 @@
+export const networkCallsData = [
+ {
+ httpReq: {
+ url: 'https://insights.algolia.io/1/events',
+ data: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'abc',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ params: {},
+ headers: { 'User-Agent': 'RudderLabs' },
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ status: 422,
+ message: 'EventType must be one of "click", "conversion" or "view"',
+ },
+ status: 422,
+ },
+ },
+ {
+ httpReq: {
+ url: 'https://insights.algolia.io/1/events',
+ data: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'abc',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'click',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ params: {},
+ headers: { 'User-Agent': 'RudderLabs' },
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ status: 422,
+ message: 'EventType must be one of "click", "conversion" or "view"',
+ },
+ status: 422,
+ },
+ },
+ {
+ httpReq: {
+ url: 'https://insights.algolia.io/1/events',
+ data: {
+ events: [
+ {
+ eventName: 'product clicked',
+ eventType: 'click',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'view',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ {
+ eventName: 'product clicked',
+ eventType: 'abc',
+ filters: ['field1:hello', 'val1:val2'],
+ index: 'products',
+ userToken: 'testuserId1',
+ },
+ ],
+ },
+ params: {},
+ headers: { 'User-Agent': 'RudderLabs' },
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ status: 422,
+ message: 'EventType must be one of "click", "conversion" or "view"',
+ },
+ status: 422,
+ },
+ },
+ {
+ httpReq: {
+ url: 'https://insights.algolia.io/1/events',
+ method: 'POST',
+ headers: {
+ 'User-Agent': 'RudderLabs',
+ },
+ },
+ httpRes: {
+ data: {
+ status: 200,
+ message: 'OK',
+ },
+ status: 200,
+ },
+ },
+];
diff --git a/test/integrations/destinations/algolia/processor/data.ts b/test/integrations/destinations/algolia/processor/data.ts
index 0cbdd8b31b..7c37c9642a 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/algolia/router/data.ts b/test/integrations/destinations/algolia/router/data.ts
index 65c74342dc..dca899693e 100644
--- a/test/integrations/destinations/algolia/router/data.ts
+++ b/test/integrations/destinations/algolia/router/data.ts
@@ -2220,4 +2220,2406 @@ export const data = [
},
},
},
+ {
+ name: 'algolia',
+ description: 'dontBatch true for all',
+ feature: 'router',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ input: [
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ ],
+ destType: 'algolia',
+ },
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: [
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ name: 'algolia',
+ description: 'dontBatch Partial true for events in a batch',
+ feature: 'router',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ input: [
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 5,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ },
+ ],
+ destType: 'algolia',
+ },
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: [
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 5,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: true,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ name: 'algolia',
+ description: 'dontBatch false for all events, all events are batched in 1',
+ feature: 'router',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ input: [
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ message: {
+ channel: 'web',
+ context: {
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ },
+ type: 'track',
+ anonymousId: '345345',
+ event: 'product clicked',
+ userId: 'test',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ },
+ metadata: {
+ jobId: 5,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ },
+ ],
+ destType: 'algolia',
+ },
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: [
+ {
+ batchedRequest: {
+ body: {
+ JSON: {
+ events: [
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ userToken: 'test',
+ eventName: 'product clicked',
+ eventType: 'click',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://insights.algolia.io/1/events',
+ headers: {
+ 'X-Algolia-Application-Id': 'O2YARRI15I',
+ 'X-Algolia-API-Key': 'dummyApiKey',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: [
+ {
+ jobId: 1,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 2,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 3,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 4,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ {
+ jobId: 5,
+ attemptNum: 0,
+ userId: '',
+ sourceId: '2bAiFXtSLvENPDRAxVRxf0Udfaz',
+ destinationId: '2dH3xYIQnTNqfgPCqfVs88gWQdQ',
+ workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
+ secret: null,
+ dontBatch: false,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ Config: {
+ cdkV2Enabled: true,
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'Product List Filtered',
+ to: 'click',
+ },
+ {
+ from: 'Product List Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Order Completed',
+ to: 'view',
+ },
+ {
+ from: 'Product Added',
+ to: 'conversion',
+ },
+ {
+ from: 'Product Viewed',
+ to: 'view',
+ },
+ {
+ from: 'Product Clicked',
+ to: 'click',
+ },
+ ],
+ pixelId: '123456789',
+ advertiserId: '429047995',
+ eventId: '429047995',
+ enhancedMatch: true,
+ enableDeduplication: true,
+ deduplicationKey: 'messageId',
+ sendingUnHashedData: true,
+ customProperties: [
+ {
+ properties: 'presentclass',
+ },
+ {
+ properties: 'presentgrade',
+ },
+ ],
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'WatchVideo',
+ },
+ ],
+ },
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'ALGOLIA',
+ Enabled: true,
+ cdkV2Enabled: true,
+ Transformations: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
];
diff --git a/test/integrations/destinations/am/processor/data.ts b/test/integrations/destinations/am/processor/data.ts
index f28606da0c..01f9feb44a 100644
--- a/test/integrations/destinations/am/processor/data.ts
+++ b/test/integrations/destinations/am/processor/data.ts
@@ -10739,6 +10739,7 @@ export const data = [
integrations: {
All: true,
Amplitude: {
+ skipUserPropertiesSync: false,
event_id: 2,
},
},
@@ -10894,6 +10895,7 @@ export const data = [
integrations: {
All: true,
Amplitude: {
+ skipUserPropertiesSync: true,
event_id: 2,
},
},
@@ -10949,6 +10951,7 @@ export const data = [
insert_id: '5e10d13a-bf9a-44bf-b884-43a9e591ea71',
ip: '1.1.1.1',
event_id: 2,
+ $skip_user_properties_sync: true,
user_properties: {
initial_referrer: 'https://docs.rudderstack.com',
initial_referring_domain: 'docs.rudderstack.com',
@@ -11327,4 +11330,57 @@ export const data = [
},
},
},
+ {
+ name: 'am',
+ description:
+ 'Test 78 -> Page call invalid event type as page name and template is not provided',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ request_ip: '1.1.1.1',
+ type: 'page',
+ userId: '12345',
+ properties: {},
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T11:15:53.296Z',
+ },
+ destination: {
+ Config: {
+ apiKey: 'abcde',
+ useUserDefinedPageEventName: true,
+ userProvidedPageEventString: '',
+ },
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ statusCode: 400,
+ error:
+ 'Event type is missing. Please send it under `event.type`. For page/screen events, send it under `event.name`',
+ statTags: {
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ destType: 'AM',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'processor',
+ },
+ },
+ ],
+ },
+ },
+ },
];
diff --git a/test/integrations/destinations/bluecore/data.ts b/test/integrations/destinations/bluecore/data.ts
new file mode 100644
index 0000000000..c2205c25a1
--- /dev/null
+++ b/test/integrations/destinations/bluecore/data.ts
@@ -0,0 +1,6 @@
+import { ecomTestData } from './ecommTestData';
+import { identifyData } from './identifyTestData';
+import { trackTestData } from './trackTestData';
+import { validationTestData } from './validationTestData';
+
+export const data = [...identifyData, ...trackTestData, ...ecomTestData, ...validationTestData];
diff --git a/test/integrations/destinations/bluecore/ecommTestData.ts b/test/integrations/destinations/bluecore/ecommTestData.ts
new file mode 100644
index 0000000000..de7584df78
--- /dev/null
+++ b/test/integrations/destinations/bluecore/ecommTestData.ts
@@ -0,0 +1,489 @@
+import { generateSimplifiedTrackPayload, transformResultBuilder } from '../../testUtils';
+
+const metadata = {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+};
+
+const destination = {
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'BLUECORE',
+ Config: {
+ bluecoreNamespace: 'dummy_sandbox',
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'search',
+ },
+ {
+ from: 'testPurchase',
+ to: 'purchase',
+ },
+ {
+ from: 'testboth',
+ to: 'wishlist',
+ },
+ {
+ from: 'testboth',
+ to: 'add_to_cart',
+ },
+ ],
+ },
+ Enabled: true,
+ Transformations: [],
+ DestinationDefinition: { Config: { cdkV2Enabled: true } },
+};
+
+const commonTraits = {
+ id: 'user@1',
+ age: '22',
+ anonymousId: '9c6bd77ea9da3e68',
+};
+
+const commonPropsWithProducts = {
+ property1: 'value1',
+ property2: 'value2',
+ products: [
+ {
+ product_id: '123',
+ sku: 'sku123',
+ name: 'Product 1',
+ price: 100,
+ quantity: 2,
+ },
+ {
+ product_id: '124',
+ sku: 'sku124',
+ name: 'Product 2',
+ price: 200,
+ quantity: 3,
+ },
+ ],
+};
+
+const commonPropsWithoutProducts = {
+ property1: 'value1',
+ property2: 'value2',
+ product_id: '123',
+};
+
+const commonOutputHeaders = {
+ 'Content-Type': 'application/json',
+};
+
+const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1';
+
+export const ecomTestData = [
+ {
+ id: 'bluecore-track-test-1',
+ name: 'bluecore',
+ description:
+ 'Track event call with custom event mapped in destination config to purchase event. This will fail as order_id is not present in the payload',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'testPurchase',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ '[Bluecore] property:: order_id is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: order_id is required for purchase event',
+ metadata,
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-2',
+ name: 'bluecore',
+ description:
+ 'Track event call with custom event mapped in destination config to purchase event. This will fail as total is not present in the payload',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'testPurchase',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: { ...commonPropsWithProducts, order_id: '123' },
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ '[Bluecore] property:: total is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: total is required for purchase event',
+ metadata,
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-3',
+ name: 'bluecore',
+ description:
+ 'Track event call with products searched event not mapped in destination config. This will fail as search_query is not present in the payload',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'Products Searched',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: { ...commonPropsWithoutProducts },
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ '[Bluecore] property:: search_query is required for search event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore] property:: search_query is required for search event',
+ metadata,
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-4',
+ name: 'bluecore',
+ description:
+ 'Track event call with Product Viewed event not mapped in destination config. This will be sent with viewed_product name. This event without properties.products will add entire property object as products as this event type is recommended to sent with products',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'product viewed',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithoutProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'test@rudderstack.com',
+ customer: {
+ age: '22',
+ email: 'test@rudderstack.com',
+ },
+ products: [
+ {
+ id: '123',
+ property1: 'value1',
+ property2: 'value2',
+ },
+ ],
+ },
+ event: 'viewed_product',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-5',
+ name: 'bluecore',
+ description:
+ 'Track event call with custom event mapped with two standard ecomm events in destination config. Both of the two corresponding standard events will be sent ',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ type: 'track',
+ event: 'testboth',
+ sentAt: '2020-08-14T05:30:30.118Z',
+ channel: 'web',
+ context: {
+ source: 'test',
+ userAgent: 'chrome',
+ traits: {
+ id: 'user@1',
+ age: '22',
+ anonymousId: '9c6bd77ea9da3e68',
+ },
+ device: {
+ advertisingId: 'abc123',
+ },
+ library: {
+ name: 'rudder-sdk-ruby-sync',
+ version: '1.0.6',
+ },
+ },
+ properties: {
+ property1: 'value1',
+ property2: 'value2',
+ product_id: '123',
+ },
+ anonymousId: 'new-id',
+ integrations: {
+ All: true,
+ },
+ },
+ metadata,
+ destination,
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'user@1',
+ customer: {
+ age: '22',
+ },
+ products: [
+ {
+ id: '123',
+ property1: 'value1',
+ property2: 'value2',
+ },
+ ],
+ },
+ event: 'wishlist',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'user@1',
+ customer: {
+ age: '22',
+ },
+ products: [
+ {
+ id: '123',
+ property1: 'value1',
+ property2: 'value2',
+ },
+ ],
+ },
+ event: 'add_to_cart',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-6',
+ name: 'bluecore',
+ description:
+ 'Track event call with Order Completed event without product array and not mapped in destination config. This will be sent with purchase name. This event without properties.products will generate error as products array is required for purchase event and ordered completed is a standard ecomm event',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'Order Completed',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithoutProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ '[Bluecore]:: products array is required for purchase event: Workflow: procWorkflow, Step: handleTrackEvent, ChildStep: preparePayload, OriginalError: [Bluecore]:: products array is required for purchase event',
+ metadata,
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/bluecore/identifyTestData.ts b/test/integrations/destinations/bluecore/identifyTestData.ts
new file mode 100644
index 0000000000..660e335bc6
--- /dev/null
+++ b/test/integrations/destinations/bluecore/identifyTestData.ts
@@ -0,0 +1,381 @@
+import {
+ overrideDestination,
+ transformResultBuilder,
+ generateSimplifiedIdentifyPayload,
+} from '../../testUtils';
+
+const destination = {
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'BLUECORE',
+ Config: {
+ bluecoreNamespace: 'dummy_sandbox',
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'search',
+ },
+ ],
+ },
+ Enabled: true,
+ Transformations: [],
+ DestinationDefinition: { Config: { cdkV2Enabled: true } },
+};
+
+const metadata = {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+};
+
+const commonTraits = {
+ anonymousId: '50be5c78-6c3f-4b60-be84-97805a316fb1',
+ phone: '+1234589947',
+ gender: 'non-binary',
+ db: '19950715',
+ lastname: 'Rudderlabs',
+ firstName: 'Test',
+ address: {
+ city: 'Kolkata',
+ state: 'WB',
+ zip: '700114',
+ country: 'IN',
+ },
+};
+
+const contextWithExternalId = {
+ traits: {
+ ...commonTraits,
+ email: 'abc@gmail.com',
+ },
+ externalId: [{ type: 'bluecoreExternalId', id: '54321' }],
+};
+
+const commonOutputCustomerProperties = {
+ first_name: 'Test',
+ last_name: 'Rudderlabs',
+ sex: 'non-binary',
+ address: {
+ city: 'Kolkata',
+ state: 'WB',
+ zip: '700114',
+ country: 'IN',
+ },
+};
+
+const commonOutputHeaders = {
+ 'Content-Type': 'application/json',
+};
+
+const anonymousId = '97c46c81-3140-456d-b2a9-690d70aaca35';
+const userId = 'user@1';
+const sentAt = '2021-01-03T17:02:53.195Z';
+const originalTimestamp = '2021-01-03T17:02:53.193Z';
+const commonEndpoint = 'https://api.bluecore.com/api/track/mobile/v1';
+
+export const identifyData = [
+ {
+ id: 'bluecore-identify-test-1',
+ name: 'bluecore',
+ description:
+ '[Success]: Identify call with all properties, that creates a customer in bluecore by default',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should containt one payload with event name as customer_patch and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ metadata,
+ message: generateSimplifiedIdentifyPayload({
+ context: {
+ traits: { ...commonTraits, email: 'abc@gmail.com' },
+ },
+ anonymousId,
+ userId,
+ sentAt,
+ originalTimestamp,
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ userId: '',
+ method: 'POST',
+ endpoint: commonEndpoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'abc@gmail.com',
+ customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' },
+ },
+ token: 'dummy_sandbox',
+ event: 'customer_patch',
+ },
+ }),
+ metadata: {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-identify-test-2',
+ name: 'bluecore',
+ description:
+ '[Success]: Identify call with all properties,along with action as identify that mandatorily needs email to link distict_id with customer in bluecore',
+ scenario: 'Business',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ metadata,
+ message: generateSimplifiedIdentifyPayload({
+ context: {
+ traits: commonTraits,
+ },
+ traits: {
+ action: 'identify',
+ },
+ anonymousId,
+ userId,
+ sentAt,
+ originalTimestamp,
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ '[Bluecore] property:: email is required for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] property:: email is required for identify action',
+ metadata: {
+ destinationId: '',
+ destinationType: '',
+ namespace: '',
+ sourceType: '',
+ },
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-identify-test-3',
+ name: 'bluecore',
+ description:
+ '[Success]: Identify call with all properties,along with action as random which is not supported by bluecore for identify action',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should containt one payload with event name as identify and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ metadata,
+ message: generateSimplifiedIdentifyPayload({
+ context: {
+ traits: commonTraits,
+ },
+ traits: {
+ action: 'random',
+ },
+ anonymousId,
+ userId,
+ sentAt,
+ originalTimestamp,
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ "[Bluecore] traits.action must be 'identify' for identify action: Workflow: procWorkflow, Step: prepareIdentifyPayload, ChildStep: undefined, OriginalError: [Bluecore] traits.action must be 'identify' for identify action",
+ metadata: {
+ destinationId: '',
+ destinationType: '',
+ namespace: '',
+ sourceType: '',
+ },
+ statTags: {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-identify-test-4',
+ name: 'bluecore',
+ description:
+ '[Success]: Identify call with all properties, that stitches a customer email with distinct_id in bluecore if action is identify and email is present in traits',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should containt one payload with event name as identify and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ metadata,
+ message: generateSimplifiedIdentifyPayload({
+ context: {
+ traits: { ...commonTraits, email: 'abc@gmail.com' },
+ },
+ traits: {
+ action: 'identify',
+ },
+ anonymousId,
+ userId,
+ sentAt,
+ originalTimestamp,
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ userId: '',
+ method: 'POST',
+ endpoint: commonEndpoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'user@1',
+ customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' },
+ },
+ token: 'dummy_sandbox',
+ event: 'identify',
+ },
+ }),
+ metadata: {
+ destinationId: '',
+ destinationType: '',
+ namespace: '',
+ sourceType: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-identify-test-5',
+ name: 'bluecore',
+ description:
+ '[Success]: Identify call with all properties and externalId, that creates a customer in bluecore by default, distinct_id is set to externalId value',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should containt one payload with event name as customer_patch and status code should be 200',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination,
+ metadata,
+ message: generateSimplifiedIdentifyPayload({
+ context: contextWithExternalId,
+ anonymousId,
+ userId,
+ sentAt,
+ originalTimestamp,
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ userId: '',
+ method: 'POST',
+ endpoint: commonEndpoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: '54321',
+ customer: { ...commonOutputCustomerProperties, email: 'abc@gmail.com' },
+ },
+ token: 'dummy_sandbox',
+ event: 'customer_patch',
+ },
+ }),
+ metadata: {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/bluecore/trackTestData.ts b/test/integrations/destinations/bluecore/trackTestData.ts
new file mode 100644
index 0000000000..72d48bf93d
--- /dev/null
+++ b/test/integrations/destinations/bluecore/trackTestData.ts
@@ -0,0 +1,439 @@
+import {
+ generateSimplifiedTrackPayload,
+ generateTrackPayload,
+ overrideDestination,
+ transformResultBuilder,
+} from '../../testUtils';
+
+const destination = {
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'BLUECORE',
+ Config: {
+ bluecoreNamespace: 'dummy_sandbox',
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'search',
+ },
+ {
+ from: 'testPurchase',
+ to: 'purchase',
+ },
+ {
+ from: 'testboth',
+ to: 'wishlist',
+ },
+ {
+ from: 'testboth',
+ to: 'add_to_cart',
+ },
+ ],
+ },
+ Enabled: true,
+ Transformations: [],
+ DestinationDefinition: { Config: { cdkV2Enabled: true } },
+};
+
+const metadata = {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+};
+
+const commonTraits = {
+ id: 'user@1',
+ age: '22',
+ anonymousId: '9c6bd77ea9da3e68',
+};
+
+const contextWithExternalId = {
+ traits: {
+ ...commonTraits,
+ email: 'abc@gmail.com',
+ },
+ externalId: [{ type: 'bluecoreExternalId', id: '54321' }],
+};
+
+const commonPropsWithProducts = {
+ property1: 'value1',
+ property2: 'value2',
+ products: [
+ {
+ product_id: '123',
+ sku: 'sku123',
+ name: 'Product 1',
+ price: 100,
+ quantity: 2,
+ },
+ {
+ product_id: '124',
+ sku: 'sku124',
+ name: 'Product 2',
+ price: 200,
+ quantity: 3,
+ },
+ ],
+};
+
+const commonPropsWithoutProducts = {
+ property1: 'value1',
+ property2: 'value2',
+ product_id: '123',
+};
+
+const commonOutputHeaders = {
+ 'Content-Type': 'application/json',
+};
+
+const eventEndPoint = 'https://api.bluecore.com/api/track/mobile/v1';
+
+export const trackTestData = [
+ {
+ id: 'bluecore-track-test-1',
+ name: 'bluecore',
+ description:
+ 'Track event call with custom event with properties not mapped in destination config. This will be sent with its original name',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'TestEven001',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'test@rudderstack.com',
+ customer: {
+ age: '22',
+ email: 'test@rudderstack.com',
+ },
+ products: [
+ {
+ name: 'Product 1',
+ price: 100,
+ id: '123',
+ quantity: 2,
+ },
+ {
+ name: 'Product 2',
+ price: 200,
+ id: '124',
+ quantity: 3,
+ },
+ ],
+ },
+ event: 'TestEven001',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-2',
+ name: 'bluecore',
+ description:
+ 'Track event call with custom event without properties not mapped in destination config. This will be sent with its original name',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only event payload and status code should be 200. As the event paylaod does not contains products, product array will not be sent',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'TestEven001',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithoutProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'test@rudderstack.com',
+ customer: {
+ age: '22',
+ email: 'test@rudderstack.com',
+ },
+ },
+ event: 'TestEven001',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-3',
+ name: 'bluecore',
+ description:
+ 'optin event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'optin',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithoutProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'test@rudderstack.com',
+ customer: {
+ age: '22',
+ email: 'test@rudderstack.com',
+ },
+ },
+ event: 'optin',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-4',
+ name: 'bluecore',
+ description:
+ 'unsubscribe event is also considered as a track event, user need to not map it from the UI , it will be sent with the same event name to bluecore',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'unsubscribe',
+ userId: 'sajal12',
+ context: {
+ traits: {
+ ...commonTraits,
+ email: 'test@rudderstack.com',
+ phone: '9112340375',
+ },
+ },
+ properties: commonPropsWithoutProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: 'test@rudderstack.com',
+ customer: {
+ age: '22',
+ email: 'test@rudderstack.com',
+ },
+ },
+ event: 'unsubscribe',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-track-test-5',
+ name: 'bluecore',
+ description:
+ 'Track event call with with externalId. This will map externalId to distinct_id in the payload',
+ scenario: 'Business',
+ successCriteria:
+ 'Response should contain only event payload and status code should be 200, for the event payload should contain flattened properties in the payload',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: destination,
+ metadata,
+ message: generateSimplifiedTrackPayload({
+ type: 'track',
+ event: 'TestEven001',
+ userId: 'sajal12',
+ context: contextWithExternalId,
+ properties: commonPropsWithProducts,
+ anonymousId: '9c6bd77ea9da3e68',
+ originalTimestamp: '2021-01-25T15:32:56.409Z',
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ method: 'POST',
+ endpoint: eventEndPoint,
+ headers: commonOutputHeaders,
+ JSON: {
+ properties: {
+ distinct_id: '54321',
+ customer: {
+ age: '22',
+ email: 'abc@gmail.com',
+ },
+ products: [
+ {
+ name: 'Product 1',
+ price: 100,
+ id: '123',
+ quantity: 2,
+ },
+ {
+ name: 'Product 2',
+ price: 200,
+ id: '124',
+ quantity: 3,
+ },
+ ],
+ },
+ event: 'TestEven001',
+ token: 'dummy_sandbox',
+ },
+ userId: '',
+ }),
+ metadata,
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/bluecore/validationTestData.ts b/test/integrations/destinations/bluecore/validationTestData.ts
new file mode 100644
index 0000000000..5b81f8c95a
--- /dev/null
+++ b/test/integrations/destinations/bluecore/validationTestData.ts
@@ -0,0 +1,150 @@
+const metadata = {
+ sourceType: '',
+ destinationType: '',
+ namespace: '',
+ destinationId: '',
+};
+
+const outputStatTags = {
+ destType: 'BLUECORE',
+ destinationId: '',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+};
+
+export const validationTestData = [
+ {
+ id: 'bluecore-validation-test-1',
+ name: 'bluecore',
+ description: '[Error]: Check for unsupported message type',
+ scenario: 'Framework',
+ successCriteria:
+ 'Response should contain error message and status code should be 400, as we are sending a message type which is not supported by bluecore destination and the error message should be Event type random is not supported',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'BLUECORE',
+ Config: {
+ bluecoreNamespace: 'dummy_sandbox',
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'search',
+ },
+ ],
+ },
+ Enabled: true,
+ Transformations: [],
+ DestinationDefinition: { Config: { cdkV2Enabled: true } },
+ },
+ metadata,
+ message: {
+ userId: 'user123',
+ type: 'random',
+ groupId: 'XUepkK',
+ traits: {
+ subscribe: true,
+ },
+ context: {
+ traits: {
+ email: 'test@rudderstack.com',
+ phone: '+12 345 678 900',
+ consent: 'email',
+ },
+ },
+ timestamp: '2020-01-21T00:21:34.208Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported',
+ metadata,
+ statTags: outputStatTags,
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'bluecore-validation-test-1',
+ name: 'bluecore',
+ description: '[Error]: Check for not finding bluecoreNamespace',
+ scenario: 'Framework',
+ successCriteria:
+ 'Response should contain error message and status code should be 400, as bluecoreNamespace is not found in the destination config',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq',
+ Name: 'BLUECORE',
+ Config: {
+ eventsMapping: [
+ {
+ from: 'ABC Searched',
+ to: 'search',
+ },
+ ],
+ },
+ Enabled: true,
+ Transformations: [],
+ DestinationDefinition: { Config: { cdkV2Enabled: true } },
+ },
+ metadata: metadata,
+ message: {
+ userId: 'user123',
+ type: 'random',
+ groupId: 'XUepkK',
+ traits: {
+ subscribe: true,
+ },
+ context: {
+ traits: {
+ email: 'test@rudderstack.com',
+ phone: '+12 345 678 900',
+ consent: 'email',
+ },
+ },
+ timestamp: '2020-01-21T00:21:34.208Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'message type random is not supported: Workflow: procWorkflow, Step: validateInput, ChildStep: undefined, OriginalError: message type random is not supported',
+ metadata,
+ statTags: outputStatTags,
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/branch/processor/data.ts b/test/integrations/destinations/branch/processor/data.ts
index 9fed354235..dc1bdd33bc 100644
--- a/test/integrations/destinations/branch/processor/data.ts
+++ b/test/integrations/destinations/branch/processor/data.ts
@@ -1490,4 +1490,160 @@ export const data = [
},
},
},
+ {
+ name: 'branch',
+ description: 'Map event name to branch standard event name in track call',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ branchKey: 'test_branch_key',
+ eventsMapping: [
+ {
+ from: 'Order Completed',
+ to: 'PURCHASE',
+ },
+ ],
+ useNativeSDK: false,
+ },
+ DestinationDefinition: {
+ DisplayName: 'Branch Metrics',
+ ID: '1WTpBSTiL3iAUHUdW7rHT4sawgU',
+ Name: 'BRANCH',
+ },
+ Enabled: true,
+ ID: '1WTpIHpH7NTBgjeiUPW1kCUgZGI',
+ Name: 'branch test',
+ Transformations: [],
+ },
+ message: {
+ anonymousId: 'anonId123',
+ channel: 'web',
+ context: {
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.0',
+ },
+ device: {
+ adTrackingEnabled: true,
+ advertisingId: '3f034872-5e28-45a1-9eda-ce22a3e36d1a',
+ id: '3f034872-5e28-45a1-9eda-ce22a3e36d1a',
+ manufacturer: 'Google',
+ model: 'AOSP on IA Emulator',
+ name: 'generic_x86_arm',
+ type: 'ios',
+ attTrackingStatus: 3,
+ },
+ ip: '0.0.0.0',
+ library: {
+ name: 'RudderLabs JavaScript SDK',
+ version: '1.0.0',
+ },
+ locale: 'en-US',
+ os: {
+ name: 'iOS',
+ version: '14.4.1',
+ },
+ screen: {
+ density: 2,
+ },
+ traits: {
+ anonymousId: 'anonId123',
+ email: 'test_user@gmail.com',
+ },
+ userAgent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
+ },
+ event: 'Order Completed',
+ integrations: {
+ All: true,
+ },
+ messageId: 'ea5cfab2-3961-4d8a-8187-3d1858c90a9f',
+ originalTimestamp: '2020-01-17T04:53:51.185Z',
+ properties: {
+ name: 't-shirt',
+ revenue: '10',
+ currency: 'USD',
+ key1: 'value1',
+ key2: 'value2',
+ order_id: 'order123',
+ },
+ receivedAt: '2020-01-17T10:23:52.688+05:30',
+ request_ip: '[::1]:64059',
+ sentAt: '2020-01-17T04:53:52.667Z',
+ timestamp: '2020-01-17T10:23:51.206+05:30',
+ type: 'track',
+ userId: 'userId123',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api2.branch.io/v2/event/standard',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ branch_key: 'test_branch_key',
+ name: 'PURCHASE',
+ content_items: [
+ {
+ $product_name: 't-shirt',
+ },
+ ],
+ event_data: {
+ revenue: '10',
+ currency: 'USD',
+ },
+ custom_data: {
+ key1: 'value1',
+ key2: 'value2',
+ order_id: 'order123',
+ },
+ user_data: {
+ os: 'iOS',
+ os_version: '14.4.1',
+ app_version: '1.0.0',
+ screen_dpi: 2,
+ developer_identity: 'userId123',
+ idfa: '3f034872-5e28-45a1-9eda-ce22a3e36d1a',
+ idfv: '3f034872-5e28-45a1-9eda-ce22a3e36d1a',
+ limit_ad_tracking: false,
+ model: 'AOSP on IA Emulator',
+ user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
+ },
+ },
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'anonId123',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
];
diff --git a/test/integrations/destinations/braze/dataDelivery/business.ts b/test/integrations/destinations/braze/dataDelivery/business.ts
new file mode 100644
index 0000000000..4997c5ffae
--- /dev/null
+++ b/test/integrations/destinations/braze/dataDelivery/business.ts
@@ -0,0 +1,377 @@
+import { ProxyMetdata } from '../../../../../src/types';
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateMetadata, generateProxyV1Payload } from '../../../testUtils';
+
+const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track';
+
+const partner = 'RudderStack';
+
+const headers = {
+ Accept: 'application/json',
+ Authorization: 'Bearer api_key',
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'RudderLabs',
+};
+
+const BrazeEvent1 = {
+ name: 'Product List Viewed',
+ time: '2023-11-30T21:48:45.634Z',
+ properties: {
+ products: [
+ {
+ sku: '23-04-52-62-01-18',
+ name: 'Broman Hoodie',
+ price: '97.99',
+ variant: [
+ {
+ id: 39653520310368,
+ sku: '23-04-52-62-01-18',
+ grams: 0,
+ price: '97.99',
+ title: '(SM)',
+ weight: 0,
+ option1: '(SM)',
+ taxable: true,
+ position: 1,
+ tax_code: '',
+ created_at: '2023-05-18T12:56:22-06:00',
+ product_id: 6660780884064,
+ updated_at: '2023-11-30T15:48:43-06:00',
+ weight_unit: 'kg',
+ quantity_rule: {
+ min: 1,
+ increment: 1,
+ },
+ compare_at_price: '139.99',
+ inventory_policy: 'deny',
+ requires_shipping: true,
+ inventory_quantity: 8,
+ fulfillment_service: 'manual',
+ inventory_management: 'shopify',
+ quantity_price_breaks: [],
+ old_inventory_quantity: 8,
+ },
+ ],
+ category: '62 OTHER/RETRO',
+ currency: 'CAD',
+ product_id: 6660780884064,
+ },
+ {
+ sku: '23-04-08-61-01-18',
+ name: 'Kipling Camo Hoodie',
+ price: '69.99',
+ variant: [
+ {
+ id: 39672628740192,
+ sku: '23-04-08-61-01-18',
+ grams: 0,
+ price: '69.99',
+ title: '(SM)',
+ weight: 0,
+ option1: '(SM)',
+ taxable: true,
+ position: 1,
+ tax_code: '',
+ created_at: '2023-06-28T12:52:56-06:00',
+ product_id: 6666835853408,
+ updated_at: '2023-11-30T15:48:43-06:00',
+ weight_unit: 'kg',
+ quantity_rule: {
+ min: 1,
+ increment: 1,
+ },
+ compare_at_price: '99.99',
+ inventory_policy: 'deny',
+ requires_shipping: true,
+ inventory_quantity: 8,
+ fulfillment_service: 'manual',
+ inventory_management: 'shopify',
+ quantity_price_breaks: [],
+ old_inventory_quantity: 8,
+ },
+ ],
+ category: 'Misc',
+ currency: 'CAD',
+ product_id: 6666835853408,
+ },
+ ],
+ },
+ _update_existing_only: false,
+ user_alias: {
+ alias_name: 'ab7de609-9bec-8e1c-42cd-084a1cd93a4e',
+ alias_label: 'rudder_id',
+ },
+};
+
+const BrazeEvent2 = {
+ name: 'Add to Cart',
+ time: '2020-01-24T11:59:02.403+05:30',
+ properties: {
+ revenue: 50,
+ },
+ external_id: 'mickeyMouse',
+};
+
+const BrazePurchaseEvent = {
+ product_id: '507f1f77bcf86cd799439011',
+ price: 0,
+ currency: 'USD',
+ quantity: 1,
+ time: '2020-01-24T11:59:02.402+05:30',
+ _update_existing_only: false,
+ user_alias: {
+ alias_name: 'e6ab2c5e-2cda-44a9-a962-e2f67df78bca',
+ alias_label: 'rudder_id',
+ },
+};
+
+const metadataArray = [generateMetadata(1), generateMetadata(2), generateMetadata(3)];
+
+const errorMessages = {
+ message_1: '{"events_processed":2,"purchases_processed":1,"message":"success"}',
+ message_2:
+ '{"events_processed":1,"message":"success","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}',
+ message_3:
+ '{"message":"Valid data must be provided in the \'attributes\', \'events\', or \'purchases\' fields.","errors":[{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":0},{"type":"\'external_id\', \'braze_id\', \'user_alias\', \'email\' or \'phone\' is required","input_array":"events","index":1},{"type":"\'quantity\' is not valid","input_array":"purchases","index":0}]}',
+};
+
+const expectedStatTags = {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'BRAZE',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+};
+
+export const testScenariosForV1API: ProxyV1TestData[] = [
+ {
+ id: 'braze_v1_scenario_1',
+ name: 'braze',
+ description:
+ '[Proxy v1 API] :: Test for a valid request - 2 events and 1 purchase event are sent where the destination responds with 200 without any error',
+ successCriteria: 'Should return 200 with no error with destination response',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ partner,
+ events: [BrazeEvent1, BrazeEvent2],
+ purchases: [BrazePurchaseEvent],
+ },
+ headers,
+ endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`,
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: errorMessages.message_1,
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ {
+ error: errorMessages.message_1,
+ statusCode: 200,
+ metadata: generateMetadata(2),
+ },
+ {
+ error: errorMessages.message_1,
+ statusCode: 200,
+ metadata: generateMetadata(3),
+ },
+ ],
+ status: 200,
+ message: 'Request for braze Processed Successfully',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_scenario_2',
+ name: 'braze',
+ description:
+ '[Proxy v1 API] :: Test for a invalid request - 2 events and 1 purchase event are sent where the destination responds with 200 with error for a one of the event and the purchase event',
+ successCriteria: 'Should return 200 with error for one of the event and the purchase event',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ partner,
+ events: [{ ...BrazeEvent1, user_alias: undefined }, BrazeEvent2], // modifying first event to be invalid
+ purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid
+ },
+ headers,
+ endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`,
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: errorMessages.message_2,
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ {
+ error: errorMessages.message_2,
+ statusCode: 200,
+ metadata: generateMetadata(2),
+ },
+ {
+ error: errorMessages.message_2,
+ statusCode: 200,
+ metadata: generateMetadata(3),
+ },
+ ],
+ status: 200,
+ message: 'Request for braze Processed Successfully',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_scenario_3',
+ name: 'braze',
+ description: '[Proxy v1 API] :: Test for an invalid request - all the payloads are invalid',
+ successCriteria: 'Should return 400 with error for all the payloads',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ partner,
+ events: [
+ { ...BrazeEvent1, user_alias: undefined },
+ { ...BrazeEvent2, external_id: undefined },
+ ], // modifying first event to be invalid
+ purchases: [{ ...BrazePurchaseEvent, quantity: 'invalid quantity' }], // modifying purchase event to be invalid
+ },
+ headers,
+ endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`,
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: errorMessages.message_3,
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ {
+ error: errorMessages.message_3,
+ statusCode: 400,
+ metadata: generateMetadata(2),
+ },
+ {
+ error: errorMessages.message_3,
+ statusCode: 400,
+ metadata: generateMetadata(3),
+ },
+ ],
+ statTags: expectedStatTags,
+ message: 'Request failed for braze with status: 400',
+ status: 400,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_scenario_4',
+ name: 'braze',
+ description: '[Proxy v1 API] :: Test for invalid auth scneario',
+ successCriteria: 'Should return 400 for all the payloads',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ partner,
+ events: [BrazeEvent1, BrazeEvent2],
+ purchases: [BrazePurchaseEvent],
+ },
+ headers,
+ endpoint: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`,
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: '{"message":"Invalid API Key"}',
+ statusCode: 401,
+ metadata: generateMetadata(1),
+ },
+ {
+ error: '{"message":"Invalid API Key"}',
+ statusCode: 401,
+ metadata: generateMetadata(2),
+ },
+ {
+ error: '{"message":"Invalid API Key"}',
+ statusCode: 401,
+ metadata: generateMetadata(3),
+ },
+ ],
+ statTags: expectedStatTags,
+ message: 'Request failed for braze with status: 401',
+ status: 401,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/braze/dataDelivery/data.ts b/test/integrations/destinations/braze/dataDelivery/data.ts
index 8162e75720..2596a4b959 100644
--- a/test/integrations/destinations/braze/dataDelivery/data.ts
+++ b/test/integrations/destinations/braze/dataDelivery/data.ts
@@ -1,6 +1,8 @@
import MockAdapter from 'axios-mock-adapter';
+import { testScenariosForV1API } from './business';
+import { otherScenariosV1 } from './other';
-export const data = [
+export const existingTestData = [
{
name: 'braze',
description: 'Test 0',
@@ -629,7 +631,7 @@ export const data = [
},
output: {
response: {
- status: 401,
+ status: 200,
body: {
output: {
status: 401,
@@ -662,7 +664,6 @@ export const data = [
module: 'destination',
workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
},
- authErrorCategory: '',
message: 'Request failed for braze with status: 401',
},
},
@@ -770,7 +771,7 @@ export const data = [
},
output: {
response: {
- status: 401,
+ status: 200,
body: {
output: {
status: 401,
@@ -840,7 +841,6 @@ export const data = [
module: 'destination',
workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
},
- authErrorCategory: '',
message: 'Request failed for braze with status: 401',
},
},
@@ -848,3 +848,5 @@ export const data = [
},
},
];
+
+export const data = [...existingTestData, ...testScenariosForV1API, ...otherScenariosV1];
diff --git a/test/integrations/destinations/braze/dataDelivery/other.ts b/test/integrations/destinations/braze/dataDelivery/other.ts
new file mode 100644
index 0000000000..9353899a65
--- /dev/null
+++ b/test/integrations/destinations/braze/dataDelivery/other.ts
@@ -0,0 +1,204 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateMetadata, generateProxyV1Payload } from '../../../testUtils';
+
+const expectedStatTags = {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'BRAZE',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+};
+
+export const otherScenariosV1: ProxyV1TestData[] = [
+ {
+ id: 'braze_v1_other_scenario_1',
+ name: 'braze',
+ 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,
+ message: 'Request failed for braze with status: 503',
+ status: 503,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_other_scenario_2',
+ name: 'braze',
+ 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,
+ message: 'Request failed for braze with status: 500',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_other_scenario_3',
+ name: 'braze',
+ 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,
+ message: 'Request failed for braze with status: 504',
+ status: 504,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_other_scenario_4',
+ name: 'braze',
+ 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,
+ message: 'Request failed for braze with status: 500',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'braze_v1_other_scenario_5',
+ name: 'braze',
+ 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,
+ message: 'Request failed for braze with status: 500',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/braze/network.ts b/test/integrations/destinations/braze/network.ts
index 40d75c9d34..ae093ce1f4 100644
--- a/test/integrations/destinations/braze/network.ts
+++ b/test/integrations/destinations/braze/network.ts
@@ -524,4 +524,104 @@ const deleteNwData = [
},
},
];
-export const networkCallsData = [...deleteNwData, ...dataDeliveryMocksData];
+
+const BRAZE_USERS_TRACK_ENDPOINT = 'https://rest.iad-03.braze.com/users/track';
+
+// New Mocks for Braze
+const updatedDataDeliveryMocksData = [
+ {
+ description:
+ 'Mock response from destination depicting a valid request for 2 valid events and 1 purchase event',
+ httpReq: {
+ url: `${BRAZE_USERS_TRACK_ENDPOINT}/valid_scenario1`,
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ events_processed: 2,
+ purchases_processed: 1,
+ message: 'success',
+ },
+ status: 200,
+ },
+ },
+
+ {
+ description:
+ 'Mock response from destination depicting a request with 1 valid and 1 invalid event and 1 invalid purchase event',
+ httpReq: {
+ url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario1`,
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ events_processed: 1,
+ message: 'success',
+ errors: [
+ {
+ type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required",
+ input_array: 'events',
+ index: 1,
+ },
+ {
+ type: "'quantity' is not valid",
+ input_array: 'purchases',
+ index: 0,
+ },
+ ],
+ },
+ status: 200,
+ },
+ },
+
+ {
+ description:
+ 'Mock response from destination depicting a request with all the payloads are invalid',
+ httpReq: {
+ url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario2`,
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ message:
+ "Valid data must be provided in the 'attributes', 'events', or 'purchases' fields.",
+ errors: [
+ {
+ type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required",
+ input_array: 'events',
+ index: 0,
+ },
+ {
+ type: "'external_id', 'braze_id', 'user_alias', 'email' or 'phone' is required",
+ input_array: 'events',
+ index: 1,
+ },
+ {
+ type: "'quantity' is not valid",
+ input_array: 'purchases',
+ index: 0,
+ },
+ ],
+ },
+ status: 400,
+ },
+ },
+ {
+ description: 'Mock response from destination depicting a request with invalid credentials',
+ httpReq: {
+ url: `${BRAZE_USERS_TRACK_ENDPOINT}/invalid_scenario3`,
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ message: 'Invalid API Key',
+ },
+ status: 401,
+ },
+ },
+];
+export const networkCallsData = [
+ ...deleteNwData,
+ ...dataDeliveryMocksData,
+ ...updatedDataDeliveryMocksData,
+];
diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/business.ts b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts
new file mode 100644
index 0000000000..e663f3212a
--- /dev/null
+++ b/test/integrations/destinations/campaign_manager/dataDelivery/business.ts
@@ -0,0 +1,606 @@
+import { ProxyMetdata } from '../../../../../src/types';
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils';
+
+// Boilerplate data for the test cases
+// ======================================
+
+const commonHeaders = {
+ Authorization: 'Bearer dummyApiKey',
+ 'Content-Type': 'application/json',
+};
+
+const encryptionInfo = {
+ kind: 'dfareporting#encryptionInfo',
+ encryptionSource: 'AD_SERVING',
+ encryptionEntityId: '3564523',
+ encryptionEntityType: 'DCM_ACCOUNT',
+};
+
+const testConversion1 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const testConversion2 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const commonRequestParameters = {
+ headers: commonHeaders,
+ JSON: {
+ kind: 'dfareporting#conversionsBatchInsertRequest',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
+ },
+};
+
+const proxyMetdata1: ProxyMetdata = {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+
+const proxyMetdata2: ProxyMetdata = {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+};
+
+const metadataArray = [proxyMetdata1, proxyMetdata2];
+
+// Test scenarios for the test cases
+// ===================================
+
+export const testScenariosForV0API = [
+ {
+ id: 'cm360_v0_scenario_1',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Test for a valid request with a successful 200 response from the destination',
+ successCriteria: 'Should return 200 with no error with destination response',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestParameters,
+ endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ hasFailures: false,
+ status: [
+ {
+ conversion: testConversion1,
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: testConversion2,
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_scenario_2',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2',
+ successCriteria: 'Should return 400 with error and with destination response',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestParameters,
+ endpoint: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ body: {
+ output: {
+ status: 400,
+ message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation',
+ destinationResponse: {
+ response: {
+ hasFailures: true,
+ status: [
+ {
+ conversion: testConversion1,
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: testConversion2,
+ errors: [
+ {
+ code: 'NOT_FOUND',
+ message: 'Floodlight config id: 213123123 was not found.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_scenario_3',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions',
+ successCriteria: 'Should return 400 with error and with destination response',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestParameters,
+ endpoint:
+ 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ body: {
+ output: {
+ status: 400,
+ message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation',
+ destinationResponse: {
+ response: {
+ hasFailures: true,
+ status: [
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ errors: [
+ {
+ code: 'INVALID_ARGUMENT',
+ message: 'Gclid is not valid.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ errors: [
+ {
+ code: 'NOT_FOUND',
+ message: 'Floodlight config id: 213123123 was not found.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+];
+
+export const testScenariosForV1API: ProxyV1TestData[] = [
+ {
+ id: 'cm360_v1_scenario_1',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 without any error',
+ successCriteria: 'Should return 200 with no error with destination response',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...commonRequestParameters,
+ endpoint: 'https://dfareporting.googleapis.com/test_url_for_valid_request',
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ hasFailures: false,
+ status: [
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ response: [
+ {
+ statusCode: 200,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'success',
+ },
+ {
+ statusCode: 200,
+ metadata: {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'success',
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_scenario_2',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v1 API] :: Test for a valid request - where the destination responds with 200 with error for conversion 2',
+ successCriteria: 'Should return 200 with partial failures within the response payload',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...commonRequestParameters,
+ endpoint:
+ 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2',
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ hasFailures: true,
+ status: [
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ errors: [
+ {
+ code: 'NOT_FOUND',
+ message: 'Floodlight config id: 213123123 was not found.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ response: [
+ {
+ statusCode: 200,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'success',
+ },
+ {
+ statusCode: 400,
+ metadata: {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'Floodlight config id: 213123123 was not found., ',
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_scenario_3',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Test for a valid request - where the destination responds with 200 with error for both conversions',
+ successCriteria: 'Should return 200 with all failures within the response payload',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ ...commonRequestParameters,
+ endpoint:
+ 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions',
+ },
+ metadataArray,
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ hasFailures: true,
+ status: [
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ errors: [
+ {
+ code: 'INVALID_ARGUMENT',
+ message: 'Gclid is not valid.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+ },
+ errors: [
+ {
+ code: 'NOT_FOUND',
+ message: 'Floodlight config id: 213123123 was not found.',
+ kind: 'dfareporting#conversionError',
+ },
+ ],
+ kind: 'dfareporting#conversionStatus',
+ },
+ ],
+ kind: 'dfareporting#conversionsBatchInsertResponse',
+ },
+ status: 200,
+ },
+ response: [
+ {
+ statusCode: 400,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'Gclid is not valid., ',
+ },
+ {
+ statusCode: 400,
+ metadata: {
+ jobId: 2,
+ attemptNum: 1,
+ userId: 'dummyUserId',
+ sourceId: 'dummySourceId',
+ destinationId: 'dummyDestinationId',
+ workspaceId: 'dummyWorkspaceId',
+ secret: {},
+ dontBatch: false,
+ },
+ error: 'Floodlight config id: 213123123 was not found., ',
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts
index 601ad56401..0373ca9992 100644
--- a/test/integrations/destinations/campaign_manager/dataDelivery/data.ts
+++ b/test/integrations/destinations/campaign_manager/dataDelivery/data.ts
@@ -1,604 +1,12 @@
+import { testScenariosForV0API, testScenariosForV1API } from './business';
+import { v0oauthScenarios, v1oauthScenarios } from './oauth';
+import { otherScenariosV0, otherScenariosV1 } from './other';
+
export const data = [
- {
- name: 'campaign_manager',
- description: 'Sucess insert request V0',
- feature: 'dataDelivery',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: {
- version: '1',
- type: 'REST',
- method: 'POST',
- endpoint:
- 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert',
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- params: {},
- body: {
- JSON: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- JSON_ARRAY: {},
- XML: {},
- FORM: {},
- },
- files: {},
- },
- method: 'POST',
- },
- },
- output: {
- response: {
- status: 200,
- body: {
- output: {
- status: 200,
- message: '[CAMPAIGN_MANAGER Response Handler] - Request Processed Successfully',
- destinationResponse: {
- response: {
- hasFailures: false,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- },
- },
- },
- },
- },
- },
- {
- name: 'campaign_manager',
- description: 'Failure insert request',
- feature: 'dataDelivery',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: {
- version: '1',
- type: 'REST',
- method: 'POST',
- endpoint:
- 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert',
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- params: {},
- body: {
- JSON: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- JSON_ARRAY: {},
- XML: {},
- FORM: {},
- },
- files: {},
- },
- method: 'POST',
- },
- },
- output: {
- response: {
- status: 400,
- body: {
- output: {
- status: 400,
- message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation',
- statTags: {
- errorCategory: 'network',
- errorType: 'aborted',
- destType: 'CAMPAIGN_MANAGER',
- module: 'destination',
- implementation: 'native',
- feature: 'dataDelivery',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
- },
- destinationResponse: {
- response: {
- hasFailures: true,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- errors: [
- {
- code: 'NOT_FOUND',
- message: 'Floodlight config id: 213123123 was not found.',
- kind: 'dfareporting#conversionError',
- },
- ],
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- },
- },
- },
- },
- },
- },
- {
- name: 'campaign_manager',
- description: 'Failure insert request Aborted',
- feature: 'dataDelivery',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: {
- version: '1',
- type: 'REST',
- method: 'POST',
- endpoint:
- 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert',
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- params: {},
- body: {
- JSON: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- JSON_ARRAY: {},
- XML: {},
- FORM: {},
- },
- files: {},
- },
- method: 'POST',
- },
- },
- output: {
- response: {
- status: 400,
- body: {
- output: {
- status: 400,
- message: 'Campaign Manager: Aborting during CAMPAIGN_MANAGER response transformation',
- statTags: {
- errorCategory: 'network',
- errorType: 'aborted',
- destType: 'CAMPAIGN_MANAGER',
- module: 'destination',
- implementation: 'native',
- feature: 'dataDelivery',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
- },
- destinationResponse: {
- response: {
- hasFailures: true,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- errors: [
- {
- code: 'INVALID_ARGUMENT',
- message: 'Floodlight config id: 213123123 was not found.',
- kind: 'dfareporting#conversionError',
- },
- ],
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- },
- },
- },
- },
- },
- },
- {
- name: 'campaign_manager',
- description: 'Sucess and fail insert request v1',
- feature: 'dataDelivery',
- module: 'destination',
- version: 'v1',
- input: {
- request: {
- body: {
- version: '1',
- type: 'REST',
- method: 'POST',
- endpoint:
- 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert',
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- params: {},
- body: {
- JSON: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 8,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- JSON_ARRAY: {},
- XML: {},
- FORM: {},
- },
- metadata: [
- {
- jobId: 2,
- attemptNum: 0,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- {
- jobId: 3,
- attemptNum: 1,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- ],
- files: {},
- },
- method: 'POST',
- },
- },
- output: {
- response: {
- status: 200,
- body: {
- output: {
- status: 200,
- message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully',
- destinationResponse: {
- response: {
- hasFailures: true,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- errors: [
- {
- code: 'INVALID_ARGUMENT',
- kind: 'dfareporting#conversionError',
- message: 'Floodlight config id: 213123123 was not found.',
- },
- ],
- },
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 8,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- rudderJobMetadata: [
- {
- jobId: 2,
- attemptNum: 0,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- {
- jobId: 3,
- attemptNum: 1,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- ],
- },
- response: [
- {
- error: 'Floodlight config id: 213123123 was not found., ',
- statusCode: 400,
- metadata: {
- attemptNum: 0,
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- jobId: 2,
- secret: {
- access_token: 'secret',
- developer_token: 'developer_Token',
- refresh_token: 'refresh',
- },
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- userId: '',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- },
- },
- {
- error: 'success',
- metadata: {
- attemptNum: 1,
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- jobId: 3,
- secret: {
- access_token: 'secret',
- developer_token: 'developer_Token',
- refresh_token: 'refresh',
- },
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- userId: '',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- },
- },
- {
- name: 'campaign_manager',
- description: 'Sucess insert request v1',
- feature: 'dataDelivery',
- module: 'destination',
- version: 'v1',
- input: {
- request: {
- body: {
- version: '1',
- type: 'REST',
- method: 'POST',
- endpoint:
- 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert',
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- params: {},
- body: {
- JSON: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- JSON_ARRAY: {},
- XML: {},
- FORM: {},
- },
- metadata: {
- jobId: 2,
- attemptNum: 0,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- files: {},
- },
- method: 'POST',
- },
- },
- output: {
- response: {
- status: 200,
- body: {
- output: {
- status: 200,
- message: '[CAMPAIGN_MANAGER Response V1 Handler] - Request Processed Successfully',
- destinationResponse: {
- response: {
- hasFailures: false,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- rudderJobMetadata: {
- jobId: 2,
- attemptNum: 0,
- userId: '',
- sourceId: '2Vsge2uWYdrLfG7pZb5Y82eo4lr',
- destinationId: '2RHh08uOsXqE9KvCDg3hoaeuK2L',
- workspaceId: '2Csl0lSTbuM3qyHdaOQB2GcDH8o',
- secret: {
- access_token: 'secret',
- refresh_token: 'refresh',
- developer_token: 'developer_Token',
- },
- },
- },
- response: [
- {
- error: 'success',
- statusCode: 200,
- },
- ],
- },
- },
- },
- },
- },
+ ...testScenariosForV0API,
+ ...testScenariosForV1API,
+ ...v0oauthScenarios,
+ ...v1oauthScenarios,
+ ...otherScenariosV0,
+ ...otherScenariosV1,
];
diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts
new file mode 100644
index 0000000000..288a06bfe6
--- /dev/null
+++ b/test/integrations/destinations/campaign_manager/dataDelivery/oauth.ts
@@ -0,0 +1,558 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV1Payload, generateProxyV0Payload } from '../../../testUtils';
+// Boilerplat data for the test cases
+// ======================================
+
+const commonHeaders = {
+ Authorization: 'Bearer dummyApiKey',
+ 'Content-Type': 'application/json',
+};
+
+const encryptionInfo = {
+ kind: 'dfareporting#encryptionInfo',
+ encryptionSource: 'AD_SERVING',
+ encryptionEntityId: '3564523',
+ encryptionEntityType: 'DCM_ACCOUNT',
+};
+
+const testConversion1 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const testConversion2 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const commonRequestParameters = {
+ headers: commonHeaders,
+ JSON: {
+ kind: 'dfareporting#conversionsBatchInsertRequest',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
+ },
+};
+
+// Test scenarios for the test cases
+// ===================================
+
+export const v0oauthScenarios = [
+ {
+ id: 'cm360_v0_oauth_scenario_1',
+ name: 'campaign_manager',
+ 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://googleapis.com/test_url_for_credentials_missing',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project. during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: {
+ error: {
+ code: 401,
+ message:
+ 'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
+ errors: [
+ {
+ message: 'Login Required.',
+ domain: 'global',
+ reason: 'required',
+ location: 'Authorization',
+ locationType: 'header',
+ },
+ ],
+ status: 'UNAUTHENTICATED',
+ details: [
+ {
+ '@type': 'type.googleapis.com/google.rpc.ErrorInfo',
+ reason: 'CREDENTIALS_MISSING',
+ domain: 'googleapis.com',
+ metadata: {
+ method: 'google.ads.xfa.op.v4.DfareportingConversions.Batchinsert',
+ service: 'googleapis.com',
+ },
+ },
+ ],
+ },
+ },
+ status: 401,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'REFRESH_TOKEN',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_oauth_scenario_2',
+ name: 'campaign_manager',
+ 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://googleapis.com/test_url_for_access_token_scope_insufficient',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: Request had insufficient authentication scopes. during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: {
+ error: {
+ code: 403,
+ message: 'Request had insufficient authentication scopes.',
+ errors: [
+ {
+ message: 'Insufficient Permission',
+ domain: 'global',
+ reason: 'insufficientPermissions',
+ },
+ ],
+ status: 'PERMISSION_DENIED',
+ details: [
+ {
+ '@type': 'type.googleapis.com/google.rpc.ErrorInfo',
+ reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
+ domain: 'googleapis.com',
+ metadata: {
+ service: 'gmail.googleapis.com',
+ method: 'caribou.api.proto.MailboxService.GetProfile',
+ },
+ },
+ ],
+ },
+ },
+ status: 403,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'AUTH_STATUS_INACTIVE',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_oauth_scenario_3',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant 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://googleapis.com/test_url_for_invalid_grant',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: invalid_grant during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: {
+ error: {
+ code: 403,
+ message: 'invalid_grant',
+ error_description: 'Bad accesss',
+ },
+ },
+ status: 403,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'AUTH_STATUS_INACTIVE',
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_oauth_scenario_4',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination',
+ successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE',
+ scenario: 'Oauth',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ ...commonRequestParameters,
+ endpoint: 'https://googleapis.com/test_url_for_refresh_error',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: {
+ error: 'unauthorized',
+ error_description: 'Access token expired: 2020-10-20T12:00:00.000Z',
+ },
+ status: 401,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'REFRESH_TOKEN',
+ },
+ },
+ },
+ },
+ },
+];
+
+export const v1oauthScenarios: ProxyV1TestData[] = [
+ {
+ id: 'cm360_v1_oauth_scenario_1',
+ name: 'campaign_manager',
+ 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://googleapis.com/test_url_for_credentials_missing',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ response: [
+ {
+ error:
+ '{"error":{"code":401,"message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","errors":[{"message":"Login Required.","domain":"global","reason":"required","location":"Authorization","locationType":"header"}],"status":"UNAUTHENTICATED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"CREDENTIALS_MISSING","domain":"googleapis.com","metadata":{"method":"google.ads.xfa.op.v4.DfareportingConversions.Batchinsert","service":"googleapis.com"}}]}}',
+ statusCode: 401,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'REFRESH_TOKEN',
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 401,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_oauth_scenario_2',
+ name: 'campaign_manager',
+ 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://googleapis.com/test_url_for_access_token_scope_insufficient',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 403,
+ body: {
+ output: {
+ response: [
+ {
+ error:
+ '{"error":{"code":403,"message":"Request had insufficient authentication scopes.","errors":[{"message":"Insufficient Permission","domain":"global","reason":"insufficientPermissions"}],"status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"ACCESS_TOKEN_SCOPE_INSUFFICIENT","domain":"googleapis.com","metadata":{"service":"gmail.googleapis.com","method":"caribou.api.proto.MailboxService.GetProfile"}}]}}',
+ statusCode: 403,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'AUTH_STATUS_INACTIVE',
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 403,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_oauth_scenario_3',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError invalid_grant 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://googleapis.com/test_url_for_invalid_grant',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 403,
+ body: {
+ output: {
+ response: [
+ {
+ error:
+ '{"error":{"code":403,"message":"invalid_grant","error_description":"Bad accesss"}}',
+ statusCode: 403,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'AUTH_STATUS_INACTIVE',
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 403,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_oauth_scenario_4',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v1 API] :: Oauth where google.auth.exceptions.RefreshError refresh error as mock response from destination',
+ successCriteria: 'Should return 500 with authErrorCategory as AUTH_STATUS_INACTIVE',
+ scenario: 'Oauth',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ ...commonRequestParameters,
+ endpoint: 'https://googleapis.com/test_url_for_refresh_error',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ response: [
+ {
+ error:
+ '{"error":"unauthorized","error_description":"Access token expired: 2020-10-20T12:00:00.000Z"}',
+ statusCode: 401,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'aborted',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ authErrorCategory: 'REFRESH_TOKEN',
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 401,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/campaign_manager/dataDelivery/other.ts b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts
new file mode 100644
index 0000000000..1c0c45728c
--- /dev/null
+++ b/test/integrations/destinations/campaign_manager/dataDelivery/other.ts
@@ -0,0 +1,529 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV0Payload, generateProxyV1Payload } from '../../../testUtils';
+
+export const otherScenariosV0 = [
+ {
+ id: 'cm360_v0_other_scenario_1',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_service_not_available',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: Service Unavailable during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: {
+ 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.',
+ },
+ },
+ status: 503,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_other_scenario_2',
+ name: 'campaign_manager',
+ description: '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_internal_server_error',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: 'Internal Server Error',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_other_scenario_3',
+ name: 'campaign_manager',
+ description: '[Proxy v0 API] :: Scenario for testing Gateway Time Out error from destination',
+ successCriteria: 'Should return 500 status code with error message',
+ scenario: 'Framework',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_gateway_time_out',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: 'Gateway Timeout',
+ status: 504,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_other_scenario_4',
+ name: 'campaign_manager',
+ description: '[Proxy v0 API] :: Scenario for testing null response from destination',
+ successCriteria: 'Should return 500 status code with error message',
+ scenario: 'Framework',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_null_response',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: '',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v0_other_scenario_5',
+ name: 'campaign_manager',
+ description:
+ '[Proxy v0 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: 'v0',
+ input: {
+ request: {
+ body: generateProxyV0Payload({
+ endpoint: 'https://random_test_url/test_for_null_and_no_status',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ status: 500,
+ message:
+ 'Campaign Manager: undefined during CAMPAIGN_MANAGER response transformation 3',
+ destinationResponse: {
+ response: '',
+ status: 500,
+ },
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ },
+ },
+ },
+ },
+ },
+];
+
+export const otherScenariosV1: ProxyV1TestData[] = [
+ {
+ id: 'cm360_v1_other_scenario_1',
+ name: 'campaign_manager',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 503,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_other_scenario_2',
+ name: 'campaign_manager',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_other_scenario_3',
+ name: 'campaign_manager',
+ description: '[Proxy v1 API] :: Scenario for testing Gateway Time Out 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_gateway_time_out',
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ response: [
+ {
+ error: '"Gateway Timeout"',
+ statusCode: 504,
+ metadata: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 504,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_other_scenario_4',
+ name: 'campaign_manager',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'cm360_v1_other_scenario_5',
+ name: 'campaign_manager',
+ 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: {
+ jobId: 1,
+ attemptNum: 1,
+ userId: 'default-userId',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ sourceId: 'default-sourceId',
+ secret: {
+ accessToken: 'default-accessToken',
+ },
+ dontBatch: false,
+ },
+ },
+ ],
+ statTags: {
+ errorCategory: 'network',
+ errorType: 'retryable',
+ destType: 'CAMPAIGN_MANAGER',
+ module: 'destination',
+ implementation: 'native',
+ feature: 'dataDelivery',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ },
+ message:
+ 'Campaign Manager: Error transformer proxy v1 during CAMPAIGN_MANAGER response transformation',
+ status: 500,
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/campaign_manager/network.ts b/test/integrations/destinations/campaign_manager/network.ts
index ddecbaf8fa..b7c2301248 100644
--- a/test/integrations/destinations/campaign_manager/network.ts
+++ b/test/integrations/destinations/campaign_manager/network.ts
@@ -1,49 +1,70 @@
-const Data = [
+const commonHeaders = {
+ Authorization: 'Bearer dummyApiKey',
+ 'Content-Type': 'application/json',
+};
+
+const encryptionInfo = {
+ kind: 'dfareporting#encryptionInfo',
+ encryptionSource: 'AD_SERVING',
+ encryptionEntityId: '3564523',
+ encryptionEntityType: 'DCM_ACCOUNT',
+};
+
+const testConversion1 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 7,
+ gclid: '123',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const testConversion2 = {
+ timestampMicros: '1668624722000000',
+ floodlightConfigurationId: '213123123',
+ ordinal: '1',
+ floodlightActivityId: '456543345245',
+ value: 8,
+ gclid: '321',
+ limitAdTracking: true,
+ childDirectedTreatment: true,
+};
+
+const commonRequestParameters = {
+ headers: commonHeaders,
+ JSON: {
+ kind: 'dfareporting#conversionsBatchInsertRequest',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
+ },
+};
+
+// MOCK DATA
+const businessMockData = [
{
+ description: 'Mock response from destination depicting a valid request',
httpReq: {
method: 'post',
- url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437689/conversions/batchinsert',
+ url: 'https://dfareporting.googleapis.com/test_url_for_valid_request',
data: {
kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
},
+ headers: commonHeaders,
},
httpRes: {
data: {
hasFailures: false,
status: [
{
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
+ conversion: testConversion1,
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: testConversion2,
kind: 'dfareporting#conversionStatus',
},
],
@@ -54,50 +75,28 @@ const Data = [
},
},
{
+ description:
+ 'Mock response from destination depicting a request with 1 valid and 1 invalid conversion',
httpReq: {
method: 'post',
- url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437690/conversions/batchinsert',
+ url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_conversion_2',
data: {
kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
},
+ headers: commonHeaders,
},
httpRes: {
data: {
hasFailures: true,
status: [
{
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
+ conversion: testConversion1,
+ kind: 'dfareporting#conversionStatus',
+ },
+ {
+ conversion: testConversion2,
errors: [
{
code: 'NOT_FOUND',
@@ -115,185 +114,37 @@ const Data = [
},
},
{
+ description: 'Mock response from destination depicting a request with 2 invalid conversions',
httpReq: {
method: 'post',
- url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/43770/conversions/batchinsert',
- data: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- },
- httpRes: {
- data: {
- hasFailures: false,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- statusText: 'OK',
- },
- },
- {
- httpReq: {
- method: 'post',
- url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437692/conversions/batchinsert',
+ url: 'https://dfareporting.googleapis.com/test_url_for_invalid_request_both_conversions',
data: {
kind: 'dfareporting#conversionsBatchInsertRequest',
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 8,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
+ encryptionInfo,
+ conversions: [testConversion1, testConversion2],
},
+ headers: commonHeaders,
},
httpRes: {
data: {
hasFailures: true,
status: [
{
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
+ conversion: testConversion1,
errors: [
{
code: 'INVALID_ARGUMENT',
- message: 'Floodlight config id: 213123123 was not found.',
+ message: 'Gclid is not valid.',
kind: 'dfareporting#conversionError',
},
],
kind: 'dfareporting#conversionStatus',
},
{
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 8,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- kind: 'dfareporting#conversionStatus',
- },
- ],
- kind: 'dfareporting#conversionsBatchInsertResponse',
- },
- status: 200,
- statusText: 'OK',
- },
- },
- {
- httpReq: {
- method: 'post',
- url: 'https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/437691/conversions/batchinsert',
- data: {
- kind: 'dfareporting#conversionsBatchInsertRequest',
- encryptionInfo: {
- kind: 'dfareporting#encryptionInfo',
- encryptionSource: 'AD_SERVING',
- encryptionEntityId: '3564523',
- encryptionEntityType: 'DCM_ACCOUNT',
- },
- conversions: [
- {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
- ],
- },
- headers: {
- Authorization: 'Bearer dummyApiKey',
- 'Content-Type': 'application/json',
- },
- },
- httpRes: {
- data: {
- hasFailures: true,
- status: [
- {
- conversion: {
- timestampMicros: '1668624722000000',
- floodlightConfigurationId: '213123123',
- ordinal: '1',
- floodlightActivityId: '456543345245',
- value: 7,
- gclid: '123',
- limitAdTracking: true,
- childDirectedTreatment: true,
- },
+ conversion: testConversion2,
errors: [
{
- code: 'INVALID_ARGUMENT',
+ code: 'NOT_FOUND',
message: 'Floodlight config id: 213123123 was not found.',
kind: 'dfareporting#conversionError',
},
@@ -308,4 +159,5 @@ const Data = [
},
},
];
-export const networkCallsData = [...Data];
+
+export const networkCallsData = [...businessMockData];
diff --git a/test/integrations/destinations/clevertap/dataDelivery/business.ts b/test/integrations/destinations/clevertap/dataDelivery/business.ts
new file mode 100644
index 0000000000..d9f83f52f3
--- /dev/null
+++ b/test/integrations/destinations/clevertap/dataDelivery/business.ts
@@ -0,0 +1,284 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateMetadata, generateProxyV1Payload } from '../../../testUtils';
+
+const params = {
+ destination: 'clevertap',
+};
+const headers = {
+ 'X-CleverTap-Account-Id': '476550467',
+ 'X-CleverTap-Passcode':
+ 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1',
+ 'Content-Type': 'application/json',
+};
+
+const statTags = {
+ destType: 'CLEVERTAP',
+ destinationId: 'default-destinationId',
+ errorCategory: 'network',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ workspaceId: 'default-workspaceId',
+};
+
+export const V1BusinessTestScenarion: ProxyV1TestData[] = [
+ {
+ id: 'clevertap_business_0',
+ scenario: 'business',
+ successCriteria: 'should return 200 status code with success message',
+ name: 'clevertap',
+ description: '[business]:: create an user through identify call',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ params,
+ headers,
+ JSON: {
+ d: [
+ {
+ type: 'profile',
+ profileData: {
+ Email: 'jamesDoe@gmail.com',
+ Name: 'James Doe',
+ Phone: '92374162212',
+ Gender: 'M',
+ Employed: true,
+ DOB: '1614775793',
+ Education: 'Science',
+ Married: 'Y',
+ 'Customer Type': 'Prime',
+ graduate: true,
+ msg_push: true,
+ 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":""}',
+ },
+ identity: 'anon_id',
+ },
+ ],
+ },
+ endpoint: 'https://api.clevertap.com/1/upload/test1',
+ },
+ [generateMetadata(123)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ response: [
+ {
+ metadata: generateMetadata(123),
+ error: '{"status":"success","processed":1,"unprocessed":[]}',
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'clevertap_business_1',
+ scenario: 'business',
+ successCriteria: 'should return 401 status code with error message',
+ name: 'clevertap',
+ description: '[business]:: event failed due to invalid credentials',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ params,
+ headers: {
+ 'X-CleverTap-Account-Id': 'fakeId123',
+ 'X-CleverTap-Passcode': 'fakePasscode123',
+ 'Content-Type': 'application/json',
+ },
+ JSON: {
+ d: [
+ {
+ identity: 'anon-id-new',
+ type: 'event',
+ evtName: 'Web Page Viewed: Rudder',
+ evtData: {
+ title: 'Home',
+ path: '/',
+ },
+ },
+ ],
+ },
+ endpoint: 'https://api.clevertap.com/1/upload/test2',
+ },
+ [generateMetadata(123)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 401,
+ message: 'Request failed with status: 401',
+ response: [
+ {
+ metadata: generateMetadata(123),
+ error: '{"status":"fail","error":"Invalid Credentials","code":401}',
+ statusCode: 401,
+ },
+ ],
+ statTags,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'clevertap_business_2',
+ scenario: 'business',
+ successCriteria: 'should return 401 status code with error message',
+ name: 'clevertap',
+ description: '[business]:: event failed due to invalid credentials',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ params,
+ headers: {
+ 'X-CleverTap-Account-Id': '476550467',
+ 'X-CleverTap-Passcode':
+ 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1',
+ 'Content-Type': 'application/json',
+ },
+ JSON: {
+ d: [
+ {
+ identity: 'anon-id-new',
+ type: 'event',
+ evtData: {
+ title: 'Home',
+ path: '/',
+ },
+ },
+ ],
+ },
+ endpoint: 'https://api.clevertap.com/1/upload/test3',
+ },
+ [generateMetadata(123)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Request failed with status: 200',
+ response: [
+ {
+ metadata: generateMetadata(123),
+ error: '{"status":"fail","processed":0,"unprocessed":[]}',
+ statusCode: 400,
+ },
+ ],
+ statTags,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'clevertap_business_3',
+ scenario: 'business',
+ successCriteria: 'should return 200 status code with success message',
+ name: 'clevertap',
+ description: '[business]:: create an user through identify call',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ params,
+ headers,
+ JSON: {
+ d: [
+ {
+ identity: 'testUser1',
+ type: 'profile',
+ profileData: {
+ Name: 'Test User1',
+ Email: 'test1@testMail.com',
+ },
+ },
+ {
+ evtData: {
+ name: 1234,
+ revenue: 4.99,
+ },
+ type: 'event',
+ identity: 'user123',
+ },
+ {
+ identity: 'testUser2',
+ type: 'profile',
+ profileData: {
+ Name: 'Test User2',
+ Email: 'test2@testMail.com',
+ },
+ },
+ ],
+ },
+ endpoint: 'https://api.clevertap.com/1/upload/test4',
+ },
+ [generateMetadata(123)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ statTags,
+ status: 400,
+ message: 'Request failed with status: 200',
+ response: [
+ {
+ metadata: generateMetadata(123),
+ error:
+ '{"status":"partial","processed":2,"unprocessed":[{"status":"fail","code":509,"error":"Event Name is incorrect. ErrorCode: 509 - Event name is mandatory. Skipped record number : 2","record":{"evtData":{"name":1234,"revenue":4.99},"type":"event","identity":"user123"}}]}',
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/clevertap/dataDelivery/data.ts b/test/integrations/destinations/clevertap/dataDelivery/data.ts
index 8032dd50c8..57e0d0ceea 100644
--- a/test/integrations/destinations/clevertap/dataDelivery/data.ts
+++ b/test/integrations/destinations/clevertap/dataDelivery/data.ts
@@ -1,4 +1,5 @@
-export const data = [
+import { V1BusinessTestScenarion } from './business';
+const oldV0TestCases = [
{
name: 'clevertap',
description: 'Test 0',
@@ -228,3 +229,5 @@ export const data = [
},
},
];
+
+export const data = [...oldV0TestCases, ...V1BusinessTestScenarion];
diff --git a/test/integrations/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts
index c4eb23ee39..9122ba1129 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,
},
},
{
@@ -86,6 +87,35 @@ const dataDeliveryMocksData = [
},
httpRes: { data: { status: 'fail', processed: 0, unprocessed: [] }, status: 200 },
},
+ {
+ httpReq: {
+ url: 'https://api.clevertap.com/1/upload/test4',
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ status: 'partial',
+ processed: 2,
+ unprocessed: [
+ {
+ status: 'fail',
+ code: 509,
+ error:
+ 'Event Name is incorrect. ErrorCode: 509 - Event name is mandatory. Skipped record number : 2',
+ record: {
+ evtData: {
+ name: 1234,
+ revenue: 4.99,
+ },
+ type: 'event',
+ identity: 'user123',
+ },
+ },
+ ],
+ },
+ status: 200,
+ },
+ },
];
const deleteNwData = [
{
diff --git a/test/integrations/destinations/clevertap/processor/data.ts b/test/integrations/destinations/clevertap/processor/data.ts
index d79ebaa8da..1d7bdd7e78 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,135 @@ export const data = [
msgSms: true,
msgemail: true,
msgwhatsapp: false,
+ 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',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clevertap',
+ description: 'Should not load email from externalId',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ passcode: 'sample_passcode',
+ accountId: '476550467',
+ trackAnonymous: true,
+ enableObjectIdMapping: false,
+ },
+ },
+ message: {
+ channel: 'web',
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ anonymousId: 'anon_id',
+ type: 'identify',
+ traits: {
+ anonymousId: 'anon_id',
+ name: 'James Doe',
+ phone: '92374162212',
+ gender: 'M',
+ employed: true,
+ birthday: '1614775793',
+ education: 'Science',
+ graduate: true,
+ married: true,
+ customerType: 'Prime',
+ msg_push: true,
+ 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: '',
+ },
+ 'category-unsubscribe': { email: ['Marketing', 'Transactional'] },
+ },
+ context: {
+ externalId: [{ type: 'someId', id: 'someID' }],
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.clevertap.com/1/upload',
+ headers: {
+ 'X-CleverTap-Account-Id': '476550467',
+ 'X-CleverTap-Passcode': 'sample_passcode',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ d: [
+ {
+ type: 'profile',
+ profileData: {
+ Name: 'James Doe',
+ Phone: '92374162212',
+ Gender: 'M',
+ Employed: true,
+ DOB: '1614775793',
+ Education: 'Science',
+ Married: true,
+ 'Customer Type': 'Prime',
+ graduate: true,
+ msg_push: true,
+ msgSms: true,
+ msgemail: true,
+ msgwhatsapp: false,
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 +368,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 +1094,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 +1237,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 +1818,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 +2203,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 +2356,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 4f1723a7da..5f25bbe83e 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 1a26209923..2cb7cde34f 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/criteo_audience/dataDelivery/business.ts b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts
new file mode 100644
index 0000000000..f30bf73d7a
--- /dev/null
+++ b/test/integrations/destinations/criteo_audience/dataDelivery/business.ts
@@ -0,0 +1,255 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { generateProxyV1Payload, generateMetadata } from '../../../testUtils';
+export const headers = {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+};
+export const params = {
+ destination: 'criteo_audience',
+};
+const method = 'PATCH';
+
+export const V1BusinessTestScenarion: ProxyV1TestData[] = [
+ {
+ id: 'criteo_audience_business_0',
+ name: 'criteo_audience',
+ description: '[Business]:: Test for gum type audience with gumCallerId with success response',
+ successCriteria: 'Should return a 200 status code with a success message',
+ scenario: 'business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'remove',
+ identifierType: 'gum',
+ identifiers: ['sample_gum3'],
+ internalIdentifiers: false,
+ gumCallerId: '329739',
+ },
+ },
+ },
+ params,
+ headers,
+ method,
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist',
+ },
+ [generateMetadata(1)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ response: [
+ {
+ error: '""',
+ metadata: generateMetadata(1),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_business_1',
+ name: 'criteo_audience',
+ scenario: 'business',
+ description: '[Business]:: Test for email type audience to add users with success response',
+ successCriteria: 'Should return a 200 status code with a success message',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ method: 'POST',
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'email',
+ internalIdentifiers: false,
+ identifiers: [
+ 'alex@email.com',
+ 'amy@email.com',
+ 'van@email.com',
+ 'alex@email.com',
+ 'amy@email.com',
+ 'van@email.com',
+ ],
+ },
+ },
+ },
+ params,
+ headers,
+ method,
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist',
+ },
+ [generateMetadata(2)],
+ ),
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ response: [
+ {
+ error: '""',
+ metadata: generateMetadata(2),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_business_2',
+ name: 'criteo_audience',
+ scenario: 'business',
+ description: '[Business]:: Test for mobile type audience to remove users with success response',
+ successCriteria: 'Should return a 200 status code with a success message',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ method: 'POST',
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'remove',
+ identifierType: 'madid',
+ internalIdentifiers: false,
+ identifiers: [
+ 'sample_madid',
+ 'sample_madid_1',
+ 'sample_madid_2',
+ 'sample_madid_10',
+ 'sample_madid_13',
+ 'sample_madid_11',
+ 'sample_madid_12',
+ ],
+ },
+ },
+ },
+ params,
+ headers,
+ method,
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34893/contactlist',
+ },
+ [generateMetadata(3)],
+ ),
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ response: [
+ {
+ error: '""',
+ metadata: generateMetadata(3),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_business_3',
+ name: 'criteo_audience',
+ scenario: 'business',
+ description: '[Business]:: Test for mobile type audience where audienceId is invalid',
+ successCriteria:
+ 'Should return a 400 status code with an error audience-invalid. It should also have the invalid audienceId in the error message as follows: "Audience is invalid"',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ method: 'POST',
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ params,
+ headers,
+ method,
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist',
+ },
+ [generateMetadata(4)],
+ ),
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'AudienceId is Invalid. Please Provide Valid AudienceId',
+ response: [
+ {
+ error:
+ '{"errors":[{"traceIdentifier":"80a1a0ba3981b04da847d05700752c77","type":"authorization","code":"audience-invalid"}]}',
+ metadata: generateMetadata(4),
+ statusCode: 400,
+ },
+ ],
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ meta: 'instrumentation',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts
index fb5b689a96..c603ef6664 100644
--- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts
+++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts
@@ -1,4 +1,9 @@
-export const data = [
+import { generateMetadata } from '../../../testUtils';
+import { V1BusinessTestScenarion } from './business';
+import { v1OauthScenarios } from './oauth';
+import { v1OtherScenarios } from './other';
+
+const v0testCases = [
{
name: 'criteo_audience',
description: 'Test 0',
@@ -38,6 +43,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(1),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -70,7 +78,7 @@ export const data = [
version: '1',
type: 'REST',
method: 'PATCH',
- endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken',
headers: {
Authorization: 'Bearer success_access_token',
'Content-Type': 'application/json',
@@ -96,6 +104,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(2),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -123,8 +134,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
errorType: 'aborted',
feature: 'dataDelivery',
implementation: 'native',
@@ -147,7 +158,7 @@ export const data = [
version: '1',
type: 'REST',
method: 'PATCH',
- endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken',
headers: {
Authorization: 'Bearer success_access_token',
'Content-Type': 'application/json',
@@ -173,6 +184,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(3),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -200,8 +214,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
errorType: 'aborted',
feature: 'dataDelivery',
implementation: 'native',
@@ -250,6 +264,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(4),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -275,8 +292,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
errorType: 'aborted',
feature: 'dataDelivery',
implementation: 'native',
@@ -327,6 +344,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(5),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -352,8 +372,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
feature: 'dataDelivery',
implementation: 'native',
errorType: 'retryable',
@@ -403,6 +423,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(6),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -421,8 +444,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
errorType: 'throttled',
feature: 'dataDelivery',
implementation: 'native',
@@ -472,6 +495,9 @@ export const data = [
params: {
destination: 'criteo_audience',
},
+ userId: '1234',
+ metadata: generateMetadata(7),
+ destinationConfig: {},
},
method: 'POST',
},
@@ -492,8 +518,8 @@ export const data = [
statTags: {
destType: 'CRITEO_AUDIENCE',
errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
errorType: 'aborted',
feature: 'dataDelivery',
implementation: 'native',
@@ -506,3 +532,10 @@ export const data = [
},
},
];
+
+export const data = [
+ ...v0testCases,
+ ...V1BusinessTestScenarion,
+ ...v1OauthScenarios,
+ ...v1OtherScenarios,
+];
diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts
new file mode 100644
index 0000000000..982397f7c3
--- /dev/null
+++ b/test/integrations/destinations/criteo_audience/dataDelivery/oauth.ts
@@ -0,0 +1,133 @@
+import { params, headers } from './business';
+import { generateProxyV1Payload, generateMetadata } from '../../../testUtils';
+
+const commonStatTags = {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+};
+
+export const v1OauthScenarios = [
+ {
+ id: 'criteo_audience_oauth_0',
+ name: 'criteo_audience',
+ description: '[OAUTH]:: Test expired access token',
+ successCriteria: 'Should return a 401 status code with authErrorCategory as REFRESH_TOKEN',
+ scenario: 'oauth',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ params,
+ headers,
+ method: 'PATCH',
+ endpoint:
+ 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken',
+ },
+ [generateMetadata(1)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ status: 401,
+ authErrorCategory: 'REFRESH_TOKEN',
+ response: [
+ {
+ error:
+ 'The authorization token has expired during criteo_audience response transformation',
+ metadata: generateMetadata(1),
+ statusCode: 401,
+ },
+ ],
+ message:
+ 'The authorization token has expired during criteo_audience response transformation',
+ statTags: commonStatTags,
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_oauth_1',
+ name: 'criteo_audience',
+ description: '[OAUTH]:: Test invalid access token',
+ successCriteria:
+ 'We should get a 401 status code with errorCode authorization-token-invalid. As we need to refresh the token for these conditions, authErrorCategory should be REFRESH_TOKEN',
+ scenario: 'oauth',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ params,
+ headers,
+ method: 'PATCH',
+ endpoint:
+ 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken',
+ },
+ [generateMetadata(2)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ status: 401,
+ authErrorCategory: 'REFRESH_TOKEN',
+ response: [
+ {
+ error:
+ 'The authorization header is invalid during criteo_audience response transformation',
+ metadata: generateMetadata(2),
+ statusCode: 401,
+ },
+ ],
+ statTags: commonStatTags,
+ message:
+ 'The authorization header is invalid during criteo_audience response transformation',
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/criteo_audience/dataDelivery/other.ts b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts
new file mode 100644
index 0000000000..145be62528
--- /dev/null
+++ b/test/integrations/destinations/criteo_audience/dataDelivery/other.ts
@@ -0,0 +1,197 @@
+import { ProxyV1TestData } from '../../../testTypes';
+import { params, headers } from './business';
+import { generateProxyV1Payload, generateMetadata } from '../../../testUtils';
+
+export const v1OtherScenarios: ProxyV1TestData[] = [
+ {
+ id: 'criteo_audience_other_0',
+ name: 'criteo_audience',
+ description: '[Other]:: Test for checking service unavailable scenario',
+ successCriteria: 'Should return a 500 status code with',
+ scenario: 'other',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ headers,
+ params,
+ method: 'PATCH',
+ endpoint: 'https://random_test_url/test_for_internal_server_error',
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ },
+ [generateMetadata(1)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 500,
+ response: [
+ {
+ error: '""',
+ metadata: generateMetadata(1),
+ statusCode: 500,
+ },
+ ],
+ message: 'Request Failed: during criteo_audience response transformation (Retryable)',
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ errorType: 'retryable',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_other_1',
+ name: 'criteo_audience',
+ description: '[Other]:: Test for checking throttling scenario',
+ successCriteria: 'Should return a 429 status code',
+ scenario: 'other',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ headers,
+ params,
+ method: 'PATCH',
+ endpoint: 'https://random_test_url/test_for_too_many_requests',
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ },
+ [generateMetadata(2)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 429,
+ response: [
+ {
+ error: '{}',
+ metadata: generateMetadata(2),
+ statusCode: 429,
+ },
+ ],
+ message:
+ 'Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)',
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ errorType: 'throttled',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'criteo_audience_other_2',
+ name: 'criteo_audience',
+ description: '[Other]:: Test for checking unknown error scenario',
+ successCriteria: 'Should return a 410 status code and abort the event',
+ scenario: 'other',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload(
+ {
+ headers,
+ params,
+ method: 'PATCH',
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+ },
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist',
+ },
+ [generateMetadata(3)],
+ ),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ response: [
+ {
+ error: '{"message":"unknown error"}',
+ metadata: generateMetadata(3),
+ statusCode: 400,
+ },
+ ],
+ message:
+ 'Request Failed: during criteo_audience response transformation with status "410" due to "{"message":"unknown error"}", (Aborted) ',
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/criteo_audience/network.ts b/test/integrations/destinations/criteo_audience/network.ts
index 959e8a2112..7ccf649e2a 100644
--- a/test/integrations/destinations/criteo_audience/network.ts
+++ b/test/integrations/destinations/criteo_audience/network.ts
@@ -1,3 +1,23 @@
+const headers = {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ 'User-Agent': 'RudderLabs',
+};
+const params = { destination: 'criteo_audience' };
+const method = 'PATCH';
+const commonData = {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
+};
+
export const networkCallsData = [
{
httpReq: {
@@ -14,117 +34,74 @@ export const networkCallsData = [
},
},
},
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
+ params,
+ headers,
+ method,
},
httpRes: { status: 200 },
},
{
httpReq: {
- url: 'https://api.criteo.com/2022-10/audiences/3485/contactlist',
+ url: 'https://api.criteo.com/2022-10/audiences/34894/contactlist',
data: {
data: {
type: 'ContactlistAmendment',
attributes: {
operation: 'add',
- identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ identifierType: 'email',
internalIdentifiers: false,
+ identifiers: [
+ 'alex@email.com',
+ 'amy@email.com',
+ 'van@email.com',
+ 'alex@email.com',
+ 'amy@email.com',
+ 'van@email.com',
+ ],
},
},
},
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
- },
- httpRes: {
- code: '400',
- data: {
- errors: [
- {
- traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
- type: 'authorization',
- code: 'authorization-token-expired',
- instance: '/2022-10/audiences/123/contactlist',
- title: 'The authorization token has expired',
- },
- ],
- },
- status: 401,
+ params,
+ headers,
+ method,
},
+ httpRes: { status: 200 },
},
{
httpReq: {
- url: 'https://api.criteo.com/2022-10/audiences/34895/contactlist',
+ url: 'https://api.criteo.com/2022-10/audiences/34893/contactlist',
data: {
data: {
type: 'ContactlistAmendment',
attributes: {
- operation: 'add',
+ operation: 'remove',
identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
internalIdentifiers: false,
+ identifiers: [
+ 'sample_madid',
+ 'sample_madid_1',
+ 'sample_madid_2',
+ 'sample_madid_10',
+ 'sample_madid_13',
+ 'sample_madid_11',
+ 'sample_madid_12',
+ ],
},
},
},
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
- },
- httpRes: {
- code: '400',
- data: {
- errors: [
- {
- traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
- type: 'authorization',
- code: 'authorization-token-invalid',
- instance: '/2022-10/audiences/123/contactlist',
- title: 'The authorization header is invalid',
- },
- ],
- },
- status: 401,
+ params,
+ headers,
+ method,
},
+ httpRes: { status: 200 },
},
{
httpReq: {
url: 'https://api.criteo.com/2022-10/audiences/34896/contactlist',
- data: {
- data: {
- type: 'ContactlistAmendment',
- attributes: {
- operation: 'add',
- identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
- internalIdentifiers: false,
- },
- },
- },
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
+ data: commonData,
+ params,
+ headers,
+ method,
},
httpRes: {
code: '400',
@@ -143,25 +120,10 @@ export const networkCallsData = [
{
httpReq: {
url: 'https://api.criteo.com/2022-10/audiences/34897/contactlist',
- data: {
- data: {
- type: 'ContactlistAmendment',
- attributes: {
- operation: 'add',
- identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
- internalIdentifiers: false,
- },
- },
- },
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
+ data: commonData,
+ params,
+ headers,
+ method,
},
httpRes: {
code: '500',
@@ -180,50 +142,20 @@ export const networkCallsData = [
{
httpReq: {
url: 'https://api.criteo.com/2022-10/audiences/34898/contactlist',
- data: {
- data: {
- type: 'ContactlistAmendment',
- attributes: {
- operation: 'add',
- identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
- internalIdentifiers: false,
- },
- },
- },
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
+ data: commonData,
+ params,
+ headers,
+ method,
},
httpRes: { code: '429', data: {}, status: 429 },
},
{
httpReq: {
url: 'https://api.criteo.com/2022-10/audiences/34899/contactlist',
- data: {
- data: {
- type: 'ContactlistAmendment',
- attributes: {
- operation: 'add',
- identifierType: 'madid',
- identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
- internalIdentifiers: false,
- },
- },
- },
- params: { destination: 'criteo_audience' },
- headers: {
- Authorization: 'Bearer success_access_token',
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- 'User-Agent': 'RudderLabs',
- },
- method: 'PATCH',
+ data: commonData,
+ params,
+ headers,
+ method,
},
httpRes: { code: '400', data: { message: 'unknown error' }, status: 410 },
},
diff --git a/test/integrations/destinations/custify/deleteUsers/data.ts b/test/integrations/destinations/custify/deleteUsers/data.ts
new file mode 100644
index 0000000000..22a120770a
--- /dev/null
+++ b/test/integrations/destinations/custify/deleteUsers/data.ts
@@ -0,0 +1,158 @@
+const destType = 'custify';
+const commonData = {
+ name: destType,
+ feature: 'userDeletion',
+ module: 'destination',
+ version: 'v0',
+};
+
+export const data = [
+ {
+ description: 'Test 0: should fail when config is not being sent',
+ input: {
+ request: {
+ body: [
+ {
+ destType: destType.toUpperCase(),
+ userAttributes: [
+ {
+ userId: 'rudder1',
+ },
+ ],
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ body: [
+ {
+ statusCode: 400,
+ error: 'Config for deletion not present',
+ },
+ ],
+ },
+ },
+ },
+ {
+ description: 'Test 1: should fail when apiKey is not present in config',
+ 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',
+ },
+ ],
+ },
+ },
+ },
+
+ {
+ description: 'Test 2: should pass when one of the users is not present in destination',
+ input: {
+ request: {
+ body: [
+ {
+ destType: destType.toUpperCase(),
+ userAttributes: [
+ {
+ userId: 'rudder1',
+ },
+ {
+ userId: 'rudder2',
+ },
+ ],
+ config: {
+ apiKey: 'dummyApiKey',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [{ statusCode: 200, status: 'successful' }],
+ },
+ },
+ },
+
+ {
+ description:
+ 'Test 3: should fail when one of the users is returning with 4xx(not 404) from destination',
+ input: {
+ request: {
+ body: [
+ {
+ destType: destType.toUpperCase(),
+ userAttributes: [
+ {
+ userId: 'rudder1',
+ },
+ {
+ userId: 'rudder3',
+ },
+ ],
+ config: {
+ apiKey: 'dummyApiKey',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ body: [{ statusCode: 400, error: '{"error":"User: rudder3 has a problem"}' }],
+ },
+ },
+ },
+
+ {
+ description: 'Test 4: should fail when one of the userAttributes does not contain `userId`',
+ input: {
+ request: {
+ body: [
+ {
+ destType: destType.toUpperCase(),
+ userAttributes: [
+ {
+ userId: 'rudder1',
+ },
+ {},
+ ],
+ config: {
+ apiKey: 'dummyApiKey',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 400,
+ body: [{ statusCode: 400, error: 'User id for deletion not present' }],
+ },
+ },
+ },
+].map((props) => ({ ...commonData, ...props }));
diff --git a/test/integrations/destinations/custify/network.ts b/test/integrations/destinations/custify/network.ts
index 4af6545f9f..242f54c97b 100644
--- a/test/integrations/destinations/custify/network.ts
+++ b/test/integrations/destinations/custify/network.ts
@@ -1,36 +1,85 @@
export const networkCallsData = [
- {
- httpReq: {
- url: 'https://api.custify.com/company',
- method: 'POST',
+ {
+ httpReq: {
+ url: 'https://api.custify.com/company',
+ method: 'POST',
+ },
+ httpRes: {
+ data: {
+ company_id: '6',
+ name: 'Pizzeria Presto',
+ signed_up_at: '2019-05-30T12:00:00.000Z',
+ size: 15,
+ website: 'www.pizzeriapresto.com',
+ industry: 'Restaurant',
+ plan: 'Platinum',
+ monthly_revenue: 1234567,
+ churned: false,
+ owners_csm: 'john.doe@mail.com',
+ owners_account: 'john.doe@mail.com',
+ parent_companies: [
+ {
+ id: '5ec50c9829d3c17c7cf455f2',
+ },
+ {
+ id: '5ec50c9829d3c17c7cf457f2',
+ },
+ ],
+ custom_attributes: {
+ restaurants: 5,
+ custom: 'template',
},
- httpRes: {
- data: {
- "company_id": "6",
- "name": "Pizzeria Presto",
- "signed_up_at": "2019-05-30T12:00:00.000Z",
- "size": 15,
- "website": "www.pizzeriapresto.com",
- "industry": "Restaurant",
- "plan": "Platinum",
- "monthly_revenue": 1234567,
- "churned": false,
- "owners_csm": "john.doe@mail.com",
- "owners_account": "john.doe@mail.com",
- "parent_companies": [
- {
- "id": "5ec50c9829d3c17c7cf455f2"
- },
- {
- "id": "5ec50c9829d3c17c7cf457f2"
- }
- ],
- "custom_attributes": {
- "restaurants": 5,
- "custom": "template"
- }
- },
- status: 200
- },
- }
+ },
+ status: 200,
+ },
+ },
+
+ {
+ httpReq: {
+ method: 'delete',
+ url: 'https://api.custify.com/people?user_id=rudder1',
+ headers: {
+ Authorization: 'Bearer dummyApiKey',
+ },
+ },
+ httpRes: {
+ data: {
+ msg: 'All users associated with rudder1 were successfully deleted',
+ code: 'Success',
+ params: null,
+ },
+ status: 200,
+ },
+ },
+ {
+ httpReq: {
+ method: 'delete',
+ url: 'https://api.custify.com/people?user_id=rudder2',
+ headers: {
+ Authorization: 'Bearer dummyApiKey',
+ },
+ },
+ httpRes: {
+ data: {
+ error: 'User: rudder2 not found',
+ },
+ status: 404,
+ },
+ },
+
+ {
+ httpReq: {
+ method: 'delete',
+ url: 'https://api.custify.com/people?user_id=rudder3',
+ headers: {
+ Authorization: 'Bearer dummyApiKey',
+ },
+ },
+ httpRes: {
+ data: {
+ error: 'User: rudder3 has a problem',
+ },
+ status: 400,
+ },
+ },
];
diff --git a/test/integrations/destinations/delighted/network.ts b/test/integrations/destinations/delighted/network.ts
index 15b0a414e6..d9896a25e8 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 beb7eb32aa..6eb90942a7 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 0000000000..9ac709978d
--- /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 239fa93a6a..dcc633e1a8 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 0000000000..c6d938c627
--- /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 0000000000..e25cc8e07c
--- /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 05b3a05fd0..a61fa44eab 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/configLevelFeaturesTestData.ts b/test/integrations/destinations/facebook_pixel/processor/configLevelFeaturesTestData.ts
new file mode 100644
index 0000000000..5a7beb4174
--- /dev/null
+++ b/test/integrations/destinations/facebook_pixel/processor/configLevelFeaturesTestData.ts
@@ -0,0 +1,766 @@
+import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+import {
+ overrideDestination,
+ generateTrackPayload,
+ generateMetadata,
+ transformResultBuilder,
+} from '../../../testUtils';
+import { Destination } from '../../../../../src/types';
+import { ProcessorTestData } from '../../../testTypes';
+
+const commonDestination: Destination = {
+ ID: '12335',
+ Name: 'sample-destination',
+ DestinationDefinition: {
+ ID: '123',
+ Name: 'facebook_pixel',
+ DisplayName: 'Facebook Pixel',
+ Config: {},
+ },
+ WorkspaceID: '123',
+ Transformations: [],
+ Config: {
+ limitedDataUSage: true,
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: '',
+ blacklistPiiHash: false,
+ },
+ ],
+ accessToken: '09876',
+ pixelId: 'dummyPixelId',
+ eventsToEvents: [
+ {
+ from: 'ABC Started',
+ to: 'InitiateCheckout',
+ },
+ ],
+ eventCustomProperties: [
+ {
+ eventCustomProperties: '',
+ },
+ ],
+ valueFieldIdentifier: '',
+ advancedMapping: false,
+ whitelistPiiProperties: [
+ {
+ whitelistPiiProperties: 'email',
+ },
+ ],
+ categoryToContent: [
+ {
+ from: 'clothing',
+ to: 'newClothing',
+ },
+ ],
+ },
+ Enabled: true,
+};
+
+const commonUserTraits = {
+ email: 'abc@gmail.com',
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ event_id: '12345',
+};
+
+// the below object has properties that are used as whitelist and blacklist properties in below test cases
+const piiPropertiesForAllowDeny = {
+ email: 'abc@gmail.com',
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ event_id: '12345',
+ firstName: 'John',
+ lastName: 'Doe',
+ whitelistProp1: 'val1',
+ blacklistProp2: 'val2',
+ blacklistProp3: 'val3',
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+};
+
+const commonPropertiesWithoutProductArray = {
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+};
+
+const commonTimestamp = new Date('2023-10-14');
+
+export const configLevelFeaturesTestData: ProcessorTestData[] = [
+ {
+ id: 'facebook_pixel-config-test-1',
+ name: 'facebook_pixel',
+ description:
+ 'config feature : limitedDataUSage switched on. Ref:https://developers.facebook.com/docs/marketing-apis/data-processing-options/#supported-tools-and-apis ',
+ scenario: 'configuration',
+ successCriteria: 'Response should contain limitedDataUSage related fields',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: commonPropertiesWithoutProductArray,
+ context: {
+ traits: commonUserTraits,
+ dataProcessingOptions: ['val1', 'val2', 'val3'],
+ },
+ timestamp: commonTimestamp,
+ }),
+ metadata: generateMetadata(1),
+ destination: commonDestination,
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id:
+ '3ffc8a075f330402d82aa0a86c596b0d2fe70df38b22c5be579f86a18e4aca47',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ data_processing_options: 'val1',
+ data_processing_options_country: 'val2',
+ data_processing_options_state: 'val3',
+ custom_data: {
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['dummy'],
+ content_type: 'product_group',
+ contents: [
+ {
+ id: 'dummy',
+ quantity: 1,
+ },
+ ],
+ content_category: 'dummy',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-2',
+ name: 'facebook_pixel',
+ scenario: 'configuration',
+ description:
+ 'config feature : While categoryToContent mapping is filled up in UI, but category is passed with message.properties as well. message.properties.category should be given priority over categoryToContent mapping',
+ successCriteria: 'Response should contain category mapped to newClothing',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...commonPropertiesWithoutProductArray, category: 'clothing' },
+ context: {
+ traits: commonUserTraits,
+ dataProcessingOptions: ['val1', 'val2', 'val3'],
+ },
+ timestamp: commonTimestamp,
+ }),
+ metadata: generateMetadata(1),
+ destination: commonDestination,
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id:
+ '3ffc8a075f330402d82aa0a86c596b0d2fe70df38b22c5be579f86a18e4aca47',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ data_processing_options: 'val1',
+ data_processing_options_country: 'val2',
+ data_processing_options_state: 'val3',
+ custom_data: {
+ category: 'clothing',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['clothing'],
+ content_type: 'newClothing',
+ contents: [
+ {
+ id: 'clothing',
+ quantity: 1,
+ },
+ ],
+ content_category: 'clothing',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-3',
+ name: 'facebook_pixel',
+ description:
+ 'config feature : ContentCategoryMapping table is filled up, and category is passed with properties along with contentType via integrations object',
+ scenario: 'configuration',
+ successCriteria: 'contentType should be used from integrations object',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...commonPropertiesWithoutProductArray, category: 'clothing' },
+ context: {
+ traits: commonUserTraits,
+ },
+ timestamp: commonTimestamp,
+ integrations: {
+ FacebookPixel: {
+ contentType: 'newClothingFromIntegrationObject',
+ },
+ },
+ }),
+ metadata: generateMetadata(1),
+ destination: commonDestination,
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id:
+ '3ffc8a075f330402d82aa0a86c596b0d2fe70df38b22c5be579f86a18e4aca47',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ custom_data: {
+ category: 'clothing',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['clothing'],
+ content_type: 'newClothingFromIntegrationObject',
+ contents: [
+ {
+ id: 'clothing',
+ quantity: 1,
+ },
+ ],
+ content_category: 'clothing',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-4',
+ name: 'facebook_pixel',
+ description:
+ 'config feature : Config mapped whiteList and blackListed properties with marked hashed within integrations object, along with default pii property email in the properties',
+ scenario: 'configuration',
+ successCriteria:
+ 'BlackListed properties should not be hashed and default pii property should be deleted from the properties',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...piiPropertiesForAllowDeny },
+ context: {
+ traits: commonUserTraits,
+ },
+ timestamp: commonTimestamp,
+ integrations: {
+ FacebookPixel: {
+ hashed: true,
+ },
+ },
+ }),
+ metadata: generateMetadata(1),
+ destination: overrideDestination(commonDestination, {
+ whitelistPiiProperties: [
+ {
+ whitelistPiiProperties: 'whitelistProp1',
+ },
+ ],
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: 'blacklistProp2',
+ },
+ {
+ blacklistPiiProperties: 'blacklistProp3',
+ },
+ ],
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id: 'default-user-id',
+ em: 'abc@gmail.com',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ custom_data: {
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ whitelistProp1: 'val1',
+ blacklistProp2: 'val2',
+ blacklistProp3: 'val3',
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['dummy'],
+ content_type: 'product_group',
+ contents: [
+ {
+ id: 'dummy',
+ quantity: 1,
+ },
+ ],
+ content_category: 'dummy',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-5',
+ name: 'facebook_pixel',
+ description:
+ 'config feature : Config mapped whiteList and blackListed properties without marked hashed within integrations object but marked hashed true from UI, along with default pii property email in the properties',
+ scenario: 'configuration',
+ successCriteria:
+ 'BlackListed properties should be hashed and default pii property should be deleted from the properties',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...piiPropertiesForAllowDeny },
+ context: {
+ traits: commonUserTraits,
+ },
+ timestamp: commonTimestamp,
+ }),
+ metadata: generateMetadata(1),
+ destination: overrideDestination(commonDestination, {
+ whitelistPiiProperties: [
+ {
+ whitelistPiiProperties: 'whitelistProp1',
+ },
+ ],
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: 'blacklistProp2',
+ blacklistPiiHash: true,
+ },
+ {
+ blacklistPiiProperties: 'blacklistProp3',
+ blacklistPiiHash: true,
+ },
+ ],
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id:
+ '3ffc8a075f330402d82aa0a86c596b0d2fe70df38b22c5be579f86a18e4aca47',
+ em: '48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ custom_data: {
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ whitelistProp1: 'val1',
+ blacklistProp2:
+ '528e5290f8ff0eb0325f0472b9c1a9ef4fac0b02ff6094b64d9382af4a10444b',
+ blacklistProp3:
+ 'bac8d4414984861d5199b7a97699c728bee36c4084299b2ca905434cf65d8944',
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['dummy'],
+ content_type: 'product_group',
+ contents: [
+ {
+ id: 'dummy',
+ quantity: 1,
+ },
+ ],
+ content_category: 'dummy',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-6',
+ name: 'facebook_pixel',
+ description:
+ 'config feature : Config mapped whiteList and blackListed properties marked hashed within integrations object but marked hashed true from UI, along with default pii property email in the properties',
+ scenario: 'configuration',
+ successCriteria:
+ 'BlackListed properties should not be hashed again and default pii property should be deleted from the properties',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...piiPropertiesForAllowDeny },
+ context: {
+ traits: commonUserTraits,
+ },
+ timestamp: commonTimestamp,
+ integrations: {
+ FacebookPixel: {
+ hashed: true,
+ },
+ },
+ }),
+ metadata: generateMetadata(1),
+ destination: overrideDestination(commonDestination, {
+ whitelistPiiProperties: [
+ {
+ whitelistPiiProperties: 'whitelistProp1',
+ },
+ ],
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: 'blacklistProp2',
+ blacklistPiiHash: true,
+ },
+ {
+ blacklistPiiProperties: 'blacklistProp3',
+ blacklistPiiHash: true,
+ },
+ ],
+ }),
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id: 'default-user-id',
+ em: 'abc@gmail.com',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ custom_data: {
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ whitelistProp1: 'val1',
+ blacklistProp2: 'val2',
+ blacklistProp3: 'val3',
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_ids: ['dummy'],
+ content_type: 'product_group',
+ contents: [
+ {
+ id: 'dummy',
+ quantity: 1,
+ },
+ ],
+ content_category: 'dummy',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel-config-test-7',
+ name: 'facebook_pixel',
+ description:
+ 'properties.content_type is given priority over populating it from categoryToContent mapping.',
+ scenario: 'configuration',
+ successCriteria: 'Response should contain content_type as product_group and not newClothing',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: generateTrackPayload({
+ event: 'product list viewed',
+ properties: { ...piiPropertiesForAllowDeny, content_type: 'product_group' },
+ context: {
+ traits: commonUserTraits,
+ },
+ timestamp: commonTimestamp,
+ integrations: {
+ FacebookPixel: {
+ hashed: true,
+ },
+ },
+ }),
+ metadata: generateMetadata(1),
+ destination: commonDestination,
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: transformResultBuilder({
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: `https://graph.facebook.com/${VERSION}/dummyPixelId/events?access_token=09876`,
+ headers: {},
+ params: {},
+ FORM: {
+ data: [
+ JSON.stringify({
+ user_data: {
+ external_id: 'default-user-id',
+ em: 'abc@gmail.com',
+ client_user_agent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.16; rv:84.0) Gecko/20100101 Firefox/84.0',
+ },
+ event_name: 'ViewContent',
+ event_time: 1697241600,
+ event_id: '12345',
+ action_source: 'website',
+ custom_data: {
+ email: 'abc@gmail.com',
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ whitelistProp1: 'val1',
+ blacklistProp2: 'val2',
+ blacklistProp3: 'val3',
+ category: 'dummy',
+ quantity: 10,
+ value: 100,
+ product_id: '12345',
+ content_type: 'product_group',
+ content_ids: ['dummy'],
+ contents: [
+ {
+ id: 'dummy',
+ quantity: 1,
+ },
+ ],
+ content_category: 'dummy',
+ currency: 'USD',
+ },
+ }),
+ ],
+ },
+ files: {},
+ userId: '',
+ }),
+ metadata: generateMetadata(1),
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/facebook_pixel/processor/data.ts b/test/integrations/destinations/facebook_pixel/processor/data.ts
index 557bc7066c..6af7e3cd9b 100644
--- a/test/integrations/destinations/facebook_pixel/processor/data.ts
+++ b/test/integrations/destinations/facebook_pixel/processor/data.ts
@@ -1,4 +1,9 @@
-import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+import { identifyTestData } from './identifyTestData';
+import { trackTestData } from './trackTestData';
+import { validationTestData } from './validationTestData';
+import { pageScreenTestData } from './pageScreenTestData';
+import { ecommTestData } from './ecommTestData';
+import { configLevelFeaturesTestData } from './configLevelFeaturesTestData';
export const mockFns = (_) => {
// @ts-ignore
@@ -6,6458 +11,10 @@ export const mockFns = (_) => {
};
export const data = [
- {
- name: 'facebook_pixel',
- description: 'Test 0',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- channel: 'mobile',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: ' aBc@gmail.com ',
- address: {
- zip: 1234,
- },
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T00:00:00.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- removeExternalId: true,
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":{"em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4"},"event_name":"spin_result","event_time":1697221800,"action_source":"app","custom_data":{"additional_bet_index":0,"value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 1',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: ' aBc@gmail.com ',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'group',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: 'Message type group not supported',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 2',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- channel: 'web',
- context: {
- traits: {
- name: 'Test',
- },
- app: {
- build: '1.0.0',
- name: 'RudderLabs JavaScript SDK',
- namespace: 'com.rudderlabs.javascript',
- version: '1.0.0',
- },
- 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,
- },
- },
- properties: {
- plan: 'standard plan',
- name: 'rudder test',
- },
- type: 'identify',
- messageId: '84e26acc-56a5-4835-8233-591137fca468',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '123456',
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T09:03:22.563Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error:
- 'For identify events, "Advanced Mapping" configuration must be enabled on the RudderStack dashboard',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'configuration',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 3',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- channel: 'web',
- context: {
- traits: {
- name: 'Rudder Test',
- email: 'abc@gmail.com',
- firstname: 'Rudder',
- lastname: 'Test',
- phone: 9000000000,
- gender: 'female',
- },
- app: {
- build: '1.0.0',
- name: 'RudderLabs JavaScript SDK',
- namespace: 'com.rudderlabs.javascript',
- version: '1.0.0',
- },
- 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,
- },
- },
- properties: {
- plan: 'standard plan',
- name: 'rudder test',
- },
- type: 'identify',
- messageId: '84e26acc-56a5-4835-8233-591137fca468',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '123456',
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T09:03:22.563Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: true,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","ph":"593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579","ge":"252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111","ln":"532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25","fn":"2c2ccf28d806f6f9a34b67aa874d2113b7ac1444f1a4092541b8b75b84771747","client_ip_address":"0.0.0.0","client_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"},"event_name":"identify","event_time":1697278611,"event_id":"84e26acc-56a5-4835-8233-591137fca468","action_source":"website"}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 4',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- channel: 'web',
- context: {
- traits: {
- name: 'Rudder Test',
- email: 'abc@gmail.com',
- phone: 9000000000,
- address: {
- postalCode: 1234,
- },
- gender: 'female',
- },
- app: {
- build: '1.0.0',
- name: 'RudderLabs JavaScript SDK',
- namespace: 'com.rudderlabs.javascript',
- version: '1.0.0',
- },
- 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,
- },
- },
- properties: {
- plan: 'standard plan',
- name: 'rudder test',
- },
- type: 'identify',
- messageId: '84e26acc-56a5-4835-8233-591137fca468',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '123456',
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T09:03:22.563Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: true,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","ph":"593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579","ge":"252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4","client_ip_address":"0.0.0.0","client_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","fn":"2c2ccf28d806f6f9a34b67aa874d2113b7ac1444f1a4092541b8b75b84771747","ln":"532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25"},"event_name":"identify","event_time":1697278611,"event_id":"84e26acc-56a5-4835-8233-591137fca468","action_source":"website"}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 5',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- email: 'abc@gmail.com',
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","custom_data":{"additional_bet_index":0,"value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 6',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- email: 'abc@gmail.com',
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","custom_data":{"additional_bet_index":0,"email":"abc@gmail.com","value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 7',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- email: 'abc@gmail.com',
- phone: 9000000000,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","custom_data":{"additional_bet_index":0,"email":"abc@gmail.com","value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 8',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- email: 'abc@gmail.com',
- phone: 9000000000,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","custom_data":{"additional_bet_index":0,"email":"abc@gmail.com","phone":"593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579","value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 9',
- 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',
- },
- 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,
- },
- },
- type: 'page',
- messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71',
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- properties: {
- path: '/abc',
- referrer: 'xyz',
- search: 'def',
- title: 'ghi',
- url: 'jkl',
- },
- integrations: {
- All: true,
- },
- name: 'ApplicationLoaded',
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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","client_ip_address":"0.0.0.0","client_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"},"event_name":"Viewed page ApplicationLoaded","event_time":1697278611,"event_source_url":"jkl","event_id":"5e10d13a-bf9a-44bf-b884-43a9e591ea71","action_source":"website","custom_data":{"path":"/abc","referrer":"xyz","search":"def","title":"ghi","url":"jkl"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 10',
- 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',
- },
- 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,
- },
- },
- type: 'page',
- messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- properties: {
- path: '/abc',
- referrer: 'xyz',
- search: 'def',
- title: 'ghi',
- url: 'jkl',
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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","client_ip_address":"0.0.0.0","client_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"},"event_name":"Viewed a page","event_time":1697278611,"event_source_url":"jkl","event_id":"5e10d13a-bf9a-44bf-b884-43a9e591ea71","action_source":"website","custom_data":{"path":"/abc","referrer":"xyz","search":"def","title":"ghi","url":"jkl"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 11',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product list viewed',
- properties: {
- phone: 9000000000,
- email: 'abc@gmail.com',
- category: 'cat 1',
- list_id: '1234',
- filters: [
- {
- type: 'department',
- value: 'beauty',
- },
- {
- type: 'price',
- value: 'under',
- },
- ],
- sorts: [
- {
- type: 'price',
- value: 'desc',
- },
- ],
- testDimension: true,
- testMetric: true,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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","client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"phone":"593a6d58f34eb5c3de4f47e38d1faaa7d389fafe332a85400b1e54498391c579","email":"abc@gmail.com","category":"cat 1","list_id":"1234","filters[0].type":"department","filters[0].value":"beauty","filters[1].type":"price","filters[1].value":"under","sorts[0].type":"price","sorts[0].value":"desc","testDimension":true,"testMetric":true,"content_ids":["cat 1"],"content_type":"product_group","contents":[{"id":"cat 1","quantity":1}],"content_category":"cat 1","value":0,"currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 12',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product list viewed',
- properties: {
- phone: 9000000000,
- email: 'abc@gmail.com',
- category: 'cat 1',
- list_id: '1234',
- filters: [
- {
- type: 'department',
- value: 'beauty',
- },
- {
- type: 'price',
- value: 'under',
- },
- ],
- sorts: [
- {
- type: 'price',
- value: 'desc',
- },
- ],
- testDimension: true,
- testMetric: true,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"category":"cat 1","list_id":"1234","filters[0].type":"department","filters[0].value":"beauty","filters[1].type":"price","filters[1].value":"under","sorts[0].type":"price","sorts[0].value":"desc","testDimension":true,"testMetric":true,"content_ids":["cat 1"],"content_type":"product_group","contents":[{"id":"cat 1","quantity":1}],"content_category":"cat 1","value":0,"currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 13',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product list viewed',
- properties: {
- email: 'abc@gmail.com',
- quantity: 2,
- category: 'cat 1',
- list_id: '1234',
- contentName: 'nutrition',
- value: 18.9,
- filters: [
- {
- type: 'department',
- value: 'beauty',
- },
- {
- type: 'price',
- value: 'under',
- },
- ],
- sorts: [
- {
- type: 'price',
- value: 'desc',
- },
- ],
- products: [
- {
- product_id: '507f1f77bcf86cd799439011',
- productDimension: 'My Product Dimension',
- productMetric: 'My Product Metric',
- position: 10,
- },
- {
- product_id: '507f1f77bcf86cdef799439011',
- productDimension: 'My Product Dimension1',
- productMetric: 'My Product Metric1',
- position: -10,
- },
- ],
- testDimension: true,
- testMetric: true,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"quantity":2,"category":"cat 1","list_id":"1234","contentName":"nutrition","value":18.9,"filters[0].type":"department","filters[0].value":"beauty","filters[1].type":"price","filters[1].value":"under","sorts[0].type":"price","sorts[0].value":"desc","products[0].product_id":"507f1f77bcf86cd799439011","products[0].productDimension":"My Product Dimension","products[0].productMetric":"My Product Metric","products[0].position":10,"products[1].product_id":"507f1f77bcf86cdef799439011","products[1].productDimension":"My Product Dimension1","products[1].productMetric":"My Product Metric1","products[1].position":-10,"testDimension":true,"testMetric":true,"content_ids":["507f1f77bcf86cd799439011","507f1f77bcf86cdef799439011"],"content_type":"product","contents":[{"id":"507f1f77bcf86cd799439011","quantity":2},{"id":"507f1f77bcf86cdef799439011","quantity":2}],"content_category":"cat 1","content_name":"nutrition","currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 14',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'my product list',
- properties: {
- email: 'abc@gmail.com',
- quantity: 2,
- category: 'cat 1',
- list_id: '1234',
- filters: [
- {
- type: 'department',
- value: 'beauty',
- },
- {
- type: 'price',
- value: 'under',
- },
- ],
- sorts: [
- {
- type: 'price',
- value: 'desc',
- },
- ],
- products: [
- {
- product_id: '507f1f77bcf86cd799439011',
- productDimension: 'My Product Dimension',
- productMetric: 'My Product Metric',
- position: 10,
- },
- {
- product_id: '507f1f77bcf86cdef799439011',
- productDimension: 'My Product Dimension1',
- productMetric: 'My Product Metric1',
- position: -10,
- },
- ],
- testDimension: true,
- testMetric: true,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- removeExternalId: false,
- eventsToEvents: [
- {
- from: 'My product list',
- to: 'ViewContent',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: 'list_id',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"quantity":2,"category":"cat 1","list_id":"1234","filters[0].type":"department","filters[0].value":"beauty","filters[1].type":"price","filters[1].value":"under","sorts[0].type":"price","sorts[0].value":"desc","products[0].product_id":"507f1f77bcf86cd799439011","products[0].productDimension":"My Product Dimension","products[0].productMetric":"My Product Metric","products[0].position":10,"products[1].product_id":"507f1f77bcf86cdef799439011","products[1].productDimension":"My Product Dimension1","products[1].productMetric":"My Product Metric1","products[1].position":-10,"testDimension":true,"testMetric":true,"content_ids":["507f1f77bcf86cd799439011","507f1f77bcf86cdef799439011"],"content_type":"product","contents":[{"id":"507f1f77bcf86cd799439011","quantity":2},{"id":"507f1f77bcf86cdef799439011","quantity":2}],"content_category":"cat 1","value":0,"currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 15',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product viewed',
- properties: {
- currency: 'CAD',
- quantity: 1,
- price: 24.75,
- name: 'my product 1',
- category: 'clothing',
- sku: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- removeExternalId: true,
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":{"client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"price":24.75,"name":"my product 1","category":"clothing","sku":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"product","content_name":"my product 1","content_category":"clothing","value":24.75,"contents":[{"id":"p-298","quantity":1,"item_price":24.75}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 16',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product added',
- properties: {
- currency: 'CAD',
- quantity: 1,
- value: 24.75,
- category: 'cat 1',
- id: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- removeExternalId: false,
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.value',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"AddToCart","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"value":24.75,"category":"cat 1","id":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"product","content_name":"","content_category":"cat 1","contents":[{"id":"p-298","quantity":1}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 17',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- 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: {
- order_id: 'rudderstackorder1',
- total: 99.99,
- revenue: 12.24,
- shipping: 13.99,
- tax: 20.99,
- currency: 'INR',
- contentName: 'all about nutrition',
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"order_id":"rudderstackorder1","total":99.99,"revenue":12.24,"shipping":13.99,"tax":20.99,"currency":"INR","contentName":"all about nutrition","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_ids":["p-298","p-299"],"content_type":"product","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,"content_name":"all about nutrition"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 18',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'checkout started',
- properties: {
- currency: 'CAD',
- category: 'clothing',
- contentName: 'abc',
- products: [
- {
- quantity: 1,
- price: 24.75,
- name: 'my product',
- sku: 'p-298',
- },
- {
- quantity: 1,
- price: 24.75,
- name: 'my product 2',
- sku: 'p-299',
- },
- ],
- step: 1,
- paymentMethod: 'Visa',
- testDimension: true,
- testMetric: true,
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: 'contentName',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"InitiateCheckout","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","category":"clothing","contentName":"abc","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":1,"products[1].price":24.75,"products[1].name":"my product 2","products[1].sku":"p-299","step":1,"paymentMethod":"Visa","testDimension":true,"testMetric":true,"content_category":"clothing","content_ids":["p-298","p-299"],"content_type":"product","value":0,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":1,"item_price":24.75}],"num_items":2}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 19',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- dataProcessingOptions: [['LDU'], 1, 1000],
- fbc: 'fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz1234567890',
- fbp: 'fb.1.1554763741205.234567890',
- fb_login_id: 'fb_id',
- lead_id: 'lead_id',
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUSage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","fbc":"fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz1234567890","fbp":"fb.1.1554763741205.234567890","lead_id":"lead_id","fb_login_id":"fb_id"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","data_processing_options":["LDU"],"data_processing_options_country":1,"data_processing_options_state":1000,"custom_data":{"additional_bet_index":0,"value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 20',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- dataProcessingOptions: [['LDU'], 1, 1000],
- fbc: 'fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz1234567890',
- fbp: 'fb.1.1554763741205.234567890',
- fb_login_id: 'fb_id',
- lead_id: 'lead_id',
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUSage: false,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","fbc":"fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz1234567890","fbp":"fb.1.1554763741205.234567890","lead_id":"lead_id","fb_login_id":"fb_id"},"event_name":"spin_result","event_time":1697278611,"action_source":"other","custom_data":{"additional_bet_index":0,"value":400}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 21',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'products searched',
- properties: {
- product_id: 'p-298',
- quantity: 2,
- price: 18.9,
- category: 'health',
- value: 18.9,
- query: 'HDMI cable',
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Search","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"product_id":"p-298","quantity":2,"price":18.9,"category":"health","value":18.9,"query":"HDMI cable","content_ids":["p-298"],"content_category":"health","contents":[{"id":"p-298","quantity":2,"item_price":18.9}],"search_string":"HDMI cable","currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 22',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'products searched',
- properties: {
- query: 'HDMI cable',
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- testDestination: true,
- testEventCode: 'TEST1001',
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Search","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"query":"HDMI cable","content_ids":[],"content_category":"","value":0,"contents":[],"search_string":"HDMI cable","currency":"USD"}}',
- ],
- test_event_code: 'TEST1001',
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 23',
- 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',
- },
- 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,
- },
- },
- type: 'page',
- messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- properties: {
- path: '/abc',
- referrer: 'xyz',
- search: 'def',
- title: 'ghi',
- url: 'jkl',
- },
- integrations: {
- All: true,
- },
- name: 'ApplicationLoaded',
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- standardPageCall: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: 'phone',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: 'url',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: 'email',
- },
- ],
- },
- 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","client_ip_address":"0.0.0.0","client_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"},"event_name":"PageView","event_time":1697278611,"event_source_url":"jkl","event_id":"5e10d13a-bf9a-44bf-b884-43a9e591ea71","action_source":"website","custom_data":{"path":"/abc","referrer":"xyz","search":"def","title":"ghi","url":"jkl"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 24',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'track page',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: 'track page',
- to: 'PageView',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: 'additional_bet_index',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"PageView","event_time":1697278611,"action_source":"other","custom_data":{"revenue":400,"additional_bet_index":0}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 25',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'my product list',
- properties: {
- email: 'abc@gmail.com',
- quantity: 2,
- category: 'cat 1',
- list_id: '1234',
- filters: [
- {
- type: 'department',
- value: 'beauty',
- },
- {
- type: 'price',
- value: 'under',
- },
- ],
- sorts: [
- {
- type: 'price',
- value: 'desc',
- },
- ],
- products: [
- {
- product_id: '507f1f77bcf86cd799439011',
- productDimension: 'My Product Dimension',
- productMetric: 'My Product Metric',
- position: 10,
- },
- {
- product_id: '507f1f77bcf86cdef799439011',
- productDimension: 'My Product Dimension1',
- productMetric: 'My Product Metric1',
- position: -10,
- },
- ],
- testDimension: true,
- testMetric: true,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: 'My product list',
- to: 'Schedule',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: 'list_id',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"Schedule","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"quantity":2,"category":"cat 1","list_id":"1234","filters[0].type":"department","filters[0].value":"beauty","filters[1].type":"price","filters[1].value":"under","sorts[0].type":"price","sorts[0].value":"desc","products[0].product_id":"507f1f77bcf86cd799439011","products[0].productDimension":"My Product Dimension","products[0].productMetric":"My Product Metric","products[0].position":10,"products[1].product_id":"507f1f77bcf86cdef799439011","products[1].productDimension":"My Product Dimension1","products[1].productMetric":"My Product Metric1","products[1].position":-10,"testDimension":true,"testMetric":true}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 26',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: "'event' is required",
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 27',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product added',
- properties: {
- currency: 'CAD',
- quantity: 1,
- value: '35.753',
- category: 'cat 1',
- id: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.value',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"AddToCart","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"value":35.75,"category":"cat 1","id":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"product","content_name":"","content_category":"cat 1","contents":[{"id":"p-298","quantity":1}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 28',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product added',
- properties: {
- currency: 'CAD',
- quantity: 1,
- value: '35.7A3',
- category: 'cat 1',
- id: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.value',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","client_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"},"event_name":"AddToCart","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"value":35.7,"category":"cat 1","id":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"product","content_name":"","content_category":"cat 1","contents":[{"id":"p-298","quantity":1}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 29',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product added',
- properties: {
- currency: 'CAD',
- quantity: 1,
- value: 'ABC',
- category: 'cat 1',
- id: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.value',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: 'Revenue could not be converted to number',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 30',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- 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: {
- category: ['clothing', 'fishing'],
- order_id: 'rudderstackorder1',
- total: 99.99,
- revenue: 12.24,
- shipping: 13.99,
- tax: 20.99,
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"category[0]":"clothing","category[1]":"fishing","order_id":"rudderstackorder1","total":99.99,"revenue":12.24,"shipping":13.99,"tax":20.99,"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"],"content_type":"product","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,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 31',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- 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: {
- category: 100,
- order_id: 'rudderstackorder1',
- total: 99.99,
- revenue: 12.24,
- shipping: 13.99,
- tax: 20.99,
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Purchase","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"category":100,"order_id":"rudderstackorder1","total":99.99,"revenue":12.24,"shipping":13.99,"tax":20.99,"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":"100","content_ids":["p-298","p-299"],"content_type":"product","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,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 32',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- 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: {
- category: {
- category1: '1',
- },
- order_id: 'rudderstackorder1',
- total: 99.99,
- revenue: 12.24,
- shipping: 13.99,
- tax: 20.99,
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: "'properties.category' must be either be a string or an array",
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 33',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2023-10-14T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: 'spin_result',
- to: 'Schedule',
- },
- {
- to: 'Schedule',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":"6dc8118ec743f5f3b758939714193f547f4a674c68757fa80d7c9564dc093b0a","em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08"},"event_name":"Schedule","event_time":1697278611,"action_source":"other","custom_data":{"revenue":400,"additional_bet_index":0}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 34',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- timestamp: '2019-08-24T15:46:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- limitedDataUsage: true,
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: 'spin_result',
- to: 'Schedule',
- },
- {
- to: 'Schedule',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error:
- 'Events must be sent within seven days of their occurrence or up to one minute in the future.',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 35',
- feature: 'processor',
- module: 'destination',
- version: 'v0',
- input: {
- request: {
- body: [
- {
- message: {
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- destination_props: {
- Fb: {
- app_id: 'RudderFbApp',
- },
- },
- context: {
- device: {
- id: 'df16bffa-5c3d-4fbb-9bce-3bab098129a7R',
- manufacturer: 'Xiaomi',
- model: 'Redmi 6',
- name: 'xiaomi',
- },
- network: {
- carrier: 'Banglalink',
- },
- os: {
- name: 'android',
- version: '8.1.0',
- },
- screen: {
- height: '100',
- density: 50,
- },
- traits: {
- email: 'abc@gmail.com',
- anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
- },
- },
- event: 'spin_result',
- integrations: {
- All: true,
- },
- message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
- properties: {
- revenue: 400,
- additional_bet_index: 0,
- },
- originalTimestamp: '2019-04-16T15:50:51.693229+05:30',
- type: 'track',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: false,
- },
- ],
- accessToken: 'validToken',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: '',
- advancedMapping: true,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error:
- 'Events must be sent within seven days of their occurrence or up to one minute in the future.',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 36',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'products searched',
- properties: {
- query: {
- key1: 'HDMI cable',
- },
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: "'query' should be in string format only",
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 37',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'products searched',
- properties: {
- query: 50,
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"Search","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"query":50,"content_ids":[],"content_category":"","value":0,"contents":[],"search_string":50,"currency":"USD"}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 38',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- page: {
- url: 'https://theminimstory.com/collections/summer-of-pearls?utm_source=facebook&utm_medium=paidsocial&utm_campaign=carousel&utm_content=ad1-jul&fbclid=IwAR2SsDcjzd_TLZN-e93kxOeGBYO4pQ3AiyeXSheHW5emDeLw8uTvo6lTMPI',
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'checkout started',
- properties: {
- currency: 'CAD',
- category: 'clothing',
- products: [
- {
- quantity: 1,
- price: 24.75,
- name: 'my product',
- sku: 'p-298',
- },
- {
- quantity: 1,
- price: 24.75,
- name: 'my product 2',
- sku: 'p-299',
- },
- ],
- step: 1,
- paymentMethod: 'Visa',
- testDimension: true,
- testMetric: true,
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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","fbc":"fb.1.1697278611693.IwAR2SsDcjzd_TLZN-e93kxOeGBYO4pQ3AiyeXSheHW5emDeLw8uTvo6lTMPI"},"event_name":"InitiateCheckout","event_time":1697278611,"event_source_url":"https://theminimstory.com/collections/summer-of-pearls?utm_source=facebook&utm_medium=paidsocial&utm_campaign=carousel&utm_content=ad1-jul&fbclid=IwAR2SsDcjzd_TLZN-e93kxOeGBYO4pQ3AiyeXSheHW5emDeLw8uTvo6lTMPI","event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","category":"clothing","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":1,"products[1].price":24.75,"products[1].name":"my product 2","products[1].sku":"p-299","step":1,"paymentMethod":"Visa","testDimension":true,"testMetric":true,"content_category":"clothing","content_ids":["p-298","p-299"],"content_type":"product","value":0,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":1,"item_price":24.75}],"num_items":2}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 39',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- page: {
- url: 'https://theminimstory.com/collections/summer-of-pearls?utm_source=facebook&utm_medium=paidsocial&utm_campaign=carousel&utm_content=ad1-jul&fbclid=IwAR2SsDcjzd_TLZN-e93kxOeGBYO4pQ3AiyeXSheHW5emDeLw8uTvo6lTMPI',
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: {
- name: 'checkout started',
- },
- properties: {
- currency: 'CAD',
- category: 'clothing',
- products: [
- {
- quantity: 1,
- price: 24.75,
- name: 'my product',
- sku: 'p-298',
- },
- {
- quantity: 1,
- price: 24.75,
- name: 'my product 2',
- sku: 'p-299',
- },
- ],
- step: 1,
- paymentMethod: 'Visa',
- testDimension: true,
- testMetric: true,
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: 'event name should be string',
- statTags: {
- errorCategory: 'dataValidation',
- errorType: 'instrumentation',
- destType: 'FACEBOOK_PIXEL',
- module: 'destination',
- implementation: 'native',
- feature: 'processor',
- },
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 40',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- page: {
- url: 'url in wrong format',
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'checkout started',
- properties: {
- currency: 'CAD',
- category: 'clothing',
- products: [
- {
- quantity: 1,
- price: 24.75,
- name: 'my product',
- sku: 'p-298',
- },
- {
- quantity: 1,
- price: 24.75,
- name: 'my product 2',
- sku: 'p-299',
- },
- ],
- step: 1,
- paymentMethod: 'Visa',
- testDimension: true,
- testMetric: true,
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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","em":"1c5e54849f5c711ce38fa60716fbbe44bff478f9ca250897b39cdfc2438cd1bd","client_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"},"event_name":"InitiateCheckout","event_time":1697278611,"event_source_url":"url in wrong format","event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","category":"clothing","products[0].quantity":1,"products[0].price":24.75,"products[0].name":"my product","products[0].sku":"p-298","products[1].quantity":1,"products[1].price":24.75,"products[1].name":"my product 2","products[1].sku":"p-299","step":1,"paymentMethod":"Visa","testDimension":true,"testMetric":true,"content_category":"clothing","content_ids":["p-298","p-299"],"content_type":"product","value":0,"contents":[{"id":"p-298","quantity":1,"item_price":24.75},{"id":"p-299","quantity":1,"item_price":24.75}],"num_items":2}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 41',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product viewed',
- properties: {
- currency: 'CAD',
- quantity: 1,
- price: 24.75,
- name: 'my product 1',
- category: 'clothing',
- sku: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- Facebook_Pixel: {
- contentType: 'sending dedicated content type for this particular payload',
- },
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- removeExternalId: true,
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":{"client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"price":24.75,"name":"my product 1","category":"clothing","sku":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"sending dedicated content type for this particular payload","content_name":"my product 1","content_category":"clothing","value":24.75,"contents":[{"id":"p-298","quantity":1,"item_price":24.75}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 42',
- 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',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- type: 'track',
- messageId: 'ec5481b6-a926-4d2e-b293-0b3a77c4d3be',
- originalTimestamp: '2023-10-14T15:46:51.693229+05:30',
- anonymousId: '00000000000000000000000000',
- userId: '12345',
- event: 'product viewed',
- properties: {
- currency: 'CAD',
- quantity: 1,
- price: 24.75,
- value: 18.9,
- name: 'my product 1',
- category: 'clothing',
- sku: 'p-298',
- testDimension: true,
- testMetric: true,
- position: 4.5,
- },
- integrations: {
- All: true,
- },
- sentAt: '2019-10-14T11:15:53.296Z',
- },
- destination: {
- Config: {
- blacklistPiiProperties: [
- {
- blacklistPiiProperties: '',
- blacklistPiiHash: true,
- },
- ],
- categoryToContent: [
- {
- from: 'clothing',
- to: 'product',
- },
- ],
- removeExternalId: true,
- accessToken: '09876',
- pixelId: 'dummyPixelId',
- eventsToEvents: [
- {
- from: '',
- to: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.value',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- 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":{"client_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"},"event_name":"ViewContent","event_time":1697278611,"event_id":"ec5481b6-a926-4d2e-b293-0b3a77c4d3be","action_source":"website","custom_data":{"currency":"CAD","quantity":1,"price":24.75,"value":18.9,"name":"my product 1","category":"clothing","sku":"p-298","testDimension":true,"testMetric":true,"position":4.5,"content_ids":["p-298"],"content_type":"product","content_name":"my product 1","content_category":"clothing","contents":[{"id":"p-298","quantity":1,"item_price":24.75}]}}',
- ],
- },
- },
- files: {},
- userId: '',
- },
- statusCode: 200,
- },
- ],
- },
- },
- },
- {
- name: 'facebook_pixel',
- description: 'Test 43',
- 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: 'test@rudderstack.com',
- },
- 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',
- os: {
- name: '',
- version: '',
- },
- screen: {
- density: 2,
- },
- },
- 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: {
- order_id: 'rudderstackorder1',
- total: 99.99,
- revenue: 12.24,
- shipping: 13.99,
- tax: 20.99,
- currency: 'INR',
- contentName: 'all about nutrition',
- products: {
- quantity: 1,
- price: 24.75,
- name: 'my product',
- sku: 'p-298',
- },
- },
- 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: '',
- },
- ],
- eventCustomProperties: [
- {
- eventCustomProperties: '',
- },
- ],
- valueFieldIdentifier: 'properties.price',
- advancedMapping: false,
- whitelistPiiProperties: [
- {
- whitelistPiiProperties: '',
- },
- ],
- },
- Enabled: true,
- },
- },
- ],
- },
- },
- output: {
- response: {
- status: 200,
- body: [
- {
- statusCode: 400,
- error: "'properties.products' is not sent as an Array