diff --git a/.eslintrc.json b/.eslintrc.json
index d2928e50fdc..7258c5c5366 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -9,9 +9,9 @@
"airbnb-base",
"airbnb-typescript/base",
"plugin:sonarjs/recommended",
- "prettier",
"plugin:json/recommended",
- "plugin:@typescript-eslint/recommended"
+ "plugin:@typescript-eslint/recommended",
+ "prettier"
],
"plugins": ["@typescript-eslint", "unicorn"],
"globals": {},
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index b18fd293573..040e07f81d2 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 fe308ce8b93..cfcb1fc0d8c 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 9f6709a0405..7ddae0a3aed 100644
--- a/.github/workflows/build-push-docker-image.yml
+++ b/.github/workflows/build-push-docker-image.yml
@@ -155,7 +155,7 @@ jobs:
if: ${{ inputs.build_type == 'dt' }}
run: |
docker buildx imagetools create -t rudderstack/rudder-transformer:latest ${{ inputs.push_tags }}-amd64 ${{ inputs.push_tags }}-arm64
-
+
- name: Create latest ut multi-arch manifest
# To be triggered only for release/hotfix PR merges coming from `prepare-for-prod-ut-deploy.yaml`
if: ${{ inputs.build_type == 'ut' }}
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
new file mode 100644
index 00000000000..a8ff39eee0d
--- /dev/null
+++ b/.github/workflows/commitlint.yml
@@ -0,0 +1,36 @@
+name: Commitlint
+
+on: [push]
+
+jobs:
+ commitlint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4.1.1
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node
+ uses: actions/setup-node@v4.0.1
+ with:
+ node-version-file: '.nvmrc'
+ cache: 'npm'
+
+ - name: Install Dependencies
+ run: npm ci
+
+ - name: Print versions
+ run: |
+ git --version
+ node --version
+ npm --version
+ npx commitlint --version
+
+ # Run the commitlint action, considering its own dependencies and yours as well 🚀
+ # `github.workspace` is the path to your repository.
+ - uses: wagoid/commitlint-github-action@v5
+ env:
+ NODE_PATH: ${{ github.workspace }}/node_modules
+ with:
+ commitDepth: 1
diff --git a/.github/workflows/component-test-report.yml b/.github/workflows/component-test-report.yml
index 99040044178..3d457df9ff8 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 a164c25beee..994283a9f43 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 33b0396705c..95431fef8ee 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 40962274003..4375b3383e0 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 2af853f6430..a5ca48e3f83 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 9ac144a21e0..825720efe1a 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 4e8f29cffa7..3e0b3aac19b 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 9d1558d8265..233e99577dc 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 30c29ceaee3..0a2ef8a3909 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 00000000000..4caef8dd918
--- /dev/null
+++ b/.github/workflows/verify.yml
@@ -0,0 +1,36 @@
+name: Verify
+
+on:
+ pull_request:
+
+jobs:
+ formatting-lint:
+ name: Check for formatting & lint errors
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4.1.1
+ with:
+ # Make sure the actual branch is checked out when running on pull requests
+ ref: ${{ github.head_ref }}
+
+ - name: Setup Node
+ uses: actions/setup-node@v3.7.0
+ with:
+ node-version-file: .nvmrc
+ cache: 'npm'
+
+ - name: Install Dependencies
+ run: npm ci
+
+ - name: Run Lint Checks
+ run: |
+ npm run lint
+
+ - run: git diff --exit-code
+
+ - name: Error message
+ if: ${{ failure() }}
+ run: |
+ echo 'Eslint check is failing Ensure you have run `npm run lint` and committed the files locally.'
diff --git a/.gitignore b/.gitignore
index f96c3ac8077..09c536ebb80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,9 +132,10 @@ dist
# Others
**/.DS_Store
-
+.dccache
.idea
# component test report
-test_reports/
\ No newline at end of file
+test_reports/
+temp/
diff --git a/.nvmrc b/.nvmrc
index a9d087399d7..3c5535cf60a 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-18.19.0
+18.19.1
diff --git a/.prettierignore b/.prettierignore
index 93eb370b0de..99747b29bbb 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,3 +7,4 @@ test/**/*.js
!test/**/data.js
src/util/lodash-es-core.js
src/util/url-search-params.min.js
+dist
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000000..13c49c08f15
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,24 @@
+{
+ "prettier.requireConfig": true,
+ "prettier.configPath": ".prettierrc",
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[jsonc]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "[yaml]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "never"
+ },
+ "eslint.validate": ["javascript", "typescript"]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 034e9251f18..5d3a37e1221 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,147 @@
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)
+
+
+### Features
+
+* add new stat for access token expired in fb custom audience ([#3043](https://github.com/rudderlabs/rudder-transformer/issues/3043)) ([1e6d540](https://github.com/rudderlabs/rudder-transformer/commit/1e6d540fafc61a84fbbaa63d4bc5b1edc17ec44e))
+* **intercom:** upgrade intercom version from 1.4 to 2.10 ([#2976](https://github.com/rudderlabs/rudder-transformer/issues/2976)) ([717639b](https://github.com/rudderlabs/rudder-transformer/commit/717639bcce605109b145eb4cc6836fe1589278fe))
+* onboard new destination rakuten ([#3046](https://github.com/rudderlabs/rudder-transformer/issues/3046)) ([c7c3110](https://github.com/rudderlabs/rudder-transformer/commit/c7c3110a4526e31bc296abb33f3246fa8eee049a))
+* trade desk real time conversions ([#3023](https://github.com/rudderlabs/rudder-transformer/issues/3023)) ([212d5f0](https://github.com/rudderlabs/rudder-transformer/commit/212d5f09d8addc618d4398029e62c9a18a9512cf))
+
+
+### Bug Fixes
+
+* adding map for marketo known values and javascript known values ([#3037](https://github.com/rudderlabs/rudder-transformer/issues/3037)) ([64ab555](https://github.com/rudderlabs/rudder-transformer/commit/64ab555d31b4c1c49863794444bd79b2b6a45927))
+* mixpanel timestamp in ms ([#3028](https://github.com/rudderlabs/rudder-transformer/issues/3028)) ([5ad55a2](https://github.com/rudderlabs/rudder-transformer/commit/5ad55a27c8b737fd96f65c68ba086769747c5360))
+* upgrade ua-parser-js from 1.0.35 to 1.0.37 ([9a4cdef](https://github.com/rudderlabs/rudder-transformer/commit/9a4cdef59ab1c2d9dc95eb8629a7009d8d633297))
+
+### [1.54.4](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.3...v1.54.4) (2024-01-31)
+
+
+### Bug Fixes
+
+* purchsse events for reddit ([56d24ec](https://github.com/rudderlabs/rudder-transformer/commit/56d24ec74c0c21bcdcdf262b4feec6edd1dff51c))
+
+### [1.54.3](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.2...v1.54.3) (2024-01-30)
+
+
+### Bug Fixes
+
+* hubspot increasing batch limit from 10 to 100 ([8075b7c](https://github.com/rudderlabs/rudder-transformer/commit/8075b7c6c6203657a8aad2525bd2f302dabd9960))
+
### [1.54.2](https://github.com/rudderlabs/rudder-transformer/compare/v1.54.1...v1.54.2) (2024-01-25)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1cef57af73e..bdd76d916cf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,7 +35,6 @@ See the project's [README](README.md) for further information about working in t
- Include instructions on how to test your changes.
3. Your branch may be merged once all configured checks pass, including:
- A review from appropriate maintainers
-4. Along with the PR in transformer raise a PR against [config-generator][config-generator] with the configurations.
## Committing
diff --git a/Dockerfile b/Dockerfile
index 6bd03c9515f..8cd4005a7b1 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 2b3908dcb55..4ff1cd4b34d 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 00000000000..df269d8a02b
--- /dev/null
+++ b/github-release.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ gitRawCommitsOpts: {
+ merges: null,
+ },
+};
diff --git a/jest.default.config.js b/jest.default.config.js
index 59ca8449021..d1b5390b9e7 100644
--- a/jest.default.config.js
+++ b/jest.default.config.js
@@ -27,7 +27,7 @@ module.exports = {
coverageDirectory: 'reports/coverage',
// An array of regexp pattern strings used to skip coverage collection
- coveragePathIgnorePatterns: ['/node_modules/', '__tests__', 'warehouse/v0' ,'test'],
+ coveragePathIgnorePatterns: ['/node_modules/', '__tests__', 'warehouse/v0', 'test'],
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ['json', 'text', 'lcov', 'clover'],
diff --git a/package-lock.json b/package-lock.json
index 59df32aa830..5701e64a623 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "rudder-transformer",
- "version": "1.54.2",
+ "version": "1.60.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rudder-transformer",
- "version": "1.54.2",
+ "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.1.8",
- "@rudderstack/workflow-engine": "^0.6.9",
+ "@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,13 +63,15 @@
"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",
"ua-parser-js": "^1.0.37",
"unset-value": "^2.0.1",
"uuid": "^9.0.0",
- "valid-url": "^1.0.9"
+ "valid-url": "^1.0.9",
+ "zod": "^3.22.4"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.6.3",
@@ -90,9 +93,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",
@@ -105,7 +109,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",
@@ -114,11 +118,45 @@
"typescript": "^5.0.4"
}
},
+ "../rudder-integrations-lib": {
+ "name": "@rudderstack/integrations-lib",
+ "version": "0.1.10",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rudderstack/workflow-engine": "^0.5.7",
+ "axios": "^1.4.0",
+ "axios-mock-adapter": "^1.22.0",
+ "crypto": "^1.0.1",
+ "get-value": "^3.0.1",
+ "handlebars": "^4.7.8",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.4",
+ "moment-timezone": "^0.5.43",
+ "set-value": "^4.1.0",
+ "sha256": "^0.2.0",
+ "tslib": "^2.4.0",
+ "winston": "^3.11.0"
+ },
+ "devDependencies": {
+ "@types/get-value": "^3.0.3",
+ "@types/jest": "^29.5.4",
+ "@types/lodash": "^4.14.195",
+ "@types/node": "^20.3.3",
+ "@types/set-value": "^4.0.1",
+ "@types/sha256": "^0.2.0",
+ "jest": "^29.4.3",
+ "pre-commit": "^1.2.2",
+ "prettier": "^2.8.4",
+ "ts-jest": "^29.0.5",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.1.6"
+ }
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"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"
}
@@ -3199,7 +3237,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"
},
@@ -3214,7 +3251,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"
}
@@ -3223,7 +3259,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",
@@ -3246,7 +3281,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",
@@ -3261,14 +3295,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"
}
@@ -3298,7 +3330,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",
@@ -3312,7 +3343,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"
},
@@ -3324,8 +3354,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",
@@ -4312,7 +4341,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"
@@ -4325,7 +4353,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"
}
@@ -4334,7 +4361,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"
@@ -4353,6 +4379,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",
@@ -4432,14 +4470,15 @@
}
},
"node_modules/@rudderstack/integrations-lib": {
- "version": "0.1.9",
- "resolved": "https://registry.npmjs.org/@rudderstack/integrations-lib/-/integrations-lib-0.1.9.tgz",
- "integrity": "sha512-ROi/LfI7PXqKDrjSig+1Rf2TQ8MgxJGJ7sAD1B0PmRKELQpxK6PLt8QF+vKXl8wYILQu2gwTkZ5o+uwmNKxGzg==",
+ "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",
@@ -4447,69 +4486,22 @@
"moment-timezone": "^0.5.43",
"set-value": "^4.1.0",
"sha256": "^0.2.0",
- "tslib": "^2.4.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"
+ "tslib": "^2.4.0",
+ "winston": "^3.11.0"
}
},
"node_modules/@rudderstack/json-template-engine": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.2.tgz",
- "integrity": "sha512-9oMBnqgNuwiXd7MUlNOAchCnJXQAy6w6XGmDqDM6iXdYDkvqYFiq7sbg5j4SdtpTTST293hahREr5PXfFVzVKg=="
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@rudderstack/json-template-engine/-/json-template-engine-0.8.5.tgz",
+ "integrity": "sha512-+iH40g+ZA2ANgwjOITdEdZJLZV+ljR28Akn/dRoDia591tMu7PptyvDaAvl+m1DijWXddpLQ8SX9xaEcIdmqlw=="
},
"node_modules/@rudderstack/workflow-engine": {
- "version": "0.6.10",
- "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.6.10.tgz",
- "integrity": "sha512-3GRdnbB0BuSPWiKf4JsSpG7QuGffAFWkT5T0JLR7Jxps25gt+PgtjQiAlwrRhO5A0WeTJMIKTI7ctz6dGmJosg==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@rudderstack/workflow-engine/-/workflow-engine-0.7.2.tgz",
+ "integrity": "sha512-aXQvoXMekvXxxDG6Yc5P5l3PJIwqVA+EmJ2w4SnQ94BUHhbsybPjgGvyzD17MUTAdWEOtqS38SuzLflBs/5T4g==",
"dependencies": {
"@aws-crypto/sha256-js": "^5.0.0",
- "@rudderstack/json-template-engine": "^0.8.1",
+ "@rudderstack/json-template-engine": "^0.8.4",
"jsonata": "^2.0.3",
"lodash": "^4.17.21",
"object-sizeof": "^2.6.3",
@@ -4539,6 +4531,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",
@@ -5429,14 +5433,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",
@@ -5518,8 +5520,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",
@@ -5570,7 +5571,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",
@@ -5604,7 +5604,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",
@@ -5631,7 +5630,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"
@@ -5648,7 +5646,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",
@@ -5675,7 +5672,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"
},
@@ -5688,7 +5684,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",
@@ -5715,7 +5710,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",
@@ -5741,7 +5735,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"
@@ -5757,8 +5750,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",
@@ -5781,7 +5773,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"
},
@@ -5793,7 +5784,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"
}
@@ -5993,7 +5983,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"
@@ -6021,7 +6010,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",
@@ -6040,7 +6028,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"
}
@@ -6049,7 +6036,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",
@@ -6068,7 +6054,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",
@@ -6086,7 +6071,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",
@@ -6104,7 +6088,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",
@@ -6168,7 +6151,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"
},
@@ -6583,7 +6565,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"
},
@@ -6790,7 +6771,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"
}
@@ -7522,8 +7502,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",
@@ -8531,7 +8510,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",
@@ -8704,8 +8682,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",
@@ -8752,7 +8729,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",
@@ -9125,7 +9101,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"
},
@@ -9137,7 +9112,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"
},
@@ -9367,7 +9341,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",
@@ -9420,7 +9393,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",
@@ -9434,7 +9406,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"
}
@@ -9443,7 +9414,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",
@@ -9527,7 +9497,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",
@@ -9582,7 +9551,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",
@@ -9601,7 +9569,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"
}
@@ -9610,7 +9577,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"
},
@@ -9637,7 +9603,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",
@@ -9648,7 +9613,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"
}
@@ -9657,7 +9621,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"
},
@@ -9674,7 +9637,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"
}
@@ -9683,7 +9645,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",
@@ -9714,7 +9675,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"
}
@@ -9723,7 +9683,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"
},
@@ -9735,7 +9694,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"
}
@@ -9753,6 +9711,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",
@@ -9904,7 +9892,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"
@@ -9917,7 +9904,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"
},
@@ -9929,7 +9915,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",
@@ -9945,7 +9930,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"
},
@@ -9960,7 +9944,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"
@@ -9976,7 +9959,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"
},
@@ -9987,14 +9969,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"
},
@@ -10006,7 +9986,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"
@@ -10022,7 +10001,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"
}
@@ -10031,7 +10009,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"
}
@@ -10039,14 +10016,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"
},
@@ -10058,7 +10033,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",
@@ -10088,7 +10062,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"
},
@@ -10100,7 +10073,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"
}
@@ -10109,7 +10081,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"
},
@@ -10121,7 +10092,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"
}
@@ -10130,7 +10100,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"
}
@@ -10139,7 +10108,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"
}
@@ -10246,11 +10214,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",
@@ -10266,7 +10239,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"
},
@@ -10277,14 +10249,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",
@@ -10329,7 +10299,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"
}
@@ -10367,7 +10336,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"
},
@@ -10430,7 +10398,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"
},
@@ -10458,7 +10425,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"
@@ -10505,7 +10471,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",
@@ -10518,8 +10483,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",
@@ -10556,7 +10520,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"
}
@@ -10708,7 +10671,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",
@@ -10726,7 +10688,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"
}
@@ -11049,7 +11010,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"
@@ -11305,7 +11265,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"
},
@@ -11395,7 +11354,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"
},
@@ -11410,7 +11368,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"
},
@@ -11425,7 +11382,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",
@@ -11522,8 +11478,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",
@@ -11572,7 +11527,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"
}
@@ -11910,7 +11864,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"
}
@@ -11919,7 +11872,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"
@@ -11935,7 +11887,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"
}
@@ -11963,7 +11914,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"
}
@@ -12125,7 +12075,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",
@@ -12191,7 +12140,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",
@@ -12210,7 +12158,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"
},
@@ -12222,7 +12169,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"
@@ -12275,7 +12221,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"
},
@@ -12298,7 +12243,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"
},
@@ -12328,7 +12272,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"
}
@@ -12384,7 +12327,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"
},
@@ -12416,7 +12358,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"
},
@@ -12428,7 +12369,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"
}
@@ -12437,7 +12377,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"
},
@@ -12470,7 +12409,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"
}
@@ -12507,7 +12445,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"
@@ -12547,7 +12484,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"
},
@@ -12570,7 +12506,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"
},
@@ -12585,7 +12520,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"
},
@@ -12612,7 +12546,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"
},
@@ -12663,7 +12596,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"
},
@@ -12706,8 +12638,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",
@@ -14516,8 +14447,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",
@@ -14562,8 +14492,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",
@@ -14657,7 +14586,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"
}
@@ -14907,7 +14835,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"
@@ -15257,7 +15184,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"
},
@@ -15334,8 +15260,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",
@@ -16102,6 +16027,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",
@@ -16112,7 +16045,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"
}
@@ -16129,7 +16061,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"
@@ -16616,14 +16547,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",
@@ -16684,6 +16613,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",
@@ -16846,7 +16816,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"
}
@@ -16886,7 +16855,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",
@@ -16904,7 +16872,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",
@@ -16918,7 +16885,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",
@@ -16935,7 +16901,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",
@@ -16947,7 +16912,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",
@@ -17018,7 +16982,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",
@@ -17178,7 +17141,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"
},
@@ -17240,7 +17202,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"
},
@@ -17322,7 +17283,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"
}
@@ -17366,7 +17326,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"
}
@@ -17381,7 +17340,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"
},
@@ -17876,7 +17834,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"
}
@@ -17891,20 +17848,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",
@@ -18099,7 +18068,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",
@@ -18136,6 +18104,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",
@@ -18366,7 +18342,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",
@@ -18567,7 +18542,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"
@@ -18665,7 +18639,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",
@@ -18697,7 +18670,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",
@@ -18714,8 +18686,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",
@@ -18749,7 +18720,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",
@@ -18864,7 +18834,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",
@@ -18909,7 +18878,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"
},
@@ -18921,7 +18889,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"
}
@@ -18973,7 +18940,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"
}
@@ -19122,6 +19088,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",
@@ -19659,7 +19633,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",
@@ -19676,7 +19649,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",
@@ -19690,7 +19662,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",
@@ -19779,7 +19750,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"
},
@@ -19893,6 +19863,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",
@@ -19990,8 +19976,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",
@@ -20056,7 +20041,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"
},
@@ -20214,7 +20198,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",
@@ -20226,7 +20209,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"
},
@@ -20238,7 +20220,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"
}
@@ -20260,7 +20241,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"
},
@@ -20274,14 +20254,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"
},
@@ -20302,7 +20280,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"
},
@@ -20326,7 +20303,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",
@@ -20340,7 +20316,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",
@@ -20358,7 +20333,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",
@@ -20377,7 +20351,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",
@@ -20397,7 +20370,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"
@@ -20444,7 +20416,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",
@@ -20712,7 +20683,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"
},
@@ -20727,7 +20697,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",
@@ -20749,7 +20718,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",
@@ -21072,6 +21040,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 93c97fedda0..5c1d9c1848a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rudder-transformer",
- "version": "1.54.2",
+ "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.1.8",
- "@rudderstack/workflow-engine": "^0.6.9",
+ "@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,13 +108,15 @@
"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",
"ua-parser-js": "^1.0.37",
"unset-value": "^2.0.1",
"uuid": "^9.0.0",
- "valid-url": "^1.0.9"
+ "valid-url": "^1.0.9",
+ "zod": "^3.22.4"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.6.3",
@@ -135,9 +138,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",
@@ -150,7 +154,7 @@
"madge": "^6.1.0",
"mocked-env": "^1.3.5",
"node-notifier": "^10.0.1",
- "prettier": "^2.8.8",
+ "prettier": "^3.2.4",
"semver": "^7.5.3",
"standard-version": "^9.5.0",
"supertest": "^6.3.3",
diff --git a/src/adapters/network.js b/src/adapters/network.js
index b0bd14374eb..0720638d12d 100644
--- a/src/adapters/network.js
+++ b/src/adapters/network.js
@@ -49,11 +49,15 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => {
const destType = statTags.destType ? statTags.destType : '';
const feature = statTags.feature ? statTags.feature : '';
const endpointPath = statTags.endpointPath ? statTags.endpointPath : '';
+ const requestMethod = statTags.requestMethod ? statTags.requestMethod : '';
+ const module = statTags.module ? statTags.module : '';
const statusCode = clientResponse.success ? clientResponse.response.status : '';
stats.timing('outgoing_request_latency', startTime, {
feature,
destType,
endpointPath,
+ requestMethod,
+ module,
});
stats.counter('outgoing_request_count', 1, {
feature,
@@ -61,6 +65,8 @@ const fireHTTPStats = (clientResponse, startTime, statTags) => {
endpointPath,
success: clientResponse.success,
statusCode,
+ requestMethod,
+ module,
});
};
diff --git a/src/adapters/networkHandlerFactory.js b/src/adapters/networkHandlerFactory.js
index e8c3748d15a..de80809a041 100644
--- a/src/adapters/networkHandlerFactory.js
+++ b/src/adapters/networkHandlerFactory.js
@@ -27,8 +27,9 @@ SUPPORTED_VERSIONS.forEach((version) => {
// },
// generic: GenericNetworkHandler,
// }
- handlers[version][dest] =
- require(`../${version}/destinations/${dest}/networkHandler`).networkHandler;
+ handlers[version][dest] = require(
+ `../${version}/destinations/${dest}/networkHandler`,
+ ).networkHandler;
} catch {
// Do nothing as exception indicates
// network handler is not defined for that destination
diff --git a/src/adapters/networkhandler/genericNetworkHandler.js b/src/adapters/networkhandler/genericNetworkHandler.js
index bcbcb212592..d9358085f44 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 b9ce7ef7fd4..f9ac8e3ae6e 100644
--- a/src/cdk/v2/destinations/algolia/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/algolia/procWorkflow.yaml
@@ -61,7 +61,7 @@ steps:
const filters = $.context.payload.filters;
const objectIDs = $.context.payload.objectIDs;
$.assert(!(filters && objectIDs), "event can't have both objectIds and filters at the same time.");
- $.assert(filters || objectIDs, "Either filters or objectIds is required.");
+ $.assert(filters.length || objectIDs.length, "Either filters or objectIds is required and must be non empty.");
- name: validatePayloadForClickEvent
condition: $.context.payload.eventType === "click"
diff --git a/src/cdk/v2/destinations/algolia/rtWorkflow.yaml b/src/cdk/v2/destinations/algolia/rtWorkflow.yaml
index 758a71bf5ba..f5442f32094 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 744c05a9a6d..3292d66c690 100644
--- a/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/bingads_audience/procWorkflow.yaml
@@ -50,4 +50,4 @@ steps:
const response = $.defaultRequestConfig();
response.body.JSON = payload;
response
- )
\ No newline at end of file
+ )
diff --git a/src/cdk/v2/destinations/bluecore/config.js b/src/cdk/v2/destinations/bluecore/config.js
new file mode 100644
index 00000000000..9b9cde9c663
--- /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 00000000000..be74c7c4b3c
--- /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 00000000000..5c3686f0ab6
--- /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 00000000000..8f6d59ec542
--- /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 00000000000..480bced699b
--- /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 00000000000..22ec254fe2e
--- /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 00000000000..829073bbccb
--- /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 50ac2a81630..1a54e8688ca 100644
--- a/src/cdk/v2/destinations/fullstory/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/fullstory/procWorkflow.yaml
@@ -5,7 +5,7 @@ bindings:
exportAll: true
- name: removeUndefinedAndNullValues
path: ../../../../v0/util
-
+
steps:
- name: validateInput
template: |
@@ -28,7 +28,7 @@ steps:
$.context.payload.uid = .message.userId;
$.context.payload.email = .message.context.traits.email;
$.context.payload.display_name = .message.context.traits.name;
-
+
- name: trackPayload
condition: $.context.messageType == "track"
template: |
@@ -42,9 +42,9 @@ steps:
condition: $.context.messageType == "track"
template: |
$.assert(.message.event, "event is required for track call")
-
+
- name: mapContextFieldsForTrack
- condition: $.context.messageType == "track"
+ condition: $.context.messageType == "track"
template: |
$.context.payload.context.browser = {
"url": .message.context.page.url,
@@ -67,7 +67,7 @@ steps:
"region": .message.properties.region,
"country": .message.properties.country,
};
-
+
- name: mapIdsForTrack
condition: $.context.messageType == "track"
template: |
@@ -99,6 +99,3 @@ steps:
"params": {},
"files": {}
})
-
-
-
diff --git a/src/cdk/v2/destinations/gladly/procWorkflow.yaml b/src/cdk/v2/destinations/gladly/procWorkflow.yaml
index fe8697bc31a..a53a0ca8f57 100644
--- a/src/cdk/v2/destinations/gladly/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/gladly/procWorkflow.yaml
@@ -13,7 +13,6 @@ bindings:
path: ../../../../adapters/network
- name: processAxiosResponse
path: ../../../../adapters/utils/networkUtils
-
steps:
- name: checkIfProcessed
diff --git a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml
index 341e5552c83..fc5d474d606 100644
--- a/src/cdk/v2/destinations/gladly/rtWorkflow.yaml
+++ b/src/cdk/v2/destinations/gladly/rtWorkflow.yaml
@@ -30,4 +30,4 @@ steps:
)[]
- name: finalPayload
template: |
- [...$.outputs.successfulEvents, ...$.outputs.failedEvents]
\ No newline at end of file
+ [...$.outputs.successfulEvents, ...$.outputs.failedEvents]
diff --git a/src/cdk/v2/destinations/heap/procWorkflow.yaml b/src/cdk/v2/destinations/heap/procWorkflow.yaml
index 0191b75d188..ac12e7e02a2 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/config.js b/src/cdk/v2/destinations/intercom/config.js
new file mode 100644
index 00000000000..518d805d41a
--- /dev/null
+++ b/src/cdk/v2/destinations/intercom/config.js
@@ -0,0 +1,81 @@
+const BASE_ENDPOINT = 'https://api.intercom.io';
+const BASE_EU_ENDPOINT = 'https://api.eu.intercom.io';
+const BASE_AU_ENDPOINT = 'https://api.au.intercom.io';
+
+const SEARCH_CONTACT_ENDPOINT = 'contacts/search';
+const CREATE_OR_UPDATE_COMPANY_ENDPOINT = 'companies';
+
+const ReservedAttributes = {
+ v1UserAttributes: [
+ 'userId',
+ 'email',
+ 'phone',
+ 'name',
+ 'createdAt',
+ 'firstName',
+ 'lastName',
+ 'firstname',
+ 'lastname',
+ 'company',
+ ],
+ v2UserAttributes: [
+ 'userId',
+ 'role',
+ 'email',
+ 'phone',
+ 'name',
+ 'avatar',
+ 'company',
+ 'ownerId',
+ 'lastName',
+ 'lastname',
+ 'firstName',
+ 'firstname',
+ 'createdAt',
+ 'timestamp',
+ 'lastSeenAt',
+ 'originalTimestamp',
+ 'unsubscribedFromEmails',
+ ],
+ v1CompanyAttributes: [
+ 'remoteCreatedAt',
+ 'monthlySpend',
+ 'industry',
+ 'website',
+ 'size',
+ 'plan',
+ 'name',
+ 'userId',
+ ],
+ v2CompanyAttributes: [
+ 'tags',
+ 'size',
+ 'plan',
+ 'name',
+ 'email',
+ 'userId',
+ 'website',
+ 'industry',
+ 'segments',
+ 'userCount',
+ 'createdAt',
+ 'sessionCount',
+ 'monthlySpend',
+ 'remoteCreatedAt',
+ ],
+};
+
+const ReservedCompanyProperties = ['id', 'name', 'industry'];
+
+const MetadataTypes = { richLink: ['url', 'value'], monetaryAmount: ['amount', 'currency'] };
+
+module.exports = {
+ BASE_ENDPOINT,
+ MetadataTypes,
+ BASE_EU_ENDPOINT,
+ BASE_AU_ENDPOINT,
+ ReservedAttributes,
+ SEARCH_CONTACT_ENDPOINT,
+ ReservedCompanyProperties,
+ CREATE_OR_UPDATE_COMPANY_ENDPOINT,
+};
diff --git a/src/cdk/v2/destinations/intercom/procWorkflow.yaml b/src/cdk/v2/destinations/intercom/procWorkflow.yaml
new file mode 100644
index 00000000000..0a8842d5e77
--- /dev/null
+++ b/src/cdk/v2/destinations/intercom/procWorkflow.yaml
@@ -0,0 +1,230 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ./utils
+ exportAll: true
+ - path: ../../bindings/jsontemplate
+ exportAll: true
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - name: getFieldValueFromMessage
+ path: ../../../../v0/util
+ - name: isDefinedAndNotNull
+ path: ../../../../v0/util
+ - name: addExternalIdToTraits
+ path: ../../../../v0/util
+ - path: ../../bindings/jsontemplate
+
+steps:
+ - name: checkIfProcessed
+ condition: .message.statusCode
+ template: |
+ $.batchMode ? .message.body.JSON : .message;
+ onComplete: return
+
+ - 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, .GROUP])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.apiKey, "Access Token is not present. Aborting");
+
+ - name: apiVersion
+ template: |
+ const version = $.isDefinedAndNotNull(.destination.Config.apiVersion) ? .destination.Config.apiVersion : "v2";
+ version;
+
+ - name: rEtlPayload
+ condition: .message.context.mappedToDestination
+ template: |
+ $.addExternalIdToTraits(.message);
+ const payload = $.getFieldValueFromMessage(.message, "traits");
+ payload;
+
+ - name: searchContact
+ condition: ($.outputs.messageType === {{$.EventType.IDENTIFY}} || $.outputs.messageType === {{$.EventType.GROUP}}) && $.outputs.apiVersion !== "v1"
+ template: |
+ const contactId = await $.searchContact(.message, .destination);
+ contactId;
+
+ - name: identifyTransformationForLatestVersion
+ condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1" && !.message.context.mappedToDestination
+ template: |
+ const payload = .message.({
+ external_id: {{{{$.getGenericPaths("userIdOnly")}}}},
+ email: {{{{$.getGenericPaths("email")}}}},
+ phone: {{{{$.getGenericPaths("phone")}}}},
+ avatar: {{{{$.getGenericPaths("avatar")}}}},
+ last_seen_at: $.toSeconds(.context.traits.lastSeenAt),
+ role: .traits.role || .context.traits.role,
+ signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt),
+ owner_id: Number(.traits.ownerId || .context.traits.ownerId) || undefined,
+ unsubscribed_from_emails: .traits.unsubscribedFromEmails || .context.traits.unsubscribedFromEmails
+ });
+ !(payload.external_id) && .destination.Config.sendAnonymousId ? payload.external_id = .message.anonymousId;
+ payload;
+
+ - name: identifyPayloadForLatestVersion
+ condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion !== "v1"
+ template: |
+ const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForLatestVersion;
+ payload.name = $.getName(.message);
+ payload.custom_attributes = .message.context.traits || {};
+ payload.custom_attributes = $.filterCustomAttributes(payload, "user", .destination);
+ payload.external_id = !payload.external_id && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.external_id;
+ $.context.payload = payload;
+ $.assert($.context.payload.external_id || $.context.payload.email, "Either email or userId is required for Identify call");
+ const endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts";
+ $.context.requestMethod = $.outputs.searchContact ? 'PUT' : 'POST';
+ $.context.endpoint = $.outputs.searchContact ? endpoint + "/" + $.outputs.searchContact : endpoint;
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: identifyTransformationForOlderVersion
+ condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion === "v1" && !.message.context.mappedToDestination
+ template: |
+ const payload = .message.({
+ user_id: {{{{$.getGenericPaths("userIdOnly")}}}},
+ email: {{{{$.getGenericPaths("email")}}}},
+ phone: {{{{$.getGenericPaths("phone")}}}},
+ signed_up_at: $.toSeconds(.traits.createdAt || .context.traits.createdAt),
+ last_seen_user_agent: .context.userAgent,
+ });
+ !(payload.user_id) && .destination.Config.sendAnonymousId ? payload.user_id = .message.anonymousId;
+ payload;
+
+ - name: identifyPayloadForOlderVersion
+ condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} && $.outputs.apiVersion === "v1"
+ template: |
+ let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.identifyTransformationForOlderVersion;
+ payload = {
+ ...payload,
+ name : $.getName(.message),
+ custom_attributes : .message.traits || .message.context.traits || {},
+ update_last_request_at: typeof .destination.Config.updateLastRequestAt === 'boolean' ? .destination.Config.updateLastRequestAt : true
+ }
+ payload.companies = $.getCompaniesList(payload);
+ payload.custom_attributes = !.message.context.mappedToDestination ? $.filterCustomAttributes(payload, "user", .destination);
+ payload.user_id = !payload.user_id && .destination.Config.sendAnonymousId && .message.anonymousId ? .message.anonymousId : payload.user_id;
+ $.context.payload = payload;
+ $.assert($.context.payload.user_id || $.context.payload.email, "Either of `email` or `userId` is required for Identify call");
+ $.context.requestMethod = 'POST';
+ $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "users";
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: trackTransformation
+ condition: $.outputs.messageType === {{$.EventType.TRACK}} && !.message.context.mappedToDestination
+ template: |
+ const timestamp = .message.().(
+ {{{{$.getGenericPaths("timestamp")}}}};
+ );
+ const payload = .message.({
+ event_name: .event,
+ user_id: {{{{$.getGenericPaths("userIdOnly")}}}},
+ email: {{{{$.getGenericPaths("email")}}}},
+ metadata: .properties
+ });
+ $.outputs.apiVersion !== "v1" ? payload.id = .message.properties.id || .message.traits.id;
+ $.outputs.apiVersion !== "v1" ? payload.created_at = $.toSeconds(timestamp);
+ $.outputs.apiVersion === "v1" ? payload.created = $.toSeconds(timestamp);
+ !(payload.user_id) && .destination.Config.sendAnonymousId ? payload.user_id = .message.anonymousId;
+ payload;
+
+ - name: trackPayload
+ condition: $.outputs.messageType === {{$.EventType.TRACK}}
+ template: |
+ let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.trackTransformation;
+ payload = $.addMetadataToPayload(payload);
+ $.context.payload = payload;
+ $.assert($.context.payload.event_name, "Event name is required for track call");
+ $.assert($.context.payload.user_id || $.context.payload.email, "Either email or userId is required for Track call");
+ $.context.requestMethod = 'POST';
+ $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "events";
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: groupTransformation
+ condition: $.outputs.messageType === {{$.EventType.GROUP}} && !.message.context.mappedToDestination
+ template: |
+ const payload = .message.({
+ company_id: {{{{$.getGenericPaths("groupId")}}}},
+ name: {{{{$.getGenericPaths("name")}}}},
+ website: {{{{$.getGenericPaths("website")}}}},
+ plan: .traits.plan || .context.traits.plan,
+ size: Number(.traits.size || .context.traits.size),
+ industry: .traits.industry || .context.traits.industry,
+ monthly_spend: .traits.monthlySpend || .context.traits.monthlySpend ? Number(.traits.monthlySpend || .context.traits.monthlySpend) : undefined,
+ remote_created_at: .traits.remoteCreatedAt || .context.traits.remoteCreatedAt ? Number(.traits.remoteCreatedAt || .context.traits.remoteCreatedAt) : undefined
+ });
+ payload;
+
+ - name: groupPayloadForLatestVersion
+ condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.outputs.apiVersion !== "v1"
+ steps:
+ - name: validateMessageAndPreparePayload
+ template: |
+ $.assert(.message.groupId, "groupId is required for group call");
+ const payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.groupTransformation;
+ payload.custom_attributes = .message.traits || {};
+ payload.custom_attributes = $.filterCustomAttributes(payload, "company", .destination);
+ $.context.payload = payload;
+ - name: whenSearchContactFound
+ condition: $.isDefinedAndNotNull($.outputs.searchContact)
+ template: |
+ const contactId = $.outputs.searchContact;
+ const companyId = await $.createOrUpdateCompany($.context.payload, .destination);
+ $.assert(companyId, "Unable to create or update company");
+ $.context.payload = {
+ id: companyId,
+ };
+ $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "contacts" + "/" + contactId + "/" + "companies";
+ else:
+ name: whenSearchContactNotFound
+ template: |
+ $.context.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies";
+ - name: prepareFinalPayload
+ template: |
+ $.context.requestMethod = 'POST';
+ $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: groupPayloadForOlderVersion
+ condition: $.outputs.messageType === {{$.EventType.GROUP}} && $.outputs.apiVersion === "v1"
+ template: |
+ $.context.response = [];
+ const response = $.defaultRequestConfig();
+ let payload = .message.context.mappedToDestination ? $.outputs.rEtlPayload : $.outputs.groupTransformation;
+ payload = {
+ ...payload,
+ custom_attributes : $.getFieldValueFromMessage(.message, "traits") || {}
+ }
+ payload.custom_attributes = $.filterCustomAttributes(payload, "company", .destination);
+ response.body.JSON = $.removeUndefinedAndNullValues(payload);
+ response.endpoint = $.getBaseEndpoint(.destination) + "/" + "companies";
+ response.headers = $.getHeaders(.destination, $.outputs.apiVersion);
+ response.method = "POST";
+ response.userId = .message.anonymousId;
+ $.context.response.push(response);
+ const attachUserAndCompanyResponse = $.attachUserAndCompany(.message, .destination.Config);
+ attachUserAndCompanyResponse ? attachUserAndCompanyResponse.userId = .message.anonymousId;
+ attachUserAndCompanyResponse ? $.context.response.push(attachUserAndCompanyResponse);
+
+ - name: buildResponseForProcessTransformation
+ description: Build response for multiple transformed event
+ condition: $.context.response && $.context.response.length > 0
+ template: |
+ $.context.response;
+ else:
+ name: buildResponseForProcessTransformation
+ description: Build response for single transformed event
+ template: |
+ const response = $.defaultRequestConfig();
+ response.body.JSON = $.context.payload;
+ response.endpoint = $.context.endpoint;
+ response.method = $.context.requestMethod;
+ response.headers = $.getHeaders(.destination, $.outputs.apiVersion);
+ $.outputs.apiVersion === "v1" && $.outputs.messageType !== {{$.EventType.GROUP}} ? response.userId = .message.anonymousId;
+ response;
diff --git a/src/cdk/v2/destinations/intercom/rtWorkflow.yaml b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml
new file mode 100644
index 00000000000..edb7267b84c
--- /dev/null
+++ b/src/cdk/v2/destinations/intercom/rtWorkflow.yaml
@@ -0,0 +1,33 @@
+bindings:
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+ - name: isDefinedAndNotNull
+ path: ../../../../v0/util
+
+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{$.isDefinedAndNotNull(.output)}.({
+ "batchedRequest": .output,
+ "batched": false,
+ "destination": ^[idx].destination,
+ "metadata": ^[idx].metadata[],
+ "statusCode": 200
+ })[]
+ - name: failedEvents
+ template: |
+ $.outputs.transform#idx.error.(
+ $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
+ )[]
+ - name: finalPayload
+ template: |
+ [...$.outputs.successfulEvents, ...$.outputs.failedEvents]
diff --git a/src/cdk/v2/destinations/intercom/utils.js b/src/cdk/v2/destinations/intercom/utils.js
new file mode 100644
index 00000000000..ba3063c9f9d
--- /dev/null
+++ b/src/cdk/v2/destinations/intercom/utils.js
@@ -0,0 +1,385 @@
+const md5 = require('md5');
+const get = require('get-value');
+const { NetworkError } = require('@rudderstack/integrations-lib');
+const tags = require('../../../../v0/util/tags');
+const { httpPOST } = require('../../../../adapters/network');
+const {
+ processAxiosResponse,
+ getDynamicErrorType,
+} = require('../../../../adapters/utils/networkUtils');
+const {
+ flattenJson,
+ getIntegrationsObj,
+ isDefinedAndNotNull,
+ isHttpStatusSuccess,
+ defaultRequestConfig,
+ getFieldValueFromMessage,
+ defaultPostRequestConfig,
+ removeUndefinedAndNullValues,
+} = require('../../../../v0/util');
+const { JSON_MIME_TYPE } = require('../../../../v0/util/constant');
+const {
+ BASE_ENDPOINT,
+ MetadataTypes,
+ BASE_EU_ENDPOINT,
+ BASE_AU_ENDPOINT,
+ ReservedAttributes,
+ SEARCH_CONTACT_ENDPOINT,
+ ReservedCompanyProperties,
+ CREATE_OR_UPDATE_COMPANY_ENDPOINT,
+} = require('./config');
+
+/**
+ * Returns destination request headers
+ * @param {*} destination
+ * @param {*} apiVersion
+ * @returns
+ */
+const getHeaders = (destination, apiVersion) => ({
+ 'Content-Type': JSON_MIME_TYPE,
+ Authorization: `Bearer ${destination.Config.apiKey}`,
+ Accept: JSON_MIME_TYPE,
+ 'Intercom-Version': apiVersion === 'v1' ? '1.4' : '2.10',
+});
+
+/**
+ * Returns destination request base endpoint
+ * @param {*} destination
+ * @returns
+ */
+const getBaseEndpoint = (destination) => {
+ const { apiServer } = destination.Config;
+ let { apiVersion } = destination.Config;
+ apiVersion = isDefinedAndNotNull(apiVersion) ? apiVersion : 'v2';
+
+ if (apiVersion === 'v1') return BASE_ENDPOINT;
+ switch (apiServer) {
+ case 'eu':
+ return BASE_EU_ENDPOINT;
+ case 'au':
+ return BASE_AU_ENDPOINT;
+ default:
+ return BASE_ENDPOINT;
+ }
+};
+
+/**
+ * Returns contact lookup field
+ * @param {*} message
+ * @returns
+ */
+const getLookUpField = (message) => {
+ let lookupField = 'email';
+ const integrationsObj = getIntegrationsObj(message, 'INTERCOM');
+ if (integrationsObj && isDefinedAndNotNull(integrationsObj.lookup)) {
+ lookupField = integrationsObj.lookup;
+ }
+ return lookupField;
+};
+
+/**
+ * Returns the value of name field
+ * @param {*} message
+ * @returns
+ */
+const getName = (message) => {
+ const name = message?.traits?.name || message?.context?.traits?.name;
+ if (name) return name;
+ const firstName = getFieldValueFromMessage(message, 'firstName');
+ const lastName = getFieldValueFromMessage(message, 'lastName');
+ if (firstName && lastName) {
+ return `${firstName} ${lastName}`;
+ }
+
+ if (firstName || lastName) {
+ return firstName || lastName;
+ }
+ return undefined;
+};
+
+/**
+ * Returns company payload
+ * @param {*} payload
+ * @returns
+ */
+const getCompaniesList = (payload) => {
+ const company = get(payload, 'custom_attributes.company');
+ if (!company) return undefined;
+ const companiesList = [];
+ if (company.name || company.id) {
+ const customAttributes = {};
+ Object.keys(company).forEach((key) => {
+ // If key is not in ReservedCompanyProperties
+ if (!ReservedCompanyProperties.includes(key)) {
+ const val = company[key];
+ if (val !== Object(val)) {
+ customAttributes[key] = val;
+ } else {
+ customAttributes[key] = JSON.stringify(val);
+ }
+ }
+ });
+
+ companiesList.push({
+ company_id: company.id || md5(company.name),
+ custom_attributes: removeUndefinedAndNullValues(customAttributes),
+ name: company.name,
+ industry: company.industry,
+ });
+ }
+ return companiesList;
+};
+
+/**
+ * Returns if email or userId is present in payload or not
+ * @param {*} message
+ * @param {*} Config
+ * @returns
+ */
+const checkIfEmailOrUserIdPresent = (message, Config) => {
+ const { context, anonymousId } = message;
+ let { userId } = message;
+ if (Config.sendAnonymousId && !userId) {
+ userId = anonymousId;
+ }
+ return !!(userId || context?.traits?.email);
+};
+
+/**
+ * Returns add user to company payload
+ * @param {*} message
+ * @param {*} Config
+ * @returns
+ */
+const attachUserAndCompany = (message, Config) => {
+ if (!checkIfEmailOrUserIdPresent(message, Config)) return undefined;
+ const email = message.context?.traits?.email;
+ const { userId, anonymousId, traits, groupId } = message;
+ const requestBody = {};
+ if (userId) {
+ requestBody.user_id = userId;
+ }
+ if (Config.sendAnonymousId && !userId) {
+ requestBody.user_id = anonymousId;
+ }
+ if (email) {
+ requestBody.email = email;
+ }
+ const companyObj = {
+ company_id: groupId,
+ };
+ if (traits?.name) {
+ companyObj.name = traits.name;
+ }
+ requestBody.companies = [companyObj];
+ const response = defaultRequestConfig();
+ response.method = defaultPostRequestConfig.requestMethod;
+ response.endpoint = `${BASE_ENDPOINT}/users`;
+ response.headers = {
+ 'Content-Type': JSON_MIME_TYPE,
+ Authorization: `Bearer ${Config.apiKey}`,
+ Accept: JSON_MIME_TYPE,
+ 'Intercom-Version': '1.4',
+ };
+ response.body.JSON = requestBody;
+ response.userId = anonymousId;
+ return response;
+};
+
+/**
+ * Returns custom attributes for identify and group calls (for contact and company in intercom)
+ * @param {*} payload
+ * @param {*} type
+ * @returns
+ */
+const filterCustomAttributes = (payload, type, destination) => {
+ let ReservedAttributesList;
+ let { apiVersion } = destination.Config;
+ apiVersion = isDefinedAndNotNull(apiVersion) ? apiVersion : 'v2';
+ if (type === 'user') {
+ ReservedAttributesList =
+ apiVersion === 'v1'
+ ? ReservedAttributes.v1UserAttributes
+ : ReservedAttributes.v2UserAttributes;
+ } else {
+ ReservedAttributesList =
+ apiVersion === 'v1'
+ ? ReservedAttributes.v1CompanyAttributes
+ : ReservedAttributes.v2CompanyAttributes;
+ }
+ let customAttributes = { ...get(payload, 'custom_attributes') };
+ if (customAttributes) {
+ ReservedAttributesList.forEach((trait) => {
+ if (customAttributes[trait]) delete customAttributes[trait];
+ });
+ if (isDefinedAndNotNull(customAttributes) && Object.keys(customAttributes).length > 0) {
+ customAttributes =
+ apiVersion === 'v1' ? flattenJson(customAttributes) : flattenJson(customAttributes, '_');
+ }
+ }
+ return Object.keys(customAttributes).length === 0 ? undefined : customAttributes;
+};
+
+/**
+ * Api call to search contact in intercom to returns id of contact
+ * Ref doc : https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Contacts/SearchContacts/
+ * @param {*} message
+ * @param {*} destination
+ * @returns
+ */
+const searchContact = async (message, destination) => {
+ const lookupField = getLookUpField(message);
+ const lookupFieldValue = getFieldValueFromMessage(message, lookupField);
+ const data = JSON.stringify({
+ query: {
+ operator: 'AND',
+ value: [
+ {
+ field: lookupField,
+ operator: '=',
+ value: lookupFieldValue,
+ },
+ ],
+ },
+ });
+
+ 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',
+ endpointPath: '/contacts/search',
+ requestMethod: 'POST',
+ module: 'router',
+ },
+ );
+ const processedUserResponse = processAxiosResponse(response);
+ if (isHttpStatusSuccess(processedUserResponse.status)) {
+ return processedUserResponse.response?.data.length > 0
+ ? processedUserResponse.response?.data[0]?.id
+ : null;
+ }
+
+ throw new NetworkError(
+ `Unable to search contact due to : ${JSON.stringify(processedUserResponse?.response?.errors)}`,
+ processedUserResponse?.status,
+ {
+ [tags]: getDynamicErrorType(processedUserResponse?.status),
+ },
+ processedUserResponse,
+ );
+};
+
+/**
+ * Api call to create or update companies in intercom
+ * Ref doc : https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Companies/createOrUpdateCompany/
+ * @param {*} payload
+ * @param {*} destination
+ * @returns
+ */
+const createOrUpdateCompany = async (payload, destination) => {
+ const headers = getHeaders(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',
+ endpointPath: '/companies',
+ requestMethod: 'POST',
+ module: 'router',
+ },
+ );
+
+ const processedResponse = processAxiosResponse(response);
+ if (isHttpStatusSuccess(processedResponse.status)) {
+ return processedResponse.response?.id;
+ }
+
+ throw new NetworkError(
+ `Unable to Create or Update Company due to : ${JSON.stringify(
+ processedResponse?.response?.errors,
+ )}`,
+ processedResponse?.status,
+ {
+ [tags]: getDynamicErrorType(processedResponse?.status),
+ },
+ processedResponse,
+ );
+};
+
+/**
+ * Returns metadata object
+ * @param {*} metadata
+ * @returns
+ */
+const separateReservedAndRestMetadata = (metadata) => {
+ const reservedMetadata = {};
+ const restMetadata = {};
+ if (metadata) {
+ Object.entries(metadata).forEach(([key, value]) => {
+ if (value && typeof value === 'object') {
+ const hasMonetaryAmountKeys = MetadataTypes.monetaryAmount.every((type) => type in value);
+ const hasRichLinkKeys = MetadataTypes.richLink.every((type) => type in value);
+ if (hasMonetaryAmountKeys || hasRichLinkKeys) {
+ reservedMetadata[key] = value;
+ } else {
+ restMetadata[key] = value;
+ }
+ } else {
+ restMetadata[key] = value;
+ }
+ });
+ }
+
+ // Return the separated metadata objects
+ return { reservedMetadata, restMetadata };
+};
+
+/**
+ * Returns final payload with metadata
+ * @param {*} payload
+ * @returns
+ */
+const addMetadataToPayload = (payload) => {
+ let finalPayload = payload;
+ if (finalPayload.metadata) {
+ // reserved metadata contains JSON objects that does not requires flattening
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(
+ finalPayload.metadata,
+ );
+ finalPayload = {
+ ...finalPayload,
+ metadata: { ...reservedMetadata, ...flattenJson(restMetadata) },
+ };
+ }
+ return finalPayload;
+};
+
+module.exports = {
+ getName,
+ getHeaders,
+ searchContact,
+ getLookUpField,
+ getBaseEndpoint,
+ getCompaniesList,
+ addMetadataToPayload,
+ attachUserAndCompany,
+ createOrUpdateCompany,
+ filterCustomAttributes,
+ checkIfEmailOrUserIdPresent,
+ separateReservedAndRestMetadata,
+};
diff --git a/src/cdk/v2/destinations/intercom/utils.test.js b/src/cdk/v2/destinations/intercom/utils.test.js
new file mode 100644
index 00000000000..e651b4ea5dc
--- /dev/null
+++ b/src/cdk/v2/destinations/intercom/utils.test.js
@@ -0,0 +1,765 @@
+const md5 = require('md5');
+const axios = require('axios');
+const {
+ getName,
+ getHeaders,
+ searchContact,
+ getLookUpField,
+ getBaseEndpoint,
+ getCompaniesList,
+ addMetadataToPayload,
+ attachUserAndCompany,
+ createOrUpdateCompany,
+ filterCustomAttributes,
+ checkIfEmailOrUserIdPresent,
+ separateReservedAndRestMetadata,
+} = require('./utils');
+const { BASE_ENDPOINT, BASE_EU_ENDPOINT, BASE_AU_ENDPOINT } = require('./config');
+
+jest.mock('axios', () => ({
+ ...jest.requireActual('axios'),
+ post: jest.fn(),
+}));
+
+describe('separateReservedAndRestMetadata utility test', () => {
+ it('separate reserved and rest metadata', () => {
+ const metadata = {
+ property1: 1,
+ property2: 'test',
+ property3: true,
+ property4: {
+ property1: 1,
+ property2: 'test',
+ property3: {
+ subProp1: {
+ a: 'a',
+ b: 'b',
+ },
+ subProp2: ['a', 'b'],
+ },
+ },
+ property5: {},
+ property6: [],
+ property7: null,
+ property8: undefined,
+ revenue: {
+ amount: 1232,
+ currency: 'inr',
+ test: 123,
+ },
+ price: {
+ amount: 3000,
+ currency: 'USD',
+ },
+ article: {
+ url: 'https://example.org/ab1de.html',
+ value: 'the dude abides',
+ },
+ };
+ const expectedReservedMetadata = {
+ revenue: {
+ amount: 1232,
+ currency: 'inr',
+ test: 123,
+ },
+ price: {
+ amount: 3000,
+ currency: 'USD',
+ },
+ article: {
+ url: 'https://example.org/ab1de.html',
+ value: 'the dude abides',
+ },
+ };
+ const expectedRestMetadata = {
+ property1: 1,
+ property2: 'test',
+ property3: true,
+ property4: {
+ property1: 1,
+ property2: 'test',
+ property3: {
+ subProp1: {
+ a: 'a',
+ b: 'b',
+ },
+ subProp2: ['a', 'b'],
+ },
+ },
+ property5: {},
+ property6: [],
+ property7: null,
+ property8: undefined,
+ };
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata);
+
+ expect(expectedReservedMetadata).toEqual(reservedMetadata);
+ expect(expectedRestMetadata).toEqual(restMetadata);
+ });
+
+ it('reserved metadata types not present in input metadata', () => {
+ const metadata = {
+ property1: 1,
+ property2: 'test',
+ property3: true,
+ property4: {
+ property1: 1,
+ property2: 'test',
+ property3: {
+ subProp1: {
+ a: 'a',
+ b: 'b',
+ },
+ subProp2: ['a', 'b'],
+ },
+ },
+ property5: {},
+ property6: [],
+ property7: null,
+ property8: undefined,
+ };
+ const expectedRestMetadata = {
+ property1: 1,
+ property2: 'test',
+ property3: true,
+ property4: {
+ property1: 1,
+ property2: 'test',
+ property3: {
+ subProp1: {
+ a: 'a',
+ b: 'b',
+ },
+ subProp2: ['a', 'b'],
+ },
+ },
+ property5: {},
+ property6: [],
+ property7: null,
+ property8: undefined,
+ };
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata);
+
+ expect({}).toEqual(reservedMetadata);
+ expect(expectedRestMetadata).toEqual(restMetadata);
+ });
+
+ it('metadata input contains only reserved metadata types', () => {
+ const metadata = {
+ revenue: {
+ amount: 1232,
+ currency: 'inr',
+ test: 123,
+ },
+ price: {
+ amount: 3000,
+ currency: 'USD',
+ },
+ article: {
+ url: 'https://example.org/ab1de.html',
+ value: 'the dude abides',
+ },
+ };
+ const expectedReservedMetadata = {
+ revenue: {
+ amount: 1232,
+ currency: 'inr',
+ test: 123,
+ },
+ price: {
+ amount: 3000,
+ currency: 'USD',
+ },
+ article: {
+ url: 'https://example.org/ab1de.html',
+ value: 'the dude abides',
+ },
+ };
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata);
+
+ expect(expectedReservedMetadata).toEqual(reservedMetadata);
+ expect({}).toEqual(restMetadata);
+ });
+
+ it('empty metadata object', () => {
+ const metadata = {};
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata);
+ expect({}).toEqual(reservedMetadata);
+ expect({}).toEqual(restMetadata);
+ });
+
+ it('null/undefined metadata', () => {
+ const metadata = null;
+ const { reservedMetadata, restMetadata } = separateReservedAndRestMetadata(metadata);
+ expect({}).toEqual(reservedMetadata);
+ expect({}).toEqual(restMetadata);
+ });
+});
+
+describe('getBaseEndpoint utility test', () => {
+ it('Should return BASE_ENDPOINT when destination.Config.apiServer is not "eu" or "au"', () => {
+ const destination = {
+ Config: {
+ apiServer: 'us',
+ },
+ };
+ const result = getBaseEndpoint(destination);
+ expect(result).toBe(BASE_ENDPOINT);
+ });
+
+ it('Should return BASE_EU_ENDPOINT when destination.Config.apiServer is "eu"', () => {
+ const destination = {
+ Config: {
+ apiServer: 'eu',
+ apiVersion: 'v2',
+ },
+ };
+ const result = getBaseEndpoint(destination);
+ expect(result).toBe(BASE_EU_ENDPOINT);
+ });
+
+ it('Should return BASE_AU_ENDPOINT when destination.Config.apiServer is "au"', () => {
+ const destination = {
+ Config: {
+ apiServer: 'au',
+ apiVersion: 'v2',
+ },
+ };
+ const result = getBaseEndpoint(destination);
+ expect(result).toBe(BASE_AU_ENDPOINT);
+ });
+
+ it('Should return BASE_ENDPOINT when destination.Config.apiServer is null', () => {
+ const destination = {
+ Config: {
+ apiServer: null,
+ },
+ };
+ const result = getBaseEndpoint(destination);
+ expect(result).toBe(BASE_ENDPOINT);
+ });
+});
+
+describe('getHeaders utility test', () => {
+ it('Should return an object with the correct headers', () => {
+ const destination = {
+ Config: {
+ apiKey: 'testApiKey',
+ },
+ };
+
+ const expectedHeaders = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${destination.Config.apiKey}`,
+ Accept: 'application/json',
+ 'Intercom-Version': '2.10',
+ };
+ const headers = getHeaders(destination, 'v2');
+ expect(headers).toEqual(expectedHeaders);
+ });
+});
+
+describe('getLookUpField utility test', () => {
+ it('Should return email as default lookup field when no integration object is found', () => {
+ const message = {};
+ const result = getLookUpField(message);
+ expect(result).toBe('email');
+ });
+});
+
+describe('getName utility test', () => {
+ it('Should return the concatenation of firstName and lastName fields when both exist', () => {
+ const message = {
+ context: {
+ traits: {
+ firstName: 'John',
+ lastName: 'Doe',
+ },
+ },
+ };
+ expect(getName(message)).toBe('John Doe');
+ });
+
+ it('Should return the firstName field when only firstName exists', () => {
+ const message = {
+ context: {
+ traits: {
+ firstName: 'John',
+ },
+ },
+ };
+ expect(getName(message)).toBe('John');
+ });
+
+ it('Should return the lastName field when only lastName exists', () => {
+ const message = {
+ context: {
+ traits: {
+ lastName: 'Doe',
+ },
+ },
+ };
+ expect(getName(message)).toBe('Doe');
+ });
+
+ it('Should return undefined when both message.traits and message.context.traits are undefined', () => {
+ const message = {};
+ expect(getName(message)).toBeUndefined();
+ });
+});
+
+describe('filterCustomAttributes utility test', () => {
+ it('Should return an empty object when all custom attributes are reserved attributes', () => {
+ const payload = { custom_attributes: { email: 'test@rudder.com', name: 'rudder test' } };
+ const result = filterCustomAttributes(payload, 'user', { Config: { apiVersion: 'v2' } });
+ expect(result).toBeUndefined();
+ });
+
+ it('Should return a flattened object when custom attributes are not null, not reserved attributes and nested', () => {
+ const payload = {
+ custom_attributes: { source: 'rudder-js-sdk', data: { nestedAttribute: 'nestedValue' } },
+ };
+ const result = filterCustomAttributes(payload, 'user', { Config: { apiVersion: 'v2' } });
+ expect(result).toEqual({ source: 'rudder-js-sdk', data_nestedAttribute: 'nestedValue' });
+ });
+
+ it('Should return null when custom_attributes is null', () => {
+ const payload = { custom_attributes: null };
+ const result = filterCustomAttributes(payload, 'company', { Config: { apiVersion: 'v2' } });
+ expect(result).toBeUndefined();
+ });
+});
+
+describe('addMetadataToPayload utility test', () => {
+ it('Should return the same payload if metadata is present but empty', () => {
+ const payload = { data: 'test', metadata: {} };
+ const result = addMetadataToPayload(payload);
+ expect(result).toEqual(payload);
+ });
+
+ it('should add flattened metadata to payload if metadata is present and not empty', () => {
+ const payload = {
+ data: 'test',
+ metadata: {
+ amount: 30,
+ currency: 'USD',
+ url: 'https//test.com',
+ restData: { source: 'rudderStack' },
+ },
+ };
+ const result = addMetadataToPayload(payload);
+ expect(result).toEqual({
+ data: 'test',
+ metadata: {
+ amount: 30,
+ currency: 'USD',
+ url: 'https//test.com',
+ 'restData.source': 'rudderStack',
+ },
+ });
+ });
+});
+
+describe('searchContact utility test', () => {
+ it('Should successfully search contact by email', async () => {
+ const message = { context: { traits: { email: 'test@rudderlabs.com' } } };
+ const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } };
+ axios.post.mockResolvedValue({
+ status: 200,
+ data: {
+ type: 'list',
+ total_count: 1,
+ pages: {
+ type: 'pages',
+ page: 1,
+ per_page: 50,
+ total_pages: 1,
+ },
+ data: [
+ {
+ type: 'contact',
+ id: '1',
+ email: 'test@rudderlabs.com',
+ },
+ ],
+ },
+ });
+
+ const result = await searchContact(message, destination);
+ expect(result).toEqual('1');
+ });
+
+ it('Should return first contact id if multiple contact exist with give search field', async () => {
+ const message = {
+ context: {
+ traits: { email: 'test@rudderlabs.com', phone: '+91 9999999999' },
+ integrations: { INTERCOM: { lookup: 'phone' } },
+ },
+ };
+ const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } };
+ axios.post.mockResolvedValue({
+ status: 200,
+ data: {
+ type: 'list',
+ total_count: 1,
+ pages: {
+ type: 'pages',
+ page: 1,
+ per_page: 50,
+ total_pages: 1,
+ },
+ data: [
+ {
+ type: 'contact',
+ id: '1',
+ email: 'test@rudderlabs.com',
+ phone: '+91 9999999999',
+ },
+ {
+ type: 'contact',
+ id: '2',
+ email: 'test+1@rudderlabs.com',
+ phone: '+91 9999999999',
+ },
+ ],
+ },
+ });
+
+ const result = await searchContact(message, destination);
+ expect(result).toEqual('1');
+ });
+
+ it('Should return null if no contact is found', async () => {
+ const message = {
+ context: {
+ traits: { email: 'test+10@rudderlabs.com', phone: '+91 9999999999' },
+ integrations: { INTERCOM: { lookup: 'email' } },
+ },
+ };
+ const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } };
+ axios.post.mockResolvedValue({
+ status: 200,
+ data: {
+ type: 'list',
+ total_count: 0,
+ pages: {
+ type: 'pages',
+ page: 1,
+ per_page: 50,
+ total_pages: 0,
+ },
+ data: [],
+ },
+ });
+
+ const result = await searchContact(message, destination);
+ expect(result).toBeNull();
+ });
+
+ it('Should throw an error in case if axios calls returns an error', async () => {
+ const message = {
+ context: {
+ traits: { email: 'test+3@rudderlabs.com', phone: '+91 9999999999' },
+ integrations: { INTERCOM: { lookup: 'email' } },
+ },
+ };
+ const destination = { Config: { apiKey: 'invalidTestApiKey', apiServer: 'us' } };
+ axios.post.mockRejectedValue({
+ status: 401,
+ data: {
+ type: 'error.list',
+ request_id: 'request_400',
+ errors: [
+ {
+ code: 'unauthorized',
+ message: 'Access Token Invalid',
+ },
+ ],
+ },
+ });
+
+ try {
+ const result = await searchContact(message, destination);
+ expect(result).toEqual('');
+ } catch (error) {
+ expect(error.message).toEqual(
+ 'Unable to search contact due to : [{"code":"unauthorized","message":"Access Token Invalid"}]',
+ );
+ }
+ });
+});
+
+describe('createOrUpdateCompany utility test', () => {
+ it('Should successfully create company', async () => {
+ const payload = {
+ company_id: 'rudderlabs',
+ name: 'RudderStack',
+ website: 'www.rudderstack.com',
+ plan: 'enterprise',
+ size: 500,
+ industry: 'CDP',
+ custom_attributes: {},
+ };
+ const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } };
+ axios.post.mockResolvedValue({
+ status: 200,
+ data: {
+ type: 'company',
+ company_id: 'rudderlabs',
+ id: '1',
+ name: 'RudderStack',
+ website: 'www.rudderstack.com',
+ plan: 'enterprise',
+ size: 500,
+ industry: 'CDP',
+ remote_created_at: 1374138000,
+ created_at: 1701930212,
+ updated_at: 1701930212,
+ },
+ });
+
+ const result = await createOrUpdateCompany(payload, destination);
+ expect(result).toEqual('1');
+ });
+
+ it('Should throw an error in case if axios calls returns an error', async () => {
+ const payload = {
+ company_id: 'rudderlabs',
+ name: 'RudderStack',
+ website: 'www.rudderstack.com',
+ plan: 'enterprise',
+ size: 500,
+ industry: 'CDP',
+ testData: true,
+ };
+ const destination = { Config: { apiKey: 'testApiKey', apiServer: 'us' } };
+ axios.post.mockRejectedValue({
+ status: 400,
+ data: {
+ type: 'error.list',
+ request_id: 'request_400',
+ errors: [
+ {
+ code: 'bad_request',
+ message: "bad 'testData' parameter",
+ },
+ ],
+ },
+ });
+
+ try {
+ const result = await createOrUpdateCompany(payload, destination);
+ expect(result).toEqual('');
+ } catch (error) {
+ expect(error.message).toEqual(
+ 'Unable to Create or Update Company due to : [{"code":"bad_request","message":"bad \'testData\' parameter"}]',
+ );
+ }
+ });
+
+ it('Should throw an error in case if axios calls returns an error', async () => {
+ const payload = {
+ company_id: 'rudderlabs',
+ name: 'RudderStack',
+ website: 'www.rudderstack.com',
+ plan: 'enterprise',
+ size: 500,
+ industry: 'CDP',
+ testData: true,
+ };
+ const destination = { Config: { apiKey: 'invalidTestApiKey', apiServer: 'us' } };
+ axios.post.mockRejectedValue({
+ status: 400,
+ data: {
+ type: 'error.list',
+ request_id: 'request_400',
+ errors: [
+ {
+ code: 'unauthorized',
+ message: 'Access Token Invalid',
+ },
+ ],
+ },
+ });
+
+ try {
+ const result = await createOrUpdateCompany(payload, destination);
+ expect(result).toEqual('');
+ } catch (error) {
+ expect(error.message).toEqual(
+ 'Unable to Create or Update Company due to : [{"code":"unauthorized","message":"Access Token Invalid"}]',
+ );
+ }
+ });
+});
+
+describe('checkIfEmailOrUserIdPresent utility test', () => {
+ it('Should return true when userId is present in message', () => {
+ const message = {
+ userId: '12345',
+ context: {
+ traits: {
+ email: 'test@example.com',
+ },
+ },
+ };
+ const Config = {
+ sendAnonymousId: true,
+ apiKey: '1234567890',
+ };
+ const result = checkIfEmailOrUserIdPresent(message, Config);
+ expect(result).toBe(true);
+ });
+
+ it('Should return true when email is present in message', () => {
+ const message = {
+ context: {
+ traits: {
+ email: 'test@example.com',
+ },
+ },
+ };
+ const Config = {
+ sendAnonymousId: true,
+ apiKey: '1234567890',
+ };
+ const result = checkIfEmailOrUserIdPresent(message, Config);
+ expect(result).toBe(true);
+ });
+
+ it('Should return true when both userId and email are present in message', () => {
+ const message = {
+ userId: '12345',
+ context: {
+ traits: {
+ email: 'test@example.com',
+ },
+ },
+ };
+ const Config = {
+ sendAnonymousId: true,
+ apiKey: '1234567890',
+ };
+ const result = checkIfEmailOrUserIdPresent(message, Config);
+ expect(result).toBe(true);
+ });
+
+ it('Should return false when no email or userId is present', () => {
+ const message = { anonymousId: 'anon@123' };
+ const Config = {
+ sendAnonymousId: false,
+ apiKey: '1234567890',
+ };
+ const result = checkIfEmailOrUserIdPresent(message, Config);
+ expect(result).toBe(false);
+ });
+});
+
+describe('getCompaniesList utility test', () => {
+ it('Should return an array with one object containing the company_id, custom_attributes, name and industry properties when the payload contains a company object with name or id properties', () => {
+ const payload = {
+ custom_attributes: {
+ company: {
+ name: 'rudderlabs',
+ industry: 'Tech',
+ },
+ },
+ };
+
+ const result = getCompaniesList(payload);
+
+ expect(result).toEqual([
+ {
+ company_id: md5('rudderlabs'),
+ custom_attributes: {},
+ name: 'rudderlabs',
+ industry: 'Tech',
+ },
+ ]);
+ });
+
+ it('Should return undefined when the payload does not contain a company object', () => {
+ const payload = {};
+ const result = getCompaniesList(payload);
+ expect(result).toBeUndefined();
+ });
+
+ it('Should return an empty array when the company object in the payload does not have name or id properties', () => {
+ const payload = {
+ custom_attributes: {
+ company: {},
+ },
+ };
+ const result = getCompaniesList(payload);
+ expect(result).toEqual([]);
+ });
+
+ it('Should return an array with one object containing the company_id, custom_attributes, name and industry properties when the payload contains a company object with name and id properties', () => {
+ const payload = {
+ custom_attributes: {
+ company: {
+ name: 'Company A',
+ id: '123',
+ industry: 'Tech',
+ },
+ },
+ };
+ const result = getCompaniesList(payload);
+ expect(result).toEqual([
+ {
+ company_id: '123',
+ custom_attributes: {},
+ name: 'Company A',
+ industry: 'Tech',
+ },
+ ]);
+ });
+});
+
+describe('attachUserAndCompany utility test', () => {
+ it('should return a valid response object when only email and groupId are present', () => {
+ const message = {
+ context: {
+ traits: {
+ email: 'test@example.com',
+ },
+ },
+ groupId: 'group123',
+ };
+ const Config = {
+ sendAnonymousId: false,
+ apiKey: 'testApiKey',
+ };
+
+ const expectedResponse = {
+ method: 'POST',
+ params: {},
+ type: 'REST',
+ version: '1',
+ endpoint: 'https://api.intercom.io/users',
+ files: {},
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer testApiKey',
+ Accept: 'application/json',
+ 'Intercom-Version': '1.4',
+ },
+ body: {
+ FORM: {},
+ JSON: {
+ email: 'test@example.com',
+ companies: [
+ {
+ company_id: 'group123',
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ },
+ userId: undefined,
+ };
+ const response = attachUserAndCompany(message, Config);
+ expect(response).toEqual(expectedResponse);
+ });
+});
diff --git a/src/cdk/v2/destinations/koala/procWorkflow.yaml b/src/cdk/v2/destinations/koala/procWorkflow.yaml
new file mode 100644
index 00000000000..9ec0202b131
--- /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 00000000000..335293b6dbc
--- /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 557b1a0b630..3e73ee15206 100644
--- a/src/cdk/v2/destinations/kochava/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/kochava/procWorkflow.yaml
@@ -10,7 +10,6 @@ bindings:
- path: ../../bindings/jsontemplate
- path: ./config.js
-
steps:
- name: validateInput
template: |
diff --git a/src/cdk/v2/destinations/lytics/procWorkflow.yaml b/src/cdk/v2/destinations/lytics/procWorkflow.yaml
index 1d6177fe09e..26221462213 100644
--- a/src/cdk/v2/destinations/lytics/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/lytics/procWorkflow.yaml
@@ -45,7 +45,7 @@ steps:
$.context.payload._e = .message.event;
- name: pageOrScreenPayload
condition: $.context.messageType === {{$.EventType.PAGE}} ||
- $.context.messageType === {{$.EventType.SCREEN}}
+ $.context.messageType === {{$.EventType.SCREEN}}
template: |
$.context.payload.event = .message.name
- name: cleanPaylod
@@ -66,4 +66,3 @@ steps:
"Content-Type": "application/json"
};
response
-
diff --git a/src/cdk/v2/destinations/movable_ink/config.js b/src/cdk/v2/destinations/movable_ink/config.js
new file mode 100644
index 00000000000..673e94620ea
--- /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 00000000000..43dbb3cbce9
--- /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 00000000000..46afb34d537
--- /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 00000000000..04d7046b1a3
--- /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 00000000000..c38496a4155
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/config.js
@@ -0,0 +1,31 @@
+const { getMappingConfig } = require('../../../../v0/util');
+
+const ConfigCategories = {
+ GENERAL: {
+ type: 'general',
+ name: 'generalPayloadMapping',
+ },
+ CONTEXT: {
+ type: 'context',
+ name: 'contextMapping',
+ },
+ TRACK: {
+ type: 'track',
+ name: 'trackMapping',
+ },
+ IDENTIFY: {
+ type: 'identify',
+ name: 'identifyMapping',
+ },
+ PAGE: {
+ type: 'page',
+ name: 'pageMapping',
+ },
+};
+
+// MAX_BATCH_SIZE : // Maximum number of events to send in a single batch
+const mappingConfig = getMappingConfig(ConfigCategories, __dirname);
+const batchEndpoint =
+ 'https://experience.ninetailed.co/v2/organizations/{{organisationId}}/environments/{{environment}}/events';
+
+module.exports = { ConfigCategories, mappingConfig, batchEndpoint, MAX_BATCH_SIZE: 200 };
diff --git a/src/cdk/v2/destinations/ninetailed/data/contextMapping.json b/src/cdk/v2/destinations/ninetailed/data/contextMapping.json
new file mode 100644
index 00000000000..f2373b61c15
--- /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 00000000000..3ab72d1b9f4
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/data/generalPayloadMapping.json
@@ -0,0 +1,25 @@
+[
+ {
+ "sourceKeys": "anonymousId",
+ "required": true,
+ "destKey": "anonymousId"
+ },
+ {
+ "sourceKeys": "messageId",
+ "required": true,
+ "destKey": "messageId"
+ },
+ {
+ "sourceKeys": "channel",
+ "required": true,
+ "destKey": "channel"
+ },
+ {
+ "sourceKeys": "type",
+ "destKey": "type"
+ },
+ {
+ "sourceKeys": "originalTimestamp",
+ "destKey": "originalTimestamp"
+ }
+]
diff --git a/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json b/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json
new file mode 100644
index 00000000000..e8d3f7797d6
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/data/identifyMapping.json
@@ -0,0 +1,14 @@
+[
+ {
+ "sourceKeys": "traits",
+ "sourceFromGenericMap": true,
+ "required": true,
+ "destKey": "traits"
+ },
+ {
+ "sourceKeys": "userIdOnly",
+ "sourceFromGenericMap": true,
+ "required": true,
+ "destKey": "userId"
+ }
+]
diff --git a/src/cdk/v2/destinations/ninetailed/data/pageMapping.json b/src/cdk/v2/destinations/ninetailed/data/pageMapping.json
new file mode 100644
index 00000000000..80ec2f58f12
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/data/pageMapping.json
@@ -0,0 +1,7 @@
+[
+ {
+ "sourceKeys": "properties",
+ "required": true,
+ "destKey": "properties"
+ }
+]
diff --git a/src/cdk/v2/destinations/ninetailed/data/trackMapping.json b/src/cdk/v2/destinations/ninetailed/data/trackMapping.json
new file mode 100644
index 00000000000..44af6dd1a34
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/data/trackMapping.json
@@ -0,0 +1,12 @@
+[
+ {
+ "sourceKeys": "properties",
+ "required": true,
+ "destKey": "properties"
+ },
+ {
+ "sourceKeys": "event",
+ "required": true,
+ "destKey": "event"
+ }
+]
diff --git a/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml b/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml
new file mode 100644
index 00000000000..6f5056ce100
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/procWorkflow.yaml
@@ -0,0 +1,33 @@
+bindings:
+ - name: EventType
+ path: ../../../../constants
+ - path: ../../bindings/jsontemplate
+ - name: defaultRequestConfig
+ path: ../../../../v0/util
+ - name: removeUndefinedAndNullValues
+ path: ../../../../v0/util
+ - path: ./utils
+
+steps:
+ - name: messageType
+ template: |
+ .message.type.toLowerCase();
+ - name: validateInput
+ template: |
+ let messageType = $.outputs.messageType;
+ $.assert(messageType, "message Type is not present. Aborting");
+ $.assert(messageType in {{$.EventType.([.TRACK,.IDENTIFY,.PAGE])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.organisationId, "Organisation ID is not present. Aborting");
+ $.assertConfig(.destination.Config.environment, "Environment is not present. Aborting");
+ - name: preparePayload
+ template: |
+ const payload = $.constructFullPayload(.message);
+ $.context.payload = $.removeUndefinedAndNullValues(payload);
+
+ - name: buildResponse
+ template: |
+ const response = $.defaultRequestConfig();
+ response.body.JSON.events = [$.context.payload];
+ response.endpoint = $.getEndpoint(.destination.Config);
+ response.method = "POST";
+ response
diff --git a/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml b/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml
new file mode 100644
index 00000000000..30dd3fdd95d
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/rtWorkflow.yaml
@@ -0,0 +1,35 @@
+bindings:
+ - path: ./config
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+ - path: ./utils
+steps:
+ - name: validateInput
+ template: |
+ $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array")
+
+ - name: transform
+ externalWorkflow:
+ path: ./procWorkflow.yaml
+ loopOverInput: true
+
+ - name: successfulEvents
+ template: |
+ $.outputs.transform#idx.output.({
+ "output": .body.JSON.events[0],
+ "destination": ^[idx].destination,
+ "metadata": ^[idx].metadata
+ })[]
+ - name: failedEvents
+ template: |
+ $.outputs.transform#idx.error.(
+ $.handleRtTfSingleEventError(^[idx], .originalError ?? ., {})
+ )[]
+ - name: batchSuccessfulEvents
+ description: Batches the successfulEvents
+ template: |
+ $.batchResponseBuilder($.outputs.successfulEvents);
+
+ - name: finalPayload
+ template: |
+ [...$.outputs.failedEvents, ...$.outputs.batchSuccessfulEvents]
diff --git a/src/cdk/v2/destinations/ninetailed/utils.js b/src/cdk/v2/destinations/ninetailed/utils.js
new file mode 100644
index 00000000000..b716422a0e8
--- /dev/null
+++ b/src/cdk/v2/destinations/ninetailed/utils.js
@@ -0,0 +1,109 @@
+const { BatchUtils } = require('@rudderstack/workflow-engine');
+const config = require('./config');
+const { constructPayload } = require('../../../../v0/util');
+
+/**
+ * This fucntion constructs payloads based upon mappingConfig for all calls
+ * We build context as it has some specific payloads with default values so just breaking them down
+ * @param {*} message
+ * @returns
+ */
+const constructFullPayload = (message) => {
+ const context = constructPayload(
+ message?.context || {},
+ config.mappingConfig[config.ConfigCategories.CONTEXT.name],
+ );
+ const payload = constructPayload(
+ message,
+ config.mappingConfig[config.ConfigCategories.GENERAL.name],
+ );
+ let typeSpecifcPayload;
+ switch (message.type) {
+ case 'track':
+ typeSpecifcPayload = constructPayload(
+ message,
+ config.mappingConfig[config.ConfigCategories.TRACK.name],
+ );
+ break;
+ case 'identify':
+ typeSpecifcPayload = constructPayload(
+ message,
+ config.mappingConfig[config.ConfigCategories.IDENTIFY.name],
+ );
+ break;
+ case 'page':
+ typeSpecifcPayload = constructPayload(
+ message,
+ config.mappingConfig[config.ConfigCategories.PAGE.name],
+ );
+ break;
+ default:
+ break;
+ }
+ payload.context = context;
+ return { ...payload, ...typeSpecifcPayload }; // merge base and type-specific payloads;
+};
+
+const getEndpoint = (Config) => {
+ const { organisationId, environment } = Config;
+ return config.batchEndpoint
+ .replace('{{organisationId}}', organisationId)
+ .replace('{{environment}}', environment);
+};
+
+const mergeMetadata = (batch) => {
+ const metadata = [];
+ batch.forEach((event) => {
+ metadata.push(event.metadata);
+ });
+ return metadata;
+};
+
+const getMergedEvents = (batch) => {
+ const events = [];
+ batch.forEach((event) => {
+ events.push(event.output);
+ });
+ return events;
+};
+
+const batchBuilder = (batch) => ({
+ batchedRequest: {
+ body: {
+ JSON: { events: getMergedEvents(batch) },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: getEndpoint(batch[0].destination.Config),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ files: {},
+ },
+ metadata: mergeMetadata(batch),
+ batched: true,
+ statusCode: 200,
+ destination: batch[0].destination,
+});
+
+/**
+ * This fucntions make chunk of successful events based on MAX_BATCH_SIZE
+ * and then build the response for each chunk to be returned as object of an array
+ * @param {*} events
+ * @returns
+ */
+const batchResponseBuilder = (events) => {
+ const batches = BatchUtils.chunkArrayBySizeAndLength(events, { maxItems: config.MAX_BATCH_SIZE });
+ const response = [];
+ batches.items.forEach((batch) => {
+ response.push(batchBuilder(batch));
+ });
+ return response;
+};
+
+module.exports = { constructFullPayload, getEndpoint, batchResponseBuilder };
diff --git a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml
index 8a956e905ca..1122a80404c 100644
--- a/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/pinterest_tag/procWorkflow.yaml
@@ -246,4 +246,4 @@ steps:
},
"params": $.outputs.checkSendTestEventConfig,
"files": {}
- })[]
\ No newline at end of file
+ })[]
diff --git a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml
index 227942dfea8..215ead12b12 100644
--- a/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml
+++ b/src/cdk/v2/destinations/pinterest_tag/rtWorkflow.yaml
@@ -12,7 +12,7 @@ steps:
If sendTestEvent is enabled, we send test event to the destination
ref: https://help.pinterest.com/en/business/article/track-conversions-with-the-conversions-api
template: |
- ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {}
+ ^[0].destination.Config.sendAsTestEvent ? {"test": true} : {}
- name: transform
externalWorkflow:
diff --git a/src/cdk/v2/destinations/rakuten/config.js b/src/cdk/v2/destinations/rakuten/config.js
new file mode 100644
index 00000000000..6a4c93ea3ab
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/config.js
@@ -0,0 +1,34 @@
+const { getMappingConfig } = require('../../../../v0/util');
+
+const ConfigCategories = {
+ TRACK: {
+ type: 'track',
+ name: 'propertiesMapping',
+ },
+};
+const mappingConfig = getMappingConfig(ConfigCategories, __dirname);
+// Following contains the keys at item level mapping where key can be considered as destkey and value can be considered as sourcekey
+const productProperties = {
+ skulist: 'sku',
+ qlist: 'quantity',
+ namelist: 'name',
+ brandlist: 'brand',
+ couponlist: 'coupon',
+ catidlist: 'categoryId',
+ catlist: 'category',
+ disamtlist: 'discountAmount',
+ distypelist: 'discountType',
+ isclearancelist: 'isClearance',
+ ismarketplacelist: 'isMarketPlace',
+ issalelist: 'isSale',
+ itmstatuslist: 'itmStatus',
+ marginlist: 'margin',
+ markdownlist: 'markdown',
+ shipidlist: 'shipId',
+ shipbylist: 'shipBy',
+ taxexemptlist: 'taxExempt',
+ sequencelist: 'sequence',
+};
+// list of all properties that are required
+const requiredProductProperties = ['skulist', 'qlist', 'namelist'];
+module.exports = { ConfigCategories, mappingConfig, productProperties, requiredProductProperties };
diff --git a/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json
new file mode 100644
index 00000000000..db5d36fc4d1
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/data/propertiesMapping.json
@@ -0,0 +1,141 @@
+[
+ {
+ "sourceKeys": ["properties.order_id", "properties.orderId"],
+ "required": true,
+ "destKey": "ord"
+ },
+ {
+ "sourceKeys": ["properties.tr", "properties.ran_site_id", "properties.ranSiteID"],
+ "required": true,
+ "destKey": "tr"
+ },
+ {
+ "sourceKeys": ["properties.land", "properties.land_time", "properties.landTime"],
+ "required": true,
+ "destKey": "land"
+ },
+ {
+ "sourceKeys": [
+ "properties.date",
+ "properties.order_completed_time",
+ "properties.orderCompletedTime"
+ ],
+ "destKey": "date"
+ },
+ {
+ "sourceKeys": [
+ "properties.alt_ord",
+ "properties.altord",
+ "properties.alter_order_id",
+ "properties.alterOrderId"
+ ],
+ "destKey": "altord"
+ },
+ {
+ "sourceKeys": "properties.currency",
+ "destKey": "cur"
+ },
+ {
+ "sourceKeys": ["properties.credit_card_type", "properties.creditCardType"],
+ "destKey": "cc"
+ },
+ {
+ "sourceKeys": ["properties.comm_reason", "properties.commReason"],
+ "destKey": "commreason"
+ },
+ {
+ "sourceKeys": ["properties.is_comm", "properties.isComm"],
+ "destKey": "iscomm"
+ },
+ {
+ "sourceKeys": "properties.consumed",
+ "destKey": "consumed"
+ },
+ {
+ "sourceKeys": "properties.coupon",
+ "destKey": "coupon"
+ },
+ {
+ "sourceKeys": [
+ "properties.cust_id",
+ "properties.custId",
+ "properties.customer_id",
+ "properties.customerId",
+ "properties.userId"
+ ],
+ "destKey": "custid"
+ },
+ {
+ "sourceKeys": [
+ "properties.cust_score",
+ "properties.custScore",
+ "properties.customer_score",
+ "properties.customerScore"
+ ],
+ "destKey": "custscore"
+ },
+ {
+ "sourceKeys": [
+ "properties.cust_status",
+ "properties.custStatus",
+ "properties.customer_status",
+ "properties.customerStatus"
+ ],
+ "destKey": "custstatus"
+ },
+ {
+ "sourceKeys": ["properties.dId", "properties.advertising_id", "properties.advertisingId"],
+ "destKey": "did"
+ },
+ {
+ "sourceKeys": ["properties.disamt", "properties.discount_amount", "properties.discountAmount"],
+ "destKey": "disamt"
+ },
+ {
+ "sourceKeys": [
+ "properties.ord_status",
+ "properties.ordStatus",
+ "properties.order_status",
+ "properties.orderStatus"
+ ],
+ "destKey": "ordstatus"
+ },
+ {
+ "sourceKeys": "properties.segment",
+ "destKey": "segment"
+ },
+ {
+ "sourceKeys": ["properties.ship_country", "properties.shipcountry"],
+ "destKey": "shipcountry"
+ },
+ {
+ "sourceKeys": "properties.shipped",
+ "destKey": "shipped"
+ },
+ {
+ "sourceKeys": [
+ "properties.site_name",
+ "properties.sitename",
+ "properties.url",
+ "context.page.url"
+ ],
+ "destKey": "sitename"
+ },
+ {
+ "sourceKeys": ["properties.store_id", "properties.storeId"],
+ "destKey": "storeid"
+ },
+ {
+ "sourceKeys": [
+ "properties.store_cat",
+ "properties.storecat",
+ "properties.store_category",
+ "properties.storeCategory"
+ ],
+ "destKey": "storecat"
+ },
+ {
+ "sourceKeys": "properties.currency",
+ "destKey": "cur"
+ }
+]
diff --git a/src/cdk/v2/destinations/rakuten/procWorkflow.yaml b/src/cdk/v2/destinations/rakuten/procWorkflow.yaml
new file mode 100644
index 00000000000..b4dcacfa092
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/procWorkflow.yaml
@@ -0,0 +1,39 @@
+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])}}, "message type " + messageType + " is not supported");
+ $.assertConfig(.destination.Config.mid, "Merchant ID is not present. Aborting");
+ - name: prepareTrackPayload
+ condition: $.outputs.messageType === {{$.EventType.TRACK}}
+ template: |
+ const properties = $.constructProperties(.message);
+ const lineItems = $.constructLineItems(.message.properties)
+ $.context.payload = {...properties,...lineItems,xml:1,source:'rudderstack', mid:.destination.Config.mid}
+ $.context.payload = $.removeUndefinedAndNullValues($.context.payload);
+
+ - name: buildResponse
+ template: |
+ const response = $.defaultRequestConfig();
+ response.params = $.context.payload;
+ response.method = "GET";
+ response.endpoint = "https://track.linksynergy.com/ep";
+ response.headers = {
+ "accept": "application/json",
+ "content-type": "application/json"
+ };
+ response
diff --git a/src/cdk/v2/destinations/rakuten/utils.js b/src/cdk/v2/destinations/rakuten/utils.js
new file mode 100644
index 00000000000..ef6b197db7a
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/utils.js
@@ -0,0 +1,70 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const { isDefinedAndNotNull } = require('rudder-transformer-cdk/build/utils');
+const {
+ mappingConfig,
+ ConfigCategories,
+ productProperties,
+ requiredProductProperties,
+} = require('./config');
+const { constructPayload } = require('../../../../v0/util');
+
+/**
+ * This fucntion constructs payloads based upon mappingConfig for Track call type
+ * @param {*} message
+ * @returns
+ */
+const constructProperties = (message) => {
+ const payload = constructPayload(message, mappingConfig[ConfigCategories.TRACK.name]);
+ return payload;
+};
+
+/**
+ * This fucntion build the item level list
+ * @param {*} properties
+ * @returns
+ */
+const constructLineItems = (properties) => {
+ // Validate the existence and non-emptiness of the 'products' array in 'properties'
+ if (!Array.isArray(properties?.products) || properties.products.length === 0) {
+ throw new InstrumentationError('Either properties.product is not an array or is empty');
+ }
+
+ const { products } = properties;
+ const productList = {};
+
+ // Iterate over product properties to construct the payload
+ Object.keys(productProperties).forEach((property) => {
+ const propertyKey = productProperties[property];
+
+ // Extract values for the current property from the 'products' array
+ const values = products.map((product) =>
+ isDefinedAndNotNull(product?.[propertyKey]) ? product[propertyKey] : '',
+ );
+
+ // Validate if a required property is missing
+ if (requiredProductProperties.includes(property) && values.includes('')) {
+ throw new InstrumentationError(`${propertyKey} is a required field. Aborting`);
+ }
+
+ // Include property in the payload if values are non-empty
+ if (values.some((element) => element !== '')) {
+ productList[property] = values.join('|');
+ }
+ });
+
+ // Map 'amountList' by evaluating 'amount' or deriving it from 'price' and 'quantity'
+ const amountList = products.map((product) => {
+ if (!product?.amount && !product?.price) {
+ throw new InstrumentationError('Either amount or price is required for every product');
+ }
+
+ if (product.price) {
+ return product.quantity * product.price * 100;
+ }
+ return product.amount * 100;
+ });
+ productList.amtlist = amountList.join('|');
+ return productList;
+};
+
+module.exports = { constructProperties, constructLineItems };
diff --git a/src/cdk/v2/destinations/rakuten/utils.test.js b/src/cdk/v2/destinations/rakuten/utils.test.js
new file mode 100644
index 00000000000..9cc7f5fd4cc
--- /dev/null
+++ b/src/cdk/v2/destinations/rakuten/utils.test.js
@@ -0,0 +1,117 @@
+const { constructLineItems } = require('./utils');
+describe('constructLineItems', () => {
+ it('should return a non-empty object when given a valid properties object with at least one product', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ price: 10,
+ quantity: 2,
+ amount: 20,
+ },
+ ],
+ };
+ const result = constructLineItems(properties);
+ const expectedObj = {
+ namelist: 'Product 1',
+ skulist: 'sku_1',
+ qlist: '2',
+ amtlist: '2000',
+ };
+ expect(result).toEqual(expectedObj);
+ });
+
+ it('should include all mapped properties in the returned object when present in at least one product', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ category: 'Category 1',
+ sku: 'sku_1',
+ brand: 'Brand 1',
+ price: 10,
+ quantity: 2,
+ amount: 20,
+ },
+ ],
+ };
+
+ const result = constructLineItems(properties);
+
+ const expectedObj = {
+ namelist: 'Product 1',
+ catlist: 'Category 1',
+ skulist: 'sku_1',
+ brandlist: 'Brand 1',
+ qlist: '2',
+ amtlist: '2000',
+ };
+ expect(result).toEqual(expectedObj);
+ });
+
+ it('should include amtlist property in the returned object with calculated values', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ price: 10,
+ quantity: 2,
+ },
+ {
+ name: 'Product 2',
+ sku: 'sku_2',
+ price: 5,
+ quantity: 3,
+ },
+ ],
+ };
+
+ const result = constructLineItems(properties);
+
+ expect(result).toHaveProperty('amtlist');
+ expect(result.amtlist).toBe('2000|1500');
+ });
+
+ it('should throw an InstrumentationError when properties object is missing or has an empty products array', () => {
+ const properties = {};
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either properties.product is not an array or is empty',
+ );
+
+ properties.products = [];
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either properties.product is not an array or is empty',
+ );
+ });
+ it('should throw an InstrumentationError when a product is missing quantity property', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ amount: '1234',
+ },
+ ],
+ };
+ expect(() => constructLineItems(properties)).toThrow('quantity is a required field. Aborting');
+ });
+ it('should throw an InstrumentationError when a product is missing both amount and price properties', () => {
+ const properties = {
+ products: [
+ {
+ name: 'Product 1',
+ sku: 'sku_1',
+ quantity: 2,
+ },
+ ],
+ };
+
+ expect(() => constructLineItems(properties)).toThrow(
+ 'Either amount or price is required for every product',
+ );
+ });
+});
diff --git a/src/cdk/v2/destinations/reddit/procWorkflow.yaml b/src/cdk/v2/destinations/reddit/procWorkflow.yaml
index 65b466bc7cc..59725c1257a 100644
--- a/src/cdk/v2/destinations/reddit/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/reddit/procWorkflow.yaml
@@ -56,13 +56,15 @@ steps:
const event_type = (eventNames.length === 0 || eventNames[0]==="") ? ({"tracking_type": "Custom", "custom_event_name": event}): ({tracking_type: eventNames[0]});
- name: customFields
- condition: $.outputs.eventType.tracking_type === "Purchase"
+ 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": .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_decimal": .properties.revenue !== undefined ? Number(.properties.revenue)/100 : undefined,
+ "value": revenue_in_cents,
"conversion_id": .properties.conversionId || .messageId,
});
$.removeUndefinedAndNullValues(customFields)
diff --git a/src/cdk/v2/destinations/sprig/procWorkflow.yaml b/src/cdk/v2/destinations/sprig/procWorkflow.yaml
index 18b46913fd6..4dcebeffcda 100644
--- a/src/cdk/v2/destinations/sprig/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/sprig/procWorkflow.yaml
@@ -69,5 +69,3 @@ steps:
"authorization": "API-Key " + .destination.Config.apiKey
};
response
-
-
diff --git a/src/cdk/v2/destinations/statsig/procWorkflow.yaml b/src/cdk/v2/destinations/statsig/procWorkflow.yaml
index b3c85e31dcf..6d3328b87e2 100644
--- a/src/cdk/v2/destinations/statsig/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/statsig/procWorkflow.yaml
@@ -26,4 +26,3 @@ steps:
"content-type": "application/json"
};
response
-
diff --git a/src/cdk/v2/destinations/the_trade_desk/config.js b/src/cdk/v2/destinations/the_trade_desk/config.js
index 9455c818fdd..300325223b3 100644
--- a/src/cdk/v2/destinations/the_trade_desk/config.js
+++ b/src/cdk/v2/destinations/the_trade_desk/config.js
@@ -1,8 +1,9 @@
-const SUPPORTED_EVENT_TYPE = 'record';
+const SUPPORTED_EVENT_TYPE = ['record'];
const ACTION_TYPES = ['insert', 'delete'];
const DATA_PROVIDER_ID = 'rudderstack';
// ref:- https://partner.thetradedesk.com/v3/portal/data/doc/DataEnvironments
+// api ref:- https://partner.thetradedesk.com/v3/portal/data/doc/post-data-advertiser-external
const DATA_SERVERS_BASE_ENDPOINTS_MAP = {
apac: 'https://sin-data.adsrvr.org',
tokyo: 'https://tok-data.adsrvr.org',
diff --git a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
index 0c8963e0ac7..5f0476dd624 100644
--- a/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
+++ b/src/cdk/v2/destinations/the_trade_desk/rtWorkflow.yaml
@@ -1,17 +1,41 @@
bindings:
- - name: processRouterDest
- path: ./utils
+ - name: EventType
+ path: ../../../../constants
+ - name: processRecordInputs
+ path: ./transformRecord
+ - name: handleRtTfSingleEventError
+ path: ../../../../v0/util/index
+ - name: InstrumentationError
+ path: '@rudderstack/integrations-lib'
steps:
- - name: validateInput
+ - name: validateConfig
template: |
- $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array")
const config = ^[0].destination.Config
- $.assertConfig(config.audienceId, "Segment name is not present. Aborting")
+ $.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: processRouterDest
+ - name: validateInput
+ template: |
+ $.assert(Array.isArray(^) && ^.length > 0, "Invalid event array")
+
+ - name: processRecordEvents
+ template: |
+ $.processRecordInputs(^.{.message.type === $.EventType.RECORD}[], ^[0].destination)
+
+ - name: failOtherEvents
+ template: |
+ 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: |
- $.processRouterDest(^)
+ [...$.outputs.processRecordEvents, ...$.outputs.failOtherEvents]
diff --git a/src/cdk/v2/destinations/the_trade_desk/transformRecord.js b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js
new file mode 100644
index 00000000000..b452f8d7bc7
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk/transformRecord.js
@@ -0,0 +1,97 @@
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
+const { BatchUtils } = require('@rudderstack/workflow-engine');
+const {
+ defaultPostRequestConfig,
+ defaultRequestConfig,
+ getSuccessRespEvents,
+ removeUndefinedAndNullValues,
+ handleRtTfSingleEventError,
+ isEmptyObject,
+} = require('../../../../v0/util');
+const { getTTLInMin, getFirstPartyEndpoint } = require('./utils');
+const tradeDeskConfig = require('./config');
+
+const { DATA_PROVIDER_ID } = tradeDeskConfig;
+
+const responseBuilder = (items, config) => {
+ const { advertiserId, dataServer } = config;
+
+ const payload = { DataProviderId: DATA_PROVIDER_ID, AdvertiserId: advertiserId, Items: items };
+
+ const response = defaultRequestConfig();
+ response.endpoint = getFirstPartyEndpoint(dataServer);
+ response.method = defaultPostRequestConfig.requestMethod;
+ response.body.JSON = removeUndefinedAndNullValues(payload);
+ return response;
+};
+
+const batchResponseBuilder = (items, config) => {
+ const response = [];
+ const itemsChunks = BatchUtils.chunkArrayBySizeAndLength(items, {
+ // TODO: use destructuring at the top of file once proper 'mocking' is implemented.
+ // eslint-disable-next-line unicorn/consistent-destructuring
+ maxSizeInBytes: tradeDeskConfig.MAX_REQUEST_SIZE_IN_BYTES,
+ });
+
+ itemsChunks.items.forEach((chunk) => {
+ response.push(responseBuilder(chunk, config));
+ });
+
+ return response;
+};
+
+const processRecordInputs = (inputs, destination) => {
+ const { Config } = destination;
+ const items = [];
+ const successMetadata = [];
+ const errorResponseList = [];
+
+ if (!inputs || inputs.length === 0) {
+ return [];
+ }
+
+ const invalidActionTypeError = new InstrumentationError(
+ 'Invalid action type. You can only add or remove IDs from the audience/segment',
+ );
+ const emptyFieldsError = new InstrumentationError('`fields` cannot be empty');
+ inputs.forEach((input) => {
+ const { fields, action } = input.message;
+ const isInsertOrDelete = action === 'insert' || action === 'delete';
+
+ if (!isInsertOrDelete) {
+ errorResponseList.push(handleRtTfSingleEventError(input, invalidActionTypeError, {}));
+ return;
+ }
+
+ if (isEmptyObject(fields)) {
+ errorResponseList.push(handleRtTfSingleEventError(input, emptyFieldsError, {}));
+ return;
+ }
+
+ successMetadata.push(input.metadata);
+ const data = [
+ {
+ Name: Config.audienceId,
+ TTLInMinutes: action === 'insert' ? getTTLInMin(Config.ttlInDays) : 0,
+ },
+ ];
+
+ Object.keys(fields).forEach((id) => {
+ const value = fields[id];
+ if (value) {
+ // adding only non empty ID's
+ items.push({ [id]: value, Data: data });
+ }
+ });
+ });
+
+ const payloads = batchResponseBuilder(items, Config);
+ if (payloads.length === 0) {
+ return errorResponseList;
+ }
+
+ const response = getSuccessRespEvents(payloads, successMetadata, destination, true);
+ return [response, ...errorResponseList];
+};
+
+module.exports = { processRecordInputs };
diff --git a/src/cdk/v2/destinations/the_trade_desk/utils.js b/src/cdk/v2/destinations/the_trade_desk/utils.js
index 632442d74e5..71b479438cb 100644
--- a/src/cdk/v2/destinations/the_trade_desk/utils.js
+++ b/src/cdk/v2/destinations/the_trade_desk/utils.js
@@ -1,23 +1,19 @@
-const lodash = require('lodash');
const CryptoJS = require('crypto-js');
-const { InstrumentationError, AbortedError } = require('@rudderstack/integrations-lib');
-const { BatchUtils } = require('@rudderstack/workflow-engine');
-const {
- defaultPostRequestConfig,
- defaultRequestConfig,
- getSuccessRespEvents,
- removeUndefinedAndNullValues,
- handleRtTfSingleEventError,
- isEmptyObject,
-} = require('../../../../v0/util');
-const tradeDeskConfig = require('./config');
+const { AbortedError } = require('@rudderstack/integrations-lib');
+const { DATA_SERVERS_BASE_ENDPOINTS_MAP } = require('./config');
-const { DATA_PROVIDER_ID, DATA_SERVERS_BASE_ENDPOINTS_MAP } = tradeDeskConfig;
-
-const ttlInMin = (ttl) => parseInt(ttl, 10) * 1440;
+const getTTLInMin = (ttl) => parseInt(ttl, 10) * 1440;
const getBaseEndpoint = (dataServer) => DATA_SERVERS_BASE_ENDPOINTS_MAP[dataServer];
const getFirstPartyEndpoint = (dataServer) => `${getBaseEndpoint(dataServer)}/data/advertiser`;
+/**
+ * Generates a signature header for a given request using a secret key.
+ *
+ * @param {Object} request - The request object to generate the signature for.
+ * @param {string} secretKey - The secret key used to generate the signature.
+ * @returns {string} - The generated signature header.
+ * @throws {AbortedError} - If the secret key is missing.
+ */
const getSignatureHeader = (request, secretKey) => {
if (!secretKey) {
throw new AbortedError('Secret key is missing. Aborting');
@@ -27,92 +23,8 @@ const getSignatureHeader = (request, secretKey) => {
return base;
};
-const responseBuilder = (items, config) => {
- const { advertiserId, dataServer } = config;
-
- const payload = { DataProviderId: DATA_PROVIDER_ID, AdvertiserId: advertiserId, Items: items };
-
- const response = defaultRequestConfig();
- response.endpoint = getFirstPartyEndpoint(dataServer);
- response.method = defaultPostRequestConfig.requestMethod;
- response.body.JSON = removeUndefinedAndNullValues(payload);
- return response;
-};
-
-const batchResponseBuilder = (items, config) => {
- const response = [];
- const itemsChunks = BatchUtils.chunkArrayBySizeAndLength(items, {
- // TODO: use destructuring at the top of file once proper 'mocking' is implemented.
- // eslint-disable-next-line unicorn/consistent-destructuring
- maxSizeInBytes: tradeDeskConfig.MAX_REQUEST_SIZE_IN_BYTES,
- });
-
- itemsChunks.items.forEach((chunk) => {
- response.push(responseBuilder(chunk, config));
- });
-
- return response;
+module.exports = {
+ getTTLInMin,
+ getFirstPartyEndpoint,
+ getSignatureHeader,
};
-
-const processRecordInputs = (inputs, destination) => {
- const { Config } = destination;
- const items = [];
- const successMetadata = [];
- const errorResponseList = [];
-
- const invalidActionTypeError = new InstrumentationError('Invalid action type');
- const emptyFieldsError = new InstrumentationError('Fields cannot be empty');
-
- inputs.forEach((input) => {
- const { fields, action } = input.message;
- const isInsertOrDelete = action === 'insert' || action === 'delete';
-
- if (!isInsertOrDelete) {
- errorResponseList.push(handleRtTfSingleEventError(input, invalidActionTypeError, {}));
- return;
- }
-
- if (isEmptyObject(fields)) {
- errorResponseList.push(handleRtTfSingleEventError(input, emptyFieldsError, {}));
- return;
- }
-
- successMetadata.push(input.metadata);
- const data = [
- {
- Name: Config.audienceId,
- TTLInMinutes: action === 'insert' ? ttlInMin(Config.ttlInDays) : 0,
- },
- ];
-
- Object.keys(fields).forEach((id) => {
- const value = fields[id];
- if (value) {
- // adding only non empty ID's
- items.push({ [id]: value, Data: data });
- }
- });
- });
-
- const payloads = batchResponseBuilder(items, Config);
- if (payloads.length === 0) {
- return errorResponseList;
- }
-
- const response = getSuccessRespEvents(payloads, successMetadata, destination, true);
- return [response, ...errorResponseList];
-};
-
-const processRouterDest = (inputs) => {
- const respList = [];
- const { destination } = inputs[0];
- const groupedInputs = lodash.groupBy(inputs, (input) => input.message.type);
- if (groupedInputs.record) {
- const transformedRecordEvent = processRecordInputs(groupedInputs.record, destination);
- respList.push(...transformedRecordEvent);
- }
-
- return respList;
-};
-
-module.exports = { getSignatureHeader, processRouterDest };
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 00000000000..7af732185f9
--- /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_real_time_conversions/data/TTDCommonConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json
new file mode 100644
index 00000000000..848d16b4058
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDCommonConfig.json
@@ -0,0 +1,22 @@
+[
+ {
+ "destKey": "currency",
+ "sourceKeys": "properties.currency"
+ },
+ {
+ "destKey": "client_ip",
+ "sourceKeys": ["context.ip", "request_ip"]
+ },
+ {
+ "destKey": "referrer_url",
+ "sourceKeys": "context.page.referrer"
+ },
+ {
+ "destKey": "imp",
+ "sourceKeys": "messageId"
+ },
+ {
+ "destKey": "order_id",
+ "sourceKeys": "properties.order_id"
+ }
+]
diff --git a/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json
new file mode 100644
index 00000000000..45aeb711ab5
--- /dev/null
+++ b/src/cdk/v2/destinations/the_trade_desk_real_time_conversions/data/TTDItemConfig.json
@@ -0,0 +1,22 @@
+[
+ {
+ "destKey": "item_code",
+ "sourceKeys": ["product_id", "sku"]
+ },
+ {
+ "destKey": "name",
+ "sourceKeys": "name"
+ },
+ {
+ "destKey": "qty",
+ "sourceKeys": "quantity"
+ },
+ {
+ "destKey": "price",
+ "sourceKeys": "price"
+ },
+ {
+ "destKey": "cat",
+ "sourceKeys": "category_id"
+ }
+]
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 00000000000..5191320cdc1
--- /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 00000000000..2232be61f06
--- /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 00000000000..20b39fab937
--- /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 cd84ecbc876..5862fbf3724 100644
--- a/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/tiktok_audience/procWorkflow.yaml
@@ -1,4 +1,3 @@
-
bindings:
- name: EventType
path: ../../../../constants
@@ -48,7 +47,6 @@ steps:
"action": $.ACTION_MAP[action],
})[]
-
- name: buildResponseForProcessTransformation
description: build response
template: |
diff --git a/src/cdk/v2/destinations/zapier/procWorkflow.yaml b/src/cdk/v2/destinations/zapier/procWorkflow.yaml
index 82d15cdec0f..9f1512836ea 100644
--- a/src/cdk/v2/destinations/zapier/procWorkflow.yaml
+++ b/src/cdk/v2/destinations/zapier/procWorkflow.yaml
@@ -44,4 +44,3 @@ steps:
"content-type": "application/json"
};
response
-
diff --git a/src/cdk/v2/handler.ts b/src/cdk/v2/handler.ts
index 47d6d101797..edd14e72988 100644
--- a/src/cdk/v2/handler.ts
+++ b/src/cdk/v2/handler.ts
@@ -50,16 +50,20 @@ export async function getWorkflowEngine(
const workflowEnginePromiseMap = new Map();
-export function getCachedWorkflowEngine(
+export async function getCachedWorkflowEngine(
destName: string,
feature: string,
bindings: Record = {},
-): WorkflowEngine {
+): Promise {
// Create a new instance of the engine for the destination if needed
// TODO: Use cache to avoid long living engine objects
workflowEnginePromiseMap[destName] = workflowEnginePromiseMap[destName] || new Map();
if (!workflowEnginePromiseMap[destName][feature]) {
- workflowEnginePromiseMap[destName][feature] = getWorkflowEngine(destName, feature, bindings);
+ workflowEnginePromiseMap[destName][feature] = await getWorkflowEngine(
+ destName,
+ feature,
+ bindings,
+ );
}
return workflowEnginePromiseMap[destName][feature];
}
@@ -97,5 +101,8 @@ export function executeStep(
): Promise {
return workflowEngine
.getStepExecutor(stepName)
- .execute(input, Object.assign(workflowEngine.bindings, getEmptyExecutionBindings(), bindings));
+ .execute(
+ input,
+ Object.assign(workflowEngine.getBindings(), getEmptyExecutionBindings(), bindings),
+ );
}
diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js
index d1e199c9e2d..b84aff1089d 100644
--- a/src/constants/destinationCanonicalNames.js
+++ b/src/constants/destinationCanonicalNames.js
@@ -141,6 +141,30 @@ const DestCanonicalNames = {
'TWITTER_ADS',
],
BRAZE: ['BRAZE', 'Braze', 'braze'],
+ THE_TRADE_DESK: [
+ 'THE_TRADE_DESK',
+ 'the_trade_desk',
+ 'The_Trade_Desk',
+ 'The Trade Desk',
+ 'thetradedesk',
+ 'theTradeDesk',
+ 'TheTradeDesk',
+ '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/constants/index.js b/src/constants/index.js
index a8976bc07e2..f8ca53a94c9 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -6,6 +6,7 @@ const EventType = {
ALIAS: 'alias',
GROUP: 'group',
RESET: 'reset',
+ RECORD: 'record',
};
const Address = {
diff --git a/src/controllers/__tests__/delivery.test.ts b/src/controllers/__tests__/delivery.test.ts
new file mode 100644
index 00000000000..0f91913f9d0
--- /dev/null
+++ b/src/controllers/__tests__/delivery.test.ts
@@ -0,0 +1,186 @@
+import request from 'supertest';
+import { createHttpTerminator } from 'http-terminator';
+import Koa from 'koa';
+import bodyParser from 'koa-bodyparser';
+import { applicationRoutes } from '../../routes';
+import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration';
+import { ServiceSelector } from '../../helpers/serviceSelector';
+
+let server: any;
+const OLD_ENV = process.env;
+
+beforeAll(async () => {
+ process.env = { ...OLD_ENV }; // Make a copy
+ const app = new Koa();
+ app.use(
+ bodyParser({
+ jsonLimit: '200mb',
+ }),
+ );
+ applicationRoutes(app);
+ server = app.listen(9090);
+});
+
+afterAll(async () => {
+ process.env = OLD_ENV; // Restore old environment
+ const httpTerminator = createHttpTerminator({
+ server,
+ });
+ await httpTerminator.terminate();
+});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+const getData = () => {
+ return { body: { JSON: { a: 'b' } }, metadata: [{ a1: 'b1' }], destinationConfig: { a2: 'b2' } };
+};
+
+describe('Delivery controller tests', () => {
+ describe('Delivery V0 tests', () => {
+ test('successful delivery', async () => {
+ const testOutput = { status: 200, message: 'success' };
+ const mockDestinationService = new NativeIntegrationDestinationService();
+ mockDestinationService.deliver = jest
+ .fn()
+ .mockImplementation((event, destinationType, requestMetadata, version) => {
+ expect(event).toEqual(getData());
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+ return testOutput;
+ });
+ const getNativeDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ output: testOutput });
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1);
+ });
+
+ test('delivery failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+ mockDestinationService.deliver = jest
+ .fn()
+ .mockImplementation((event, destinationType, requestMetadata, version) => {
+ expect(event).toEqual(getData());
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+ throw new Error('test error');
+ });
+ const getNativeDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expectedResp = {
+ output: {
+ message: 'test error',
+ statTags: {
+ errorCategory: 'transformation',
+ },
+ destinationResponse: '',
+ status: 500,
+ },
+ };
+ expect(response.status).toEqual(500);
+ expect(response.body).toEqual(expectedResp);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('Delivery V1 tests', () => {
+ test('successful delivery', async () => {
+ const testOutput = { status: 200, message: 'success' };
+ const mockDestinationService = new NativeIntegrationDestinationService();
+ mockDestinationService.deliver = jest
+ .fn()
+ .mockImplementation((event, destinationType, requestMetadata, version) => {
+ expect(event).toEqual(getData());
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v1');
+ return testOutput;
+ });
+ const getNativeDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/v1/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ output: testOutput });
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1);
+ });
+
+ test('delivery failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+ mockDestinationService.deliver = jest
+ .fn()
+ .mockImplementation((event, destinationType, requestMetadata, version) => {
+ expect(event).toEqual(getData());
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v1');
+ throw new Error('test error');
+ });
+ const getNativeDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/v1/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expectedResp = {
+ output: {
+ message: 'test error',
+ statTags: {
+ errorCategory: 'transformation',
+ },
+ status: 500,
+ response: [{ error: 'test error', metadata: { a1: 'b1' }, statusCode: 500 }],
+ },
+ };
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedResp);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.deliver).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/src/controllers/__tests__/destination.test.ts b/src/controllers/__tests__/destination.test.ts
new file mode 100644
index 00000000000..3c49a9a0aff
--- /dev/null
+++ b/src/controllers/__tests__/destination.test.ts
@@ -0,0 +1,337 @@
+import request from 'supertest';
+import { createHttpTerminator } from 'http-terminator';
+import Koa from 'koa';
+import bodyParser from 'koa-bodyparser';
+import { applicationRoutes } from '../../routes';
+import { ServiceSelector } from '../../helpers/serviceSelector';
+import { DynamicConfigParser } from '../../util/dynamicConfigParser';
+import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration';
+
+let server: any;
+const OLD_ENV = process.env;
+
+beforeAll(async () => {
+ process.env = { ...OLD_ENV }; // Make a copy
+ const app = new Koa();
+ app.use(
+ bodyParser({
+ jsonLimit: '200mb',
+ }),
+ );
+ applicationRoutes(app);
+ server = app.listen(9090);
+});
+
+afterAll(async () => {
+ process.env = OLD_ENV; // Restore old environment
+ const httpTerminator = createHttpTerminator({
+ server,
+ });
+ await httpTerminator.terminate();
+});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+const getData = () => {
+ return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }];
+};
+
+const getRouterTransformInputData = () => {
+ return {
+ input: [
+ { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } },
+ { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } },
+ ],
+ destType: '__rudder_test__',
+ };
+};
+
+describe('Destination controller tests', () => {
+ describe('Destination processor transform tests', () => {
+ test('successful transformation at processor', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ const expectedOutput = [
+ {
+ event: { a: 'b1' },
+ request: { query: {} },
+ message: {},
+ },
+ {
+ event: { a: 'b2' },
+ request: { query: {} },
+ message: {},
+ },
+ ];
+ mockDestinationService.doProcessorTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ expect(events).toEqual(expectedOutput);
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+
+ return events;
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doProcessorTransformation).toHaveBeenCalledTimes(1);
+ });
+
+ test('transformation at processor failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ const expectedOutput = [
+ {
+ statusCode: 500,
+ error: 'Processor transformation failed',
+ statTags: { errorCategory: 'transformation' },
+ },
+ {
+ statusCode: 500,
+ error: 'Processor transformation failed',
+ statTags: { errorCategory: 'transformation' },
+ },
+ ];
+
+ mockDestinationService.doProcessorTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+
+ throw new Error('Processor transformation failed');
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doProcessorTransformation).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('Destination router transform tests', () => {
+ test('successful transformation at router', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ const expectedOutput = [
+ {
+ message: { a: 'b1' },
+ destination: {},
+ metadata: { jobId: 1 },
+ request: { query: {} },
+ },
+ {
+ message: { a: 'b2' },
+ destination: {},
+ metadata: { jobId: 2 },
+ request: { query: {} },
+ },
+ ];
+
+ mockDestinationService.doRouterTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ expect(events).toEqual(expectedOutput);
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+
+ return events;
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/routerTransform')
+ .set('Accept', 'application/json')
+ .send(getRouterTransformInputData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ output: expectedOutput });
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doRouterTransformation).toHaveBeenCalledTimes(1);
+ });
+
+ test('transformation at router failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ mockDestinationService.doRouterTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ throw new Error('Router transformation failed');
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/routerTransform')
+ .set('Accept', 'application/json')
+ .send(getRouterTransformInputData());
+
+ const expectedOutput = [
+ {
+ metadata: [{ jobId: 1 }, { jobId: 2 }],
+ batched: false,
+ statusCode: 500,
+ error: 'Router transformation failed',
+ statTags: { errorCategory: 'transformation' },
+ },
+ ];
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ output: expectedOutput });
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doRouterTransformation).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('Batch transform tests', () => {
+ test('successful batching at router', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ const expectedOutput = [
+ {
+ message: { a: 'b1' },
+ destination: {},
+ metadata: { jobId: 1 },
+ request: { query: {} },
+ },
+ {
+ message: { a: 'b2' },
+ destination: {},
+ metadata: { jobId: 2 },
+ request: { query: {} },
+ },
+ ];
+
+ mockDestinationService.doBatchTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ expect(events).toEqual(expectedOutput);
+ expect(destinationType).toEqual('__rudder_test__');
+ expect(version).toEqual('v0');
+
+ return events;
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/batch')
+ .set('Accept', 'application/json')
+ .send(getRouterTransformInputData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doBatchTransformation).toHaveBeenCalledTimes(1);
+ });
+
+ test('batch transformation failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ mockDestinationService.doBatchTransformation = jest
+ .fn()
+ .mockImplementation((events, destinationType, version, requestMetadata) => {
+ throw new Error('Batch transformation failed');
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ DynamicConfigParser.process = jest.fn().mockImplementation((events) => {
+ return events;
+ });
+
+ const response = await request(server)
+ .post('/batch')
+ .set('Accept', 'application/json')
+ .send(getRouterTransformInputData());
+
+ const expectedOutput = [
+ {
+ metadata: [{ jobId: 1 }, { jobId: 2 }],
+ batched: false,
+ statusCode: 500,
+ error: 'Batch transformation failed',
+ statTags: { errorCategory: 'transformation' },
+ },
+ ];
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.doBatchTransformation).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/src/controllers/__tests__/regulation.test.ts b/src/controllers/__tests__/regulation.test.ts
new file mode 100644
index 00000000000..55cd8f2d376
--- /dev/null
+++ b/src/controllers/__tests__/regulation.test.ts
@@ -0,0 +1,107 @@
+import request from 'supertest';
+import { createHttpTerminator } from 'http-terminator';
+import Koa from 'koa';
+import bodyParser from 'koa-bodyparser';
+import { applicationRoutes } from '../../routes';
+import { ServiceSelector } from '../../helpers/serviceSelector';
+import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration';
+
+let server: any;
+const OLD_ENV = process.env;
+
+beforeAll(async () => {
+ process.env = { ...OLD_ENV }; // Make a copy
+ const app = new Koa();
+ app.use(
+ bodyParser({
+ jsonLimit: '200mb',
+ }),
+ );
+ applicationRoutes(app);
+ server = app.listen(9090);
+});
+
+afterAll(async () => {
+ process.env = OLD_ENV; // Restore old environment
+ const httpTerminator = createHttpTerminator({
+ server,
+ });
+ await httpTerminator.terminate();
+});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+const getDeletionData = () => {
+ return [
+ { userAttributes: [{ a: 'b1' }], destType: '__rudder_test__' },
+ { userAttributes: [{ a: 'b1' }], destType: '__rudder_test__' },
+ ];
+};
+
+describe('Regulation controller tests', () => {
+ describe('Delete users tests', () => {
+ test('successful delete users request', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ const expectedOutput = [{ statusCode: 400 }, { statusCode: 200 }];
+
+ mockDestinationService.processUserDeletion = jest
+ .fn()
+ .mockImplementation((reqs, destInfo) => {
+ expect(reqs).toEqual(getDeletionData());
+ expect(destInfo).toEqual({ a: 'test' });
+
+ return expectedOutput;
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/deleteUsers')
+ .set('Accept', 'application/json')
+ .set('x-rudder-dest-info', '{"a": "test"}')
+ .send(getDeletionData());
+
+ expect(response.status).toEqual(400);
+ expect(response.body).toEqual(expectedOutput);
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.processUserDeletion).toHaveBeenCalledTimes(1);
+ });
+
+ test('delete users request failure', async () => {
+ const mockDestinationService = new NativeIntegrationDestinationService();
+
+ mockDestinationService.processUserDeletion = jest
+ .fn()
+ .mockImplementation((reqs, destInfo) => {
+ expect(reqs).toEqual(getDeletionData());
+ expect(destInfo).toEqual({ a: 'test' });
+
+ throw new Error('processUserDeletion error');
+ });
+ const getDestinationServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeDestinationService')
+ .mockImplementation(() => {
+ return mockDestinationService;
+ });
+
+ const response = await request(server)
+ .post('/deleteUsers')
+ .set('Accept', 'application/json')
+ .set('x-rudder-dest-info', '{"a": "test"}')
+ .send(getDeletionData());
+
+ expect(response.status).toEqual(500);
+ expect(response.body).toEqual([{ error: {}, statusCode: 500 }]);
+
+ expect(getDestinationServiceSpy).toHaveBeenCalledTimes(1);
+ expect(mockDestinationService.processUserDeletion).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/src/controllers/__tests__/source.test.ts b/src/controllers/__tests__/source.test.ts
new file mode 100644
index 00000000000..565f39d559f
--- /dev/null
+++ b/src/controllers/__tests__/source.test.ts
@@ -0,0 +1,220 @@
+import request from 'supertest';
+import { createHttpTerminator } from 'http-terminator';
+import Koa from 'koa';
+import bodyParser from 'koa-bodyparser';
+import { applicationRoutes } from '../../routes';
+import { NativeIntegrationSourceService } from '../../services/source/nativeIntegration';
+import { ServiceSelector } from '../../helpers/serviceSelector';
+import { ControllerUtility } from '../util/index';
+
+let server: any;
+const OLD_ENV = process.env;
+
+beforeAll(async () => {
+ process.env = { ...OLD_ENV }; // Make a copy
+ const app = new Koa();
+ app.use(
+ bodyParser({
+ jsonLimit: '200mb',
+ }),
+ );
+ applicationRoutes(app);
+ server = app.listen(9090);
+});
+
+afterAll(async () => {
+ process.env = OLD_ENV; // Restore old environment
+ const httpTerminator = createHttpTerminator({
+ server,
+ });
+ await httpTerminator.terminate();
+});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+const getData = () => {
+ return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }];
+};
+
+describe('Source controller tests', () => {
+ describe('V0 Source transform tests', () => {
+ test('successful source transform', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v0';
+ const testOutput = [{ event: { a: 'b' } }];
+
+ const mockSourceService = new NativeIntegrationSourceService();
+ mockSourceService.sourceTransformRoutine = jest
+ .fn()
+ .mockImplementation((i, s, v, requestMetadata) => {
+ expect(i).toEqual(getData());
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ return testOutput;
+ });
+ const getNativeSourceServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeSourceService')
+ .mockImplementation(() => {
+ return mockSourceService;
+ });
+
+ const adaptInputToVersionSpy = jest
+ .spyOn(ControllerUtility, 'adaptInputToVersion')
+ .mockImplementation((s, v, e) => {
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ expect(e).toEqual(getData());
+ return { implementationVersion: version, input: e };
+ });
+
+ const response = await request(server)
+ .post('/v0/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(testOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1);
+ expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1);
+ expect(mockSourceService.sourceTransformRoutine).toHaveBeenCalledTimes(1);
+ });
+
+ test('failing source transform', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v0';
+
+ const mockSourceService = new NativeIntegrationSourceService();
+ const getNativeSourceServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeSourceService')
+ .mockImplementation(() => {
+ return mockSourceService;
+ });
+
+ const adaptInputToVersionSpy = jest
+ .spyOn(ControllerUtility, 'adaptInputToVersion')
+ .mockImplementation((s, v, e) => {
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ expect(e).toEqual(getData());
+ throw new Error('test error');
+ });
+
+ const response = await request(server)
+ .post('/v0/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expectedResp = [
+ {
+ error: 'test error',
+ statTags: {
+ errorCategory: 'transformation',
+ },
+ statusCode: 500,
+ },
+ ];
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedResp);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1);
+ expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('V1 Source transform tests', () => {
+ test('successful source transform', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v1';
+ const testOutput = [{ event: { a: 'b' }, source: { id: 'id' } }];
+
+ const mockSourceService = new NativeIntegrationSourceService();
+ mockSourceService.sourceTransformRoutine = jest
+ .fn()
+ .mockImplementation((i, s, v, requestMetadata) => {
+ expect(i).toEqual(getData());
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ return testOutput;
+ });
+ const getNativeSourceServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeSourceService')
+ .mockImplementation(() => {
+ return mockSourceService;
+ });
+
+ const adaptInputToVersionSpy = jest
+ .spyOn(ControllerUtility, 'adaptInputToVersion')
+ .mockImplementation((s, v, e) => {
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ expect(e).toEqual(getData());
+ return { implementationVersion: version, input: e };
+ });
+
+ const response = await request(server)
+ .post('/v1/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(testOutput);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1);
+ expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1);
+ expect(mockSourceService.sourceTransformRoutine).toHaveBeenCalledTimes(1);
+ });
+
+ test('failing source transform', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v1';
+ const mockSourceService = new NativeIntegrationSourceService();
+ const getNativeSourceServiceSpy = jest
+ .spyOn(ServiceSelector, 'getNativeSourceService')
+ .mockImplementation(() => {
+ return mockSourceService;
+ });
+
+ const adaptInputToVersionSpy = jest
+ .spyOn(ControllerUtility, 'adaptInputToVersion')
+ .mockImplementation((s, v, e) => {
+ expect(s).toEqual(sourceType);
+ expect(v).toEqual(version);
+ expect(e).toEqual(getData());
+ throw new Error('test error');
+ });
+
+ const response = await request(server)
+ .post('/v1/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expectedResp = [
+ {
+ error: 'test error',
+ statTags: {
+ errorCategory: 'transformation',
+ },
+ statusCode: 500,
+ },
+ ];
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual(expectedResp);
+
+ expect(response.header['apiversion']).toEqual('2');
+
+ expect(getNativeSourceServiceSpy).toHaveBeenCalledTimes(1);
+ expect(adaptInputToVersionSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/src/controllers/bulkUpload.ts b/src/controllers/bulkUpload.ts
index babb8b6db16..dbd77dc07f8 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 { client as errNotificationClient } from '../util/errorNotifier';
import logger from '../logger';
+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 eba24ccf583..4334dc33b2c 100644
--- a/src/controllers/delivery.ts
+++ b/src/controllers/delivery.ts
@@ -1,13 +1,14 @@
/* eslint-disable prefer-destructuring */
/* eslint-disable sonarjs/no-duplicate-string */
import { Context } from 'koa';
+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';
@@ -22,9 +23,9 @@ const NON_DETERMINABLE = 'Non-determinable';
export class DeliveryController {
public static async deliverToDestination(ctx: Context) {
logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body));
- let deliveryResponse: DeliveryResponse;
+ 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 +34,7 @@ export class DeliveryController {
destination,
requestMetadata,
'v0',
- )) as DeliveryResponse;
+ )) as DeliveryV0Response;
} catch (error: any) {
const { metadata } = deliveryRequest;
const metaTO = integrationService.getTags(
@@ -57,9 +58,9 @@ export class DeliveryController {
public static async deliverToDestinationV1(ctx: Context) {
logger.debug('Native(Delivery):: Request to transformer::', JSON.stringify(ctx.request.body));
- let deliveryResponse: DeliveriesResponse;
+ 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 +69,7 @@ export class DeliveryController {
destination,
requestMetadata,
'v1',
- )) as DeliveriesResponse;
+ )) as DeliveryV1Response;
} catch (error: any) {
const { metadata } = deliveryRequest;
const metaTO = integrationService.getTags(
@@ -84,7 +85,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::', JSON.stringify(ctx.body));
return ctx;
diff --git a/src/controllers/destination.ts b/src/controllers/destination.ts
index 71075d1b4ce..d8b3c945247 100644
--- a/src/controllers/destination.ts
+++ b/src/controllers/destination.ts
@@ -15,6 +15,7 @@ import logger from '../logger';
import { getIntegrationVersion } from '../util/utils';
import tags from '../v0/util/tags';
import { DynamicConfigParser } from '../util/dynamicConfigParser';
+import { checkInvalidRtTfEvents } from '../v0/util';
export class DestinationController {
public static async destinationTransformAtProcessor(ctx: Context) {
@@ -101,6 +102,20 @@ export class DestinationController {
const routerRequest = ctx.request.body as RouterTransformationRequest;
const destination = routerRequest.destType;
let events = routerRequest.input;
+ const errorRespEvents = checkInvalidRtTfEvents(events);
+ if (errorRespEvents.length > 0) {
+ errorRespEvents[0].metadata = [
+ {
+ destType: destination,
+ },
+ ];
+ logger.debug(
+ `[${destination}] Invalid router transform payload structure: ${JSON.stringify(events)}`,
+ );
+ ctx.body = { output: errorRespEvents };
+ ControllerUtility.postProcess(ctx);
+ return ctx;
+ }
const metaTags = MiscService.getMetaTags(events[0].metadata);
stats.histogram('dest_transform_input_events', events.length, {
destination,
diff --git a/src/controllers/obs.delivery.js b/src/controllers/obs.delivery.js
index 4a93afe1dcb..8e99650af64 100644
--- a/src/controllers/obs.delivery.js
+++ b/src/controllers/obs.delivery.js
@@ -1,7 +1,7 @@
/**
* --------------------------------------
* --------------------------------------
- * ---------TO BE DEPRICIATED------------
+ * ---------TO BE DEPRECATED-------------
* --------------------------------------
* --------------------------------------
*/
@@ -96,11 +96,15 @@ const DestProxyController = {
destination,
});
- response = generateErrorObject(err, {
- [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(),
- [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION,
- [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY,
- }, false);
+ response = generateErrorObject(
+ err,
+ {
+ [tags.TAG_NAMES.DEST_TYPE]: destination.toUpperCase(),
+ [tags.TAG_NAMES.MODULE]: tags.MODULES.DESTINATION,
+ [tags.TAG_NAMES.FEATURE]: tags.FEATURES.DATA_DELIVERY,
+ },
+ false,
+ );
response.message = `[TransformerProxyTest] Error occurred while testing proxy for destination ("${destination}"): "${err.message}"`;
logger.error(response.message);
logger.error(err);
diff --git a/src/controllers/regulation.ts b/src/controllers/regulation.ts
index a50541780d6..318b5ed4e78 100644
--- a/src/controllers/regulation.ts
+++ b/src/controllers/regulation.ts
@@ -34,7 +34,7 @@ export class RegulationController {
rudderDestInfo,
);
ctx.body = resplist;
- ctx.status = resplist[0].statusCode;
+ ctx.status = resplist[0].statusCode; // TODO: check if this is the right way to set status
} catch (error: CatchErr) {
const metaTO = integrationService.getTags(
userDeletionRequests[0].destType,
@@ -46,8 +46,8 @@ export class RegulationController {
const errResp = DestinationPostTransformationService.handleUserDeletionFailureEvents(
error,
metaTO,
- );
- ctx.body = [{ error, statusCode: 500 }] as UserDeletionResponse[];
+ ); // TODO: this is not used. Fix it.
+ ctx.body = [{ error, statusCode: 500 }] as UserDeletionResponse[]; // TODO: responses array length is always 1. Is that okay?
ctx.status = 500;
}
stats.timing('dest_transform_request_latency', startTime, {
diff --git a/src/controllers/trackingPlan.ts b/src/controllers/trackingPlan.ts
index 74e47e0ec92..e4802cfc4d8 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 c344bd072ad..3e01686a52a 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',
JSON.stringify(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 75d3d8ffa7c..c5bf7ab3580 100644
--- a/src/controllers/util/index.ts
+++ b/src/controllers/util/index.ts
@@ -51,7 +51,7 @@ export class ControllerUtility {
private static convertSourceInputv0Tov1(sourceEvents: unknown[]): SourceInput[] {
return sourceEvents.map(
- (sourceEvent) => ({ event: sourceEvent, source: undefined } as SourceInput),
+ (sourceEvent) => ({ event: sourceEvent, source: undefined }) as SourceInput,
);
}
diff --git a/src/features.json b/src/features.json
index 8709dce4326..267923fdb4d 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 00000000000..2135317cafb
--- /dev/null
+++ b/src/helpers/__tests__/fetchHandlers.test.ts
@@ -0,0 +1,36 @@
+import { FetchHandler } from '../fetchHandlers';
+import { MiscService } from '../../services/misc';
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('FetchHandlers Service', () => {
+ test('should save the handlers in the respective maps', async () => {
+ const dest = 'dest';
+ const source = 'source';
+ const version = 'version';
+
+ MiscService.getDestHandler = jest.fn().mockImplementation((dest, version) => {
+ return {};
+ });
+ MiscService.getSourceHandler = jest.fn().mockImplementation((source, version) => {
+ return {};
+ });
+ MiscService.getDeletionHandler = jest.fn().mockImplementation((source, version) => {
+ return {};
+ });
+
+ expect(FetchHandler['sourceHandlerMap'].get(dest)).toBeUndefined();
+ FetchHandler.getSourceHandler(dest, version);
+ expect(FetchHandler['sourceHandlerMap'].get(dest)).toBeDefined();
+
+ expect(FetchHandler['destHandlerMap'].get(dest)).toBeUndefined();
+ FetchHandler.getDestHandler(dest, version);
+ expect(FetchHandler['destHandlerMap'].get(dest)).toBeDefined();
+
+ expect(FetchHandler['deletionHandlerMap'].get(dest)).toBeUndefined();
+ FetchHandler.getDeletionHandler(dest, version);
+ expect(FetchHandler['deletionHandlerMap'].get(dest)).toBeDefined();
+ });
+});
diff --git a/src/helpers/__tests__/serviceSelector.test.ts b/src/helpers/__tests__/serviceSelector.test.ts
new file mode 100644
index 00000000000..c48d6bbe8b7
--- /dev/null
+++ b/src/helpers/__tests__/serviceSelector.test.ts
@@ -0,0 +1,105 @@
+import { ServiceSelector } from '../serviceSelector';
+import { INTEGRATION_SERVICE } from '../../routes/utils/constants';
+import { ProcessorTransformationRequest } from '../../types/index';
+import { CDKV1DestinationService } from '../../services/destination/cdkV1Integration';
+import { CDKV2DestinationService } from '../../services/destination/cdkV2Integration';
+import { NativeIntegrationDestinationService } from '../../services/destination/nativeIntegration';
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('ServiceSelector Service', () => {
+ test('should save the service in the cache', async () => {
+ expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_DEST)).toBeUndefined();
+ expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_SOURCE)).toBeUndefined();
+
+ ServiceSelector.getNativeDestinationService();
+ ServiceSelector.getNativeSourceService();
+
+ expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_DEST)).toBeDefined();
+ expect(ServiceSelector['serviceMap'].get(INTEGRATION_SERVICE.NATIVE_SOURCE)).toBeDefined();
+ });
+
+ test('fetchCachedService should throw error for invalidService', async () => {
+ expect(() => ServiceSelector['fetchCachedService']('invalidService')).toThrow(
+ 'Invalid Service',
+ );
+ });
+
+ test('isCdkDestination should return true', async () => {
+ const destinationDefinitionConfig = {
+ cdkEnabled: true,
+ };
+ expect(ServiceSelector['isCdkDestination'](destinationDefinitionConfig)).toBe(true);
+ });
+
+ test('isCdkDestination should return false', async () => {
+ const destinationDefinitionConfig = {
+ cdkEnabledXYZ: true,
+ };
+ expect(ServiceSelector['isCdkDestination'](destinationDefinitionConfig)).toBe(false);
+ });
+
+ test('isCdkV2Destination should return true', async () => {
+ const destinationDefinitionConfig = {
+ cdkV2Enabled: true,
+ };
+ expect(ServiceSelector['isCdkV2Destination'](destinationDefinitionConfig)).toBe(true);
+ });
+
+ test('isCdkV2Destination should return false', async () => {
+ const destinationDefinitionConfig = {
+ cdkV2EnabledXYZ: true,
+ };
+ expect(ServiceSelector['isCdkV2Destination'](destinationDefinitionConfig)).toBe(false);
+ });
+
+ test('getPrimaryDestinationService should return cdk v1 dest service', async () => {
+ const events = [
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkEnabled: true,
+ },
+ },
+ },
+ },
+ ] as ProcessorTransformationRequest[];
+ expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf(
+ CDKV1DestinationService,
+ );
+ });
+
+ test('getPrimaryDestinationService should return cdk v2 dest service', async () => {
+ const events = [
+ {
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ },
+ },
+ },
+ },
+ ] as ProcessorTransformationRequest[];
+ expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf(
+ CDKV2DestinationService,
+ );
+ });
+
+ test('getPrimaryDestinationService should return native dest service', async () => {
+ const events = [{}] as ProcessorTransformationRequest[];
+ expect(ServiceSelector['getPrimaryDestinationService'](events)).toBeInstanceOf(
+ NativeIntegrationDestinationService,
+ );
+ });
+
+ test('getDestinationService should return native dest service', async () => {
+ const events = [{}] as ProcessorTransformationRequest[];
+ expect(ServiceSelector.getDestinationService(events)).toBeInstanceOf(
+ NativeIntegrationDestinationService,
+ );
+ });
+});
diff --git a/src/helpers/serviceSelector.ts b/src/helpers/serviceSelector.ts
index 89678e94074..faa1c58240c 100644
--- a/src/helpers/serviceSelector.ts
+++ b/src/helpers/serviceSelector.ts
@@ -79,7 +79,7 @@ export class ServiceSelector {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public static getSourceService(arg: unknown) {
- // Implement source event based descision logic for selecting service
+ // Implement source event based decision logic for selecting service
}
public static getDestinationService(
diff --git a/src/interfaces/DestinationService.ts b/src/interfaces/DestinationService.ts
index bf39024d85e..4947089b5d1 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 f8deb3fe622..9dd83b5988f 100644
--- a/src/legacy/router.js
+++ b/src/legacy/router.js
@@ -7,7 +7,7 @@ const Router = require('@koa/router');
const lodash = require('lodash');
const fs = require('fs');
const path = require('path');
-const { PlatformError } = require('@rudderstack/integrations-lib');
+const { PlatformError, getErrorRespEvents } = require('@rudderstack/integrations-lib');
const logger = require('../logger');
const stats = require('../util/stats');
const { SUPPORTED_VERSIONS, API_VERSION } = require('../routes/utils/constants');
@@ -18,7 +18,6 @@ const {
isNonFuncObject,
getMetadata,
generateErrorObject,
- getErrorRespEvents,
isCdkDestination,
checkAndCorrectUserId,
} = require('../v0/util');
diff --git a/src/services/__tests__/misc.test.ts b/src/services/__tests__/misc.test.ts
new file mode 100644
index 00000000000..5dcd948b34e
--- /dev/null
+++ b/src/services/__tests__/misc.test.ts
@@ -0,0 +1,26 @@
+import { DestHandlerMap } from '../../constants/destinationCanonicalNames';
+import { MiscService } from '../misc';
+
+describe('Misc tests', () => {
+ test('should return the right transform', async () => {
+ const version = 'v0';
+
+ Object.keys(DestHandlerMap).forEach((key) => {
+ expect(MiscService.getDestHandler(key, version)).toEqual(
+ require(`../../${version}/destinations/${DestHandlerMap[key]}/transform`),
+ );
+ });
+
+ expect(MiscService.getDestHandler('am', version)).toEqual(
+ require(`../../${version}/destinations/am/transform`),
+ );
+
+ expect(MiscService.getSourceHandler('shopify', version)).toEqual(
+ require(`../../${version}/sources/shopify/transform`),
+ );
+
+ expect(MiscService.getDeletionHandler('intercom', version)).toEqual(
+ require(`../../${version}/destinations/intercom/deleteUsers`),
+ );
+ });
+});
diff --git a/src/services/comparator.ts b/src/services/comparator.ts
index d1e085b4bd6..36cb0ebd5a4 100644
--- a/src/services/comparator.ts
+++ b/src/services/comparator.ts
@@ -1,8 +1,8 @@
/* eslint-disable class-methods-use-this */
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 00000000000..59c8b418817
--- /dev/null
+++ b/src/services/destination/__tests__/nativeIntegration.test.ts
@@ -0,0 +1,100 @@
+import { NativeIntegrationDestinationService } from '../nativeIntegration';
+import { DestinationPostTransformationService } from '../postTransformation';
+import {
+ ProcessorTransformationRequest,
+ ProcessorTransformationOutput,
+ ProcessorTransformationResponse,
+} from '../../../types/index';
+import { FetchHandler } from '../../../helpers/fetchHandlers';
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('NativeIntegration Service', () => {
+ test('doProcessorTransformation - success', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+ const requestMetadata = {};
+ const event = { message: { a: 'b' } } as ProcessorTransformationRequest;
+ const events: ProcessorTransformationRequest[] = [event, event];
+
+ const tevent = { version: 'v0', endpoint: 'http://abc' } as ProcessorTransformationOutput;
+ const tresp = { output: tevent, statusCode: 200 } as ProcessorTransformationResponse;
+ const tresponse: ProcessorTransformationResponse[] = [tresp, tresp];
+
+ FetchHandler.getDestHandler = jest.fn().mockImplementation((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ process: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const postTransformSpy = jest
+ .spyOn(DestinationPostTransformationService, 'handleProcessorTransformSucessEvents')
+ .mockImplementation((e, p, d) => {
+ expect(e).toEqual(event);
+ expect(p).toEqual(tevent);
+ return [tresp];
+ });
+
+ const service = new NativeIntegrationDestinationService();
+ const resp = await service.doProcessorTransformation(
+ events,
+ destType,
+ version,
+ requestMetadata,
+ );
+
+ expect(resp).toEqual(tresponse);
+
+ expect(postTransformSpy).toHaveBeenCalledTimes(2);
+ });
+
+ test('doProcessorTransformation - failure', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+ const requestMetadata = {};
+ const event = { message: { a: 'b' } } as ProcessorTransformationRequest;
+ const events: ProcessorTransformationRequest[] = [event, event];
+
+ FetchHandler.getDestHandler = jest.fn().mockImplementation((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ process: jest.fn(() => {
+ throw new Error('test error');
+ }),
+ };
+ });
+
+ const service = new NativeIntegrationDestinationService();
+ const resp = await service.doProcessorTransformation(
+ events,
+ destType,
+ version,
+ requestMetadata,
+ );
+
+ const expected = [
+ {
+ metadata: undefined,
+ statusCode: 500,
+ error: 'test error',
+ statTags: { errorCategory: 'transformation' },
+ },
+ {
+ metadata: undefined,
+ statusCode: 500,
+ error: 'test error',
+ statTags: { errorCategory: 'transformation' },
+ },
+ ];
+
+ console.log('resp:', resp);
+ expect(resp).toEqual(expected);
+ });
+});
diff --git a/src/services/destination/__tests__/postTransformation.test.ts b/src/services/destination/__tests__/postTransformation.test.ts
new file mode 100644
index 00000000000..f961dcbce7e
--- /dev/null
+++ b/src/services/destination/__tests__/postTransformation.test.ts
@@ -0,0 +1,22 @@
+import { MetaTransferObject, ProcessorTransformationRequest } from '../../../types/index';
+import { DestinationPostTransformationService } from '../postTransformation';
+import { ProcessorTransformationResponse } from '../../../types';
+
+describe('PostTransformation Service', () => {
+ test('should handleProcessorTransformFailureEvents', async () => {
+ const e = new Error('test error');
+ const metaTo = { errorContext: 'error Context' } as MetaTransferObject;
+ const resp = DestinationPostTransformationService.handleProcessorTransformFailureEvents(
+ e,
+ metaTo,
+ );
+
+ const expected = {
+ statusCode: 500,
+ error: 'test error',
+ statTags: { errorCategory: 'transformation' },
+ } as ProcessorTransformationResponse;
+
+ expect(resp).toEqual(expected);
+ });
+});
diff --git a/src/services/destination/__tests__/preTransformation.test.ts b/src/services/destination/__tests__/preTransformation.test.ts
new file mode 100644
index 00000000000..c10bab78acb
--- /dev/null
+++ b/src/services/destination/__tests__/preTransformation.test.ts
@@ -0,0 +1,23 @@
+import { createMockContext } from '@shopify/jest-koa-mocks';
+import { ProcessorTransformationRequest } from '../../../types/index';
+import { DestinationPreTransformationService } from '../../destination/preTransformation';
+
+describe('PreTransformation Service', () => {
+ test('should enhance events with query params', async () => {
+ const ctx = createMockContext();
+ ctx.request.query = { cycle: 'true', x: 'y' };
+
+ const events: ProcessorTransformationRequest[] = [
+ { message: { a: 'b' } } as ProcessorTransformationRequest,
+ ];
+ const expected: ProcessorTransformationRequest[] = [
+ {
+ message: { a: 'b' },
+ request: { query: { cycle: 'true', x: 'y' } },
+ } as ProcessorTransformationRequest,
+ ];
+
+ const resp = DestinationPreTransformationService.preProcess(events, ctx);
+ expect(resp).toEqual(expected);
+ });
+});
diff --git a/src/services/destination/cdkV1Integration.ts b/src/services/destination/cdkV1Integration.ts
index 197e3162ea6..c6e60f58577 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 be7f0e51d5c..c18a5cd936d 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';
@@ -170,7 +170,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 6b680e3f4a1..2bb82fc602f 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';
@@ -181,7 +181,7 @@ export class NativeIntegrationDestinationService implements DestinationService {
destinationType: string,
_requestMetadata: NonNullable,
version: string,
- ): Promise {
+ ): Promise {
try {
const { networkHandler, handlerVersion } = networkHandlerFactory.getNetworkHandler(
destinationType,
@@ -191,37 +191,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) {
@@ -236,10 +238,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 eef4152b2bd..161547683b5 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';
@@ -75,7 +75,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}`;
}
});
@@ -145,7 +151,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,
@@ -155,7 +161,7 @@ export class DestinationPostTransformationService {
...(errObj.authErrorCategory && {
authErrorCategory: errObj.authErrorCategory,
}),
- } as DeliveryResponse;
+ } as DeliveryV0Response;
ErrorReportingService.reportError(error, metaTo.errorContext, resp);
return resp;
@@ -164,7 +170,7 @@ export class DestinationPostTransformationService {
public static handlevV1DeliveriesFailureEvents(
error: FixMe,
metaTo: MetaTransferObject,
- ): DeliveriesResponse {
+ ): DeliveryV1Response {
const errObj = generateErrorObject(error, metaTo.errorDetails, false);
const metadataArray = metaTo.metadatas;
if (!Array.isArray(metadataArray)) {
@@ -186,10 +192,12 @@ export class DestinationPostTransformationService {
const resp = {
response: responses,
statTags: errObj.statTags,
- authErrorCategory: errObj.authErrorCategory,
message: errObj.message.toString(),
status: errObj.status,
- } as DeliveriesResponse;
+ ...(errObj.authErrorCategory && {
+ authErrorCategory: errObj.authErrorCategory,
+ }),
+ } as DeliveryV1Response;
ErrorReportingService.reportError(error, metaTo.errorContext, resp);
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 00000000000..bb40438811f
--- /dev/null
+++ b/src/services/source/__tests__/nativeIntegration.test.ts
@@ -0,0 +1,89 @@
+import { NativeIntegrationSourceService } from '../nativeIntegration';
+import { SourcePostTransformationService } from '../postTransformation';
+import { SourceTransformationResponse, RudderMessage } from '../../../types/index';
+import stats from '../../../util/stats';
+import { FetchHandler } from '../../../helpers/fetchHandlers';
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+describe('NativeIntegration Source Service', () => {
+ test('sourceTransformRoutine - success', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v0';
+ const requestMetadata = {};
+
+ const event = { message: { a: 'b' } };
+ const events = [event, event];
+
+ const tevent = { anonymousId: 'test' } as RudderMessage;
+ const tresp = { output: { batch: [tevent] }, statusCode: 200 } as SourceTransformationResponse;
+
+ const tresponse = [
+ { output: { batch: [{ anonymousId: 'test' }] }, statusCode: 200 },
+ { output: { batch: [{ anonymousId: 'test' }] }, statusCode: 200 },
+ ];
+
+ FetchHandler.getSourceHandler = jest.fn().mockImplementationOnce((d, v) => {
+ expect(d).toEqual(sourceType);
+ expect(v).toEqual(version);
+ return {
+ process: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const postTransformSpy = jest
+ .spyOn(SourcePostTransformationService, 'handleSuccessEventsSource')
+ .mockImplementation((e) => {
+ expect(e).toEqual(tevent);
+ return tresp;
+ });
+
+ const service = new NativeIntegrationSourceService();
+ const resp = await service.sourceTransformRoutine(events, sourceType, version, requestMetadata);
+
+ expect(resp).toEqual(tresponse);
+
+ expect(postTransformSpy).toHaveBeenCalledTimes(2);
+ });
+
+ test('sourceTransformRoutine - failure', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v0';
+ const requestMetadata = {};
+
+ const event = { message: { a: 'b' } };
+ const events = [event, event];
+
+ const tresp = { error: 'error' } as SourceTransformationResponse;
+
+ const tresponse = [{ error: 'error' }, { error: 'error' }];
+
+ FetchHandler.getSourceHandler = jest.fn().mockImplementationOnce((d, v) => {
+ expect(d).toEqual(sourceType);
+ expect(v).toEqual(version);
+ return {
+ process: jest.fn(() => {
+ throw new Error('test error');
+ }),
+ };
+ });
+
+ const postTransformSpy = jest
+ .spyOn(SourcePostTransformationService, 'handleFailureEventsSource')
+ .mockImplementation((e, m) => {
+ return tresp;
+ });
+ jest.spyOn(stats, 'increment').mockImplementation(() => {});
+
+ const service = new NativeIntegrationSourceService();
+ const resp = await service.sourceTransformRoutine(events, sourceType, version, requestMetadata);
+
+ expect(resp).toEqual(tresponse);
+
+ expect(postTransformSpy).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/services/source/__tests__/postTransformation.test.ts b/src/services/source/__tests__/postTransformation.test.ts
new file mode 100644
index 00000000000..e5efbe8194f
--- /dev/null
+++ b/src/services/source/__tests__/postTransformation.test.ts
@@ -0,0 +1,49 @@
+import {
+ MetaTransferObject,
+ RudderMessage,
+ SourceTransformationResponse,
+} from '../../../types/index';
+import { SourcePostTransformationService } from '../../source/postTransformation';
+
+describe('Source PostTransformation Service', () => {
+ test('should handleFailureEventsSource', async () => {
+ const e = new Error('test error');
+ const metaTo = { errorContext: 'error Context' } as MetaTransferObject;
+ const resp = SourcePostTransformationService.handleFailureEventsSource(e, metaTo);
+
+ const expected = {
+ statusCode: 500,
+ error: 'test error',
+ statTags: { errorCategory: 'transformation' },
+ } as SourceTransformationResponse;
+
+ expect(resp).toEqual(expected);
+ });
+
+ test('should return the event as SourceTransformationResponse if it has outputToSource property', () => {
+ const event = {
+ outputToSource: {},
+ output: { batch: [{ anonymousId: 'test' }] },
+ } as SourceTransformationResponse;
+
+ const result = SourcePostTransformationService.handleSuccessEventsSource(event);
+
+ expect(result).toEqual(event);
+ });
+
+ test('should return the events as batch in SourceTransformationResponse if it is an array', () => {
+ const events = [{ anonymousId: 'test' }, { anonymousId: 'test' }] as RudderMessage[];
+
+ const result = SourcePostTransformationService.handleSuccessEventsSource(events);
+
+ expect(result).toEqual({ output: { batch: events } });
+ });
+
+ test('should return the event as batch in SourceTransformationResponse if it is a single object', () => {
+ const event = { anonymousId: 'test' } as RudderMessage;
+
+ const result = SourcePostTransformationService.handleSuccessEventsSource(event);
+
+ expect(result).toEqual({ output: { batch: [event] } });
+ });
+});
diff --git a/src/services/trackingPlan.ts b/src/services/trackingPlan.ts
index 2e68df55e99..93b6ee11ffe 100644
--- a/src/services/trackingPlan.ts
+++ b/src/services/trackingPlan.ts
@@ -1,88 +1,87 @@
import logger from '../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.error(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 };
diff --git a/src/services/userTransform.ts b/src/services/userTransform.ts
index bf34e3d82a3..18c47ddc83a 100644
--- a/src/services/userTransform.ts
+++ b/src/services/userTransform.ts
@@ -14,7 +14,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 logger from '../logger';
import stats from '../util/stats';
@@ -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 f4432e5c2ac..68dfe3870db 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 00000000000..75f12c5e9b3
--- /dev/null
+++ b/src/types/zodTypes.ts
@@ -0,0 +1,242 @@
+import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib';
+import { z } from 'zod';
+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 60f8e493faa..7e854a3714b 100644
--- a/src/util/customTransformer-v1.js
+++ b/src/util/customTransformer-v1.js
@@ -55,7 +55,7 @@ async function userTransformHandlerV1(
testMode = false,
) {
if (!userTransformation.versionId) {
- return { transformedEvents : events };
+ return { transformedEvents: events };
}
const isolatevmFactory = await getFactory(
@@ -88,9 +88,9 @@ async function userTransformHandlerV1(
const tags = {
identifier: 'v1',
errored: transformationError ? true : false,
- ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {},
- ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}
- }
+ ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}),
+ ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}),
+ };
stats.counter('user_transform_function_input_events', events.length, tags);
stats.timing('user_transform_function_latency', invokeTime, tags);
}
diff --git a/src/util/customTransformer.js b/src/util/customTransformer.js
index 001fe3216cf..a87c12dd6e2 100644
--- a/src/util/customTransformer.js
+++ b/src/util/customTransformer.js
@@ -254,9 +254,9 @@ async function runUserTransform(
const tags = {
identifier: 'v0',
errored: transformationError ? true : false,
- ...events.length && events[0].metadata ? getMetadata(events[0].metadata) : {},
- ...events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}
- }
+ ...(events.length && events[0].metadata ? getMetadata(events[0].metadata) : {}),
+ ...(events.length && events[0].metadata ? getTransformationMetadata(events[0].metadata) : {}),
+ };
stats.counter('user_transform_function_input_events', events.length, tags);
stats.timing('user_transform_function_latency', invokeTime, tags);
diff --git a/src/util/customTransformerFactory.js b/src/util/customTransformerFactory.js
index 1bf10e5d458..ee535319460 100644
--- a/src/util/customTransformerFactory.js
+++ b/src/util/customTransformerFactory.js
@@ -1,12 +1,6 @@
-const {
- setOpenFaasUserTransform,
- runOpenFaasUserTransform,
-} = require('./customTransformer-faas');
+const { setOpenFaasUserTransform, runOpenFaasUserTransform } = require('./customTransformer-faas');
-const {
- userTransformHandlerV1,
- setUserTransformHandlerV1,
-} = require('./customTransformer-v1');
+const { userTransformHandlerV1, setUserTransformHandlerV1 } = require('./customTransformer-v1');
const UserTransformHandlerFactory = (userTransformation) => {
return {
@@ -23,20 +17,10 @@ const UserTransformHandlerFactory = (userTransformation) => {
switch (userTransformation.language) {
case 'pythonfaas':
case 'python':
- return runOpenFaasUserTransform(
- events,
- userTransformation,
- libraryVersionIds,
- testMode
- );
+ return runOpenFaasUserTransform(events, userTransformation, libraryVersionIds, testMode);
default:
- return userTransformHandlerV1(
- events,
- userTransformation,
- libraryVersionIds,
- testMode
- );
+ return userTransformHandlerV1(events, userTransformation, libraryVersionIds, testMode);
}
},
};
diff --git a/src/util/error-extractor/index.ts b/src/util/error-extractor/index.ts
index b8cfef4136a..6ff374b8697 100644
--- a/src/util/error-extractor/index.ts
+++ b/src/util/error-extractor/index.ts
@@ -1,16 +1,18 @@
/* eslint-disable max-classes-per-file */
-import { MessageDetails, StatusCode } from "./types";
+import { MessageDetails, StatusCode, Stat } from './types';
export class ErrorDetailsExtractor {
status: StatusCode;
messageDetails: MessageDetails;
- constructor (builder: ErrorDetailsExtractorBuilder) {
+ stat: Stat;
+
+ constructor(builder: ErrorDetailsExtractorBuilder) {
this.status = builder.getStatus();
this.messageDetails = builder.getMessageDetails();
+ this.stat = builder.getStat();
}
-
}
export class ErrorDetailsExtractorBuilder {
@@ -18,21 +20,29 @@ export class ErrorDetailsExtractorBuilder {
messageDetails: MessageDetails;
+ stat: Stat;
+
constructor() {
this.status = 0;
this.messageDetails = {};
+ this.stat = {};
}
-
+
setStatus(status: number): ErrorDetailsExtractorBuilder {
this.status = status;
return this;
}
+ setStat(stat: Record): ErrorDetailsExtractorBuilder {
+ this.stat = stat;
+ 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) {
@@ -40,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) {
@@ -57,22 +67,24 @@ 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 {
return this.status;
}
-
+
+ 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 7c500177cb3..b93d2fafe50 100644
--- a/src/util/error-extractor/types.ts
+++ b/src/util/error-extractor/types.ts
@@ -1,2 +1,3 @@
export type MessageDetails = Record;
-export type StatusCode = number;
\ No newline at end of file
+export type StatusCode = number;
+export type Stat = Record;
diff --git a/src/util/fetchDestinationHandlers.ts b/src/util/fetchDestinationHandlers.ts
new file mode 100644
index 00000000000..2661ef2e686
--- /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 2ab5f9548aa..9a6419295d6 100644
--- a/src/util/ivmFactory.js
+++ b/src/util/ivmFactory.js
@@ -23,7 +23,9 @@ async function evaluateModule(isolate, context, moduleCode) {
}
async function loadModule(isolateInternal, contextInternal, moduleName, moduleCode) {
- const module = await isolateInternal.compileModule(moduleCode, { filename: `library ${moduleName}` });
+ const module = await isolateInternal.compileModule(moduleCode, {
+ filename: `library ${moduleName}`,
+ });
await module.instantiate(contextInternal, () => {});
return module;
}
@@ -256,7 +258,7 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode)
}
});
- await jail.set('extractStackTrace', function(trace, stringLiterals) {
+ await jail.set('extractStackTrace', function (trace, stringLiterals) {
return extractStackTraceUptoLastSubstringMatch(trace, stringLiterals);
});
@@ -346,7 +348,9 @@ async function createIvm(code, libraryVersionIds, versionId, secrets, testMode)
// Now we can execute the script we just compiled:
const bootstrapScriptResult = await bootstrap.run(context);
// const customScript = await isolate.compileScript(`${library} ;\n; ${code}`);
- const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, { filename: 'base transformation' });
+ const customScriptModule = await isolate.compileModule(`${codeWithWrapper}`, {
+ filename: 'base transformation',
+ });
await customScriptModule.instantiate(context, async (spec) => {
if (librariesMap[spec]) {
return compiledModules[spec].module;
diff --git a/src/util/prometheus.js b/src/util/prometheus.js
index d7ba3b7c611..c2a7470b18e 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
@@ -567,13 +575,31 @@ class Prometheus {
type: 'gauge',
labelNames: ['destination_id'],
},
+ {
+ name: 'mixpanel_batch_engage_pack_size',
+ help: 'mixpanel_batch_engage_pack_size',
+ type: 'gauge',
+ labelNames: ['destination_id'],
+ },
+ {
+ name: 'mixpanel_batch_group_pack_size',
+ help: 'mixpanel_batch_group_pack_size',
+ type: 'gauge',
+ labelNames: ['destination_id'],
+ },
+ {
+ name: 'mixpanel_batch_import_pack_size',
+ help: 'mixpanel_batch_import_pack_size',
+ type: 'gauge',
+ labelNames: ['destination_id'],
+ },
// Histograms
{
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 +608,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_request_latency',
- help: 'tp_request_latency',
+ 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_batch_validation_latency',
+ help: 'Latency of validating tracking plan at batch level',
type: 'histogram',
labelNames: [
'sourceType',
@@ -642,6 +692,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 +768,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 840f222e373..e0491132ffd 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 53c60472988..04b80b8fc96 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 e57ab85731e..9a32fd1de3b 100644
--- a/src/util/stats.js
+++ b/src/util/stats.js
@@ -13,17 +13,19 @@ function init() {
switch (statsClientType) {
case 'statsd':
- logger.info("setting up statsd client")
+ logger.info('setting up statsd client');
statsClient = new statsd.Statsd();
break;
case 'prometheus':
- logger.info("setting up prometheus client")
+ logger.info('setting up prometheus client');
statsClient = new prometheus.Prometheus();
break;
default:
- logger.error(`invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`)
+ logger.error(
+ `invalid stats client type: ${statsClientType}, supported values are 'statsd' and 'prometheues'`,
+ );
}
}
diff --git a/src/v0/destinations/active_campaign/transform.js b/src/v0/destinations/active_campaign/transform.js
index 981dbd7520d..3978f868b18 100644
--- a/src/v0/destinations/active_campaign/transform.js
+++ b/src/v0/destinations/active_campaign/transform.js
@@ -62,6 +62,8 @@ const syncContact = async (contactPayload, category, destination) => {
destType: 'active_campaign',
feature: 'transformation',
endpointPath: endPoint,
+ requestMethod: 'POST',
+ module: 'router',
});
if (res.success === false) {
errorHandler(res, 'Failed to create new contact');
@@ -129,6 +131,8 @@ const customTagProcessor = async (message, category, destination, contactId) =>
destType: 'active_campaign',
feature: 'transformation',
endpointPath: `/api/3/tags`,
+ requestMethod: 'GET',
+ module: 'router',
});
promises.push(resp);
}
@@ -253,6 +257,8 @@ const customFieldProcessor = async (message, category, destination) => {
destType: 'active_campaign',
feature: 'transformation',
endpointPath: `/api/3/fields`,
+ requestMethod: 'GET',
+ module: 'router',
});
promises.push(resp);
}
@@ -351,6 +357,8 @@ const customListProcessor = async (message, category, destination, contactId) =>
destType: 'active_campaign',
feature: 'transformation',
endpointPath: mergeListWithContactUrl,
+ requestMethod: 'POST',
+ module: 'router',
});
promises.push(res);
}
@@ -409,6 +417,8 @@ const screenRequestHandler = async (message, category, destination) => {
destType: 'active_campaign',
feature: 'transformation',
endpointPath: `/api/3/eventTrackingEvents`,
+ requestMethod: 'GET',
+ module: 'router',
});
if (res.success === false) {
errorHandler(res, 'Failed to retrieve events');
@@ -473,6 +483,8 @@ const trackRequestHandler = async (message, category, destination) => {
destType: 'active_campaign',
feature: 'transformation',
endpointPath: `/api/3/eventTrackingEvents`,
+ requestMethod: 'GET',
+ module: 'router',
});
if (res.success === false) {
diff --git a/src/v0/destinations/adobe_analytics/networkHandler.js b/src/v0/destinations/adobe_analytics/networkHandler.js
index 0ec1fad2869..8715721f853 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 b4281387249..5d3d6e7d00f 100644
--- a/src/v0/destinations/adobe_analytics/transform.js
+++ b/src/v0/destinations/adobe_analytics/transform.js
@@ -18,6 +18,7 @@ const {
getIntegrationsObj,
removeUndefinedAndNullValues,
simpleProcessRouterDest,
+ validateEventAndLowerCaseConversion,
} = require('../../util');
const {
@@ -307,7 +308,7 @@ const processTrackEvent = (message, adobeEventName, destinationConfig, extras =
destinationConfig;
const { event: rawMessageEvent, properties } = message;
const { overrideEventString, overrideProductString } = properties;
- const event = rawMessageEvent.toLowerCase();
+ const event = validateEventAndLowerCaseConversion(rawMessageEvent, true, true);
const adobeEventArr = adobeEventName ? adobeEventName.split(',') : [];
// adobeEventArr is an array of events which is defined as
// ["eventName", "mapped Adobe Event=mapped merchproperty's value", "mapped Adobe Event=mapped merchproperty's value", . . .]
diff --git a/src/v0/destinations/adobe_analytics/utils.js b/src/v0/destinations/adobe_analytics/utils.js
index 97dc6e90bbd..ceba177ff14 100644
--- a/src/v0/destinations/adobe_analytics/utils.js
+++ b/src/v0/destinations/adobe_analytics/utils.js
@@ -93,7 +93,7 @@ function escapeToHTML(inputString) {
'&': '&',
'<': '<',
'>': '>',
- }[match]),
+ })[match],
);
}
diff --git a/src/v0/destinations/af/deleteUsers.js b/src/v0/destinations/af/deleteUsers.js
index bb711292c00..ab515642aaa 100644
--- a/src/v0/destinations/af/deleteUsers.js
+++ b/src/v0/destinations/af/deleteUsers.js
@@ -39,6 +39,8 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) =
destType: 'af',
feature: 'deleteUsers',
endpointPath: `appsflyer.com/api/gdpr/v1/opendsr_requests`,
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
const handledDelResponse = processAxiosResponse(response);
@@ -48,6 +50,7 @@ const deleteUser = async (config, endpoint, body, identityType, identityValue) =
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/af/transform.js b/src/v0/destinations/af/transform.js
index 4d7ed7e635c..d6c41937a1e 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 3e51a671371..78f8d43e948 100644
--- a/src/v0/destinations/am/config.js
+++ b/src/v0/destinations/am/config.js
@@ -136,5 +136,5 @@ module.exports = {
batchEventsWithUserIdLengthLowerThanFive,
IDENTIFY_AM,
AMBatchSizeLimit,
- AMBatchEventLimit
+ AMBatchEventLimit,
};
diff --git a/src/v0/destinations/am/deleteUsers.js b/src/v0/destinations/am/deleteUsers.js
index 6de9cf64a18..96c4f7b19c7 100644
--- a/src/v0/destinations/am/deleteUsers.js
+++ b/src/v0/destinations/am/deleteUsers.js
@@ -43,6 +43,8 @@ const userDeletionHandler = async (userAttributes, config) => {
destType: 'am',
feature: 'deleteUsers',
endpointPath,
+ requestMethod: 'POST',
+ module: 'deletion',
});
const handledDelResponse = processAxiosResponse(resp);
if (!isHttpStatusSuccess(handledDelResponse.status)) {
@@ -51,6 +53,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/am/transform.js b/src/v0/destinations/am/transform.js
index 911ec51be08..ce157e76743 100644
--- a/src/v0/destinations/am/transform.js
+++ b/src/v0/destinations/am/transform.js
@@ -22,13 +22,13 @@ const {
getFieldValueFromMessage,
getValueFromMessage,
deleteObjectProperty,
- getErrorRespEvents,
removeUndefinedAndNullValues,
isDefinedAndNotNull,
isAppleFamily,
isDefinedAndNotNullAndNotEmpty,
simpleProcessRouterDest,
isValidInteger,
+ handleRtTfSingleEventError,
} = require('../../util');
const {
BASE_URL,
@@ -40,7 +40,6 @@ const {
AMBatchSizeLimit,
AMBatchEventLimit,
} = require('./config');
-const tags = require('../../util/tags');
const AMUtils = require('./utils');
@@ -269,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');
@@ -336,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;
@@ -343,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
@@ -358,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 };
};
@@ -518,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;
@@ -615,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`;
@@ -702,6 +712,7 @@ const processSingleMessage = (message, destination) => {
logger.debug('could not determine type');
throw new InstrumentationError('message type not supported');
}
+ AMUtils.validateEventType(evType);
return responseBuilderSimple(
groupInfo,
payloadObjectName,
@@ -904,16 +915,10 @@ const batch = (destEvents) => {
// this case shold not happen and should be filtered already
// by the first pass of single event transformation
if (messageEvent && !userId && !deviceId) {
- const errorResponse = getErrorRespEvents(
- metadata,
- 400,
+ const MissingUserIdDeviceIdError = new InstrumentationError(
'Both userId and deviceId cannot be undefined',
- {
- [tags.TAG_NAMES.ERROR_CATEGORY]: tags.ERROR_CATEGORIES.DATA_VALIDATION,
- [tags.TAG_NAMES.ERROR_TYPE]: tags.ERROR_TYPES.INSTRUMENTATION,
- },
);
- respList.push(errorResponse);
+ respList.push(handleRtTfSingleEventError(ev, MissingUserIdDeviceIdError, {}));
return;
}
/* check if not a JSON body or (userId length < 5 && batchEventsWithUserIdLengthLowerThanFive is false) or
diff --git a/src/v0/destinations/am/util.test.js b/src/v0/destinations/am/util.test.js
index faaa9170f04..498980d1823 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 71fe0ab4590..8de899182bb 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 598a97946d3..8ee96aecf15 100644
--- a/src/v0/destinations/bqstream/transform.js
+++ b/src/v0/destinations/bqstream/transform.js
@@ -5,7 +5,6 @@ const { EventType } = require('../../../constants');
const {
defaultBatchRequestConfig,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
groupEventsByType,
} = require('../../util');
@@ -130,10 +129,6 @@ const processEachTypedEventList = (
};
const processRouterDest = (inputs) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs, DESTINATION);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
const finalResp = [];
const batchedEvents = groupEventsByType(inputs);
diff --git a/src/v0/destinations/branch/transform.js b/src/v0/destinations/branch/transform.js
index 23dcd6c8db4..2626d8aa812 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 00000000000..1415f0de391
--- /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 00000000000..e7da007d898
--- /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 9e82a235f11..7b6a93d3597 100644
--- a/src/v0/destinations/braze/braze.util.test.js
+++ b/src/v0/destinations/braze/braze.util.test.js
@@ -305,7 +305,14 @@ describe('dedup utility tests', () => {
},
timeout: 10000,
},
- { destType: 'braze', feature: 'transformation' },
+ {
+ destType: 'braze',
+ feature: 'transformation',
+ endpointPath: '/users/export/ids',
+ feature: 'transformation',
+ module: 'router',
+ requestMethod: 'POST',
+ },
);
});
diff --git a/src/v0/destinations/braze/deleteUsers.js b/src/v0/destinations/braze/deleteUsers.js
index b94d901138c..33c0f2ef7fa 100644
--- a/src/v0/destinations/braze/deleteUsers.js
+++ b/src/v0/destinations/braze/deleteUsers.js
@@ -22,7 +22,7 @@ const userDeletionHandler = async (userAttributes, config) => {
// Endpoints different for different data centers.
// DOC: https://www.braze.com/docs/user_guide/administrative/access_braze/braze_instances/
let endPoint;
- const endpointPath = '/users/delete'; // TODO: to handle for destinations dynamically by extracting from endpoint
+ const endpointPath = '/users/delete';
const dataCenterArr = dataCenter.trim().split('-');
if (dataCenterArr[0].toLowerCase() === 'eu') {
endPoint = 'https://rest.fra-01.braze.eu/users/delete';
@@ -46,6 +46,8 @@ const userDeletionHandler = async (userAttributes, config) => {
destType: 'braze',
feature: 'deleteUsers',
endpointPath,
+ requestMethod: 'POST',
+ module: 'deletion',
});
const handledDelResponse = processAxiosResponse(resp);
if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) {
@@ -54,6 +56,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/braze/networkHandler.js b/src/v0/destinations/braze/networkHandler.js
index c6cf7222eaa..b1363419b31 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 6549f5658fc..d45640272ed 100644
--- a/src/v0/destinations/braze/transform.js
+++ b/src/v0/destinations/braze/transform.js
@@ -223,6 +223,9 @@ async function processIdentify(message, destination) {
{
destType: 'braze',
feature: 'transformation',
+ requestMethod: 'POST',
+ module: 'router',
+ endpointPath: '/users/identify',
},
);
if (!isHttpStatusSuccess(brazeIdentifyResp.status)) {
diff --git a/src/v0/destinations/braze/util.js b/src/v0/destinations/braze/util.js
index 40b9a7eadab..5f1f1e62057 100644
--- a/src/v0/destinations/braze/util.js
+++ b/src/v0/destinations/braze/util.js
@@ -163,6 +163,9 @@ const BrazeDedupUtility = {
{
destType: 'braze',
feature: 'transformation',
+ requestMethod: 'POST',
+ module: 'router',
+ endpointPath: '/users/export/ids',
},
);
stats.counter('braze_lookup_failure_count', 1, {
diff --git a/src/v0/destinations/campaign_manager/networkHandler.js b/src/v0/destinations/campaign_manager/networkHandler.js
index a1fa24835c1..df13b72adc0 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 3b480dbac23..14bc6d2c19f 100644
--- a/src/v0/destinations/campaign_manager/transform.js
+++ b/src/v0/destinations/campaign_manager/transform.js
@@ -9,7 +9,6 @@ const {
removeUndefinedAndNullValues,
getSuccessRespEvents,
isDefinedAndNotNull,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
getAccessToken,
} = require('../../util');
@@ -245,11 +244,6 @@ const batchEvents = (eventChunksArray) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const batchErrorRespList = [];
const eventChunksArray = [];
const { destination } = inputs[0];
diff --git a/src/v0/destinations/canny/util.js b/src/v0/destinations/canny/util.js
index f514a01e5c5..1d03eed4b9b 100644
--- a/src/v0/destinations/canny/util.js
+++ b/src/v0/destinations/canny/util.js
@@ -46,6 +46,8 @@ const retrieveUserId = async (apiKey, message) => {
destType: 'canny',
feature: 'transformation',
endpointPath: `/v1/users/retrieve`,
+ requestMethod: 'POST',
+ module: 'processor',
},
);
logger.debug(response);
diff --git a/src/v0/destinations/clevertap/data/CleverTapIdentify.json b/src/v0/destinations/clevertap/data/CleverTapIdentify.json
index 577e13c3399..cdc4b28d938 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 3c07a63d93c..52119bf0f19 100644
--- a/src/v0/destinations/clevertap/deleteUsers.js
+++ b/src/v0/destinations/clevertap/deleteUsers.js
@@ -53,6 +53,8 @@ const userDeletionHandler = async (userAttributes, config) => {
destType: 'clevertap',
feature: 'deleteUsers',
endpointPath,
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
const handledDelResponse = processAxiosResponse(deletionResponse);
@@ -62,6 +64,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/clevertap/networkHandler.js b/src/v0/destinations/clevertap/networkHandler.js
index e17afb57d19..02b523f3fc2 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 efcd101668a..51ed5c851e3 100644
--- a/src/v0/destinations/clevertap/transform.js
+++ b/src/v0/destinations/clevertap/transform.js
@@ -22,7 +22,6 @@ const {
handleRtTfSingleEventError,
batchMultiplexedEvents,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
} = require('../../util');
const { generateClevertapBatchedPayload } = require('./utils');
@@ -83,16 +82,21 @@ const responseWrapper = (payload, destination) => {
}
*
}
- * This function stringify the payload attributes if it's an array or objects.
+ * This function stringify the payload attributes if it's an array or objects. The keys that are not stringified are present in the `stringifyExcludeList` array.
* @param {*} payload
* @returns
* return the final payload after converting to the relevant data-types.
*/
const convertObjectAndArrayToString = (payload, event) => {
const finalPayload = {};
+ const stringifyExcludeList = ['category-unsubscribe', 'category-resubscribe'];
if (payload) {
Object.keys(payload).forEach((key) => {
- if (payload[key] && (Array.isArray(payload[key]) || typeof payload[key] === 'object')) {
+ if (
+ payload[key] &&
+ (Array.isArray(payload[key]) || typeof payload[key] === 'object') &&
+ !stringifyExcludeList.includes(key)
+ ) {
finalPayload[key] = JSON.stringify(payload[key]);
} else {
finalPayload[key] = payload[key];
@@ -384,13 +388,6 @@ const processEvent = (message, destination) => {
const process = (event) => processEvent(event.message, event.destination);
const processRouterDest = (inputs, reqMetadata) => {
- // const respList = await simpleProcessRouterDest(inputs, process, reqMetadata);
- // return respList;
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const eventsChunk = [];
const errorRespList = [];
// const { destination } = inputs[0];
diff --git a/src/v0/destinations/clickup/util.js b/src/v0/destinations/clickup/util.js
index 148fe1bd073..74e961906cf 100644
--- a/src/v0/destinations/clickup/util.js
+++ b/src/v0/destinations/clickup/util.js
@@ -217,6 +217,9 @@ const retrieveCustomFields = async (listId, apiToken) => {
const customFieldsResponse = await httpGET(endpoint, requestOptions, {
destType: 'clickup',
feature: 'transformation',
+ endpointPath: '/list/listId/field',
+ requestMethod: 'GET',
+ module: 'router',
});
const processedCustomFieldsResponse = processAxiosResponse(customFieldsResponse);
diff --git a/src/v0/destinations/criteo_audience/networkHandler.js b/src/v0/destinations/criteo_audience/networkHandler.js
index 18bd9a93a06..6032aabcdd3 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 147fcc602ca..690768a170a 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 8ecabccd2e2..b6f34465039 100644
--- a/src/v0/destinations/custify/util.js
+++ b/src/v0/destinations/custify/util.js
@@ -41,6 +41,8 @@ const createUpdateCompany = async (companyPayload, Config) => {
destType: 'custify',
feature: 'transformation',
endpointPath: `/company`,
+ requestMethod: 'POST',
+ module: 'router',
},
);
const processedCompanyResponse = processAxiosResponse(companyResponse);
diff --git a/src/v0/destinations/customerio/transform.js b/src/v0/destinations/customerio/transform.js
index be4486717c1..6f2e053001f 100644
--- a/src/v0/destinations/customerio/transform.js
+++ b/src/v0/destinations/customerio/transform.js
@@ -5,7 +5,6 @@ const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { EventType, MappedToDestinationKey } = require('../../../constants');
const {
- getErrorRespEvents,
getSuccessRespEvents,
defaultRequestConfig,
addExternalIdToTraits,
@@ -174,10 +173,6 @@ const batchEvents = (successRespList) => {
};
const processRouterDest = (inputs, reqMetadata) => {
- if (!Array.isArray(inputs) || inputs.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
let batchResponseList = [];
const batchErrorRespList = [];
const successRespList = [];
diff --git a/src/v0/destinations/delighted/util.js b/src/v0/destinations/delighted/util.js
index 2c92685fd77..c690bf5f5ca 100644
--- a/src/v0/destinations/delighted/util.js
+++ b/src/v0/destinations/delighted/util.js
@@ -61,7 +61,13 @@ const userValidity = async (channel, Config, userId) => {
},
params: paramsdata,
},
- { destType: 'delighted', feature: 'transformation' },
+ {
+ destType: 'delighted',
+ feature: 'transformation',
+ requestMethod: 'GET',
+ endpointPath: '/people.json',
+ module: 'router',
+ },
);
if (response && response.data && response.status === 200 && Array.isArray(response.data)) {
return response.data.length > 0;
diff --git a/src/v0/destinations/drip/util.js b/src/v0/destinations/drip/util.js
index a502cf0d202..b7015c9351c 100644
--- a/src/v0/destinations/drip/util.js
+++ b/src/v0/destinations/drip/util.js
@@ -31,7 +31,13 @@ const userExists = async (Config, id) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'drip', feature: 'transformation' },
+ {
+ destType: 'drip',
+ feature: 'transformation',
+ requestMethod: 'GET',
+ endpointPath: '/subscribers/id',
+ module: 'router',
+ },
);
if (response && response.status) {
return response.status === 200;
@@ -70,7 +76,13 @@ const createUpdateUser = async (finalpayload, Config, basicAuth) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'drip', feature: 'transformation' },
+ {
+ destType: 'drip',
+ feature: 'transformation',
+ requestMethod: 'POST',
+ endpointPath: '/subscribers',
+ module: 'router',
+ },
);
if (response) {
return response.status === 200 || response.status === 201;
diff --git a/src/v0/destinations/engage/deleteUsers.js b/src/v0/destinations/engage/deleteUsers.js
index a3c3055c7d9..3616d2408d2 100644
--- a/src/v0/destinations/engage/deleteUsers.js
+++ b/src/v0/destinations/engage/deleteUsers.js
@@ -42,6 +42,9 @@ const userDeletionHandler = async (userAttributes, config) => {
{
destType: 'engage',
feature: 'deleteUsers',
+ requestMethod: 'DELETE',
+ endpointPath: '/users/userId',
+ module: 'deletion',
},
);
const handledDelResponse = processAxiosResponse(response);
@@ -51,6 +54,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/facebook_conversions/utils.js b/src/v0/destinations/facebook_conversions/utils.js
index 26204ec61a3..c6e3993e33e 100644
--- a/src/v0/destinations/facebook_conversions/utils.js
+++ b/src/v0/destinations/facebook_conversions/utils.js
@@ -93,28 +93,26 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego
);
const contentCategory = eventTypeCustomData.content_category;
- let contentType;
+ let defaultContentType;
if (contentIds.length > 0) {
- contentType = 'product';
+ defaultContentType = 'product';
} else if (contentCategory) {
contentIds.push(contentCategory);
contents.push({
id: contentCategory,
quantity: 1,
});
- contentType = 'product_group';
+ defaultContentType = 'product_group';
}
+ const contentType =
+ message.properties?.content_type ||
+ getContentType(message, defaultContentType, categoryToContent, DESTINATION.toLowerCase());
eventTypeCustomData = {
...eventTypeCustomData,
content_ids: contentIds,
contents,
- content_type: getContentType(
- message,
- contentType,
- categoryToContent,
- DESTINATION.toLowerCase(),
- ),
+ content_type: contentType,
content_category: getContentCategory(contentCategory),
};
break;
@@ -125,18 +123,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego
case 'payment info entered':
case 'product added to wishlist': {
const contentCategory = eventTypeCustomData.content_category;
- const contentType = eventTypeCustomData.content_type;
+ const contentType =
+ message.properties?.content_type ||
+ getContentType(
+ message,
+ eventTypeCustomData.content_type,
+ categoryToContent,
+ DESTINATION.toLowerCase(),
+ );
const { contentIds, contents } = populateContentsAndContentIDs([message.properties]);
eventTypeCustomData = {
...eventTypeCustomData,
content_ids: contentIds,
contents,
- content_type: getContentType(
- message,
- contentType,
- categoryToContent,
- DESTINATION.toLowerCase(),
- ),
+ content_type: contentType,
content_category: getContentCategory(contentCategory),
};
validateProductSearchedData(eventTypeCustomData);
@@ -151,18 +151,20 @@ const populateCustomDataBasedOnCategory = (customData, message, category, catego
);
const contentCategory = eventTypeCustomData.content_category;
- const contentType = eventTypeCustomData.content_type;
+ const contentType =
+ message.properties?.content_type ||
+ getContentType(
+ message,
+ eventTypeCustomData.content_type,
+ categoryToContent,
+ DESTINATION.toLowerCase(),
+ );
eventTypeCustomData = {
...eventTypeCustomData,
content_ids: contentIds,
contents,
- content_type: getContentType(
- message,
- contentType,
- categoryToContent,
- DESTINATION.toLowerCase(),
- ),
+ content_type: contentType,
content_category: getContentCategory(contentCategory),
num_items: contentIds.length,
};
diff --git a/src/v0/destinations/facebook_pixel/transform.test.js b/src/v0/destinations/facebook_pixel/transform.test.js
index 25332d770c7..d3c5baa0702 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 8a63a0b0fe2..cfa625ee3db 100644
--- a/src/v0/destinations/facebook_pixel/utils.js
+++ b/src/v0/destinations/facebook_pixel/utils.js
@@ -53,13 +53,9 @@ const getActionSource = (payload, channel) => {
* Handles order completed and checkout started types of specific events
*/
const handleOrder = (message, categoryToContent) => {
- const { products, revenue } = message.properties;
- const value = formatRevenue(revenue);
-
- const contentType = getContentType(message, 'product', categoryToContent);
- const contentIds = [];
- const contents = [];
const {
+ products,
+ revenue,
category,
quantity,
price,
@@ -67,6 +63,12 @@ const handleOrder = (message, categoryToContent) => {
contentName,
delivery_category: deliveryCategory,
} = message.properties;
+ const value = formatRevenue(revenue);
+ let { content_type: contentType } = message.properties;
+ contentType = contentType || getContentType(message, 'product', categoryToContent);
+ const contentIds = [];
+ const contents = [];
+
if (products) {
if (products.length > 0 && Array.isArray(products)) {
products.forEach((singleProduct) => {
@@ -109,10 +111,17 @@ const handleOrder = (message, categoryToContent) => {
* Handles product list viewed
*/
const handleProductListViewed = (message, categoryToContent) => {
- let contentType;
+ let defaultContentType;
const contentIds = [];
const contents = [];
- const { products, category, quantity, value, contentName } = message.properties;
+ const {
+ products,
+ category,
+ quantity,
+ value,
+ contentName,
+ content_type: contentType,
+ } = message.properties;
if (products && products.length > 0 && Array.isArray(products)) {
products.forEach((product, index) => {
if (isObject(product)) {
@@ -132,7 +141,7 @@ const handleProductListViewed = (message, categoryToContent) => {
}
if (contentIds.length > 0) {
- contentType = 'product';
+ defaultContentType = 'product';
// for viewContent event content_ids and content arrays are not mandatory
} else if (category) {
contentIds.push(category);
@@ -140,12 +149,12 @@ const handleProductListViewed = (message, categoryToContent) => {
id: category,
quantity: 1,
});
- contentType = 'product_group';
+ defaultContentType = 'product_group';
}
return {
content_ids: contentIds,
- content_type: getContentType(message, contentType, categoryToContent),
+ content_type: contentType || getContentType(message, defaultContentType, categoryToContent),
contents,
content_category: getContentCategory(category),
content_name: contentName,
@@ -165,7 +174,8 @@ const handleProduct = (message, categoryToContent, valueFieldIdentifier) => {
const useValue = valueFieldIdentifier === 'properties.value';
const contentId =
message.properties?.product_id || message.properties?.sku || message.properties?.id;
- const contentType = getContentType(message, 'product', categoryToContent);
+ const contentType =
+ message.properties?.content_type || getContentType(message, 'product', categoryToContent);
const contentName = message.properties.product_name || message.properties.name || '';
const contentCategory = message.properties.category || '';
const currency = message.properties.currency || 'USD';
diff --git a/src/v0/destinations/facebook_pixel/utils.test.js b/src/v0/destinations/facebook_pixel/utils.test.js
new file mode 100644
index 00000000000..f32d7d7024d
--- /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 06235fab402..7ba5b88adc2 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 5e3ba6e67ec..c80711ff8d4 100644
--- a/src/v0/destinations/freshmarketer/utils.js
+++ b/src/v0/destinations/freshmarketer/utils.js
@@ -50,6 +50,8 @@ const createUpdateAccount = async (payload, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/sales_accounts/upsert`,
+ requestMethod: 'POST',
+ module: 'router',
});
accountResponse = processAxiosResponse(accountResponse);
if (accountResponse.status !== 200 && accountResponse.status !== 201) {
@@ -95,6 +97,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `crm/sales/api/contacts/upsert?include=sales_accounts`,
+ requestMethod: 'POST',
+ module: 'router',
});
userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse);
if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) {
@@ -145,6 +149,8 @@ const createOrUpdateListDetails = async (listName, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/lists`,
+ requestMethod: 'GET',
+ module: 'router',
});
listResponse = processAxiosResponse(listResponse);
if (listResponse.status !== 200) {
@@ -165,6 +171,8 @@ const createOrUpdateListDetails = async (listName, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/lists`,
+ requestMethod: 'POST',
+ module: 'router',
});
listResponse = processAxiosResponse(listResponse);
if (listResponse.status !== 200) {
@@ -240,6 +248,8 @@ const getContactsDetails = async (userEmail, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/contacts/upsert`,
+ requestMethod: 'POST',
+ module: 'router',
});
userResponse = processAxiosResponse(userResponse);
if (userResponse.status !== 200 && userResponse.status !== 201) {
@@ -314,6 +324,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/selector/lifecycle_stages`,
+ requestMethod: 'GET',
+ module: 'router',
});
lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse);
if (lifeCycleStagesResponse.status !== 200) {
@@ -400,6 +412,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => {
destType: 'freshmarketer',
feature: 'transformation',
endpointPath: `/crm/sales/api/selector/sales_activity_types`,
+ requestMethod: 'GET',
+ module: 'router',
});
salesActivityResponse = processAxiosResponse(salesActivityResponse);
if (salesActivityResponse.status !== 200) {
diff --git a/src/v0/destinations/freshsales/utils.js b/src/v0/destinations/freshsales/utils.js
index 5008fedc2da..977bde0abb3 100644
--- a/src/v0/destinations/freshsales/utils.js
+++ b/src/v0/destinations/freshsales/utils.js
@@ -48,6 +48,8 @@ const createUpdateAccount = async (payload, Config) => {
destType: 'freshsales',
feature: 'transformation',
endpointPath: `/crm/sales/api/sales_accounts/upsert`,
+ requestMethod: 'POST',
+ module: 'router',
});
accountResponse = processAxiosResponse(accountResponse);
if (accountResponse.status !== 200 && accountResponse.status !== 201) {
@@ -92,6 +94,8 @@ const getUserAccountDetails = async (payload, userEmail, Config) => {
destType: 'freshsales',
feature: 'transformation',
endpointPath: `/crm/sales/api/contacts/upsert?include=sales_accounts`,
+ requestMethod: 'POST',
+ module: 'router',
});
userSalesAccountResponse = processAxiosResponse(userSalesAccountResponse);
if (userSalesAccountResponse.status !== 200 && userSalesAccountResponse.status !== 201) {
@@ -148,6 +152,8 @@ const getContactsDetails = async (userEmail, Config) => {
destType: 'freshsales',
feature: 'transformation',
endpointPath: `/crm/sales/api/contacts/upsert`,
+ requestMethod: 'POST',
+ module: 'router',
});
userResponse = processAxiosResponse(userResponse);
if (userResponse.status !== 200 && userResponse.status !== 201) {
@@ -239,6 +245,8 @@ const UpdateContactWithSalesActivity = async (payload, message, Config) => {
destType: 'freshsales',
feature: 'transformation',
endpointPath: `/crm/sales/api/sales_activity_types`,
+ requestMethod: 'GET',
+ module: 'router',
});
salesActivityResponse = processAxiosResponse(salesActivityResponse);
if (salesActivityResponse.status !== 200) {
@@ -319,6 +327,8 @@ const UpdateContactWithLifeCycleStage = async (message, Config) => {
destType: 'freshsales',
feature: 'transformation',
endpointPath: `/crm/sales/api/lifecycle_stages`,
+ requestMethod: 'GET',
+ module: 'router',
});
lifeCycleStagesResponse = processAxiosResponse(lifeCycleStagesResponse);
if (lifeCycleStagesResponse.status !== 200) {
diff --git a/src/v0/destinations/ga/deleteUsers.js b/src/v0/destinations/ga/deleteUsers.js
index 06e674048a6..524e2c14b47 100644
--- a/src/v0/destinations/ga/deleteUsers.js
+++ b/src/v0/destinations/ga/deleteUsers.js
@@ -81,6 +81,8 @@ const userDeletionHandler = async (userAttributes, config, rudderDestInfo) => {
destType: 'ga',
feature: 'deleteUsers',
endpointPath: '/userDeletion/userDeletionRequests:upsert',
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
// process the response to know about refreshing scenario
diff --git a/src/v0/destinations/ga4/networkHandler.js b/src/v0/destinations/ga4/networkHandler.js
index b62fcc8d3bd..da8ae2ea30c 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 39e666c1a58..4c7fd581934 100644
--- a/src/v0/destinations/gainsight/util.js
+++ b/src/v0/destinations/gainsight/util.js
@@ -22,7 +22,13 @@ const searchGroup = async (groupName, Config) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'gainsight', feature: 'transformation' },
+ {
+ destType: 'gainsight',
+ feature: 'transformation',
+ requestMethod: 'POST',
+ endpointPath: '/data/objects/query/Company',
+ module: 'router',
+ },
);
} catch (error) {
let errMessage = '';
@@ -56,7 +62,13 @@ const createGroup = async (payload, Config) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'gainsight', feature: 'transformation' },
+ {
+ destType: 'gainsight',
+ feature: 'transformation',
+ requestMethod: 'POST',
+ endpointPath: '/data/objects/Company',
+ module: 'router',
+ },
);
} catch (error) {
let errMessage = '';
@@ -93,7 +105,13 @@ const updateGroup = async (payload, Config) => {
keys: 'Name',
},
},
- { destType: 'gainsight', feature: 'transformation' },
+ {
+ destType: 'gainsight',
+ feature: 'transformation',
+ requestMethod: 'PUT',
+ endpointPath: '/data/objects/Company',
+ module: 'router',
+ },
);
} catch (error) {
let errMessage = '';
diff --git a/src/v0/destinations/gainsight_px/util.js b/src/v0/destinations/gainsight_px/util.js
index 5109286b3f3..83d23566dd8 100644
--- a/src/v0/destinations/gainsight_px/util.js
+++ b/src/v0/destinations/gainsight_px/util.js
@@ -6,13 +6,13 @@ const { getDynamicErrorType } = require('../../../adapters/utils/networkUtils');
const { JSON_MIME_TYPE } = require('../../util/constant');
const handleErrorResponse = (error, customErrMessage, expectedErrStatus, defaultStatus = 400) => {
+ let destResp;
let errMessage = '';
let errorStatus = defaultStatus;
if (error.response && error.response.data) {
- errMessage = error.response.data.externalapierror
- ? JSON.stringify(error.response.data.externalapierror)
- : JSON.stringify(error.response.data);
+ destResp = error.response?.data?.externalapierror ?? error.response?.data;
+ errMessage = JSON.stringify(destResp);
errorStatus = error.response.status;
@@ -26,7 +26,7 @@ const handleErrorResponse = (error, customErrMessage, expectedErrStatus, default
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(errorStatus),
},
- error,
+ destResp,
);
};
@@ -56,7 +56,13 @@ const objectExists = async (id, Config, objectType) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'gainsight_px', feature: 'transformation' },
+ {
+ destType: 'gainsight_px',
+ feature: 'transformation',
+ requestMethod: 'GET',
+ endpointPath: '/accounts/accountId',
+ module: 'router',
+ },
);
if (response && response.status === 200) {
return { success: true, err: null };
@@ -88,7 +94,13 @@ const createAccount = async (payload, Config) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'gainsight_px', feature: 'transformation' },
+ {
+ destType: 'gainsight_px',
+ feature: 'transformation',
+ requestMethod: 'POST',
+ endpointPath: '/accounts',
+ module: 'router',
+ },
);
if (response && response.status === 201) {
return { success: true, err: null };
@@ -121,7 +133,13 @@ const updateAccount = async (accountId, payload, Config) => {
'Content-Type': JSON_MIME_TYPE,
},
},
- { destType: 'gainsight_px', feature: 'transformation' },
+ {
+ destType: 'gainsight_px',
+ feature: 'transformation',
+ requestMethod: 'PUT',
+ endpointPath: '/accounts/accountId',
+ module: 'router',
+ },
);
if (response && response.status === 204) {
return { success: true, err: null };
diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js
index 7266154a095..f7ac660f53f 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 a02732894fb..f065be946cd 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 aeecc3e01b8..9c88e59ddb3 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json
+++ b/src/v0/destinations/google_adwords_offline_conversions/data/TrackAddStoreConversionsConfig.json
@@ -1,10 +1,7 @@
[
{
"destKey": "operations.create.transaction_attribute.store_attribute.store_code",
- "sourceKeys": [
- "properties.store_code",
- "properties.storeCode"
- ],
+ "sourceKeys": ["properties.store_code", "properties.storeCode"],
"required": false,
"metadata": {
"type": "toString"
@@ -25,10 +22,7 @@
},
{
"destKey": "operations.create.transaction_attribute.order_id",
- "sourceKeys": [
- "properties.order_id",
- "properties.orderId"
- ],
+ "sourceKeys": ["properties.order_id", "properties.orderId"],
"required": false,
"metadata": {
"type": "toString"
@@ -52,4 +46,4 @@
],
"required": true
}
-]
\ No newline at end of file
+]
diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js
index 6922cde8c8f..5541fd6e1e3 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js
@@ -34,6 +34,8 @@ const createJob = async (endpoint, headers, payload) => {
destType: 'google_adwords_offline_conversions',
feature: 'proxy',
endpointPath: `/create`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
},
);
createJobResponse = processAxiosResponse(createJobResponse);
@@ -59,6 +61,8 @@ const addConversionToJob = async (endpoint, headers, jobId, payload) => {
destType: 'google_adwords_offline_conversions',
feature: 'proxy',
endpointPath: `/addOperations`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
},
);
addConversionToJobResponse = processAxiosResponse(addConversionToJobResponse);
@@ -83,6 +87,8 @@ const runTheJob = async (endpoint, headers, payload, jobId) => {
destType: 'google_adwords_offline_conversions',
feature: 'proxy',
endpointPath: `/run`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
},
);
return executeJobResponse;
@@ -110,6 +116,8 @@ const getConversionCustomVariable = async (headers, params) => {
destType: 'google_adwords_offline_conversions',
feature: 'proxy',
endpointPath: `/searchStream`,
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
searchStreamResponse = processAxiosResponse(searchStreamResponse);
if (!isHttpStatusSuccess(searchStreamResponse.status)) {
@@ -247,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 397895c6032..c3be0f7cabb 100644
--- a/src/v0/destinations/google_adwords_offline_conversions/transform.js
+++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js
@@ -3,24 +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');
@@ -41,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;
@@ -55,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);
}
@@ -119,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.');
}
@@ -185,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 = [];
@@ -229,7 +223,7 @@ const processRouterDest = async (inputs, reqMetadata) => {
.concat(storeSalesEventsBatchedResponseList)
.concat(clickCallEvents)
.concat(errorRespList);
- return batchedResponseList;
+ return combineBatchRequestsWithSameJobIds(batchedResponseList);
};
module.exports = { process, processRouterDest };
diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js
index 599a163c548..70b42e2157a 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 8deaa3ab0a5..2d1863413c9 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 5bf0d8a299c..0f08b3866dc 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 bf703ccb1b1..3045c1713f5 100644
--- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js
+++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js
@@ -41,6 +41,8 @@ const createJob = async (endpoint, headers, method, params) => {
destType: 'google_adwords_remarketing_lists',
feature: 'proxy',
endpointPath: '/customers/create',
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
return response;
};
@@ -65,6 +67,8 @@ const addUserToJob = async (endpoint, headers, method, jobId, body) => {
destType: 'google_adwords_remarketing_lists',
feature: 'proxy',
endpointPath: '/addOperations',
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
return response;
};
@@ -87,6 +91,8 @@ const runTheJob = async (endpoint, headers, method, jobId) => {
destType: 'google_adwords_remarketing_lists',
feature: 'proxy',
endpointPath: '/run',
+ requestMethod: 'POST',
+ module: 'dataDelivery',
});
return response;
};
@@ -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 9526973fb8f..b0dfaa0c354 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 b218615b44d..5e870b95817 100644
--- a/src/v0/destinations/google_cloud_function/transform.js
+++ b/src/v0/destinations/google_cloud_function/transform.js
@@ -1,9 +1,5 @@
const lodash = require('lodash');
-const {
- getSuccessRespEvents,
- checkInvalidRtTfEvents,
- handleRtTfSingleEventError,
-} = require('../../util');
+const { getSuccessRespEvents, handleRtTfSingleEventError } = require('../../util');
const { generateBatchedPayload, validateDestinationConfig } = require('./util');
@@ -40,11 +36,6 @@ function batchEvents(successRespList, maxBatchSize = 10) {
// Router transform with batching by default
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const successResponseList = [];
const errorRespList = [];
const { destination } = inputs[0];
diff --git a/src/v0/destinations/googlesheets/transform.js b/src/v0/destinations/googlesheets/transform.js
index 6e27f6192c1..79dcf1bdf27 100644
--- a/src/v0/destinations/googlesheets/transform.js
+++ b/src/v0/destinations/googlesheets/transform.js
@@ -5,7 +5,6 @@ const {
getValueFromMessage,
getSuccessRespEvents,
handleRtTfSingleEventError,
- checkInvalidRtTfEvents,
} = require('../../util');
const SOURCE_KEYS = ['properties', 'traits', 'context.traits'];
@@ -111,10 +110,6 @@ const process = (event) => {
const processRouterDest = async (inputs, reqMetadata) => {
const successRespList = [];
const errorRespList = [];
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
await Promise.all(
inputs.map(async (input) => {
try {
diff --git a/src/v0/destinations/hs/config.js b/src/v0/destinations/hs/config.js
index 3e91035facc..fb9790f0e5b 100644
--- a/src/v0/destinations/hs/config.js
+++ b/src/v0/destinations/hs/config.js
@@ -37,7 +37,7 @@ const IDENTIFY_CRM_UPDATE_CONTACT = `${BASE_ENDPOINT}/crm/v3/objects/contacts/:c
const BATCH_IDENTIFY_CRM_CREATE_NEW_CONTACT = `${BASE_ENDPOINT}/crm/v3/objects/contacts/batch/create`;
const BATCH_IDENTIFY_CRM_UPDATE_CONTACT = `${BASE_ENDPOINT}/crm/v3/objects/contacts/batch/update`;
// Ref - https://developers.hubspot.com/docs/api/crm/contacts#endpoint?spec=GET-/crm/v3/objects/contacts
-const MAX_BATCH_SIZE_CRM_CONTACT = 10;
+const MAX_BATCH_SIZE_CRM_CONTACT = 100;
// Track
// Ref - https://developers.hubspot.com/docs/api/analytics/events
@@ -64,6 +64,8 @@ const API_VERSION = {
v3: 'newApi',
};
+const MAX_CONTACTS_PER_REQUEST = 100;
+
const ConfigCategory = {
COMMON: {
name: 'HSCommonConfig',
@@ -109,5 +111,6 @@ module.exports = {
SEARCH_LIMIT_VALUE,
RETL_SOURCE,
RETL_CREATE_ASSOCIATION_OPERATION,
+ MAX_CONTACTS_PER_REQUEST,
DESTINATION: 'HS',
};
diff --git a/src/v0/destinations/hs/transform.js b/src/v0/destinations/hs/transform.js
index c26e024a6c5..9eed244af4b 100644
--- a/src/v0/destinations/hs/transform.js
+++ b/src/v0/destinations/hs/transform.js
@@ -1,11 +1,7 @@
const get = require('get-value');
const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { EventType } = require('../../../constants');
-const {
- checkInvalidRtTfEvents,
- handleRtTfSingleEventError,
- getDestinationExternalIDInfoForRetl,
-} = require('../../util');
+const { handleRtTfSingleEventError, getDestinationExternalIDInfoForRetl } = require('../../util');
const { API_VERSION } = require('./config');
const {
processLegacyIdentify,
@@ -71,10 +67,6 @@ const process = async (event) => {
// we are batching by default at routerTransform
const processRouterDest = async (inputs, reqMetadata) => {
let tempInputs = inputs;
- const errorRespEvents = checkInvalidRtTfEvents(tempInputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
const successRespList = [];
const errorRespList = [];
diff --git a/src/v0/destinations/hs/util.js b/src/v0/destinations/hs/util.js
index 5c8f4a908a8..359c93dc1a5 100644
--- a/src/v0/destinations/hs/util.js
+++ b/src/v0/destinations/hs/util.js
@@ -1,3 +1,5 @@
+/* eslint-disable no-await-in-loop */
+const lodash = require('lodash');
const get = require('get-value');
const {
NetworkInstrumentationError,
@@ -17,6 +19,7 @@ const {
getHashFromArray,
getDestinationExternalIDInfoForRetl,
getValueFromMessage,
+ isNull,
} = require('../../util');
const {
CONTACT_PROPERTY_MAP_ENDPOINT,
@@ -25,6 +28,7 @@ const {
SEARCH_LIMIT_VALUE,
hsCommonConfigJson,
DESTINATION,
+ MAX_CONTACTS_PER_REQUEST,
} = require('./config');
const tags = require('../../util/tags');
@@ -101,6 +105,8 @@ const getProperties = async (destination) => {
destType: 'hs',
feature: 'transformation',
endpointPath: `/properties/v1/contacts/properties`,
+ requestMethod: 'GET',
+ module: 'router',
});
hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse);
} else {
@@ -113,6 +119,8 @@ const getProperties = async (destination) => {
destType: 'hs',
feature: 'transformation',
endpointPath: `/properties/v1/contacts/properties?hapikey`,
+ requestMethod: 'GET',
+ module: 'router',
},
);
hubspotPropertyMapResponse = processAxiosResponse(hubspotPropertyMapResponse);
@@ -216,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);
}
@@ -362,6 +372,8 @@ const searchContacts = async (message, destination) => {
destType: 'hs',
feature: 'transformation',
endpointPath,
+ requestMethod: 'POST',
+ module: 'router',
},
);
searchContactsResponse = processAxiosResponse(searchContactsResponse);
@@ -372,6 +384,8 @@ const searchContacts = async (message, destination) => {
destType: 'hs',
feature: 'transformation',
endpointPath,
+ requestMethod: 'POST',
+ module: 'router',
});
searchContactsResponse = processAxiosResponse(searchContactsResponse);
}
@@ -464,42 +478,129 @@ const getEventAndPropertiesFromConfig = (message, destination, payload) => {
};
/**
- * DOC: https://developers.hubspot.com/docs/api/crm/search
+ * Validates object and identifier type is present in message
+ * @param {*} firstMessage
+ * @returns
+ */
+const getObjectAndIdentifierType = (firstMessage) => {
+ const { objectType, identifierType } = getDestinationExternalIDInfoForRetl(
+ firstMessage,
+ DESTINATION,
+ );
+ if (!objectType || !identifierType) {
+ throw new InstrumentationError('rETL - external Id not found.');
+ }
+ return { objectType, identifierType };
+};
+
+/**
+ * Returns values for search api call
* @param {*} inputs
+ * @returns
+ */
+const extractIDsForSearchAPI = (inputs) => {
+ const values = inputs.map((input) => {
+ const { message } = input;
+ const { destinationExternalId } = getDestinationExternalIDInfoForRetl(message, DESTINATION);
+ return destinationExternalId.toString().toLowerCase();
+ });
+
+ return Array.from(new Set(values));
+};
+
+/**
+ * Returns hubspot records
+ * Ref : https://developers.hubspot.com/docs/api/crm/search
+ * @param {*} data
+ * @param {*} requestOptions
+ * @param {*} objectType
+ * @param {*} identifierType
* @param {*} destination
+ * @returns
*/
-const getExistingData = async (inputs, destination) => {
+const performHubSpotSearch = async (
+ reqdata,
+ reqOptions,
+ objectType,
+ identifierType,
+ destination,
+) => {
+ let checkAfter = 1;
+ const searchResults = [];
+ const requestData = reqdata;
const { Config } = destination;
- let values = [];
- let searchResponse;
- let updateHubspotIds = [];
- const firstMessage = inputs[0].message;
- let objectType = null;
- let identifierType = null;
-
- if (firstMessage) {
- objectType = getDestinationExternalIDInfoForRetl(firstMessage, DESTINATION).objectType;
- identifierType = getDestinationExternalIDInfoForRetl(firstMessage, DESTINATION).identifierType;
- if (!objectType || !identifierType) {
- throw new InstrumentationError('rETL - external Id not found.');
+
+ const endpoint = IDENTIFY_CRM_SEARCH_ALL_OBJECTS.replace(':objectType', objectType);
+ const endpointPath = `objects/:objectType/search`;
+
+ const url =
+ Config.authorizationType === 'newPrivateAppApi'
+ ? endpoint
+ : `${endpoint}?hapikey=${Config.apiKey}`;
+
+ const requestOptions = Config.authorizationType === 'newPrivateAppApi' ? reqOptions : {};
+
+ /* *
+ * This is needed for processing paginated response when searching hubspot.
+ * we can't avoid await in loop as response to the request contains the pagination details
+ * */
+
+ while (checkAfter) {
+ const searchResponse = await httpPOST(url, requestData, requestOptions, {
+ destType: 'hs',
+ feature: 'transformation',
+ endpointPath,
+ requestMethod: 'POST',
+ module: 'router',
+ });
+
+ const processedResponse = processAxiosResponse(searchResponse);
+
+ if (processedResponse.status !== 200) {
+ throw new NetworkError(
+ `rETL - Error during searching object record. ${JSON.stringify(
+ processedResponse.response?.message,
+ )}`,
+ processedResponse.status,
+ {
+ [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(processedResponse.status),
+ },
+ processedResponse,
+ );
+ }
+
+ const after = processedResponse.response?.paging?.next?.after || 0;
+ requestData.after = after; // assigning to the new value of after
+ checkAfter = after; // assigning to the new value if no after we assign it to 0 and no more calls will take place
+
+ const results = processedResponse.response?.results;
+ if (results) {
+ searchResults.push(
+ ...results.map((result) => ({
+ id: result.id,
+ property: result.properties[identifierType],
+ })),
+ );
}
- } else {
- throw new InstrumentationError('rETL - objectType or identifier type not found. ');
}
- inputs.map(async (input) => {
- const { message } = input;
- const { destinationExternalId } = getDestinationExternalIDInfoForRetl(message, DESTINATION);
- values.push(destinationExternalId.toString().toLowerCase());
- });
- values = Array.from(new Set(values));
+ return searchResults;
+};
+
+/**
+ * Returns requestData
+ * @param {*} identifierType
+ * @param {*} chunk
+ * @returns
+ */
+const getRequestData = (identifierType, chunk) => {
const requestData = {
filterGroups: [
{
filters: [
{
propertyName: identifierType,
- values,
+ values: chunk,
operator: 'IN',
},
],
@@ -510,65 +611,45 @@ const getExistingData = async (inputs, destination) => {
after: 0,
};
+ return requestData;
+};
+
+/**
+ * DOC: https://developers.hubspot.com/docs/api/crm/search
+ * @param {*} inputs
+ * @param {*} destination
+ */
+const getExistingContactsData = async (inputs, destination) => {
+ const { Config } = destination;
+ const updateHubspotIds = [];
+ const firstMessage = inputs[0].message;
+
+ if (!firstMessage) {
+ throw new InstrumentationError('rETL - objectType or identifier type not found.');
+ }
+
+ const { objectType, identifierType } = getObjectAndIdentifierType(firstMessage);
+
+ const values = extractIDsForSearchAPI(inputs);
+ const valuesChunk = lodash.chunk(values, MAX_CONTACTS_PER_REQUEST);
const requestOptions = {
headers: {
'Content-Type': JSON_MIME_TYPE,
Authorization: `Bearer ${Config.accessToken}`,
},
};
- let checkAfter = 1; // variable to keep checking if we have more results
-
- /* eslint-disable no-await-in-loop */
-
- /* *
- * This is needed for processing paginated response when searching hubspot.
- * we can't avoid await in loop as response to the request contains the pagination details
- * */
-
- while (checkAfter) {
- const endpoint = IDENTIFY_CRM_SEARCH_ALL_OBJECTS.replace(':objectType', objectType);
- const endpointPath = `objects/:objectType/search`;
-
- const url =
- Config.authorizationType === 'newPrivateAppApi'
- ? endpoint
- : `${endpoint}?hapikey=${Config.apiKey}`;
- searchResponse =
- Config.authorizationType === 'newPrivateAppApi'
- ? await httpPOST(url, requestData, requestOptions, {
- destType: 'hs',
- feature: 'transformation',
- endpointPath,
- })
- : await httpPOST(url, requestData, {
- destType: 'hs',
- feature: 'transformation',
- endpointPath,
- });
- searchResponse = processAxiosResponse(searchResponse);
-
- if (searchResponse.status !== 200) {
- throw new NetworkError(
- `rETL - Error during searching object record. ${searchResponse.response?.message}`,
- searchResponse.status,
- {
- [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(searchResponse.status),
- },
- searchResponse,
- );
- }
-
- const after = searchResponse.response?.paging?.next?.after || 0;
-
- requestData.after = after; // assigning to the new value of after
- checkAfter = after; // assigning to the new value if no after we assign it to 0 and no more calls will take place
-
- const results = searchResponse.response?.results;
- if (results) {
- updateHubspotIds = results.map((result) => {
- const propertyValue = result.properties[identifierType];
- return { id: result.id, property: propertyValue };
- });
+ // eslint-disable-next-line no-restricted-syntax
+ for (const chunk of valuesChunk) {
+ const requestData = getRequestData(identifierType, chunk);
+ const searchResults = await performHubSpotSearch(
+ requestData,
+ requestOptions,
+ objectType,
+ identifierType,
+ destination,
+ );
+ if (searchResults.length > 0) {
+ updateHubspotIds.push(...searchResults);
}
}
return updateHubspotIds;
@@ -601,7 +682,7 @@ const setHsSearchId = (input, id) => {
const splitEventsForCreateUpdate = async (inputs, destination) => {
// get all the id and properties of already existing objects needed for update.
- const updateHubspotIds = await getExistingData(inputs, destination);
+ const updateHubspotIds = await getExistingContactsData(inputs, destination);
const resultInput = inputs.map((input) => {
const { message } = input;
@@ -680,4 +761,7 @@ module.exports = {
validatePayloadDataTypes,
getUTCMidnightTimeStampValue,
populateTraits,
+ getObjectAndIdentifierType,
+ extractIDsForSearchAPI,
+ getRequestData,
};
diff --git a/src/v0/destinations/hs/util.test.js b/src/v0/destinations/hs/util.test.js
index 737b2064014..30e89d3aee5 100644
--- a/src/v0/destinations/hs/util.test.js
+++ b/src/v0/destinations/hs/util.test.js
@@ -1,4 +1,9 @@
-const { validatePayloadDataTypes } = require('../../../../src/v0/destinations/hs/util');
+const {
+ getRequestData,
+ extractIDsForSearchAPI,
+ validatePayloadDataTypes,
+ getObjectAndIdentifierType,
+} = require('./util');
const propertyMap = {
firstName: 'string',
@@ -40,3 +45,187 @@ describe('Validate payload data types utility function test cases', () => {
}
});
});
+
+describe('getObjectAndIdentifierType utility test cases', () => {
+ it('should return an object with objectType and identifierType properties when given a valid input', () => {
+ const firstMessage = {
+ type: 'identify',
+ traits: {
+ to: {
+ id: 1,
+ },
+ from: {
+ id: 940,
+ },
+ },
+ userId: '1',
+ context: {
+ externalId: [
+ {
+ id: 1,
+ type: 'HS-association',
+ toObjectType: 'contacts',
+ fromObjectType: 'companies',
+ identifierType: 'id',
+ associationTypeId: 'engineer',
+ },
+ ],
+ mappedToDestination: 'true',
+ },
+ };
+ const result = getObjectAndIdentifierType(firstMessage);
+ expect(result).toEqual({ objectType: 'association', identifierType: 'id' });
+ });
+
+ it('should throw an error when objectType or identifierType is not present in input', () => {
+ const firstMessage = {
+ type: 'identify',
+ traits: {
+ to: {
+ id: 1,
+ },
+ from: {
+ id: 940,
+ },
+ },
+ userId: '1',
+ context: {
+ externalId: [
+ {
+ id: 1,
+ type: 'HS-',
+ toObjectType: 'contacts',
+ fromObjectType: 'companies',
+ associationTypeId: 'engineer',
+ },
+ ],
+ mappedToDestination: 'true',
+ },
+ };
+ try {
+ getObjectAndIdentifierType(firstMessage);
+ } catch (err) {
+ expect(err.message).toBe('rETL - external Id not found.');
+ }
+ });
+});
+
+describe('extractUniqueValues utility test cases', () => {
+ it('Should return an array of unique values', () => {
+ const inputs = [
+ {
+ message: {
+ context: {
+ externalId: [
+ {
+ identifierType: 'email',
+ id: 'testhubspot2@email.com',
+ type: 'HS-lead',
+ },
+ ],
+ mappedToDestination: true,
+ },
+ },
+ },
+ {
+ message: {
+ context: {
+ externalId: [
+ {
+ identifierType: 'email',
+ id: 'Testhubspot3@email.com',
+ type: 'HS-lead',
+ },
+ ],
+ mappedToDestination: true,
+ },
+ },
+ },
+ {
+ message: {
+ context: {
+ externalId: [
+ {
+ identifierType: 'email',
+ id: 'testhubspot4@email.com',
+ type: 'HS-lead',
+ },
+ ],
+ mappedToDestination: true,
+ },
+ },
+ },
+ {
+ message: {
+ context: {
+ externalId: [
+ {
+ identifierType: 'email',
+ id: 'testHUBSPOT5@email.com',
+ type: 'HS-lead',
+ },
+ ],
+ mappedToDestination: true,
+ },
+ },
+ },
+ {
+ message: {
+ context: {
+ externalId: [
+ {
+ identifierType: 'email',
+ id: 'testhubspot2@email.com',
+ type: 'HS-lead',
+ },
+ ],
+ mappedToDestination: true,
+ },
+ },
+ },
+ ];
+
+ const result = extractIDsForSearchAPI(inputs);
+
+ expect(result).toEqual([
+ 'testhubspot2@email.com',
+ 'testhubspot3@email.com',
+ 'testhubspot4@email.com',
+ 'testhubspot5@email.com',
+ ]);
+ });
+
+ it('Should return an empty array when the input is empty', () => {
+ const inputs = [];
+ const result = extractIDsForSearchAPI(inputs);
+ expect(result).toEqual([]);
+ });
+});
+
+describe('getRequestDataAndRequestOptions utility test cases', () => {
+ it('Should return an object with requestData and requestOptions', () => {
+ const identifierType = 'email';
+ const chunk = 'test1@gmail.com';
+ const accessToken = 'dummyAccessToken';
+
+ const expectedRequestData = {
+ filterGroups: [
+ {
+ filters: [
+ {
+ propertyName: identifierType,
+ values: chunk,
+ operator: 'IN',
+ },
+ ],
+ },
+ ],
+ properties: [identifierType],
+ limit: 100,
+ after: 0,
+ };
+
+ const requestData = getRequestData(identifierType, chunk, accessToken);
+ expect(requestData).toEqual(expectedRequestData);
+ });
+});
diff --git a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json
index 174f828a56c..6857c4e1045 100644
--- a/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json
+++ b/src/v0/destinations/intercom/data/INTERCOMGroupConfig.json
@@ -12,12 +12,12 @@
},
{
"destKey": "plan",
- "sourceKeys": ["traits.plan","context.traits.plan"],
+ "sourceKeys": ["traits.plan", "context.traits.plan"],
"required": false
},
{
"destKey": "size",
- "sourceKeys": ["traits.size","context.traits.size"],
+ "sourceKeys": ["traits.size", "context.traits.size"],
"metadata": {
"type": "toNumber"
},
@@ -31,12 +31,12 @@
},
{
"destKey": "industry",
- "sourceKeys": ["traits.industry","context.traits.industry"],
+ "sourceKeys": ["traits.industry", "context.traits.industry"],
"required": false
},
{
"destKey": "monthly_spend",
- "sourceKeys": ["traits.monthlySpend","context.traits.monthlySpend"],
+ "sourceKeys": ["traits.monthlySpend", "context.traits.monthlySpend"],
"metadata": {
"type": "toNumber"
},
@@ -44,7 +44,7 @@
},
{
"destKey": "remote_created_at",
- "sourceKeys": ["traits.remoteCreatedAt","context.traits.remoteCreatedAt"],
+ "sourceKeys": ["traits.remoteCreatedAt", "context.traits.remoteCreatedAt"],
"metadata": {
"type": "toNumber"
},
diff --git a/src/v0/destinations/intercom/deleteUsers.js b/src/v0/destinations/intercom/deleteUsers.js
index 085e8424589..2c35f29e533 100644
--- a/src/v0/destinations/intercom/deleteUsers.js
+++ b/src/v0/destinations/intercom/deleteUsers.js
@@ -14,7 +14,7 @@ const userDeletionHandler = async (userAttributes, config) => {
}
const { apiKey } = config;
if (!apiKey) {
- throw new ConfigurationError('api key for deletion not present');
+ throw new ConfigurationError('The access token is not available');
}
const validUserIds = [];
userAttributes.forEach((userAttribute) => {
@@ -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 a4106257b38..8485dac52e9 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 a179a8930f0..015a9de9a05 100644
--- a/src/v0/destinations/iterable/deleteUsers.js
+++ b/src/v0/destinations/iterable/deleteUsers.js
@@ -36,6 +36,9 @@ const userDeletionHandler = async (userAttributes, config) => {
const resp = await httpDELETE(url, requestOptions, {
destType: 'iterable',
feature: 'deleteUsers',
+ endpointPath: '/users/byUserId/uId',
+ requestMethod: 'DELETE',
+ module: 'deletion',
});
const handledDelResponse = processAxiosResponse(resp);
if (!isHttpStatusSuccess(handledDelResponse.status) && handledDelResponse.status !== 404) {
@@ -46,6 +49,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/iterable/transform.js b/src/v0/destinations/iterable/transform.js
index 64bdcfcfa46..207a8d11865 100644
--- a/src/v0/destinations/iterable/transform.js
+++ b/src/v0/destinations/iterable/transform.js
@@ -18,7 +18,6 @@ const {
const {
constructPayload,
defaultRequestConfig,
- checkInvalidRtTfEvents,
defaultPostRequestConfig,
handleRtTfSingleEventError,
removeUndefinedAndNullValues,
@@ -162,11 +161,6 @@ const process = (event) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const batchedEvents = batchEvents(inputs);
const response = await Promise.all(
batchedEvents.map(async (listOfEvents) => {
diff --git a/src/v0/destinations/kafka/transform.js b/src/v0/destinations/kafka/transform.js
index b08c7174753..78f278575a5 100644
--- a/src/v0/destinations/kafka/transform.js
+++ b/src/v0/destinations/kafka/transform.js
@@ -6,7 +6,6 @@ const {
getHashFromArray,
removeUndefinedAndNullValues,
getSuccessRespEvents,
- getErrorRespEvents,
} = require('../../util');
const filterConfigTopics = (message, destination) => {
@@ -38,10 +37,6 @@ const filterConfigTopics = (message, destination) => {
const batch = (destEvents) => {
const respList = [];
- if (!Array.isArray(destEvents) || destEvents.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
// Grouping the events by topic
const groupedEvents = groupBy(destEvents, (event) => event.message.topic);
diff --git a/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json b/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json
index e128f2666c8..b358919bc1e 100644
--- a/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json
+++ b/src/v0/destinations/klaviyo/data/KlaviyoIdentify.json
@@ -57,7 +57,12 @@
"traits.address.region",
"context.traits.region",
"context.traits.address.region",
- "properties.region"
+ "properties.region",
+ "traits.state",
+ "traits.address.state",
+ "context.traits.address.state",
+ "context.traits.state",
+ "properties.state"
],
"required": false
},
@@ -77,14 +82,19 @@
"sourceKeys": [
"traits.zip",
"traits.postalcode",
+ "traits.postalCode",
"traits.address.zip",
"traits.address.postalcode",
+ "traits.address.postalCode",
"context.traits.zip",
"context.traits.postalcode",
+ "context.traits.postalCode",
"context.traits.address.zip",
"context.traits.address.postalcode",
+ "context.traits.address.postalCode",
"properties.zip",
- "properties.postalcode"
+ "properties.postalcode",
+ "properties.postalCode"
],
"required": false
},
@@ -97,5 +107,16 @@
"destKey": "location.timezone",
"sourceKeys": ["traits.timezone", "context.traits.timezone", "properties.timezone"],
"required": false
+ },
+ {
+ "destKey": "location.address1",
+ "sourceKeys": [
+ "traits.street",
+ "traits.address.street",
+ "context.traits.street",
+ "context.traits.address.street",
+ "properties.street"
+ ],
+ "required": false
}
]
diff --git a/src/v0/destinations/klaviyo/data/KlaviyoProfile.json b/src/v0/destinations/klaviyo/data/KlaviyoProfile.json
index e2a8d86085b..329ecd978f5 100644
--- a/src/v0/destinations/klaviyo/data/KlaviyoProfile.json
+++ b/src/v0/destinations/klaviyo/data/KlaviyoProfile.json
@@ -41,7 +41,12 @@
"traits.address.region",
"context.traits.region",
"context.traits.address.region",
- "properties.region"
+ "properties.region",
+ "traits.state",
+ "traits.address.state",
+ "context.traits.address.state",
+ "context.traits.state",
+ "properties.state"
],
"required": false
},
@@ -61,14 +66,19 @@
"sourceKeys": [
"traits.zip",
"traits.postalcode",
+ "traits.postalCode",
"traits.address.zip",
"traits.address.postalcode",
+ "traits.address.postalCode",
"context.traits.zip",
"context.traits.postalcode",
+ "context.traits.postalCode",
"context.traits.address.zip",
"context.traits.address.postalcode",
+ "context.traits.address.postalCode",
"properties.zip",
- "properties.postalcode"
+ "properties.postalcode",
+ "properties.postalCode"
],
"required": false
},
@@ -81,5 +91,16 @@
"destKey": "$image",
"sourceKeys": ["traits.image", "context.traits.image", "properties.image"],
"required": false
+ },
+ {
+ "destKey": "$address1",
+ "sourceKeys": [
+ "traits.street",
+ "traits.address.street",
+ "context.traits.street",
+ "context.traits.address.street",
+ "properties.street"
+ ],
+ "required": false
}
]
diff --git a/src/v0/destinations/klaviyo/transform.js b/src/v0/destinations/klaviyo/transform.js
index 3c2f8137f27..a0fe3e81a70 100644
--- a/src/v0/destinations/klaviyo/transform.js
+++ b/src/v0/destinations/klaviyo/transform.js
@@ -32,7 +32,6 @@ const {
addExternalIdToTraits,
adduserIdFromExternalId,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
flattenJson,
isNewStatusCodesAccepted,
@@ -320,10 +319,6 @@ const getEventChunks = (event, subscribeRespList, nonSubscribeRespList) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
let batchResponseList = [];
const batchErrorRespList = [];
const subscribeRespList = [];
diff --git a/src/v0/destinations/klaviyo/util.js b/src/v0/destinations/klaviyo/util.js
index 60b334f3a2f..df2dbb47121 100644
--- a/src/v0/destinations/klaviyo/util.js
+++ b/src/v0/destinations/klaviyo/util.js
@@ -45,6 +45,8 @@ const getIdFromNewOrExistingProfile = async (endpoint, payload, requestOptions)
destType: 'klaviyo',
feature: 'transformation',
endpointPath,
+ requestMethod: 'POST',
+ module: 'router',
},
);
diff --git a/src/v0/destinations/kustomer/util.js b/src/v0/destinations/kustomer/util.js
index 571a03f1399..530983bb26b 100644
--- a/src/v0/destinations/kustomer/util.js
+++ b/src/v0/destinations/kustomer/util.js
@@ -139,7 +139,13 @@ const fetchKustomer = async (url, destination) => {
Authorization: `Bearer ${destination.Config.apiKey}`,
},
},
- { destType: 'kustomer', feature: 'transformation' },
+ {
+ destType: 'kustomer',
+ feature: 'transformation',
+ endpointPath: '/customers/email',
+ requestMethod: 'GET',
+ module: 'processor',
+ },
);
} catch (err) {
if (err.response) {
diff --git a/src/v0/destinations/lambda/transform.js b/src/v0/destinations/lambda/transform.js
index 1570a69ec31..efc68b89d6f 100644
--- a/src/v0/destinations/lambda/transform.js
+++ b/src/v0/destinations/lambda/transform.js
@@ -1,5 +1,6 @@
const _ = require('lodash');
-const { getErrorRespEvents, getSuccessRespEvents } = require('../../util');
+const { getErrorRespEvents } = require('@rudderstack/integrations-lib');
+const { getSuccessRespEvents } = require('../../util');
const { ConfigurationError } = require('@rudderstack/integrations-lib');
const DEFAULT_INVOCATION_TYPE = 'Event'; // asynchronous invocation
diff --git a/src/v0/destinations/mailchimp/transform.js b/src/v0/destinations/mailchimp/transform.js
index 894f70672a5..87f547c1248 100644
--- a/src/v0/destinations/mailchimp/transform.js
+++ b/src/v0/destinations/mailchimp/transform.js
@@ -3,7 +3,6 @@ const { InstrumentationError, ConfigurationError } = require('@rudderstack/integ
const {
defaultPutRequestConfig,
handleRtTfSingleEventError,
- checkInvalidRtTfEvents,
constructPayload,
defaultPostRequestConfig,
isDefinedAndNotNull,
@@ -162,10 +161,6 @@ const getEventChunks = (event, identifyRespList, trackRespList) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
let batchResponseList = [];
const batchErrorRespList = [];
const identifyRespList = [];
diff --git a/src/v0/destinations/mailchimp/utils.js b/src/v0/destinations/mailchimp/utils.js
index e1e2e9883b7..1f4fc03ee5b 100644
--- a/src/v0/destinations/mailchimp/utils.js
+++ b/src/v0/destinations/mailchimp/utils.js
@@ -163,7 +163,13 @@ const checkIfMailExists = async (apiKey, datacenterId, audienceId, email) => {
Authorization: `Basic ${basicAuth}`,
},
},
- { destType: 'mailchimp', feature: 'transformation' },
+ {
+ destType: 'mailchimp',
+ feature: 'transformation',
+ endpointPath: '/lists/audienceId/members/email',
+ requestMethod: 'GET',
+ module: 'router',
+ },
);
if (response?.data?.contact_id) {
userStatus.exists = true;
@@ -194,7 +200,13 @@ const checkIfDoubleOptIn = async (apiKey, datacenterId, audienceId) => {
Authorization: `Basic ${basicAuth}`,
},
},
- { destType: 'mailchimp', feature: 'transformation' },
+ {
+ destType: 'mailchimp',
+ feature: 'transformation',
+ endpointPath: '/lists/audienceId',
+ requestMethod: 'GET',
+ module: 'router',
+ },
);
} catch (error) {
const status = error.status || 400;
diff --git a/src/v0/destinations/mailjet/transform.js b/src/v0/destinations/mailjet/transform.js
index 9156bf45e95..78b4f766d1d 100644
--- a/src/v0/destinations/mailjet/transform.js
+++ b/src/v0/destinations/mailjet/transform.js
@@ -1,7 +1,6 @@
const lodash = require('lodash');
const { TransformationError, InstrumentationError } = require('@rudderstack/integrations-lib');
const {
- getErrorRespEvents,
getSuccessRespEvents,
defaultRequestConfig,
defaultPostRequestConfig,
@@ -121,10 +120,6 @@ const batchEvents = (successRespList) => {
};
const processRouterDest = (inputs, reqMetadata) => {
- if (!Array.isArray(inputs) || inputs.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
let batchResponseList = [];
const batchErrorRespList = [];
const successRespList = [];
diff --git a/src/v0/destinations/mailmodo/transform.js b/src/v0/destinations/mailmodo/transform.js
index 756522939da..a61ca8a73d0 100644
--- a/src/v0/destinations/mailmodo/transform.js
+++ b/src/v0/destinations/mailmodo/transform.js
@@ -10,7 +10,6 @@ const {
defaultPostRequestConfig,
defaultBatchRequestConfig,
removeUndefinedAndNullValues,
- getErrorRespEvents,
getSuccessRespEvents,
handleRtTfSingleEventError,
} = require('../../util');
@@ -191,11 +190,6 @@ function getEventChunks(event, identifyEventChunks, eventResponseList) {
}
const processRouterDest = (inputs, reqMetadata) => {
- if (!Array.isArray(inputs) || inputs.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
-
const identifyEventChunks = []; // list containing identify events in batched format
const eventResponseList = []; // list containing other events in batched format
const errorRespList = [];
diff --git a/src/v0/destinations/marketo/networkHandler.js b/src/v0/destinations/marketo/networkHandler.js
index 7abcc65c02a..ac555accfe2 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 5000ef506b3..b811596f95a 100644
--- a/src/v0/destinations/marketo/transform.js
+++ b/src/v0/destinations/marketo/transform.js
@@ -7,6 +7,7 @@ const {
InstrumentationError,
ConfigurationError,
UnauthorizedError,
+ getErrorRespEvents,
} = require('@rudderstack/integrations-lib');
const stats = require('../../../util/stats');
const { EventType, MappedToDestinationKey } = require('../../../constants');
@@ -28,10 +29,8 @@ const {
getFieldValueFromMessage,
getDestinationExternalID,
getSuccessRespEvents,
- getErrorRespEvents,
isDefinedAndNotNull,
generateErrorObject,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
} = require('../../util');
const Cache = require('../../util/cache');
@@ -456,10 +455,6 @@ const process = async (event) => {
const processRouterDest = async (inputs, reqMetadata) => {
// Token needs to be generated for marketo which will be done on input level.
// If destination information is not present Error should be thrown
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
let token;
try {
token = await getAuthToken(formatConfig(inputs[0].destination));
diff --git a/src/v0/destinations/marketo/util.js b/src/v0/destinations/marketo/util.js
index 54ff70708a7..b3a24fb4110 100644
--- a/src/v0/destinations/marketo/util.js
+++ b/src/v0/destinations/marketo/util.js
@@ -248,6 +248,8 @@ const sendGetRequest = async (url, options) => {
destType: 'marketo',
feature: 'transformation',
endpointPath: `/v1/leads`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedResponse = processAxiosResponse(clientResponse);
return processedResponse;
@@ -264,6 +266,8 @@ const sendPostRequest = async (url, data, options) => {
destType: 'marketo',
feature: 'transformation',
endpointPath: `/v1/leads`,
+ requestMethod: 'POST',
+ module: 'router',
});
const processedResponse = processAxiosResponse(clientResponse);
return processedResponse;
diff --git a/src/v0/destinations/marketo_bulk_upload/config.js b/src/v0/destinations/marketo_bulk_upload/config.js
index 487e11fe240..e3268711fe1 100644
--- a/src/v0/destinations/marketo_bulk_upload/config.js
+++ b/src/v0/destinations/marketo_bulk_upload/config.js
@@ -18,6 +18,24 @@ const FETCH_FAILURE_JOB_STATUS_ERR_MSG = 'Could not fetch failure job status';
const FETCH_WARNING_JOB_STATUS_ERR_MSG = 'Could not fetch warning job status';
const ACCESS_TOKEN_FETCH_ERR_MSG = 'Error during fetching access token';
+const SCHEMA_DATA_TYPE_MAP = {
+ string: 'string',
+ number: 'number',
+ boolean: 'boolean',
+ undefined: 'undefined',
+ float: 'number',
+ text: 'string',
+ currency: 'string',
+ integer: 'number',
+ reference: 'string',
+ datetime: 'string',
+ date: 'string',
+ email: 'string',
+ phone: 'string',
+ url: 'string',
+ object: 'object',
+};
+
module.exports = {
ABORTABLE_CODES,
RETRYABLE_CODES,
@@ -33,4 +51,5 @@ module.exports = {
FETCH_FAILURE_JOB_STATUS_ERR_MSG,
FETCH_WARNING_JOB_STATUS_ERR_MSG,
ACCESS_TOKEN_FETCH_ERR_MSG,
+ SCHEMA_DATA_TYPE_MAP,
};
diff --git a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js
index e6f56620003..db3b13eeb84 100644
--- a/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js
+++ b/src/v0/destinations/marketo_bulk_upload/fetchJobStatus.js
@@ -33,6 +33,9 @@ const getJobsStatus = async (event, type, accessToken) => {
const { processedResponse: resp } = await handleHttpRequest('get', url, requestOptions, {
destType: 'marketo_bulk_upload',
feature: 'transformation',
+ endpointPath: '/leads/batch/',
+ requestMethod: 'GET',
+ module: 'router',
});
const endTime = Date.now();
const requestTime = endTime - startTime;
diff --git a/src/v0/destinations/marketo_bulk_upload/fileUpload.js b/src/v0/destinations/marketo_bulk_upload/fileUpload.js
index 9c42fdc98d0..b49a265fd53 100644
--- a/src/v0/destinations/marketo_bulk_upload/fileUpload.js
+++ b/src/v0/destinations/marketo_bulk_upload/fileUpload.js
@@ -198,6 +198,9 @@ const getImportID = async (input, config, accessToken, csvHeader) => {
{
destType: 'marketo_bulk_upload',
feature: 'transformation',
+ endpointPath: '/leads.json',
+ requestMethod: 'POST',
+ module: 'router',
},
);
const endTime = Date.now();
diff --git a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js
index 769fa4006d2..aa4b3aacc4f 100644
--- a/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js
+++ b/src/v0/destinations/marketo_bulk_upload/marketo_bulk_upload.util.test.js
@@ -3,6 +3,7 @@ const {
handlePollResponse,
handleFileUploadResponse,
getAccessToken,
+ checkEventStatusViaSchemaMatching,
} = require('./util');
const {
@@ -295,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',
});
});
@@ -353,3 +358,187 @@ describe('getAccessToken', () => {
await expect(getAccessToken(config)).rejects.toThrow(TransformationError);
});
});
+
+describe('checkEventStatusViaSchemaMatching', () => {
+ // The function correctly identifies fields with expected data types.
+ it('if event data types match with expected data types we send no field as mismatch', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ email: 'value1',
+ id: 123,
+ isLead: true,
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'string',
+ id: 'integer',
+ isLead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({});
+ });
+
+ // The function correctly identifies fields with unexpected data types.
+ it('if event data types do not match with expected data types we send that field as mismatch', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ email: 123,
+ city: '123',
+ islead: true,
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'string',
+ city: 'number',
+ islead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({
+ job1: 'invalid email',
+ });
+ });
+
+ // The function correctly handles events with multiple fields.
+ it('For array of events the mismatch object fills up with each event errors', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ id: 'value1',
+ testCustomFieldScore: 123,
+ isLead: true,
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ {
+ message: {
+ email: 'value2',
+ id: 456,
+ testCustomFieldScore: false,
+ },
+ metadata: {
+ job_id: 'job2',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'email',
+ id: 'integer',
+ testCustomFieldScore: 'integer',
+ isLead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({
+ job1: 'invalid id',
+ job2: 'invalid testCustomFieldScore',
+ });
+ });
+
+ // The function correctly handles events with missing fields.
+ it('it is not mandatory to send all the fields present in schema', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ email: 'value1',
+ isLead: true,
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'string',
+ id: 'number',
+ isLead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({});
+ });
+
+ // The function correctly handles events with additional fields. But this will not happen in our use case
+ it('for any field beyond schema fields will be mapped as invalid', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ email: 'value1',
+ id: 124,
+ isLead: true,
+ abc: 'value2',
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'string',
+ id: 'number',
+ isLead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({
+ job1: 'invalid abc',
+ });
+ });
+
+ // The function correctly handles events with null values.
+ it('should correctly handle events with null values', () => {
+ const event = {
+ input: [
+ {
+ message: {
+ email: 'value1',
+ id: null,
+ isLead: true,
+ },
+ metadata: {
+ job_id: 'job1',
+ },
+ },
+ ],
+ };
+ const fieldSchemaMapping = {
+ email: 'string',
+ id: 'number',
+ isLead: 'boolean',
+ };
+
+ const result = checkEventStatusViaSchemaMatching(event, fieldSchemaMapping);
+
+ expect(result).toEqual({
+ job1: 'invalid id',
+ });
+ });
+});
diff --git a/src/v0/destinations/marketo_bulk_upload/poll.js b/src/v0/destinations/marketo_bulk_upload/poll.js
index db7a6347744..f53347d6e52 100644
--- a/src/v0/destinations/marketo_bulk_upload/poll.js
+++ b/src/v0/destinations/marketo_bulk_upload/poll.js
@@ -26,6 +26,9 @@ const getPollStatus = async (event) => {
{
destType: 'marketo_bulk_upload',
feature: 'transformation',
+ endpointPath: '/leads/batch/importId.json',
+ requestMethod: 'GET',
+ module: 'router',
},
);
if (!isHttpStatusSuccess(pollStatus.status)) {
diff --git a/src/v0/destinations/marketo_bulk_upload/transform.js b/src/v0/destinations/marketo_bulk_upload/transform.js
index 5431e67d38a..1943d817cde 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 8b46212b876..4c99ba7483f 100644
--- a/src/v0/destinations/marketo_bulk_upload/util.js
+++ b/src/v0/destinations/marketo_bulk_upload/util.js
@@ -18,6 +18,7 @@ const {
POLL_STATUS_ERR_MSG,
FILE_UPLOAD_ERR_MSG,
ACCESS_TOKEN_FETCH_ERR_MSG,
+ SCHEMA_DATA_TYPE_MAP,
} = require('./config');
const logger = require('../../../logger');
@@ -127,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}
@@ -351,6 +355,9 @@ const getFieldSchemaMap = async (accessToken, munchkinId) => {
{
destType: 'marketo_bulk_upload',
feature: 'transformation',
+ endpointPath: '/leads/describe2.json',
+ requestMethod: 'GET',
+ module: 'router',
},
);
@@ -401,14 +408,9 @@ const checkEventStatusViaSchemaMatching = (event, fieldMap) => {
const { job_id } = metadata;
Object.entries(message).forEach(([paramName, paramValue]) => {
- let expectedDataType = fieldMap[paramName];
+ const expectedDataType = SCHEMA_DATA_TYPE_MAP[fieldMap[paramName]];
const actualDataType = typeof paramValue;
- // If expectedDataType is not one of the primitive data types, treat it as a string
- if (!['string', 'number', 'boolean', 'undefined'].includes(expectedDataType)) {
- expectedDataType = 'string';
- }
-
if (!mismatchedFields[job_id] && actualDataType !== expectedDataType) {
mismatchedFields[job_id] = `invalid ${paramName}`;
}
diff --git a/src/v0/destinations/marketo_static_list/networkHandler.js b/src/v0/destinations/marketo_static_list/networkHandler.js
index 30b053b9d3e..086378cf6ac 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 294e34f91bc..92c137c614d 100644
--- a/src/v0/destinations/marketo_static_list/transform.js
+++ b/src/v0/destinations/marketo_static_list/transform.js
@@ -1,6 +1,10 @@
const lodash = require('lodash');
const cloneDeep = require('lodash/cloneDeep');
-const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib');
+const {
+ InstrumentationError,
+ UnauthorizedError,
+ getErrorRespEvents,
+} = require('@rudderstack/integrations-lib');
const {
defaultPostRequestConfig,
defaultDeleteRequestConfig,
@@ -9,11 +13,7 @@ const {
} = require('../../util');
const { AUTH_CACHE_TTL, JSON_MIME_TYPE } = require('../../util/constant');
const { getIds, validateMessageType } = require('./util');
-const {
- getDestinationExternalID,
- defaultRequestConfig,
- getErrorRespEvents,
-} = require('../../util');
+const { getDestinationExternalID, defaultRequestConfig } = require('../../util');
const { formatConfig, MAX_LEAD_IDS_SIZE } = require('./config');
const Cache = require('../../util/cache');
const { getAuthToken } = require('../marketo/transform');
diff --git a/src/v0/destinations/marketo_static_list/transformV2.js b/src/v0/destinations/marketo_static_list/transformV2.js
index 912d548d098..73d4bec8f81 100644
--- a/src/v0/destinations/marketo_static_list/transformV2.js
+++ b/src/v0/destinations/marketo_static_list/transformV2.js
@@ -1,5 +1,9 @@
const lodash = require('lodash');
-const { InstrumentationError, UnauthorizedError } = require('@rudderstack/integrations-lib');
+const {
+ InstrumentationError,
+ UnauthorizedError,
+ getErrorRespEvents,
+} = require('@rudderstack/integrations-lib');
const {
defaultPostRequestConfig,
defaultDeleteRequestConfig,
@@ -7,7 +11,6 @@ const {
getSuccessRespEvents,
isDefinedAndNotNull,
generateErrorObject,
- getErrorRespEvents,
} = require('../../util');
const { JSON_MIME_TYPE } = require('../../util/constant');
const { MAX_LEAD_IDS_SIZE } = require('./config');
diff --git a/src/v0/destinations/mautic/utils.js b/src/v0/destinations/mautic/utils.js
index d8ad8dbffcd..7a1827e769a 100644
--- a/src/v0/destinations/mautic/utils.js
+++ b/src/v0/destinations/mautic/utils.js
@@ -183,6 +183,9 @@ const searchContactIds = async (message, Config, baseUrl) => {
{
destType: 'mautic',
feature: 'transformation',
+ endpointPath: '/contacts',
+ requestMethod: 'GET',
+ module: 'router',
},
);
searchContactsResponse = processAxiosResponse(searchContactsResponse);
diff --git a/src/v0/destinations/monday/util.js b/src/v0/destinations/monday/util.js
index 736f0133fde..872fad42a75 100644
--- a/src/v0/destinations/monday/util.js
+++ b/src/v0/destinations/monday/util.js
@@ -195,6 +195,8 @@ const getBoardDetails = async (url, boardID, apiToken) => {
destType: 'monday',
feature: 'transformation',
endpointPath: '/v2',
+ requestMethod: 'POST',
+ module: 'router',
},
);
const boardDetailsResponse = processAxiosResponse(clientResponse);
diff --git a/src/v0/destinations/mp/deleteUsers.js b/src/v0/destinations/mp/deleteUsers.js
index f01475ef2b8..e1240c609d6 100644
--- a/src/v0/destinations/mp/deleteUsers.js
+++ b/src/v0/destinations/mp/deleteUsers.js
@@ -49,6 +49,8 @@ const deleteProfile = async (userAttributes, config) => {
destType: 'mp',
feature: 'deleteUsers',
endpointPath,
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
if (!isHttpStatusSuccess(handledDelResponse.status)) {
@@ -57,6 +59,7 @@ const deleteProfile = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
@@ -104,6 +107,8 @@ const createDeletionTask = async (userAttributes, config) => {
destType: 'mp',
feature: 'deleteUsers',
endpointPath,
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
if (!isHttpStatusSuccess(handledDelResponse.status)) {
diff --git a/src/v0/destinations/mp/transform.js b/src/v0/destinations/mp/transform.js
index ef18b3d0d17..43e3c4b3768 100644
--- a/src/v0/destinations/mp/transform.js
+++ b/src/v0/destinations/mp/transform.js
@@ -14,10 +14,10 @@ const {
removeUndefinedValues,
toUnixTimestampInMS,
getFieldValueFromMessage,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
groupEventsByType,
parseConfigArray,
+ combineBatchRequestsWithSameJobIds,
} = require('../../util');
const {
ConfigCategory,
@@ -32,10 +32,11 @@ const {
createIdentifyResponse,
isImportAuthCredentialsAvailable,
buildUtmParams,
- combineBatchRequestsWithSameJobIds,
groupEventsByEndpoint,
batchEvents,
trimTraits,
+ generatePageOrScreenCustomEventName,
+ recordBatchSizeMetrics,
} = require('./util');
const { CommonUtils } = require('../../../util/common');
@@ -48,11 +49,11 @@ const setImportCredentials = (destConfig) => {
const params = { strict: destConfig.strictMode ? 1 : 0 };
const { serviceAccountUserName, serviceAccountSecret, projectId, token } = destConfig;
let credentials;
- if (serviceAccountUserName && serviceAccountSecret && projectId) {
+ if (token) {
+ credentials = `${token}:`;
+ } else if (serviceAccountUserName && serviceAccountSecret && projectId) {
credentials = `${serviceAccountUserName}:${serviceAccountSecret}`;
params.projectId = projectId;
- } else {
- credentials = `${token}:`;
}
const headers = {
'Content-Type': 'application/json',
@@ -285,17 +286,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}`,
@@ -314,7 +323,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,
@@ -447,10 +467,11 @@ 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 batchSize = {
+ engage: 0,
+ groups: 0,
+ import: 0,
+ };
const groupedEvents = groupEventsByType(inputs);
const response = await Promise.all(
@@ -488,12 +509,19 @@ const processRouterDest = async (inputs, reqMetadata) => {
const importRespList = batchEvents(importEvents, IMPORT_MAX_BATCH_SIZE, reqMetadata);
const batchSuccessRespList = [...engageRespList, ...groupsRespList, ...importRespList];
+ batchSize.engage += engageRespList.length;
+ batchSize.groups += groupsRespList.length;
+ batchSize.import += importRespList.length;
+
return [...batchSuccessRespList, ...batchErrorRespList];
}),
);
// Flatten the response array containing batched events from multiple groups
const allBatchedEvents = lodash.flatMap(response);
+
+ const { destination } = allBatchedEvents[0];
+ recordBatchSizeMetrics(batchSize, destination.ID);
return combineBatchRequestsWithSameJobIds(allBatchedEvents);
};
diff --git a/src/v0/destinations/mp/util.js b/src/v0/destinations/mp/util.js
index 802438184c6..398a0452d5b 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,
@@ -24,6 +25,7 @@ const {
mappingConfig,
} = require('./config');
const { CommonUtils } = require('../../../util/common');
+const stats = require('../../../util/stats');
const mPIdentifyConfigJson = mappingConfig[ConfigCategory.IDENTIFY.name];
const mPProfileAndroidConfigJson = mappingConfig[ConfigCategory.PROFILE_ANDROID.name];
@@ -139,44 +141,6 @@ const isImportAuthCredentialsAvailable = (destination) =>
destination.Config.serviceAccountUserName &&
destination.Config.projectId);
-/**
- * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap.
- * @param {*} batch
- * @param {*} metadataMap The map containing metadata items indexed by JobIds.
- * @returns
- */
-const findExistingBatch = (batch, metadataMap) => {
- let existingBatch = null;
-
- // eslint-disable-next-line no-restricted-syntax
- for (const metadataItem of batch.metadata) {
- if (metadataMap.has(metadataItem.jobId)) {
- existingBatch = metadataMap.get(metadataItem.jobId);
- break;
- }
- }
-
- return existingBatch;
-};
-
-/**
- * Removes duplicate metadata within each merged batch object.
- * @param {*} mergedBatches An array of merged batch objects.
- */
-const removeDuplicateMetadata = (mergedBatches) => {
- mergedBatches.forEach((batch) => {
- const metadataSet = new Set();
- // eslint-disable-next-line no-param-reassign
- batch.metadata = batch.metadata.filter((metadataItem) => {
- if (!metadataSet.has(metadataItem.jobId)) {
- metadataSet.add(metadataItem.jobId);
- return true;
- }
- return false;
- });
- });
-};
-
/**
* Builds UTM parameters from a campaign object.
*
@@ -271,58 +235,6 @@ const batchEvents = (successRespList, maxBatchSize, reqMetadata) => {
});
};
-/**
- * Combines batched requests with the same JobIds.
- * @param {*} inputBatches The array of batched request objects.
- * @returns The combined batched requests with merged JobIds.
- *
- */
-const combineBatchRequestsWithSameJobIds = (inputBatches) => {
- const combineBatches = (batches) => {
- const clonedBatches = [...batches];
- const mergedBatches = [];
- const metadataMap = new Map();
-
- clonedBatches.forEach((batch) => {
- const existingBatch = findExistingBatch(batch, metadataMap);
-
- if (existingBatch) {
- // Merge batchedRequests arrays
- existingBatch.batchedRequest = [
- ...(Array.isArray(existingBatch.batchedRequest)
- ? existingBatch.batchedRequest
- : [existingBatch.batchedRequest]),
- ...(Array.isArray(batch.batchedRequest) ? batch.batchedRequest : [batch.batchedRequest]),
- ];
-
- // Merge metadata
- batch.metadata.forEach((metadataItem) => {
- if (!metadataMap.has(metadataItem.jobId)) {
- metadataMap.set(metadataItem.jobId, existingBatch);
- }
- existingBatch.metadata.push(metadataItem);
- });
- } else {
- mergedBatches.push(batch);
- batch.metadata.forEach((metadataItem) => {
- metadataMap.set(metadataItem.jobId, batch);
- });
- }
- });
-
- // Remove duplicate metadata within each merged object
- removeDuplicateMetadata(mergedBatches);
-
- return mergedBatches;
- };
- // We need to run this twice because in first pass some batches might not get merged
- // and in second pass they might get merged
- // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]]
- // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]]
- // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]]
- return combineBatches(combineBatches(inputBatches));
-};
-
/**
* Trims the traits and contextTraits objects based on the setOnceProperties array and returns an object containing the modified traits, contextTraits, and setOnce properties.
*
@@ -389,6 +301,68 @@ 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;
+};
+
+/**
+ * Records the batch size metrics for different endpoints.
+ *
+ * @param {Object} batchSize - The object containing the batch size for different endpoints.
+ * @param {number} batchSize.engage - The batch size for engage endpoint.
+ * @param {number} batchSize.groups - The batch size for group endpoint.
+ * @param {number} batchSize.import - The batch size for import endpoint.
+ * @param {string} destinationId - The ID of the destination.
+ * @returns {void}
+ */
+const recordBatchSizeMetrics = (batchSize, destinationId) => {
+ stats.gauge('mixpanel_batch_engage_pack_size', batchSize.engage, {
+ destination_id: destinationId,
+ });
+ stats.gauge('mixpanel_batch_group_pack_size', batchSize.groups, {
+ destination_id: destinationId,
+ });
+ stats.gauge('mixpanel_batch_import_pack_size', batchSize.import, {
+ destination_id: destinationId,
+ });
+};
+
module.exports = {
createIdentifyResponse,
isImportAuthCredentialsAvailable,
@@ -396,6 +370,7 @@ module.exports = {
groupEventsByEndpoint,
generateBatchedPayloadForArray,
batchEvents,
- combineBatchRequestsWithSameJobIds,
trimTraits,
+ generatePageOrScreenCustomEventName,
+ recordBatchSizeMetrics,
};
diff --git a/src/v0/destinations/mp/util.test.js b/src/v0/destinations/mp/util.test.js
index 530208736a7..3666081f593 100644
--- a/src/v0/destinations/mp/util.test.js
+++ b/src/v0/destinations/mp/util.test.js
@@ -1,669 +1,491 @@
const {
- combineBatchRequestsWithSameJobIds,
groupEventsByEndpoint,
batchEvents,
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: [],
- 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: [],
+ 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 = [
- {
- message: {
- endpoint: '/engage',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:1}]',
- },
+ 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',
},
+ userId: 'user1',
},
- {
- message: {
- endpoint: '/engage',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:2}]',
- },
+ },
+ {
+ message: {
+ endpoint: '/engage',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{prop:2}]',
},
- userId: 'user2',
},
+ userId: 'user2',
},
- {
- message: {
- endpoint: '/groups',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:3}]',
- },
+ },
+ {
+ message: {
+ endpoint: '/groups',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{prop:3}]',
},
- userId: 'user1',
},
+ userId: 'user1',
},
- {
- message: {
- endpoint: '/import',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:4}]',
- },
+ },
+ {
+ message: {
+ endpoint: '/track',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{prop:4}]',
},
- userId: 'user1',
},
+ userId: 'user1',
},
- {
- message: {
- endpoint: '/import',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:5}]',
- },
+ },
+ {
+ message: {
+ endpoint: '/import',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{prop:5}]',
},
- userId: 'user2',
},
+ 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',
- },
- },
- ],
- importEvents: [
- {
- message: {
- endpoint: '/import',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:4}]',
- },
- },
- userId: 'user1',
- },
- },
- {
- message: {
- endpoint: '/import',
- body: {
- JSON_ARRAY: {
- batch: '[{prop:5}]',
- },
- },
- userId: 'user2',
- },
- },
- ],
- 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 = [
+ },
+ { error: 'Message type abc not supported' },
+ ];
+ const result = groupEventsByEndpoint(events);
+ expect(result).toEqual({
+ engageEvents: [
{
message: {
endpoint: '/engage',
body: {
JSON_ARRAY: {
- batch: '[{"prop":1}]',
+ batch: '[{prop:1}]',
},
},
- headers: {},
- params: {},
userId: 'user1',
},
- metadata: { jobId: 3 },
},
{
message: {
endpoint: '/engage',
body: {
JSON_ARRAY: {
- batch: '[{"prop":2}]',
+ batch: '[{prop:2}]',
},
},
- headers: {},
- params: {},
userId: 'user2',
},
- metadata: { jobId: 4 },
},
+ ],
+ groupsEvents: [
{
message: {
- endpoint: '/engage',
+ endpoint: '/groups',
body: {
JSON_ARRAY: {
- batch: '[{"prop":3}]',
+ batch: '[{prop:3}]',
},
},
- headers: {},
- params: {},
- userId: 'user2',
- },
- 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',
+ userId: 'user1',
},
- destination: undefined,
- metadata: [{ jobId: 3 }, { jobId: 4 }],
- statusCode: 200,
},
+ ],
+ importEvents: [
{
- batched: true,
- batchedRequest: {
- body: { FORM: {}, JSON: {}, JSON_ARRAY: { batch: '[{"prop":3}]' }, XML: {} },
- endpoint: '/engage',
- files: {},
- headers: {},
- method: 'POST',
- params: {},
- type: 'REST',
- version: '1',
+ message: {
+ endpoint: '/import',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{prop:5}]',
+ },
+ },
+ userId: 'user2',
},
- 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([]);
+ ],
+ batchErrorRespList: [{ error: 'Message type abc not supported' }],
});
});
+});
- describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => {
- it('Combine batch request with same jobIds', async () => {
- const input = [
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
- },
- metadata: [
- {
- jobId: 1,
- },
- {
- jobId: 4,
+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}]',
},
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
},
- metadata: [
- {
- jobId: 3,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
+ headers: {},
+ params: {},
+ userId: 'user1',
},
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- metadata: [
- {
- jobId: 1,
+ metadata: { jobId: 3 },
+ },
+ {
+ message: {
+ endpoint: '/engage',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{"prop":2}]',
},
- {
- jobId: 3,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
},
- metadata: [
- {
- jobId: 6,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- ];
-
- const expectedOutput = [
- {
- batchedRequest: [
- {
- endpoint: 'https://api.mixpanel.com/import/',
- },
- {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- {
- endpoint: 'https://api.mixpanel.com/import/',
- },
- ],
- metadata: [
- {
- jobId: 1,
- },
- {
- jobId: 4,
- },
- {
- jobId: 3,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
+ headers: {},
+ params: {},
+ userId: 'user2',
},
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
- },
- metadata: [
- {
- jobId: 6,
+ metadata: { jobId: 4 },
+ },
+ {
+ message: {
+ endpoint: '/engage',
+ body: {
+ JSON_ARRAY: {
+ batch: '[{"prop":3}]',
},
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- ];
- expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput);
- });
-
- it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => {
- const input = [
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
},
- metadata: [
- {
- jobId: 1,
- },
- {
- jobId: 4,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
+ headers: {},
+ params: {},
+ userId: 'user2',
},
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- metadata: [
- {
- jobId: 2,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
+ 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',
},
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- metadata: [
- {
- jobId: 5,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
+ 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',
},
- ];
-
- const expectedOutput = [
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/import/',
- },
+ destination: undefined,
+ metadata: [{ jobId: 6 }],
+ statusCode: 200,
+ },
+ ]);
+ });
- metadata: [
- {
- jobId: 1,
- },
- {
- jobId: 4,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- metadata: [
- {
- jobId: 2,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- {
- batchedRequest: {
- endpoint: 'https://api.mixpanel.com/engage/',
- },
- metadata: [
- {
- jobId: 5,
- },
- ],
- batched: true,
- statusCode: 200,
- destination: destinationMock,
- },
- ];
- expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput);
- });
+ it('should return an empty array when successRespList is empty', () => {
+ const successRespList = [];
+ const result = batchEvents(successRespList, maxBatchSizeMock);
+ expect(result).toEqual([]);
});
+});
- describe('Unit test cases for generateBatchedPayloadForArray', () => {
- it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => {
- const events = [
- {
- body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } },
- endpoint: '/import',
- headers: { 'Content-Type': 'application/json' },
- params: {},
- },
- {
- body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } },
- endpoint: '/import',
- headers: { 'Content-Type': 'application/json' },
- params: {},
- },
- ];
- const expectedBatchedRequest = {
- body: {
- FORM: {},
- JSON: {},
- JSON_ARRAY: {},
- XML: {},
- GZIP: {
- payload: '[{"event":"event1"},{"event":"event2"}]',
- },
- },
+describe('Unit test cases for generateBatchedPayloadForArray', () => {
+ it('should generate a batched payload with GZIP payload for /import endpoint when given an array of events', () => {
+ const events = [
+ {
+ body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } },
endpoint: '/import',
- files: {},
headers: { 'Content-Type': 'application/json' },
- method: 'POST',
params: {},
- type: 'REST',
- version: '1',
- };
-
- const result = generateBatchedPayloadForArray(events, {
- features: { [FEATURE_GZIP_SUPPORT]: true },
- });
-
- expect(result).toEqual(expectedBatchedRequest);
+ },
+ {
+ body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } },
+ endpoint: '/import',
+ headers: { 'Content-Type': 'application/json' },
+ params: {},
+ },
+ ];
+ const expectedBatchedRequest = {
+ body: {
+ FORM: {},
+ JSON: {},
+ JSON_ARRAY: {},
+ XML: {},
+ GZIP: {
+ payload: '[{"event":"event1"},{"event":"event2"}]',
+ },
+ },
+ endpoint: '/import',
+ files: {},
+ headers: { 'Content-Type': 'application/json' },
+ method: 'POST',
+ params: {},
+ type: 'REST',
+ version: '1',
+ };
+
+ const result = generateBatchedPayloadForArray(events, {
+ features: { [FEATURE_GZIP_SUPPORT]: true },
});
- it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => {
- const events = [
- {
- body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } },
- endpoint: '/endpoint',
- headers: { 'Content-Type': 'application/json' },
- params: {},
- },
- {
- body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } },
- endpoint: '/endpoint',
- headers: { 'Content-Type': 'application/json' },
- params: {},
- },
- ];
- const expectedBatchedRequest = {
- body: {
- FORM: {},
- JSON: {},
- JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' },
- XML: {},
- },
+ expect(result).toEqual(expectedBatchedRequest);
+ });
+
+ it('should generate a batched payload with JSON_ARRAY body when given an array of events', () => {
+ const events = [
+ {
+ body: { JSON_ARRAY: { batch: '[{"event": "event1"}]' } },
endpoint: '/endpoint',
- files: {},
headers: { 'Content-Type': 'application/json' },
- method: 'POST',
params: {},
- type: 'REST',
- version: '1',
- };
-
- const result = generateBatchedPayloadForArray(events, {
- features: { [FEATURE_GZIP_SUPPORT]: true },
- });
-
- expect(result).toEqual(expectedBatchedRequest);
+ },
+ {
+ body: { JSON_ARRAY: { batch: '[{"event": "event2"}]' } },
+ endpoint: '/endpoint',
+ headers: { 'Content-Type': 'application/json' },
+ params: {},
+ },
+ ];
+ const expectedBatchedRequest = {
+ body: {
+ FORM: {},
+ JSON: {},
+ JSON_ARRAY: { batch: '[{"event":"event1"},{"event":"event2"}]' },
+ XML: {},
+ },
+ endpoint: '/endpoint',
+ files: {},
+ headers: { 'Content-Type': 'application/json' },
+ method: 'POST',
+ params: {},
+ type: 'REST',
+ version: '1',
+ };
+
+ const result = generateBatchedPayloadForArray(events, {
+ features: { [FEATURE_GZIP_SUPPORT]: true },
});
+
+ expect(result).toEqual(expectedBatchedRequest);
});
+});
- describe('Unit test cases for buildUtmParams', () => {
- it('should return an empty object when campaign is undefined', () => {
- const campaign = undefined;
- const result = buildUtmParams(campaign);
- expect(result).toEqual({});
- });
+describe('Unit test cases for buildUtmParams', () => {
+ it('should return an empty object when campaign is undefined', () => {
+ const campaign = undefined;
+ const result = buildUtmParams(campaign);
+ expect(result).toEqual({});
+ });
- it('should return an empty object when campaign is an empty object', () => {
- const campaign = {};
- const result = buildUtmParams(campaign);
- expect(result).toEqual({});
- });
+ it('should return an empty object when campaign is an empty object', () => {
+ const campaign = {};
+ const result = buildUtmParams(campaign);
+ expect(result).toEqual({});
+ });
- it('should return an empty object when campaign is not an object', () => {
- const campaign = [{ name: 'test' }];
- const result = buildUtmParams(campaign);
- expect(result).toEqual({});
- });
+ it('should return an empty object when campaign is not an object', () => {
+ const campaign = [{ name: 'test' }];
+ const result = buildUtmParams(campaign);
+ expect(result).toEqual({});
+ });
- it('should handle campaign object with null/undefined values', () => {
- const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined };
- const result = buildUtmParams(campaign);
- expect(result).toEqual({
- utm_campaign: null,
- utm_source: 'rudder',
- utm_medium: 'rudder',
- test: undefined,
- });
+ it('should handle campaign object with null/undefined values', () => {
+ const campaign = { name: null, source: 'rudder', medium: 'rudder', test: undefined };
+ const result = buildUtmParams(campaign);
+ expect(result).toEqual({
+ utm_campaign: null,
+ utm_source: 'rudder',
+ utm_medium: 'rudder',
+ test: undefined,
});
});
- describe('Unit test cases for trimTraits', () => {
- // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties.
- it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => {
- const traits = { name: 'John', age: 30 };
- const contextTraits = { email: 'john@example.com' };
- const setOnceProperties = ['name', 'email'];
-
- const result = trimTraits(traits, contextTraits, setOnceProperties);
-
- expect(result).toEqual({
- traits: {
- age: 30,
- },
- contextTraits: {},
- setOnce: { $name: 'John', $email: 'john@example.com' },
- });
+});
+describe('Unit test cases for trimTraits', () => {
+ // Given a valid traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing traits, contextTraits, and setOnce properties.
+ it('should return an object containing traits, contextTraits, and setOnce properties when given valid inputs', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email'];
+
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
+
+ expect(result).toEqual({
+ traits: {
+ age: 30,
+ },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
});
+ });
- // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property.
- it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => {
- const traits = {};
- const contextTraits = {};
- const setOnceProperties = ['name', 'email'];
+ // Given an empty traits object and contextTraits object, and a valid setOnceProperties array, the function should return an object containing empty traits and contextTraits, and an empty setOnce property.
+ it('should return an object containing empty traits and contextTraits, and an empty setOnce property when given empty traits and contextTraits objects', () => {
+ const traits = {};
+ const contextTraits = {};
+ const setOnceProperties = ['name', 'email'];
- const result = trimTraits(traits, contextTraits, setOnceProperties);
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
- expect(result).toEqual({
- traits: {},
- contextTraits: {},
- setOnce: {},
- });
+ expect(result).toEqual({
+ traits: {},
+ contextTraits: {},
+ setOnce: {},
});
+ });
- // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property.
- it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => {
- const traits = { name: 'John', age: 30 };
- const contextTraits = { email: 'john@example.com' };
- const setOnceProperties = [];
+ // Given an empty setOnceProperties array, the function should return an object containing the original traits and contextTraits objects, and an empty setOnce property.
+ it('should return an object containing the original traits and contextTraits objects, and an empty setOnce property when given an empty setOnceProperties array', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = [];
- const result = trimTraits(traits, contextTraits, setOnceProperties);
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
- expect(result).toEqual({
- traits: { name: 'John', age: 30 },
- contextTraits: { email: 'john@example.com' },
- setOnce: {},
- });
+ expect(result).toEqual({
+ traits: { name: 'John', age: 30 },
+ contextTraits: { email: 'john@example.com' },
+ setOnce: {},
});
+ });
- // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
- it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => {
- const traits = { name: 'John', age: 30 };
- const contextTraits = { email: 'john@example.com' };
- const setOnceProperties = ['name', 'email', 'address'];
+ // Given a setOnceProperties array containing properties that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
+ it('should not add properties to the setOnce property when given setOnceProperties array with non-existent properties', () => {
+ const traits = { name: 'John', age: 30 };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address'];
- const result = trimTraits(traits, contextTraits, setOnceProperties);
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
- expect(result).toEqual({
- traits: { age: 30 },
- contextTraits: {},
- setOnce: { $name: 'John', $email: 'john@example.com' },
- });
+ expect(result).toEqual({
+ traits: { age: 30 },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
});
+ });
- // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
- it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => {
- const traits = { name: 'John', age: 30, address: 'kolkata' };
- const contextTraits = { email: 'john@example.com' };
- const setOnceProperties = ['name', 'email', 'address.city'];
+ // Given a setOnceProperties array containing properties with nested paths that do not exist in either traits or contextTraits objects, the function should not add the property to the setOnce property.
+ it('should not add properties to the setOnce property when given setOnceProperties array with non-existent nested properties', () => {
+ const traits = { name: 'John', age: 30, address: 'kolkata' };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address.city'];
- const result = trimTraits(traits, contextTraits, setOnceProperties);
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
- expect(result).toEqual({
- traits: { age: 30, address: 'kolkata' },
- contextTraits: {},
- setOnce: { $name: 'John', $email: 'john@example.com' },
- });
+ expect(result).toEqual({
+ traits: { age: 30, address: 'kolkata' },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com' },
});
+ });
- it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => {
- const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false };
- const contextTraits = { email: 'john@example.com' };
- const setOnceProperties = ['name', 'email', 'address.city'];
+ it('should add properties to the setOnce property when given setOnceProperties array with existent nested properties', () => {
+ const traits = { name: 'John', age: 30, address: { city: 'kolkata' }, isAdult: false };
+ const contextTraits = { email: 'john@example.com' };
+ const setOnceProperties = ['name', 'email', 'address.city'];
- const result = trimTraits(traits, contextTraits, setOnceProperties);
+ const result = trimTraits(traits, contextTraits, setOnceProperties);
- expect(result).toEqual({
- traits: { age: 30, address: {}, isAdult: false },
- contextTraits: {},
- setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' },
- });
+ expect(result).toEqual({
+ traits: { age: 30, address: {}, isAdult: false },
+ contextTraits: {},
+ setOnce: { $name: 'John', $email: 'john@example.com', $city: 'kolkata' },
});
});
});
+
+describe('generatePageOrScreenCustomEventName', () => {
+ it('should throw a ConfigurationError when userDefinedEventTemplate is not provided', () => {
+ const message = { name: 'Home' };
+ const userDefinedEventTemplate = undefined;
+ expect(() => {
+ generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ }).toThrow(ConfigurationError);
+ });
+
+ it('should generate a custom event name when userDefinedEventTemplate contains event template and message object is provided', () => {
+ let message = { name: 'Doc', properties: { category: 'Integration' } };
+ const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page';
+ let expected = 'Viewed Integration Doc page';
+ let result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+
+ message = { name: true, properties: { category: 0 } };
+ expected = 'Viewed 0 true page';
+ result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+ });
+
+ it('should generate a custom event name when userDefinedEventTemplate contains event template and category or name is missing in message object', () => {
+ const message = { name: 'Doc', properties: { category: undefined } };
+ const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword';
+ const expected = 'Viewed Doc page someKeyword';
+ const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+ });
+
+ it('should generate a custom event name when userDefinedEventTemplate contains only category or name placeholder and message object is provided', () => {
+ const message = { name: 'Doc', properties: { category: 'Integration' } };
+ const userDefinedEventTemplate = 'Viewed {{ name }} page';
+ const expected = 'Viewed Doc page';
+ const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+ });
+
+ it('should return the userDefinedEventTemplate when it does not contain placeholder {{}}', () => {
+ const message = { name: 'Index' };
+ const userDefinedEventTemplate = 'Viewed a Home page';
+ const expected = 'Viewed a Home page';
+ const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+ });
+
+ it('should return a event name when message object is not provided/empty', () => {
+ const message = {};
+ const userDefinedEventTemplate = 'Viewed {{ category }} {{ name }} page someKeyword';
+ const expected = 'Viewed page someKeyword';
+ const result = generatePageOrScreenCustomEventName(message, userDefinedEventTemplate);
+ expect(result).toBe(expected);
+ });
+});
diff --git a/src/v0/destinations/ometria/transform.js b/src/v0/destinations/ometria/transform.js
index 55038e10b83..5eff77bd15d 100644
--- a/src/v0/destinations/ometria/transform.js
+++ b/src/v0/destinations/ometria/transform.js
@@ -14,7 +14,6 @@ const {
getFieldValueFromMessage,
getIntegrationsObj,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
} = require('../../util/index');
const {
@@ -250,10 +249,6 @@ const process = (event) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
const inputChunks = returnArrayOfSubarrays(inputs, MAX_BATCH_SIZE);
const successList = [];
const errorList = [];
diff --git a/src/v0/destinations/one_signal/transform.js b/src/v0/destinations/one_signal/transform.js
index a072aef0e4f..b025660fa42 100644
--- a/src/v0/destinations/one_signal/transform.js
+++ b/src/v0/destinations/one_signal/transform.js
@@ -122,7 +122,7 @@ const trackResponseBuilder = (message, { Config }) => {
if (!externalUserId) {
throw new InstrumentationError('userId is required for track events/updating a device');
}
- endpoint = `${endpoint}/${appId}/users/${externalUserId}`;
+ endpoint = `${endpoint}/${appId}/users/${encodeURIComponent(externalUserId)}`;
const payload = {};
const tags = {};
/* Populating event as true in tags.
@@ -163,7 +163,7 @@ const groupResponseBuilder = (message, { Config }) => {
if (!externalUserId) {
throw new InstrumentationError('userId is required for group events');
}
- endpoint = `${endpoint}/${appId}/users/${externalUserId}`;
+ endpoint = `${endpoint}/${appId}/users/${encodeURIComponent(externalUserId)}`;
const payload = {};
const tags = {
groupId,
diff --git a/src/v0/destinations/pardot/networkHandler.js b/src/v0/destinations/pardot/networkHandler.js
index 12b4abbc530..60d2f7ee23d 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 b32b8967bd2..3dbe57ecc74 100644
--- a/src/v0/destinations/pardot/transform.js
+++ b/src/v0/destinations/pardot/transform.js
@@ -44,7 +44,6 @@ const {
getFieldValueFromMessage,
removeUndefinedValues,
getSuccessRespEvents,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
getAccessToken,
} = require('../../util');
@@ -150,11 +149,6 @@ const processEvent = (metadata, message, destination) => {
const process = (event) => processEvent(event.metadata, event.message, event.destination);
const processRouterDest = (events, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(events);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const responseList = events.map((event) => {
try {
return getSuccessRespEvents(process(event), [event.metadata], event.destination);
diff --git a/src/v0/destinations/pinterest_tag/transform.js b/src/v0/destinations/pinterest_tag/transform.js
index ee7e2e5b192..f8ccfd48eab 100644
--- a/src/v0/destinations/pinterest_tag/transform.js
+++ b/src/v0/destinations/pinterest_tag/transform.js
@@ -5,7 +5,6 @@ const {
defaultRequestConfig,
defaultPostRequestConfig,
getSuccessRespEvents,
- getErrorRespEvents,
constructPayload,
defaultBatchRequestConfig,
removeUndefinedAndNullValues,
@@ -172,11 +171,6 @@ const batchEvents = (successRespList) => {
};
const processRouterDest = (inputs, reqMetadata) => {
- if (!Array.isArray(inputs) || inputs.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
-
const successRespList = [];
const batchErrorRespList = [];
inputs.forEach((input) => {
diff --git a/src/v0/destinations/profitwell/utils.js b/src/v0/destinations/profitwell/utils.js
index acc4db20353..1b235617217 100644
--- a/src/v0/destinations/profitwell/utils.js
+++ b/src/v0/destinations/profitwell/utils.js
@@ -188,6 +188,9 @@ const getSubscriptionHistory = async (endpoint, options) => {
const res = await httpGET(endpoint, requestOptions, {
destType: 'profitwell',
feature: 'transformation',
+ endpointPath: '/users/userId',
+ requestMethod: 'GET',
+ module: 'router',
});
return res;
};
diff --git a/src/v0/destinations/rakuten/networkHandler.js b/src/v0/destinations/rakuten/networkHandler.js
new file mode 100644
index 00000000000..4c97a23e518
--- /dev/null
+++ b/src/v0/destinations/rakuten/networkHandler.js
@@ -0,0 +1,110 @@
+const { NetworkError } = require('@rudderstack/integrations-lib');
+const { httpSend } = require('../../../adapters/network');
+const {
+ processAxiosResponse,
+ getDynamicErrorType,
+} = require('../../../adapters/utils/networkUtils');
+const { TAG_NAMES } = require('../../util/tags');
+const { HTTP_STATUS_CODES } = require('../../util/constant');
+
+const DESTINATION = 'RAKUTEN';
+const prepareProxyRequest = (request) => request;
+const proxyRequest = async (request, destType) => {
+ const { endpoint, data, method, params, headers } = prepareProxyRequest(request);
+ const requestOptions = {
+ url: endpoint,
+ data,
+ params,
+ headers,
+ method,
+ };
+ const response = await httpSend(requestOptions, {
+ feature: 'proxy',
+ destType,
+ endpointPath: '/ep',
+ requestMethod: 'GET',
+ module: 'dataDelivery',
+ });
+ return response;
+};
+const extractContent = (xmlPayload, tagName) => {
+ const pattern = new RegExp(`<${tagName}>(.*?)${tagName}>`);
+ const match = xmlPayload.match(pattern);
+ return match ? match[1] : null;
+};
+
+const responseHandler = (responseParams) => {
+ const { destinationResponse } = responseParams;
+ const msg = `[${DESTINATION} Response Handler] - Request Processed Successfully`;
+ const { response, status } = destinationResponse;
+ if (status === 400) {
+ throw new NetworkError(
+ `Request failed with status: ${status} due to invalid Marketing Id`,
+ 400,
+ {
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ // Extract errors, good and bad between different tags
+ const badRecords = extractContent(response, 'bad');
+ const errors = extractContent(response, 'error');
+
+ // For access denied for a mid rakuten sends status code 200 with response as Access denied
+ if (errors) {
+ throw new NetworkError(
+ `Request failed with status: ${status} due to ${errors}. Can you try to enable pixel tracking for this mid.`,
+ 400,
+ {
+ // status would be 200 but since no error type for this status code hence it will take it as aborted
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ if (parseInt(badRecords, 10)) {
+ throw new NetworkError(
+ `Request failed with status: ${status} with number of bad records ${badRecords}`,
+ 400,
+ {
+ // status would be 200 but since no error type for this status code hence it will take it as aborted
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ /* just puttting it here for 429 and 500's we dont have documentation for these two
+ neither we have any sample response but just in case if we recoeve non 2xx status
+ */
+ if (status !== 200) {
+ throw new NetworkError(
+ `Request failed with status: ${status}`,
+ status,
+ {
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ },
+ destinationResponse,
+ );
+ }
+ // if no error or bad record is found and status is 200 the request is successfull
+ return {
+ status: HTTP_STATUS_CODES.OK,
+ message: msg,
+ destinationResponse,
+ };
+};
+// eslint-disable-next-line @typescript-eslint/naming-convention
+class networkHandler {
+ constructor() {
+ this.responseHandler = responseHandler;
+ this.proxy = proxyRequest;
+ this.prepareProxy = prepareProxyRequest;
+ this.processAxiosResponse = processAxiosResponse;
+ }
+}
+
+module.exports = {
+ networkHandler,
+ responseHandler,
+};
diff --git a/src/v0/destinations/rakuten/networkHandler.test.js b/src/v0/destinations/rakuten/networkHandler.test.js
new file mode 100644
index 00000000000..da74e05cb31
--- /dev/null
+++ b/src/v0/destinations/rakuten/networkHandler.test.js
@@ -0,0 +1,63 @@
+const { responseHandler } = require('./networkHandler');
+// Generated by CodiumAI
+
+describe('responseHandler', () => {
+ it('should return a success message with status code 200 when the request is successful and no bad records or errors are found', () => {
+ const destinationResponse = {
+ response: '',
+ status: 200,
+ };
+
+ const result = responseHandler({ destinationResponse });
+
+ expect(result.status).toBe(200);
+ expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
+ expect(result.destinationResponse).toEqual(destinationResponse);
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response status is 400 due to invalid Marketing Id', () => {
+ const destinationResponse = {
+ response: 'Invalid marketing id',
+ status: 400,
+ };
+ expect(() => {
+ responseHandler({ destinationResponse });
+ }).toThrow('Request failed with status: 400 due to invalid Marketing Id');
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response contains errors', () => {
+ const destinationResponse = {
+ response: 'Access denied',
+ status: 200,
+ };
+ expect(() => {
+ responseHandler({ destinationResponse });
+ }).toThrow(
+ 'Request failed with status: 200 due to Access denied. Can you try to enable pixel tracking for this mid.',
+ );
+ });
+
+ it('should return a success message with status code 200 when the response status is 200 and no bad records or errors are found', () => {
+ const destinationResponse = {
+ response: '',
+ status: 200,
+ };
+
+ const result = responseHandler({ destinationResponse });
+
+ expect(result.status).toBe(200);
+ expect(result.message).toBe('[RAKUTEN Response Handler] - Request Processed Successfully');
+ expect(result.destinationResponse).toEqual(destinationResponse);
+ });
+
+ it('should throw a NetworkError with status code 400 and error message when the response status is 200 and the response contains only bad records', () => {
+ const destinationResponse = {
+ response: '1',
+ status: 200,
+ };
+
+ expect(() => {
+ 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 836c0158592..55087b52ac4 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 918084cc899..ac312417756 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 5ada9dfaa07..b8f032c5bfe 100644
--- a/src/v0/destinations/salesforce/transform.js
+++ b/src/v0/destinations/salesforce/transform.js
@@ -3,6 +3,7 @@ const cloneDeep = require('lodash/cloneDeep');
const {
InstrumentationError,
NetworkInstrumentationError,
+ getErrorRespEvents,
} = require('@rudderstack/integrations-lib');
const { EventType, MappedToDestinationKey } = require('../../../constants');
const {
@@ -20,10 +21,8 @@ const {
constructPayload,
getFirstAndLastName,
getSuccessRespEvents,
- getErrorRespEvents,
addExternalIdToTraits,
getDestinationExternalIDObjectForRetl,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
generateErrorObject,
isHttpStatusSuccess,
@@ -120,6 +119,9 @@ async function getSaleforceIdForRecord(
{
destType: 'salesforce',
feature: 'transformation',
+ endpointPath: '/parameterizedSearch',
+ requestMethod: 'GET',
+ module: 'router',
},
);
if (!isHttpStatusSuccess(processedsfSearchResponse.status)) {
@@ -233,6 +235,9 @@ async function getSalesforceIdFromPayload(
{
destType: 'salesforce',
feature: 'transformation',
+ endpointPath: '/parameterizedSearch',
+ requestMethod: 'GET',
+ module: 'router',
},
);
@@ -348,10 +353,6 @@ async function process(event) {
}
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
let authInfo;
try {
authInfo = await collectAuthorizationInfo(inputs[0]);
diff --git a/src/v0/destinations/salesforce/utils.js b/src/v0/destinations/salesforce/utils.js
index 96735ecc174..85061ce2b2a 100644
--- a/src/v0/destinations/salesforce/utils.js
+++ b/src/v0/destinations/salesforce/utils.js
@@ -133,6 +133,9 @@ const getAccessToken = async (destination) => {
{
destType: 'salesforce',
feature: 'transformation',
+ endpointPath: '/services/oauth2/token',
+ requestMethod: 'POST',
+ module: 'router',
},
);
// If the request fails, throwing error.
diff --git a/src/v0/destinations/salesforce_oauth/networkHandler.js b/src/v0/destinations/salesforce_oauth/networkHandler.js
index 2bcace31c9a..b6cbed77f95 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 8410f41296f..ccd277d90d6 100644
--- a/src/v0/destinations/sendgrid/deleteUsers.js
+++ b/src/v0/destinations/sendgrid/deleteUsers.js
@@ -85,6 +85,9 @@ const userDeletionHandler = async (userAttributes, config) => {
const deletionResponse = await httpDELETE(endpoint, requestOptions, {
destType: 'sendgrid',
feature: 'deleteUsers',
+ endpointPath: '/marketing/contacts',
+ requestMethod: 'DELETE',
+ module: 'deletion',
});
const handledDelResponse = processAxiosResponse(deletionResponse);
@@ -94,6 +97,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/sendgrid/transform.js b/src/v0/destinations/sendgrid/transform.js
index 5038fedf7b7..c32e34c4890 100644
--- a/src/v0/destinations/sendgrid/transform.js
+++ b/src/v0/destinations/sendgrid/transform.js
@@ -9,7 +9,6 @@ const {
ErrorMessage,
isEmptyObject,
constructPayload,
- getErrorRespEvents,
extractCustomFields,
getValueFromMessage,
defaultRequestConfig,
@@ -236,10 +235,6 @@ const batchEvents = (successRespList) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- if (!Array.isArray(inputs) || inputs.length <= 0) {
- const respEvents = getErrorRespEvents(null, 400, 'Invalid event array');
- return [respEvents];
- }
let batchResponseList = [];
const batchErrorRespList = [];
const successRespList = [];
diff --git a/src/v0/destinations/sendgrid/util.js b/src/v0/destinations/sendgrid/util.js
index 1df34bfe69d..7105c5cda5f 100644
--- a/src/v0/destinations/sendgrid/util.js
+++ b/src/v0/destinations/sendgrid/util.js
@@ -445,6 +445,9 @@ const fetchCustomFields = async (destination) => {
const resonse = await httpGET(endpoint, requestOptions, {
destType: 'sendgrid',
feature: 'transformation',
+ endpointPath: '/marketing/field_definitions',
+ requestMethod: 'GET',
+ module: 'router',
});
const processedResponse = processAxiosResponse(resonse);
if (isHttpStatusSuccess(processedResponse.status)) {
diff --git a/src/v0/destinations/sendinblue/util.js b/src/v0/destinations/sendinblue/util.js
index 9ad37fc9b79..9fded8e493b 100644
--- a/src/v0/destinations/sendinblue/util.js
+++ b/src/v0/destinations/sendinblue/util.js
@@ -60,6 +60,9 @@ const checkIfContactExists = async (identifier, apiKey) => {
const contactDetailsResponse = await httpGET(endpoint, requestOptions, {
destType: 'sendinblue',
feature: 'transformation',
+ endpointPath: '/contacts',
+ requestMethod: 'GET',
+ module: 'router',
});
const processedContactDetailsResponse = processAxiosResponse(contactDetailsResponse);
diff --git a/src/v0/destinations/sfmc/config.js b/src/v0/destinations/sfmc/config.js
index f856c44d6b3..1b1f5c323ba 100644
--- a/src/v0/destinations/sfmc/config.js
+++ b/src/v0/destinations/sfmc/config.js
@@ -4,6 +4,7 @@ const ENDPOINTS = {
GET_TOKEN: `auth.marketingcloudapis.com/v2/token`,
CONTACTS: `rest.marketingcloudapis.com/contacts/v1/contacts`,
INSERT_CONTACTS: `rest.marketingcloudapis.com/hub/v1/dataevents/key:`,
+ EVENT: 'rest.marketingcloudapis.com/interaction/v1/events',
};
const CONFIG_CATEGORIES = {
diff --git a/src/v0/destinations/sfmc/transform.js b/src/v0/destinations/sfmc/transform.js
index 7623d751f1a..53925bc7ed5 100644
--- a/src/v0/destinations/sfmc/transform.js
+++ b/src/v0/destinations/sfmc/transform.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-param-reassign */
/* eslint-disable no-nested-ternary */
const {
NetworkError,
@@ -44,7 +45,13 @@ const getToken = async (clientId, clientSecret, subdomain) => {
{
'Content-Type': JSON_MIME_TYPE,
},
- { destType: 'sfmc', feature: 'transformation' },
+ {
+ destType: 'sfmc',
+ feature: 'transformation',
+ endpointPath: '/token',
+ requestMethod: 'POST',
+ module: 'router',
+ },
);
if (resp && resp.data) {
return resp.data.access_token;
@@ -182,6 +189,26 @@ const responseBuilderForInsertData = (
return response;
};
+// DOC : https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_interaction/postEvent.html
+
+const responseBuilderForMessageEvent = (message, subDomain, authToken, hashMapEventDefinition) => {
+ const contactKey = message.properties.contactId;
+ delete message.properties.contactId;
+ const response = defaultRequestConfig();
+ response.method = defaultPostRequestConfig.requestMethod;
+ response.endpoint = `https://${subDomain}.${ENDPOINTS.EVENT}`;
+ response.headers = {
+ 'Content-Type': JSON_MIME_TYPE,
+ Authorization: `Bearer ${authToken}`,
+ };
+ response.body.JSON = {
+ ContactKey: contactKey,
+ EventDefinitionKey: hashMapEventDefinition[message.event.toLowerCase()],
+ Data: { ...message.properties },
+ };
+ return response;
+};
+
const responseBuilderSimple = async (message, category, destination) => {
const {
clientId,
@@ -192,6 +219,7 @@ const responseBuilderSimple = async (message, category, destination) => {
eventToExternalKey,
eventToPrimaryKey,
eventToUUID,
+ eventToDefinitionMapping,
} = destination.Config;
// map from an event name to an external key of a data extension.
const hashMapExternalKey = getHashFromArray(eventToExternalKey, 'from', 'to');
@@ -201,6 +229,8 @@ const responseBuilderSimple = async (message, category, destination) => {
const hashMapUUID = getHashFromArray(eventToUUID, 'event', 'uuid');
// token needed for authorization for subsequent calls
const authToken = await getToken(clientId, clientSecret, subDomain);
+ // map from an event name to an event definition key.
+ const hashMapEventDefinition = getHashFromArray(eventToDefinitionMapping, 'from', 'to');
// if createOrUpdateContacts is true identify calls for create and update of contacts will not occur.
if (category.type === 'identify' && !createOrUpdateContacts) {
// first call to identify the contact
@@ -234,10 +264,12 @@ const responseBuilderSimple = async (message, category, destination) => {
if (typeof message.event !== 'string') {
throw new ConfigurationError('Event name must be a string');
}
+ if (hashMapEventDefinition[message.event.toLowerCase()]) {
+ return responseBuilderForMessageEvent(message, subDomain, authToken, hashMapEventDefinition);
+ }
if (!isDefinedAndNotNull(hashMapExternalKey[message.event.toLowerCase()])) {
throw new ConfigurationError('Event not mapped for this track call');
}
-
return responseBuilderForInsertData(
message,
hashMapExternalKey[message.event.toLowerCase()],
@@ -287,4 +319,9 @@ const processRouterDest = async (inputs, reqMetadata) => {
return respList;
};
-module.exports = { process, processRouterDest, responseBuilderSimple };
+module.exports = {
+ process,
+ processRouterDest,
+ responseBuilderSimple,
+ responseBuilderForMessageEvent,
+};
diff --git a/src/v0/destinations/sfmc/transform.test.js b/src/v0/destinations/sfmc/transform.test.js
index c49c49017c6..8d382ef6499 100644
--- a/src/v0/destinations/sfmc/transform.test.js
+++ b/src/v0/destinations/sfmc/transform.test.js
@@ -1,7 +1,7 @@
const { ConfigurationError } = require('@rudderstack/integrations-lib');
const axios = require('axios');
const MockAxiosAdapter = require('axios-mock-adapter');
-const { responseBuilderSimple } = require('./transform');
+const { responseBuilderSimple, responseBuilderForMessageEvent } = require('./transform');
beforeAll(() => {
const mock = new MockAxiosAdapter(axios);
mock
@@ -122,4 +122,44 @@ describe('responseBuilderSimple', () => {
expect(response).toHaveProperty('body.JSON');
expect(response).toHaveProperty('headers');
});
+
+ it('should build response object with correct details for message event', () => {
+ const message = {
+ userId: 'u123',
+ event: 'testEvent',
+ properties: {
+ contactId: '12345',
+ prop1: 'value1',
+ prop2: 'value2',
+ },
+ };
+ const subDomain = 'subdomain';
+ const authToken = 'token';
+ const hashMapEventDefinition = {
+ testevent: 'eventDefinitionKey',
+ };
+
+ const response = responseBuilderForMessageEvent(
+ message,
+ subDomain,
+ authToken,
+ hashMapEventDefinition,
+ );
+ expect(response.method).toBe('POST');
+ expect(response.endpoint).toBe(
+ 'https://subdomain.rest.marketingcloudapis.com/interaction/v1/events',
+ );
+ expect(response.headers).toEqual({
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer token',
+ });
+ expect(response.body.JSON).toEqual({
+ ContactKey: '12345',
+ EventDefinitionKey: 'eventDefinitionKey',
+ Data: {
+ prop1: 'value1',
+ prop2: 'value2',
+ },
+ });
+ });
});
diff --git a/src/v0/destinations/snapchat_conversion/transform.js b/src/v0/destinations/snapchat_conversion/transform.js
index 37d321a468d..6fec6313a4a 100644
--- a/src/v0/destinations/snapchat_conversion/transform.js
+++ b/src/v0/destinations/snapchat_conversion/transform.js
@@ -12,7 +12,6 @@ const {
getSuccessRespEvents,
isAppleFamily,
getValidDynamicFormConfig,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
batchMultiplexedEvents,
} = require('../../util');
@@ -358,11 +357,6 @@ const process = (event) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const eventsChunk = []; // temporary variable to divide payload into chunks
const errorRespList = [];
inputs.forEach((event) => {
diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js
index db36f6f5180..6044216293a 100644
--- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js
+++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js
@@ -43,6 +43,9 @@ const scAudienceProxyRequest = async (request) => {
const response = await httpSend(requestOptions, {
feature: 'proxy',
destType: 'snapchat_custom_audience',
+ endpointPath: '/segments/segmentId/users',
+ requestMethod: requestOptions?.method,
+ module: 'dataDelivery',
});
return response;
};
@@ -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 a886bbbafc0..01044adcd1c 100644
--- a/src/v0/destinations/sprig/deleteUsers.js
+++ b/src/v0/destinations/sprig/deleteUsers.js
@@ -49,7 +49,9 @@ const userDeletionHandler = async (userAttributes, config) => {
{
destType: 'sprig',
feature: 'deleteUsers',
- endpointPath: 'api.sprig.com/v2/purge/visitors',
+ endpointPath: '/purge/visitors',
+ requestMethod: 'POST',
+ module: 'deletion',
},
);
const handledDelResponse = processAxiosResponse(deletionResponse);
@@ -59,6 +61,7 @@ const userDeletionHandler = async (userAttributes, config) => {
handledDelResponse.status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(handledDelResponse.status),
+ [tags.TAG_NAMES.STATUS]: handledDelResponse.status,
},
handledDelResponse,
);
diff --git a/src/v0/destinations/the_trade_desk/networkHandler.js b/src/v0/destinations/the_trade_desk/networkHandler.js
index ca5ac68be87..aebbfc0785e 100644
--- a/src/v0/destinations/the_trade_desk/networkHandler.js
+++ b/src/v0/destinations/the_trade_desk/networkHandler.js
@@ -22,12 +22,12 @@ const proxyRequest = async (request) => {
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,
),
- 'Content-Type': JSON_MIME_TYPE,
};
const requestOptions = {
@@ -37,18 +37,25 @@ 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;
// if the response from destination is not a success case build an explicit error
if (!isHttpStatusSuccess(status)) {
throw new NetworkError(
- `Request failed with status: ${status} due to ${response}`,
+ `Request failed with status: ${status} due to ${JSON.stringify(response)}`,
status,
{
[tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
@@ -57,7 +64,7 @@ const responseHandler = (destinationResponse) => {
);
}
- // Trade desk returns 200 with an error in case of "Failed to parse TDID, DAID, UID2, IDL, EUID, or failed to decrypt UID2Token or EUIDToken"
+ // 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"}]}
if ('FailedLines' in response && response.FailedLines.length > 0) {
@@ -69,7 +76,7 @@ const responseHandler = (destinationResponse) => {
}
// else successfully return status, message and original destination response
- // Trade desk returns 200 with empty object '{}' in response if all the events are processed successfully
+ // For first party api trade desk returns 200 with empty object '{}' in response if all the events are processed successfully
return {
status,
message,
diff --git a/src/v0/destinations/tiktok_ads/networkHandler.js b/src/v0/destinations/tiktok_ads/networkHandler.js
index ae93b1ec15b..5d4b7fd4e06 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 bdf3a0defe3..17a37984c39 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 91078dfe656..3bd8699e3ad 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 5d5e80c7167..4bb3bda850e 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 945c31ea632..dbe9a06dd6e 100644
--- a/src/v0/destinations/tiktok_ads_offline_events/transform.js
+++ b/src/v0/destinations/tiktok_ads_offline_events/transform.js
@@ -9,7 +9,6 @@ const {
removeUndefinedAndNullValues,
isDefinedAndNotNullAndNotEmpty,
getHashFromArrayWithDuplicate,
- checkInvalidRtTfEvents,
handleRtTfSingleEventError,
getSuccessRespEvents,
defaultBatchRequestConfig,
@@ -199,11 +198,6 @@ const batchEvents = (eventChunksArray) => {
};
const processRouterDest = async (inputs, reqMetadata) => {
- const errorRespEvents = checkInvalidRtTfEvents(inputs);
- if (errorRespEvents.length > 0) {
- return errorRespEvents;
- }
-
const batchErrorRespList = [];
const eventChunksArray = [];
const { destination } = inputs[0];
diff --git a/src/v0/destinations/trengo/transform.js b/src/v0/destinations/trengo/transform.js
index 06e5496a1eb..01c5cfeb254 100644
--- a/src/v0/destinations/trengo/transform.js
+++ b/src/v0/destinations/trengo/transform.js
@@ -90,7 +90,13 @@ const lookupContact = async (term, destination) => {
Authorization: `Bearer ${destination.Config.apiToken}`,
},
},
- { destType: 'trengo', feature: 'transformation' },
+ {
+ destType: 'trengo',
+ feature: 'transformation',
+ endpointPath: '/contacts',
+ requestMethod: 'GET',
+ module: 'router',
+ },
);
} catch (err) {
// check if exists err.response && err.response.status else 500
diff --git a/src/v0/destinations/twitter_ads/config.js b/src/v0/destinations/twitter_ads/config.js
index 601675fc2ff..6b0db0622c8 100644
--- a/src/v0/destinations/twitter_ads/config.js
+++ b/src/v0/destinations/twitter_ads/config.js
@@ -14,5 +14,5 @@ const mappingConfig = getMappingConfig(ConfigCategories, __dirname);
module.exports = {
mappingConfig,
ConfigCategories,
- BASE_URL
+ BASE_URL,
};
diff --git a/src/v0/destinations/twitter_ads/util.js b/src/v0/destinations/twitter_ads/util.js
index 2f237b1dd86..ad59a812673 100644
--- a/src/v0/destinations/twitter_ads/util.js
+++ b/src/v0/destinations/twitter_ads/util.js
@@ -2,23 +2,20 @@ const crypto = require('crypto');
const oauth1a = require('oauth-1.0a');
function getAuthHeaderForRequest(request, oAuthObject) {
- const oauth = oauth1a({
- consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret },
- signature_method: 'HMAC-SHA1',
- hash_function(base_string, k) {
- return crypto
- .createHmac('sha1', k)
- .update(base_string)
- .digest('base64')
- },
- })
+ const oauth = oauth1a({
+ consumer: { key: oAuthObject.consumerKey, secret: oAuthObject.consumerSecret },
+ signature_method: 'HMAC-SHA1',
+ hash_function(base_string, k) {
+ return crypto.createHmac('sha1', k).update(base_string).digest('base64');
+ },
+ });
- const authorization = oauth.authorize(request, {
- key: oAuthObject.accessToken,
- secret: oAuthObject.accessTokenSecret,
- });
+ const authorization = oauth.authorize(request, {
+ key: oAuthObject.accessToken,
+ secret: oAuthObject.accessTokenSecret,
+ });
- return oauth.toHeader(authorization);
+ return oauth.toHeader(authorization);
}
-module.exports = { getAuthHeaderForRequest };
\ No newline at end of file
+module.exports = { getAuthHeaderForRequest };
diff --git a/src/v0/destinations/user/utils.js b/src/v0/destinations/user/utils.js
index 52fba2167ea..f332d7a4a71 100644
--- a/src/v0/destinations/user/utils.js
+++ b/src/v0/destinations/user/utils.js
@@ -238,6 +238,8 @@ const createCompany = async (message, destination) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/companies/`,
+ requestMethod: 'POST',
+ module: 'router',
});
const data = processAxiosResponse(response);
return data.response;
@@ -279,6 +281,8 @@ const updateCompany = async (message, destination, company) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/companies/`,
+ requestMethod: 'PUT',
+ module: 'router',
});
const data = processAxiosResponse(response);
return data.response;
@@ -306,6 +310,8 @@ const getUserByUserKey = async (apiKey, userKey, appSubdomain) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/users/search`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(userResponse);
if (processedUserResponse.status === 200) {
@@ -340,6 +346,8 @@ const getUserByEmail = async (apiKey, email, appSubdomain) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/users/search/?email`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(userResponse);
@@ -379,6 +387,8 @@ const getUserByPhoneNumber = async (apiKey, phoneNumber, appSubdomain) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/users/search/?phone_number`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(userResponse);
@@ -424,6 +434,8 @@ const getUserByCustomId = async (message, destination) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/users-by-id/`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(userResponse);
@@ -460,6 +472,8 @@ const getCompanyByCustomId = async (message, destination) => {
destType: 'user',
feature: 'transformation',
endpointPath: `/companies-by-id/`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(response);
if (processedUserResponse.status === 200) {
diff --git a/src/v0/destinations/wootric/util.js b/src/v0/destinations/wootric/util.js
index eb61a472cf2..c2505c635be 100644
--- a/src/v0/destinations/wootric/util.js
+++ b/src/v0/destinations/wootric/util.js
@@ -47,6 +47,8 @@ const getAccessToken = async (destination) => {
destType: 'wootric',
feature: 'transformation',
endpointPath: `/oauth/token`,
+ requestMethod: 'POST',
+ module: 'router',
});
const processedAuthResponse = processAxiosResponse(wootricAuthResponse);
// If the request fails, throwing error.
@@ -100,6 +102,8 @@ const retrieveUserDetails = async (endUserId, externalId, accessToken) => {
destType: 'wootric',
feature: 'transformation',
endpointPath: `/v1/end_users/`,
+ requestMethod: 'GET',
+ module: 'router',
});
const processedUserResponse = processAxiosResponse(userResponse);
diff --git a/src/v0/destinations/yahoo_dsp/util.js b/src/v0/destinations/yahoo_dsp/util.js
index d41716935fd..255f84d1c91 100644
--- a/src/v0/destinations/yahoo_dsp/util.js
+++ b/src/v0/destinations/yahoo_dsp/util.js
@@ -137,6 +137,9 @@ const getAccessToken = async (destination) => {
const dspAuthorisationData = await httpSend(request, {
destType: 'yahoo_dsp',
feature: 'transformation',
+ endpointPath: '/identity/oauth2/access_token',
+ requestMethod: 'POST',
+ module: 'router',
});
// If the request fails, throwing error.
if (dspAuthorisationData.success === false) {
diff --git a/src/v0/destinations/zendesk/transform.js b/src/v0/destinations/zendesk/transform.js
index bf2bc01ed28..58620147845 100644
--- a/src/v0/destinations/zendesk/transform.js
+++ b/src/v0/destinations/zendesk/transform.js
@@ -36,7 +36,7 @@ const tags = require('../../util/tags');
const { JSON_MIME_TYPE } = require('../../util/constant');
const CONTEXT_TRAITS_KEY_PATH = 'context.traits';
-
+const endpointPath = '/users/search.json';
function responseBuilder(message, headers, payload, endpoint) {
const response = defaultRequestConfig();
@@ -102,6 +102,9 @@ const payloadBuilderforUpdatingEmail = async (userId, headers, userEmail, baseEn
const res = await httpGET(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: 'users/userId/identities',
+ requestMethod: 'POST',
+ module: 'router',
});
if (res?.response?.data?.count > 0) {
const { identities } = res.response.data;
@@ -147,6 +150,9 @@ async function createUserFields(url, config, newFields, fieldJson) {
const response = await myAxios.post(url, fieldData, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: '/users/userId/identities',
+ requestMethod: 'POST',
+ module: 'router',
});
if (response.status !== 201) {
logger.debug(`${NAME}:: Failed to create User Field : `, field);
@@ -176,6 +182,8 @@ async function checkAndCreateUserFields(
const response = await myAxios.get(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ requestMethod: 'POST',
+ module: 'router',
});
const fields = get(response.data, fieldJson);
if (response.data && fields) {
@@ -253,6 +261,9 @@ const getUserIdByExternalId = async (message, headers, baseEndpoint) => {
const resp = await httpGET(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath,
+ requestMethod: 'GET',
+ module: 'router',
});
if (resp?.response?.data?.count > 0) {
@@ -283,6 +294,9 @@ async function getUserId(message, headers, baseEndpoint, type) {
const resp = await myAxios.get(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath,
+ requestMethod: 'GET',
+ module: 'router',
});
if (!resp || !resp.data || resp.data.count === 0) {
logger.debug(`${NAME}:: User not found`);
@@ -307,6 +321,9 @@ async function isUserAlreadyAssociated(userId, orgId, headers, baseEndpoint) {
const response = await myAxios.get(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: '/users/userId/organization_memberships.json',
+ requestMethod: 'GET',
+ module: 'router',
});
if (response?.data?.organization_memberships?.[0]?.organization_id === orgId) {
return true;
@@ -339,6 +356,9 @@ async function createUser(message, headers, destinationConfig, baseEndpoint, typ
const resp = await myAxios.post(url, payload, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: '/users/create_or_update.json',
+ requestMethod: 'POST',
+ module: 'router',
});
if (!resp.data || !resp.data.user || !resp.data.user.id) {
@@ -420,6 +440,9 @@ async function createOrganization(message, category, headers, destinationConfig,
const resp = await myAxios.post(url, payload, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: '/organizations/create_or_update.json',
+ requestMethod: 'POST',
+ module: 'router',
});
if (!resp.data || !resp.data.organization) {
@@ -488,6 +511,9 @@ async function processIdentify(message, destinationConfig, headers, baseEndpoint
const response = await myAxios.get(membershipUrl, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath: '/users/userId/organization_memberships.json',
+ requestMethod: 'GET',
+ module: 'router',
});
if (
response.data &&
@@ -535,6 +561,9 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) {
const userResponse = await myAxios.get(url, config, {
destType: 'zendesk',
feature: 'transformation',
+ endpointPath,
+ requestMethod: 'GET',
+ module: 'router',
});
if (!get(userResponse, 'data.users.0.id') || userResponse.data.count === 0) {
const { zendeskUserId, email } = await createUser(
diff --git a/src/v0/sources/auth0/transform.js b/src/v0/sources/auth0/transform.js
index d2721780d82..4b78621418e 100644
--- a/src/v0/sources/auth0/transform.js
+++ b/src/v0/sources/auth0/transform.js
@@ -1,5 +1,6 @@
const path = require('path');
const fs = require('fs');
+const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { removeUndefinedAndNullValues } = require('../../util');
const { getGroupId } = require('./util');
// import mapping json using JSON.parse to preserve object key order
@@ -73,7 +74,11 @@ function process(events) {
if (!Array.isArray(events)) {
eventList = events.logs || [events];
}
- return processEvents(eventList);
+ const responses = processEvents(eventList);
+ if (responses.length === 0) {
+ throw new InstrumentationError('UserId is not present');
+ }
+ return responses;
}
exports.process = process;
diff --git a/src/v0/sources/formsort/transform.js b/src/v0/sources/formsort/transform.js
index 18d7b8fc0eb..dd37482bc49 100644
--- a/src/v0/sources/formsort/transform.js
+++ b/src/v0/sources/formsort/transform.js
@@ -1,18 +1,16 @@
-const path = require("path");
-const fs = require("fs");
-const { generateUUID, isDefinedAndNotNull } = require("../../util");
-const Message = require("../message");
+const path = require('path');
+const fs = require('fs');
+const { generateUUID, isDefinedAndNotNull } = require('../../util');
+const Message = require('../message');
// import mapping json using JSON.parse to preserve object key order
-const mapping = JSON.parse(
- fs.readFileSync(path.resolve(__dirname, "./mapping.json"), "utf-8")
-);
+const mapping = JSON.parse(fs.readFileSync(path.resolve(__dirname, './mapping.json'), 'utf-8'));
function process(event) {
const message = new Message(`Formsort`);
// we are setting event type as track always
- message.setEventType("track");
+ message.setEventType('track');
message.setPropertiesV2(event, mapping);
@@ -23,9 +21,9 @@ function process(event) {
// setting event Name
if (event.finalized) {
- message.setEventName("FlowFinalized");
+ message.setEventName('FlowFinalized');
} else {
- message.setEventName("FlowLoaded");
+ message.setEventName('FlowLoaded');
}
return message;
diff --git a/src/v0/sources/formsort/transform.test.js b/src/v0/sources/formsort/transform.test.js
index 9b0d814d6af..e3d686fcefb 100644
--- a/src/v0/sources/formsort/transform.test.js
+++ b/src/v0/sources/formsort/transform.test.js
@@ -1,52 +1,51 @@
const { process } = require('./transform');
it(`Transform.js Tests`, () => {
- const data = {
- input: {
- "answers": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "flow_label": "new-flow-2022-11-25",
- "variant_label": "main",
- "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea",
- "finalized": false,
- "created_at": "2022-11-25T14:41:22+00:00"
+ const data = {
+ input: {
+ answers: {
+ yes: true,
+ enter_email: 'test@user.com',
+ enter_name: '2022-11-17',
+ yes_or_no: false,
+ },
+ responder_uuid: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7',
+ flow_label: 'new-flow-2022-11-25',
+ variant_label: 'main',
+ variant_uuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea',
+ finalized: false,
+ created_at: '2022-11-25T14:41:22+00:00',
+ },
+ output: {
+ context: {
+ library: {
+ name: 'unknown',
+ version: 'unknown',
},
- output: {
- "context": {
- "library": {
- "name": "unknown",
- "version": "unknown"
- },
- "integration": {
- "name": "Formsort"
- },
- "page": {
- "title": "new-flow-2022-11-25"
- },
- "variantLabel": "main",
- "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea"
- },
- "integrations": {
- "Formsort": false
- },
- "type": "track",
- "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "originalTimestamp": "2022-11-25T14:41:22+00:00",
- "properties": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "event": "FlowLoaded"
- }
- };
- const output = process(data.input);
- expect(output).toEqual(data.output);
-
-});
\ No newline at end of file
+ integration: {
+ name: 'Formsort',
+ },
+ page: {
+ title: 'new-flow-2022-11-25',
+ },
+ variantLabel: 'main',
+ variantUuid: '0828efa7-7215-4e7d-a7ab-6c1079010cea',
+ },
+ integrations: {
+ Formsort: false,
+ },
+ type: 'track',
+ userId: '66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7',
+ originalTimestamp: '2022-11-25T14:41:22+00:00',
+ properties: {
+ yes: true,
+ enter_email: 'test@user.com',
+ enter_name: '2022-11-17',
+ yes_or_no: false,
+ },
+ event: 'FlowLoaded',
+ },
+ };
+ const output = process(data.input);
+ expect(output).toEqual(data.output);
+});
diff --git a/src/v0/sources/shopify/shopify.util.test.js b/src/v0/sources/shopify/shopify.util.test.js
index 9c570dde41b..d058db36b5c 100644
--- a/src/v0/sources/shopify/shopify.util.test.js
+++ b/src/v0/sources/shopify/shopify.util.test.js
@@ -1,5 +1,4 @@
-const { getShopifyTopic,
-} = require('./util');
+const { getShopifyTopic } = require('./util');
jest.mock('ioredis', () => require('../../../../test/__mocks__/redis'));
describe('Shopify Utils Test', () => {
describe('Fetching Shopify Topic Test Cases', () => {
@@ -58,5 +57,4 @@ describe('Shopify Utils Test', () => {
}
});
});
-
});
diff --git a/src/v0/sources/shopify/shopify_redis.util.test.js b/src/v0/sources/shopify/shopify_redis.util.test.js
index db596e1dfb1..fb998379328 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 013580d7a3c..93e3ed0c72b 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.debug(`{{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 6f31ade4a70..c4bbb61b9cc 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.debug(`{{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.debug(`{{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 4c095185592..c7753d255fb 100644
--- a/src/v0/util/facebookUtils/index.js
+++ b/src/v0/util/facebookUtils/index.js
@@ -147,7 +147,7 @@ const getContentType = (message, defaultValue, categoryToContent, destinationNam
return integrationsObj.contentType;
}
- let { category } = properties;
+ let { category } = properties || {};
if (!category) {
const { products } = properties;
if (products && products.length > 0 && Array.isArray(products) && isObject(products[0])) {
@@ -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 98e4ccec402..20c4ee59f25 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 e0d69fa5c8b..d5a731067f5 100644
--- a/src/v0/util/facebookUtils/networkHandler.js
+++ b/src/v0/util/facebookUtils/networkHandler.js
@@ -1,12 +1,18 @@
const { isEmpty } = require('lodash');
const get = require('get-value');
-const { NetworkError } = require('@rudderstack/integrations-lib');
+const {
+ NetworkError,
+ ConfigurationAuthError,
+ isDefinedAndNotNull,
+ ERROR_TYPES,
+ TAG_NAMES,
+ METADATA,
+} = require('@rudderstack/integrations-lib');
const {
processAxiosResponse,
getDynamicErrorType,
} = require('../../../adapters/utils/networkUtils');
const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network');
-const tags = require('../tags');
const { ErrorDetailsExtractorBuilder } = require('../../../util/error-extractor');
/**
@@ -100,12 +106,26 @@ const errorDetailsMap = {
190: {
460: new ErrorDetailsExtractorBuilder()
.setStatus(400)
+ .setStat({
+ [TAG_NAMES.ERROR_TYPE]: ERROR_TYPES.AUTH,
+ })
.setMessage(
'The session has been invalidated because the user changed their password or Facebook has changed the session for security reasons',
)
.build(),
+
+ 463: new ErrorDetailsExtractorBuilder()
+ .setStatus(400)
+ .setStat({
+ [TAG_NAMES.ERROR_TYPE]: ERROR_TYPES.AUTH,
+ })
+ .setMessageField('message')
+ .build(),
default: new ErrorDetailsExtractorBuilder()
.setStatus(400)
+ .setStat({
+ [TAG_NAMES.ERROR_TYPE]: ERROR_TYPES.AUTH,
+ })
.setMessage('Invalid OAuth 2.0 access token')
.build(),
},
@@ -217,7 +237,7 @@ const getStatus = (error) => {
// Unhandled error response
return {
status: errorStatus,
- tags: { [tags.TAG_NAMES.META]: tags.METADATA.UNHANDLED_STATUS_CODE },
+ stats: { [TAG_NAMES.META]: METADATA.UNHANDLED_STATUS_CODE },
};
}
errorStatus = errorDetail.status;
@@ -227,7 +247,7 @@ const getStatus = (error) => {
errorMessage = get(error, errorDetail?.messageDetails?.field);
}
- return { status: errorStatus, errorMessage };
+ return { status: errorStatus, errorMessage, stats: errorDetail?.stat };
};
const errorResponseHandler = (destResponse) => {
@@ -237,19 +257,26 @@ const errorResponseHandler = (destResponse) => {
return;
}
const { error } = response;
- const { status, errorMessage, tags: errorStatTags } = getStatus(error);
+ const { status, errorMessage, stats: errorStatTags } = getStatus(error);
+ if (
+ isDefinedAndNotNull(errorStatTags) &&
+ errorStatTags?.[TAG_NAMES.ERROR_TYPE] === ERROR_TYPES.AUTH
+ ) {
+ throw new ConfigurationAuthError(errorMessage, { ...response, status: destResponse.status });
+ }
throw new NetworkError(
`${errorMessage || error.message || 'Unknown failure during response transformation'}`,
status,
{
...errorStatTags,
- [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
+ [TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status),
},
{ ...response, status: destResponse.status },
);
};
-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 c8d872e90e9..c153731e730 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 27eff2a7936..28e0fa9ac80 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 c7a26b6a2fb..32872cc5d9c 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');
@@ -33,6 +34,7 @@ const {
AUTH_STATUS_INACTIVE,
} = require('../../adapters/networkhandler/authConstants');
const { FEATURE_FILTER_CODE, FEATURE_GZIP_SUPPORT } = require('./constant');
+const { CommonUtils } = require('../../util/common');
// ========================================================================
// INLINERS
@@ -50,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
// ========================================================================
@@ -481,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
// ========================================================================
@@ -1279,6 +1272,31 @@ function getFullName(message) {
return fullName;
}
+/**
+ * Generates an exclusion list from mapping config.
+ *
+ * @param {Array} mappingConfig - The mapping config.
+ * [
+ * {
+ * "destKey": "item_code",
+ * "sourceKeys": [
+ * "product_id",
+ * "sku"
+ * ]
+ * },
+ * {
+ * "destKey": "name",
+ * "sourceKeys": "name"
+ * }
+ * ]
+ * @returns {Array} - The generated exclusion list.
+ * ["product_id", "sku", "name"]
+ */
+const generateExclusionList = (mappingConfig) =>
+ mappingConfig.flatMap((mapping) =>
+ Array.isArray(mapping.sourceKeys) ? [...mapping.sourceKeys] : [mapping.sourceKeys],
+ );
+
/**
* Extract fileds from message with exclusions
* Pass the keys of message for extraction and
@@ -1305,10 +1323,10 @@ function getFullName(message) {
* )
* -------------------------------------------
* The above call will map the fields other than the
- * exlusion list from the given keys to the destination payload
+ * exclusion list from the given keys to the destination payload
*
*/
-function extractCustomFields(message, destination, keys, exclusionFields) {
+function extractCustomFields(message, payload, keys, exclusionFields) {
const mappingKeys = [];
if (Array.isArray(keys)) {
keys.forEach((key) => {
@@ -1319,7 +1337,7 @@ function extractCustomFields(message, destination, keys, exclusionFields) {
});
mappingKeys.forEach((mappingKey) => {
if (!(typeof messageContext[mappingKey] === 'undefined')) {
- set(destination, mappingKey, get(messageContext, mappingKey));
+ set(payload, mappingKey, get(messageContext, mappingKey));
}
});
}
@@ -1330,14 +1348,14 @@ function extractCustomFields(message, destination, keys, exclusionFields) {
});
mappingKeys.forEach((mappingKey) => {
if (!(typeof message[mappingKey] === 'undefined')) {
- set(destination, mappingKey, get(message, mappingKey));
+ set(payload, mappingKey, get(message, mappingKey));
}
});
} else {
logger.debug('unable to parse keys');
}
- return destination;
+ return payload;
}
// Deleting nested properties from objects
@@ -1401,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,
@@ -1635,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 [];
@@ -1697,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 {
@@ -1729,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) {
@@ -2111,6 +2125,106 @@ const parseConfigArray = (arr, key) => {
return arr.map((item) => item[key]);
};
+/**
+ * Finds an existing batch based on metadata JobIds from the provided batch and metadataMap.
+ * @param {*} batch
+ * @param {*} metadataMap The map containing metadata items indexed by JobIds.
+ * @returns
+ */
+const findExistingBatch = (batch, metadataMap) => {
+ const existingMetadataItem = batch.metadata.find((metadataItem) =>
+ metadataMap.has(metadataItem.jobId),
+ );
+ return existingMetadataItem ? metadataMap.get(existingMetadataItem.jobId) : null;
+};
+
+/**
+ * Removes duplicate metadata within each merged batch object.
+ * @param {*} mergedBatches An array of merged batch objects.
+ */
+const removeDuplicateMetadata = (mergedBatches) => {
+ mergedBatches.forEach((batch) => {
+ const metadataSet = new Set();
+ // eslint-disable-next-line no-param-reassign
+ batch.metadata = batch.metadata.filter((metadataItem) => {
+ if (!metadataSet.has(metadataItem.jobId)) {
+ metadataSet.add(metadataItem.jobId);
+ return true;
+ }
+ return false;
+ });
+ });
+};
+
+/**
+ * Combines batched requests with the same JobIds.
+ * @param {*} inputBatches The array of batched request objects.
+ * @returns The combined batched requests with merged JobIds.
+ *
+ */
+const combineBatchRequestsWithSameJobIds = (inputBatches) => {
+ const combineBatches = (batches) => {
+ const clonedBatches = [...batches];
+ const mergedBatches = [];
+ const metadataMap = new Map();
+
+ clonedBatches.forEach((batch) => {
+ const existingBatch = findExistingBatch(batch, metadataMap);
+
+ if (existingBatch) {
+ // Merge batchedRequests arrays
+ existingBatch.batchedRequest = [
+ ...CommonUtils.toArray(existingBatch.batchedRequest),
+ ...CommonUtils.toArray(batch.batchedRequest),
+ ];
+
+ // Merge metadata
+ batch.metadata.forEach((metadataItem) => {
+ if (!metadataMap.has(metadataItem.jobId)) {
+ metadataMap.set(metadataItem.jobId, existingBatch);
+ }
+ existingBatch.metadata.push(metadataItem);
+ });
+ } else {
+ mergedBatches.push(batch);
+ batch.metadata.forEach((metadataItem) => {
+ metadataMap.set(metadataItem.jobId, batch);
+ });
+ }
+ });
+
+ // Remove duplicate metadata within each merged object
+ removeDuplicateMetadata(mergedBatches);
+
+ return mergedBatches;
+ };
+ // We need to run this twice because in first pass some batches might not get merged
+ // and in second pass they might get merged
+ // Example: [[{jobID:1}, {jobID:2}], [{jobID:3}], [{jobID:1}, {jobID:3}]]
+ // 1st pass: [[{jobID:1}, {jobID:2}, {jobID:3}], [{jobID:3}]]
+ // 2nd pass: [[{jobID:1}, {jobID:2}, {jobID:3}]]
+ 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
// ========================================================================
@@ -2132,6 +2246,7 @@ module.exports = {
defaultPutRequestConfig,
defaultRequestConfig,
deleteObjectProperty,
+ generateExclusionList,
extractCustomFields,
flattenJson,
flattenMap,
@@ -2146,7 +2261,6 @@ module.exports = {
getDestinationExternalIDInfoForRetl,
getDestinationExternalIDObjectForRetl,
getDeviceModel,
- getErrorRespEvents,
getEventTime,
getFieldValueFromMessage,
getFirstAndLastName,
@@ -2158,6 +2272,7 @@ module.exports = {
getMappingConfig,
getMetadata,
getTransformationMetadata,
+ getTrackingPlanMetadata,
getParsedIP,
getStringValueOfJSON,
getSuccessRespEvents,
@@ -2177,6 +2292,7 @@ module.exports = {
isDefinedAndNotNullAndNotEmpty,
isEmpty,
isNotEmpty,
+ isNull,
isEmptyObject,
isHttpStatusRetryable,
isHttpStatusSuccess,
@@ -2223,4 +2339,8 @@ module.exports = {
isNewStatusCodesAccepted,
IsGzipSupported,
parseConfigArray,
+ findExistingBatch,
+ removeDuplicateMetadata,
+ combineBatchRequestsWithSameJobIds,
+ validateEventAndLowerCaseConversion,
};
diff --git a/src/v0/util/index.test.js b/src/v0/util/index.test.js
index a46b55fdd4e..810eb5a9d48 100644
--- a/src/v0/util/index.test.js
+++ b/src/v0/util/index.test.js
@@ -1,8 +1,14 @@
-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');
-const { hasCircularReference, flattenJson } = require('./index');
+const {
+ hasCircularReference,
+ flattenJson,
+ generateExclusionList,
+ combineBatchRequestsWithSameJobIds,
+ validateEventAndLowerCaseConversion,
+} = require('./index');
// Names of the utility functions to test
const functionNames = [
@@ -31,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);
@@ -125,3 +130,379 @@ describe('tests for generateErrorObject', () => {
expect(outputErrObj.statTags).toEqual({});
});
});
+
+describe('generateExclusionList', () => {
+ it('should return an array of excluded keys when given a mapping config', () => {
+ const mappingConfig = [
+ {
+ destKey: 'item_code',
+ sourceKeys: ['product_id', 'sku'],
+ },
+ {
+ destKey: 'name',
+ sourceKeys: 'name',
+ },
+ ];
+ const expected = ['product_id', 'sku', 'name'];
+ const result = generateExclusionList(mappingConfig);
+ expect(result).toEqual(expected);
+ });
+
+ it('should return an empty array when the mapping config is empty', () => {
+ const mappingConfig = [];
+ const expected = [];
+ const result = generateExclusionList(mappingConfig);
+ expect(result).toEqual(expected);
+ });
+
+ it('should return an array with unique keys when the mapping config has duplicate destination keys', () => {
+ const mappingConfig = [
+ {
+ destKey: 'item_code',
+ sourceKeys: ['product_id'],
+ },
+ {
+ destKey: 'item_code',
+ sourceKeys: ['sku'],
+ },
+ ];
+ const expected = ['product_id', 'sku'];
+ const result = generateExclusionList(mappingConfig);
+ expect(result).toEqual(expected);
+ });
+});
+
+describe('Unit test cases for combineBatchRequestsWithSameJobIds', () => {
+ it('Combine batch request with same jobIds', async () => {
+ const input = [
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint1',
+ },
+ metadata: [
+ {
+ jobId: 1,
+ },
+ {
+ jobId: 4,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint2',
+ },
+ metadata: [
+ {
+ jobId: 3,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint1',
+ },
+ metadata: [
+ {
+ jobId: 5,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint3',
+ },
+ metadata: [
+ {
+ jobId: 1,
+ },
+ {
+ jobId: 3,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint2',
+ },
+ metadata: [
+ {
+ jobId: 6,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ ];
+
+ const expectedOutput = [
+ {
+ batchedRequest: [
+ {
+ endpoint: 'https://endpoint1',
+ },
+ {
+ endpoint: 'https://endpoint3',
+ },
+ {
+ endpoint: 'https://endpoint2',
+ },
+ ],
+ metadata: [
+ {
+ jobId: 1,
+ },
+ {
+ jobId: 4,
+ },
+ {
+ jobId: 3,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint1',
+ },
+ metadata: [
+ {
+ jobId: 5,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint2',
+ },
+ metadata: [
+ {
+ jobId: 6,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ ];
+ expect(combineBatchRequestsWithSameJobIds(input)).toEqual(expectedOutput);
+ });
+
+ it('Each batchRequest contains unique jobIds (no event multiplexing)', async () => {
+ const input = [
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint1',
+ },
+ metadata: [
+ {
+ jobId: 1,
+ },
+ {
+ jobId: 4,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint3',
+ },
+ metadata: [
+ {
+ jobId: 2,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint3',
+ },
+ metadata: [
+ {
+ jobId: 5,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ ];
+
+ const expectedOutput = [
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint1',
+ },
+
+ metadata: [
+ {
+ jobId: 1,
+ },
+ {
+ jobId: 4,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint3',
+ },
+ metadata: [
+ {
+ jobId: 2,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ {
+ batchedRequest: {
+ endpoint: 'https://endpoint3',
+ },
+ metadata: [
+ {
+ jobId: 5,
+ },
+ ],
+ batched: true,
+ statusCode: 200,
+ destination: {
+ Config: {
+ key: 'value',
+ },
+ },
+ },
+ ];
+ 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 81e6b9a2a65..dce8c0a338a 100644
--- a/src/v0/util/tags.js
+++ b/src/v0/util/tags.js
@@ -13,6 +13,7 @@ const TAG_NAMES = {
DESTINATION_ID: 'destinationId',
WORKSPACE_ID: 'workspaceId',
SOURCE_ID: 'sourceId',
+ STATUS: 'statusCode',
};
const MODULES = {
diff --git a/src/v1/destinations/algolia/networkHandler.js b/src/v1/destinations/algolia/networkHandler.js
new file mode 100644
index 00000000000..de25993fb1b
--- /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 431cbd69662..300b5f96766 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 3305a52762f..b3d1c5e4bc7 100644
--- a/src/warehouse/index.js
+++ b/src/warehouse/index.js
@@ -23,7 +23,7 @@ const whPageColumnMappingRules = require('./config/WHPageConfig.js');
const whScreenColumnMappingRules = require('./config/WHScreenConfig.js');
const whGroupColumnMappingRules = require('./config/WHGroupConfig.js');
const whAliasColumnMappingRules = require('./config/WHAliasConfig.js');
-const {isDataLakeProvider, isBlank} = require('./config/helpers');
+const { isDataLakeProvider, isBlank } = require('./config/helpers');
const { InstrumentationError } = require('@rudderstack/integrations-lib');
const whExtractEventTableColumnMappingRules = require('./config/WHExtractEventTableConfig.js');
diff --git a/src/warehouse/util.js b/src/warehouse/util.js
index 11d72bfbfd5..b4b22721fd3 100644
--- a/src/warehouse/util.js
+++ b/src/warehouse/util.js
@@ -4,7 +4,7 @@ const get = require('get-value');
const v0 = require('./v0/util');
const v1 = require('./v1/util');
const { PlatformError, InstrumentationError } = require('@rudderstack/integrations-lib');
-const {isBlank} = require('./config/helpers');
+const { isBlank } = require('./config/helpers');
const minTimeInMs = Date.parse('0001-01-01T00:00:00Z');
const maxTimeInMs = Date.parse('9999-12-31T23:59:59.999Z');
diff --git a/test/__mocks__/data/sources/shopify/response.json b/test/__mocks__/data/sources/shopify/response.json
index ead25067e63..4eef747b940 100644
--- a/test/__mocks__/data/sources/shopify/response.json
+++ b/test/__mocks__/data/sources/shopify/response.json
@@ -31,4 +31,4 @@
"shopify_test_set_redis_error": {
"itemsHash": "EMPTY"
}
-}
\ No newline at end of file
+}
diff --git a/test/__tests__/auth0_source.test.js b/test/__tests__/auth0_source.test.js
index c67db693e34..75d103d46fd 100644
--- a/test/__tests__/auth0_source.test.js
+++ b/test/__tests__/auth0_source.test.js
@@ -17,7 +17,7 @@ testData.forEach((data, index) => {
const output = transformer.process(data.input);
expect(output).toEqual(data.output);
} catch (error) {
- expect(error.message).toEqual(data.output.message);
+ expect(error.message).toEqual(data.output.error);
}
});
});
diff --git a/test/__tests__/data/auth0_source.json b/test/__tests__/data/auth0_source.json
index d47d7e0180a..e3190bd683c 100644
--- a/test/__tests__/data/auth0_source.json
+++ b/test/__tests__/data/auth0_source.json
@@ -1116,5 +1116,106 @@
"originalTimestamp": "2022-10-31T06:15:25.196Z"
}
]
+ },
+ {
+ "description": "Missing userId",
+ "input": {
+ "log_id": "90020221031055712103169676686005480714681762668315934738",
+ "data": {
+ "date": "2022-10-31T05:57:06.859Z",
+ "type": "ss",
+ "description": "",
+ "connection": "Username-Password-Authentication",
+ "connection_id": "con_djwCjiwyID0vZy1S",
+ "client_id": "vQcJNDTxsM1W72eHFonRJdzyOvawlwIt",
+ "client_name": "All Applications",
+ "ip": "35.166.202.113",
+ "user_agent": "unknown",
+ "details": {
+ "body": {
+ "email": "testRudderlabs+21@gmail.com",
+ "tenant": "dev-cu4jy2zgao6yx15x",
+ "password": "dummyPassword",
+ "client_id": "vQcJNDTxsM1W72eHFonRJdzyOvawlwIt",
+ "connection": "Username-Password-Authentication"
+ }
+ },
+ "user_id": "",
+ "user_name": "testRudderlabs+21@gmail.com",
+ "strategy": "auth0",
+ "strategy_type": "database",
+ "log_id": "90020221031055712103169676686005480714681762668315934738"
+ }
+ },
+ "output": {
+ "statusCode": 400,
+ "error": "UserId is not present",
+ "statTags": {
+ "errorCategory": "dataValidation",
+ "errorType": "instrumentation",
+ "module": "source",
+ "implementation": "native",
+ "destinationId": "Non determinable",
+ "workspaceId": "Non determinable"
+ }
+ }
+ },
+ {
+ "description": "UserId is missing for all the requests in a batch",
+ "input": [
+ {
+ "log_id": "90020221031055712103169676686005480714681762668315934738",
+ "data": {
+ "date": "2022-10-31T05:57:06.859Z",
+ "type": "ss",
+ "description": "",
+ "connection": "Username-Password-Authentication",
+ "connection_id": "con_djwCjiwyID0vZy1S",
+ "client_id": "vQcJNDTxsM1W72eHFonRJdzyOvawlwIt",
+ "client_name": "All Applications",
+ "ip": "35.166.202.113",
+ "user_agent": "unknown",
+ "details": {
+ "body": {
+ "email": "testRudderlabs+21@gmail.com",
+ "tenant": "dev-cu4jy2zgao6yx15x",
+ "password": "dummyPassword",
+ "client_id": "vQcJNDTxsM1W72eHFonRJdzyOvawlwIt",
+ "connection": "Username-Password-Authentication"
+ }
+ },
+ "user_id": "",
+ "user_name": "testRudderlabs+21@gmail.com",
+ "strategy": "auth0",
+ "strategy_type": "database",
+ "log_id": "90020221031055712103169676686005480714681762668315934738"
+ }
+ },
+ {
+ "log_id": "90020221031055712103169676686007898566320991926665347090",
+ "data": {
+ "date": "2022-10-31T05:57:06.874Z",
+ "type": "sapi",
+ "description": "Create a User",
+ "client_id": "vQcJNDTxsM1W72eHFonRJdzyOvawlwIt",
+ "client_name": "",
+ "ip": "35.166.202.113",
+ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
+ "log_id": "90020221031055712103169676686007898566320991926665347090"
+ }
+ }
+ ],
+ "output": {
+ "statusCode": 400,
+ "error": "UserId is not present",
+ "statTags": {
+ "errorCategory": "dataValidation",
+ "errorType": "instrumentation",
+ "module": "source",
+ "implementation": "native",
+ "destinationId": "Non determinable",
+ "workspaceId": "Non determinable"
+ }
+ }
}
]
diff --git a/test/__tests__/data/customerio_source_input.json b/test/__tests__/data/customerio_source_input.json
index 5b825d6a00d..769c1b7fd3c 100644
--- a/test/__tests__/data/customerio_source_input.json
+++ b/test/__tests__/data/customerio_source_input.json
@@ -111,9 +111,7 @@
"customer_id": "user-123",
"delivery_id": "RAECAAFwnUSneIa0ZXkmq8EdkAM==",
"headers": {
- "Custom-Header": [
- "custom-value"
- ]
+ "Custom-Header": ["custom-value"]
},
"identifiers": {
"id": "user-123"
@@ -389,4 +387,4 @@
"metric": "delivered",
"timestamp": 1585751830
}
-]
\ No newline at end of file
+]
diff --git a/test/__tests__/data/customerio_source_output.json b/test/__tests__/data/customerio_source_output.json
index 24b964d01b4..52df88e833f 100644
--- a/test/__tests__/data/customerio_source_output.json
+++ b/test/__tests__/data/customerio_source_output.json
@@ -648,4 +648,4 @@
"originalTimestamp": "2020-04-01T14:37:10.000Z",
"sentAt": "2020-04-01T14:37:10.000Z"
}
-]
\ No newline at end of file
+]
diff --git a/test/__tests__/data/formsort_source.json b/test/__tests__/data/formsort_source.json
index a12d84a98a3..d94cfd677bd 100644
--- a/test/__tests__/data/formsort_source.json
+++ b/test/__tests__/data/formsort_source.json
@@ -1,94 +1,94 @@
[
- {
- "description": "when we receive finalized as false",
- "input": {
- "answers": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "flow_label": "new-flow-2022-11-25",
- "variant_label": "main",
- "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea",
- "finalized": false,
- "created_at": "2022-11-25T14:41:22+00:00"
+ {
+ "description": "when we receive finalized as false",
+ "input": {
+ "answers": {
+ "yes": true,
+ "enter_email": "test@user.com",
+ "enter_name": "2022-11-17",
+ "yes_or_no": false
+ },
+ "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
+ "flow_label": "new-flow-2022-11-25",
+ "variant_label": "main",
+ "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea",
+ "finalized": false,
+ "created_at": "2022-11-25T14:41:22+00:00"
+ },
+ "output": {
+ "context": {
+ "library": {
+ "name": "unknown",
+ "version": "unknown"
+ },
+ "integration": {
+ "name": "Formsort"
+ },
+ "page": {
+ "title": "new-flow-2022-11-25"
},
- "output": {
- "context": {
- "library": {
- "name": "unknown",
- "version": "unknown"
- },
- "integration": {
- "name": "Formsort"
- },
- "page": {
- "title": "new-flow-2022-11-25"
- },
- "variantLabel": "main",
- "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea"
- },
- "integrations": {
- "Formsort": false
- },
- "type": "track",
- "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "originalTimestamp": "2022-11-25T14:41:22+00:00",
- "properties": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "event": "FlowLoaded"
- }
+ "variantLabel": "main",
+ "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea"
+ },
+ "integrations": {
+ "Formsort": false
+ },
+ "type": "track",
+ "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
+ "originalTimestamp": "2022-11-25T14:41:22+00:00",
+ "properties": {
+ "yes": true,
+ "enter_email": "test@user.com",
+ "enter_name": "2022-11-17",
+ "yes_or_no": false
+ },
+ "event": "FlowLoaded"
+ }
+ },
+ {
+ "description": "when we receive finalized as true",
+ "input": {
+ "answers": {
+ "yes": true,
+ "enter_email": "test@user.com",
+ "enter_name": "2022-11-17",
+ "yes_or_no": false
+ },
+ "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
+ "flow_label": "new-flow-2022-11-25",
+ "variant_label": "main",
+ "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea",
+ "finalized": true,
+ "created_at": "2022-11-25T14:41:22+00:00"
},
- {
- "description": "when we receive finalized as true",
- "input": {
- "answers": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "responder_uuid": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "flow_label": "new-flow-2022-11-25",
- "variant_label": "main",
- "variant_uuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea",
- "finalized": true,
- "created_at": "2022-11-25T14:41:22+00:00"
+ "output": {
+ "context": {
+ "library": {
+ "name": "unknown",
+ "version": "unknown"
+ },
+ "integration": {
+ "name": "Formsort"
+ },
+ "page": {
+ "title": "new-flow-2022-11-25"
},
- "output": {
- "context": {
- "library": {
- "name": "unknown",
- "version": "unknown"
- },
- "integration": {
- "name": "Formsort"
- },
- "page": {
- "title": "new-flow-2022-11-25"
- },
- "variantLabel": "main",
- "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea"
- },
- "integrations": {
- "Formsort": false
- },
- "type": "track",
- "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
- "originalTimestamp": "2022-11-25T14:41:22+00:00",
- "properties": {
- "yes": true,
- "enter_email": "test@user.com",
- "enter_name": "2022-11-17",
- "yes_or_no": false
- },
- "event": "FlowFinalized"
- }
+ "variantLabel": "main",
+ "variantUuid": "0828efa7-7215-4e7d-a7ab-6c1079010cea"
+ },
+ "integrations": {
+ "Formsort": false
+ },
+ "type": "track",
+ "userId": "66a8e5bb-67e1-47ec-b55f-a26fd4be2dc7",
+ "originalTimestamp": "2022-11-25T14:41:22+00:00",
+ "properties": {
+ "yes": true,
+ "enter_email": "test@user.com",
+ "enter_name": "2022-11-17",
+ "yes_or_no": false
+ },
+ "event": "FlowFinalized"
}
-]
\ No newline at end of file
+ }
+]
diff --git a/test/__tests__/data/proxy_input.json b/test/__tests__/data/proxy_input.json
index a647238f9f8..0d7ff24ab7d 100644
--- a/test/__tests__/data/proxy_input.json
+++ b/test/__tests__/data/proxy_input.json
@@ -263,4 +263,4 @@
"destination": "any"
}
}
-]
\ No newline at end of file
+]
diff --git a/test/__tests__/data/shopify.json b/test/__tests__/data/shopify.json
index 0153df4d26a..48ca8c75a01 100644
--- a/test/__tests__/data/shopify.json
+++ b/test/__tests__/data/shopify.json
@@ -4,9 +4,7 @@
"input": {
"id": "shopify_test3",
"query_parameters": {
- "topic": [
- "carts_create"
- ]
+ "topic": ["carts_create"]
},
"token": "shopify_test3",
"line_items": [],
@@ -33,12 +31,8 @@
"description": "Invalid topic",
"input": {
"query_parameters": {
- "signature": [
- "rudderstack"
- ],
- "writeKey": [
- "sample-write-key"
- ]
+ "signature": ["rudderstack"],
+ "writeKey": ["sample-write-key"]
}
},
"output": {
@@ -50,12 +44,8 @@
"input": {
"query_parameters": {
"topic": [],
- "signature": [
- "rudderstack"
- ],
- "writeKey": [
- "sample-write-key"
- ]
+ "signature": ["rudderstack"],
+ "writeKey": ["sample-write-key"]
}
},
"output": {
@@ -66,15 +56,9 @@
"description": "Unsupported Event Type",
"input": {
"query_parameters": {
- "topic": [
- "random_event"
- ],
- "signature": [
- "rudderstack"
- ],
- "writeKey": [
- "sample-write-key"
- ]
+ "topic": ["random_event"],
+ "signature": ["rudderstack"],
+ "writeKey": ["sample-write-key"]
}
},
"output": {
@@ -89,15 +73,9 @@
"description": "Identify Call for customers create event",
"input": {
"query_parameters": {
- "topic": [
- "customers_create"
- ],
- "signature": [
- "rudderstack"
- ],
- "writeKey": [
- "sample-write-key"
- ]
+ "topic": ["customers_create"],
+ "signature": ["rudderstack"],
+ "writeKey": ["sample-write-key"]
},
"id": 5747017285820,
"email": "anuraj@rudderstack.com",
@@ -256,15 +234,9 @@
"description": "Unsupported checkout event",
"input": {
"query_parameters": {
- "topic": [
- "checkout_delete"
- ],
- "writeKey": [
- "sample-write-key"
- ],
- "signature": [
- "rudderstack"
- ]
+ "topic": ["checkout_delete"],
+ "writeKey": ["sample-write-key"],
+ "signature": ["rudderstack"]
},
"admin_graphql_api_id": "gid://shopify/Fulfillment/4124667937024",
"created_at": "2022-01-05T18:13:02+05:30",
@@ -292,13 +264,9 @@
"status": "success",
"tracking_company": "Amazon Logistics UK",
"tracking_number": "Sample001test",
- "tracking_numbers": [
- "Sample001test"
- ],
+ "tracking_numbers": ["Sample001test"],
"tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530",
- "tracking_urls": [
- "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"
- ],
+ "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"],
"updated_at": "2022-01-05T18:16:48+05:30"
},
"output": {
@@ -313,15 +281,9 @@
"description": "Track Call -> Fullfillments updated event",
"input": {
"query_parameters": {
- "topic": [
- "fulfillments_update"
- ],
- "writeKey": [
- "sample-write-key"
- ],
- "signature": [
- "rudderstack"
- ]
+ "topic": ["fulfillments_update"],
+ "writeKey": ["sample-write-key"],
+ "signature": ["rudderstack"]
},
"shipping_address": {
"address1": "11 Rani Sankari Lane Patuapara Bhowanipore"
@@ -420,13 +382,9 @@
"status": "success",
"tracking_company": "Amazon Logistics UK",
"tracking_number": "Sample001test",
- "tracking_numbers": [
- "Sample001test"
- ],
+ "tracking_numbers": ["Sample001test"],
"tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530",
- "tracking_urls": [
- "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"
- ],
+ "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"],
"updated_at": "2022-01-05T18:16:48+05:30"
},
"output": {
@@ -462,9 +420,7 @@
"status": "success",
"tracking_company": "Amazon Logistics UK",
"tracking_number": "Sample001test",
- "tracking_numbers": [
- "Sample001test"
- ],
+ "tracking_numbers": ["Sample001test"],
"tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530",
"tracking_urls": [
"https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"
@@ -556,4 +512,4 @@
}
}
}
-]
\ No newline at end of file
+]
diff --git a/test/apitests/data_scenarios/destination/batch/failure_batch.json b/test/apitests/data_scenarios/destination/batch/failure_batch.json
index 8063bc74a12..6352ca1a11f 100644
--- a/test/apitests/data_scenarios/destination/batch/failure_batch.json
+++ b/test/apitests/data_scenarios/destination/batch/failure_batch.json
@@ -1051,125 +1051,314 @@
},
"output": [
{
- "metadata": {
- "userId": "<<>>testUser<<>>testUser",
- "jobId": 2,
- "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC",
- "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR",
- "attemptNum": 0,
- "receivedAt": "2022-12-24T17:29:00.699+05:30",
- "createdAt": "2022-12-24T11:59:03.125Z",
- "firstAttemptedAt": "",
- "transformAt": "processor",
- "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg",
- "secret": null,
- "jobsT": {
- "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c",
- "JobID": 2,
- "UserID": "<<>>testUser<<>>testUser",
- "CreatedAt": "2022-12-24T11:59:03.125515Z",
- "ExpireAt": "2022-12-24T11:59:03.125515Z",
- "CustomVal": "AM",
- "EventCount": 1,
- "EventPayload": {
- "body": {
- "XML": {},
- "FORM": {},
- "JSON": {
- "events": [
- {
- "ip": "[::1]",
- "time": 1671883143047,
- "library": "rudderstack",
- "user_id": "testUser",
- "device_id": "anon-id",
- "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9",
- "event_type": "Product Added",
- "session_id": -1,
- "user_properties": {
- "email": "test.c97@gmail.com",
- "phone": "+919876543210",
- "gender": "Male",
- "lastName": "Rudderlabs",
- "firstName": "test"
- },
- "event_properties": {
- "sku": "F15",
- "url": "https://www.website.com/product/path",
- "name": "Game",
- "brand": "Gamepro",
- "price": 13.49,
- "coupon": "DISC21",
- "variant": "111",
- "category": "Games",
- "position": 1,
- "quantity": 11,
- "image_url": "https://www.website.com/product/path.png",
- "product_id": "123"
+ "metadata": [
+ {
+ "userId": "<<>>testUser<<>>testUser",
+ "jobId": 2,
+ "sourceId": "27O0bmEEx3GgfmEhZHUcPwJQVWC",
+ "destinationId": "2JK3ACpBjq9AmvUbxR1u2pDPSYR",
+ "attemptNum": 0,
+ "receivedAt": "2022-12-24T17:29:00.699+05:30",
+ "createdAt": "2022-12-24T11:59:03.125Z",
+ "firstAttemptedAt": "",
+ "transformAt": "processor",
+ "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg",
+ "secret": null,
+ "jobsT": {
+ "UUID": "aaa8b7c4-2600-478b-b275-01740e1ef50c",
+ "JobID": 2,
+ "UserID": "<<>>testUser<<>>testUser",
+ "CreatedAt": "2022-12-24T11:59:03.125515Z",
+ "ExpireAt": "2022-12-24T11:59:03.125515Z",
+ "CustomVal": "AM",
+ "EventCount": 1,
+ "EventPayload": {
+ "body": {
+ "XML": {},
+ "FORM": {},
+ "JSON": {
+ "events": [
+ {
+ "ip": "[::1]",
+ "time": 1671883143047,
+ "library": "rudderstack",
+ "user_id": "testUser",
+ "device_id": "anon-id",
+ "insert_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9",
+ "event_type": "Product Added",
+ "session_id": -1,
+ "user_properties": {
+ "email": "test.c97@gmail.com",
+ "phone": "+919876543210",
+ "gender": "Male",
+ "lastName": "Rudderlabs",
+ "firstName": "test"
+ },
+ "event_properties": {
+ "sku": "F15",
+ "url": "https://www.website.com/product/path",
+ "name": "Game",
+ "brand": "Gamepro",
+ "price": 13.49,
+ "coupon": "DISC21",
+ "variant": "111",
+ "category": "Games",
+ "position": 1,
+ "quantity": 11,
+ "image_url": "https://www.website.com/product/path.png",
+ "product_id": "123"
+ }
}
+ ],
+ "api_key": "dummyApiKey",
+ "options": {
+ "min_id_length": 1
}
- ],
- "api_key": "dummyApiKey",
- "options": {
- "min_id_length": 1
- }
+ },
+ "JSON_ARRAY": {}
},
- "JSON_ARRAY": {}
+ "type": "REST",
+ "files": {},
+ "method": "POST",
+ "params": {},
+ "userId": "anon-id",
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "version": "1",
+ "endpoint": "https://api2.amplitude.com/2/httpapi"
},
- "type": "REST",
- "files": {},
- "method": "POST",
- "params": {},
- "userId": "anon-id",
- "headers": {
- "Content-Type": "application/json"
+ "PayloadSize": 1133,
+ "LastJobStatus": {
+ "JobID": 0,
+ "JobState": "",
+ "AttemptNum": 0,
+ "ExecTime": "0001-01-01T00:00:00Z",
+ "RetryTime": "0001-01-01T00:00:00Z",
+ "ErrorCode": "",
+ "ErrorResponse": null,
+ "Parameters": null,
+ "WorkspaceId": ""
},
- "version": "1",
- "endpoint": "https://api2.amplitude.com/2/httpapi"
- },
- "PayloadSize": 1133,
- "LastJobStatus": {
- "JobID": 0,
- "JobState": "",
- "AttemptNum": 0,
- "ExecTime": "0001-01-01T00:00:00Z",
- "RetryTime": "0001-01-01T00:00:00Z",
- "ErrorCode": "",
- "ErrorResponse": null,
- "Parameters": null,
- "WorkspaceId": ""
- },
- "Parameters": {
- "record_id": null,
- "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC",
- "event_name": "Product Added",
- "event_type": "track",
- "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9",
- "received_at": "2022-12-24T17:29:00.699+05:30",
- "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg",
- "transform_at": "processor",
- "source_job_id": "",
- "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR",
- "gateway_job_id": 1,
- "source_task_id": "",
- "source_batch_id": "",
- "source_category": "",
- "source_job_run_id": "",
- "source_task_run_id": "",
- "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591",
- "destination_definition_id": ""
+ "Parameters": {
+ "record_id": null,
+ "source_id": "27O0bmEEx3GgfmEhZHUcPwJQVWC",
+ "event_name": "Product Added",
+ "event_type": "track",
+ "message_id": "14642496-9a12-4db7-b0f2-9a336cf6cea9",
+ "received_at": "2022-12-24T17:29:00.699+05:30",
+ "workspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg",
+ "transform_at": "processor",
+ "source_job_id": "",
+ "destination_id": "2JK3ACpBjq9AmvUbxR1u2pDPSYR",
+ "gateway_job_id": 1,
+ "source_task_id": "",
+ "source_batch_id": "",
+ "source_category": "",
+ "source_job_run_id": "",
+ "source_task_run_id": "",
+ "source_definition_id": "1b6gJdqOPOCadT3cddw8eidV591",
+ "destination_definition_id": ""
+ },
+ "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg"
},
- "WorkspaceId": "27O0bhB6p5ehfOWeeZlOSsSDTLg"
- },
- "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30"
- },
+ "workerAssignedTime": "2022-12-24T17:29:04.051596+05:30"
+ }
+ ],
"batched": false,
"statusCode": 400,
"statTags": {
"errorCategory": "dataValidation",
"errorType": "instrumentation"
},
- "error": "Both userId and deviceId cannot be undefined"
+ "error": "Both userId and deviceId cannot be undefined",
+ "destination": {
+ "ID": "2JK3ACpBjq9AmvUbxR1u2pDPSYR",
+ "Name": "Amplitude-2",
+ "DestinationDefinition": {
+ "ID": "1QGzO4fWSyq3lsyFHf4eQAMDSr9",
+ "Name": "AM",
+ "DisplayName": "Amplitude",
+ "Config": {
+ "destConfig": {
+ "android": [
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "useNativeSDK",
+ "enableLocationListening",
+ "trackSessionEvents",
+ "useAdvertisingIdForDeviceId"
+ ],
+ "defaultConfig": [
+ "apiKey",
+ "groupTypeTrait",
+ "groupValueTrait",
+ "trackAllPages",
+ "trackCategorizedPages",
+ "trackNamedPages",
+ "traitsToIncrement",
+ "traitsToSetOnce",
+ "traitsToAppend",
+ "traitsToPrepend",
+ "trackProductsOnce",
+ "trackRevenuePerProduct",
+ "versionName",
+ "apiSecret",
+ "residencyServer",
+ "blacklistedEvents",
+ "whitelistedEvents",
+ "eventFilteringOption",
+ "mapDeviceBrand"
+ ],
+ "flutter": [
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "useNativeSDK",
+ "enableLocationListening",
+ "trackSessionEvents",
+ "useAdvertisingIdForDeviceId",
+ "useIdfaAsDeviceId"
+ ],
+ "ios": [
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "useNativeSDK",
+ "trackSessionEvents",
+ "useIdfaAsDeviceId"
+ ],
+ "reactnative": [
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "useNativeSDK",
+ "enableLocationListening",
+ "trackSessionEvents",
+ "useAdvertisingIdForDeviceId",
+ "useIdfaAsDeviceId"
+ ],
+ "web": [
+ "useNativeSDK",
+ "preferAnonymousIdForDeviceId",
+ "deviceIdFromUrlParam",
+ "forceHttps",
+ "trackGclid",
+ "trackReferrer",
+ "saveParamsReferrerOncePerSession",
+ "trackUtmProperties",
+ "unsetParamsReferrerOnNewSession",
+ "batchEvents",
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "oneTrustCookieCategories"
+ ]
+ },
+ "excludeKeys": [],
+ "includeKeys": [
+ "apiKey",
+ "groupTypeTrait",
+ "groupValueTrait",
+ "trackAllPages",
+ "trackCategorizedPages",
+ "trackNamedPages",
+ "traitsToIncrement",
+ "traitsToSetOnce",
+ "traitsToAppend",
+ "traitsToPrepend",
+ "trackProductsOnce",
+ "trackRevenuePerProduct",
+ "preferAnonymousIdForDeviceId",
+ "deviceIdFromUrlParam",
+ "forceHttps",
+ "trackGclid",
+ "trackReferrer",
+ "saveParamsReferrerOncePerSession",
+ "trackUtmProperties",
+ "unsetParamsReferrerOnNewSession",
+ "batchEvents",
+ "eventUploadPeriodMillis",
+ "eventUploadThreshold",
+ "versionName",
+ "enableLocationListening",
+ "useAdvertisingIdForDeviceId",
+ "trackSessionEvents",
+ "useIdfaAsDeviceId",
+ "blacklistedEvents",
+ "whitelistedEvents",
+ "oneTrustCookieCategories",
+ "eventFilteringOption",
+ "mapDeviceBrand"
+ ],
+ "saveDestinationResponse": true,
+ "secretKeys": ["apiKey", "apiSecret"],
+ "supportedMessageTypes": ["alias", "group", "identify", "page", "screen", "track"],
+ "supportedSourceTypes": [
+ "android",
+ "ios",
+ "web",
+ "unity",
+ "amp",
+ "cloud",
+ "warehouse",
+ "reactnative",
+ "flutter",
+ "cordova"
+ ],
+ "supportsVisualMapper": true,
+ "transformAt": "processor",
+ "transformAtV1": "processor"
+ },
+ "ResponseRules": null
+ },
+ "Config": {
+ "apiKey": "dummyApiKey",
+ "apiSecret": "",
+ "blacklistedEvents": [
+ {
+ "eventName": ""
+ }
+ ],
+ "eventFilteringOption": "disable",
+ "groupTypeTrait": "",
+ "groupValueTrait": "",
+ "mapDeviceBrand": false,
+ "residencyServer": "standard",
+ "trackAllPages": false,
+ "trackCategorizedPages": true,
+ "trackNamedPages": true,
+ "trackProductsOnce": false,
+ "trackRevenuePerProduct": false,
+ "traitsToAppend": [
+ {
+ "traits": ""
+ }
+ ],
+ "traitsToIncrement": [
+ {
+ "traits": ""
+ }
+ ],
+ "traitsToPrepend": [
+ {
+ "traits": ""
+ }
+ ],
+ "traitsToSetOnce": [
+ {
+ "traits": ""
+ }
+ ],
+ "versionName": "",
+ "whitelistedEvents": [
+ {
+ "eventName": ""
+ }
+ ]
+ },
+ "Enabled": true,
+ "WorkspaceID": "27O0bhB6p5ehfOWeeZlOSsSDTLg",
+ "Transformations": [],
+ "IsProcessorEnabled": true,
+ "RevisionID": "2JMKUgZX3b8sbtDSZrkUB7okeOY"
+ }
},
{
"batchedRequest": {
diff --git a/test/apitests/service.api.test.ts b/test/apitests/service.api.test.ts
index cbc2abb3b25..266619b6ac2 100644
--- a/test/apitests/service.api.test.ts
+++ b/test/apitests/service.api.test.ts
@@ -6,6 +6,8 @@ import Koa from 'koa';
import bodyParser from 'koa-bodyparser';
import setValue from 'set-value';
import { applicationRoutes } from '../../src/routes';
+import { FetchHandler } from '../../src/helpers/fetchHandlers';
+import networkHandlerFactory from '../../src/adapters/networkHandlerFactory';
let server: any;
const OLD_ENV = process.env;
@@ -30,6 +32,10 @@ afterAll(async () => {
await httpTerminator.terminate();
});
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
const getDataFromPath = (pathInput) => {
const testDataFile = fs.readFileSync(path.resolve(__dirname, pathInput));
return JSON.parse(testDataFile.toString());
@@ -76,6 +82,332 @@ describe('features tests', () => {
});
});
+describe('Api tests with a mock source/destination', () => {
+ test('(mock destination) Processor transformation scenario with single event', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+
+ const getInputData = () => {
+ return [
+ { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } },
+ { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } },
+ ];
+ };
+ const tevent = { version: 'v0', endpoint: 'http://abc' };
+
+ const getDestHandlerSpy = jest
+ .spyOn(FetchHandler, 'getDestHandler')
+ .mockImplementationOnce((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ process: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const expected = [
+ {
+ output: { version: 'v0', endpoint: 'http://abc', userId: '' },
+ metadata: { jobId: 1 },
+ statusCode: 200,
+ },
+ {
+ output: { version: 'v0', endpoint: 'http://abc', userId: '' },
+ metadata: { jobId: 2 },
+ statusCode: 200,
+ },
+ ];
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getInputData());
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual(expected);
+ expect(getDestHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock destination) Batching', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+
+ const getBatchInputData = () => {
+ return {
+ input: [
+ { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } },
+ { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } },
+ ],
+ destType: destType,
+ };
+ };
+ const tevent = [
+ {
+ batchedRequest: { version: 'v0', endpoint: 'http://abc' },
+ metadata: [{ jobId: 1 }, { jobId: 2 }],
+ statusCode: 200,
+ },
+ ];
+
+ const getDestHandlerSpy = jest
+ .spyOn(FetchHandler, 'getDestHandler')
+ .mockImplementationOnce((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ batch: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const response = await request(server)
+ .post('/batch')
+ .set('Accept', 'application/json')
+ .send(getBatchInputData());
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual(tevent);
+ expect(getDestHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock destination) Router transformation', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+
+ const getRouterTransformInputData = () => {
+ return {
+ input: [
+ { message: { a: 'b1' }, destination: {}, metadata: { jobId: 1 } },
+ { message: { a: 'b2' }, destination: {}, metadata: { jobId: 2 } },
+ ],
+ destType: destType,
+ };
+ };
+ const tevent = [
+ {
+ batchedRequest: { version: 'v0', endpoint: 'http://abc' },
+ metadata: [{ jobId: 1 }, { jobId: 2 }],
+ statusCode: 200,
+ },
+ ];
+
+ const getDestHandlerSpy = jest
+ .spyOn(FetchHandler, 'getDestHandler')
+ .mockImplementationOnce((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ processRouterDest: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const response = await request(server)
+ .post('/routerTransform')
+ .set('Accept', 'application/json')
+ .send(getRouterTransformInputData());
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual({ output: tevent });
+ expect(getDestHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock destination) v0 proxy', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v0';
+
+ const getData = () => {
+ return {
+ body: { JSON: { a: 'b' } },
+ metadata: { a1: 'b1' },
+ destinationConfig: { a2: 'b2' },
+ };
+ };
+
+ const proxyResponse = { success: true, response: { response: 'response', code: 200 } };
+
+ const mockNetworkHandler = {
+ proxy: jest.fn((r, d) => {
+ expect(r).toEqual(getData());
+ expect(d).toEqual(destType);
+ return proxyResponse;
+ }),
+ processAxiosResponse: jest.fn((r) => {
+ expect(r).toEqual(proxyResponse);
+ return { response: 'response', status: 200 };
+ }),
+ responseHandler: jest.fn((o, d) => {
+ expect(o.destinationResponse).toEqual({ response: 'response', status: 200 });
+ expect(o.rudderJobMetadata).toEqual({ a1: 'b1' });
+ expect(o.destType).toEqual(destType);
+ return { status: 200, message: 'response', destinationResponse: 'response' };
+ }),
+ };
+
+ const getNetworkHandlerSpy = jest
+ .spyOn(networkHandlerFactory, 'getNetworkHandler')
+ .mockImplementationOnce((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ networkHandler: mockNetworkHandler,
+ handlerVersion: version,
+ };
+ });
+
+ const response = await request(server)
+ .post('/v0/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual({
+ output: { status: 200, message: 'response', destinationResponse: 'response' },
+ });
+ expect(getNetworkHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock destination) v1 proxy', async () => {
+ const destType = '__rudder_test__';
+ const version = 'v1';
+
+ const getData = () => {
+ return {
+ body: { JSON: { a: 'b' } },
+ metadata: [{ a1: 'b1' }],
+ destinationConfig: { a2: 'b2' },
+ };
+ };
+
+ const proxyResponse = { success: true, response: { response: 'response', code: 200 } };
+ const respHandlerResponse = {
+ status: 200,
+ message: 'response',
+ destinationResponse: 'response',
+ response: [{ statusCode: 200, metadata: { a1: 'b1' } }],
+ };
+
+ const mockNetworkHandler = {
+ proxy: jest.fn((r, d) => {
+ expect(r).toEqual(getData());
+ expect(d).toEqual(destType);
+ return proxyResponse;
+ }),
+ processAxiosResponse: jest.fn((r) => {
+ expect(r).toEqual(proxyResponse);
+ return { response: 'response', status: 200 };
+ }),
+ responseHandler: jest.fn((o, d) => {
+ expect(o.destinationResponse).toEqual({ response: 'response', status: 200 });
+ expect(o.rudderJobMetadata).toEqual([{ a1: 'b1' }]);
+ expect(o.destType).toEqual(destType);
+ return respHandlerResponse;
+ }),
+ };
+
+ const getNetworkHandlerSpy = jest
+ .spyOn(networkHandlerFactory, 'getNetworkHandler')
+ .mockImplementationOnce((d, v) => {
+ expect(d).toEqual(destType);
+ expect(v).toEqual(version);
+ return {
+ networkHandler: mockNetworkHandler,
+ handlerVersion: version,
+ };
+ });
+
+ const response = await request(server)
+ .post('/v1/destinations/__rudder_test__/proxy')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual({
+ output: respHandlerResponse,
+ });
+ expect(getNetworkHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock source) v0 source transformation', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v0';
+
+ const getData = () => {
+ return [{ event: { a: 'b1' } }, { event: { a: 'b2' } }];
+ };
+
+ const tevent = { event: 'clicked', type: 'track' };
+
+ const getSourceHandlerSpy = jest
+ .spyOn(FetchHandler, 'getSourceHandler')
+ .mockImplementationOnce((s, v) => {
+ expect(s).toEqual(sourceType);
+ return {
+ process: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const response = await request(server)
+ .post('/v0/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expected = [
+ { output: { batch: [{ event: 'clicked', type: 'track' }] } },
+ { output: { batch: [{ event: 'clicked', type: 'track' }] } },
+ ];
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual(expected);
+ expect(getSourceHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test('(mock source) v1 source transformation', async () => {
+ const sourceType = '__rudder_test__';
+ const version = 'v1';
+
+ const getData = () => {
+ return [
+ { event: { a: 'b1' }, source: { id: 'id' } },
+ { event: { a: 'b2' }, source: { id: 'id' } },
+ ];
+ };
+
+ const tevent = { event: 'clicked', type: 'track' };
+
+ const getSourceHandlerSpy = jest
+ .spyOn(FetchHandler, 'getSourceHandler')
+ .mockImplementationOnce((s, v) => {
+ expect(s).toEqual(sourceType);
+ return {
+ process: jest.fn(() => {
+ return tevent;
+ }),
+ };
+ });
+
+ const response = await request(server)
+ .post('/v1/sources/__rudder_test__')
+ .set('Accept', 'application/json')
+ .send(getData());
+
+ const expected = [
+ { output: { batch: [{ event: 'clicked', type: 'track' }] } },
+ { output: { batch: [{ event: 'clicked', type: 'track' }] } },
+ ];
+
+ expect(response.status).toEqual(200);
+ expect(JSON.parse(response.text)).toEqual(expected);
+ expect(getSourceHandlerSpy).toHaveBeenCalledTimes(1);
+ });
+});
+
describe('Destination api tests', () => {
describe('Processor transform tests', () => {
test('(webhook) success scenario with single event', async () => {
@@ -183,6 +515,7 @@ describe('Destination api tests', () => {
expect(response.status).toEqual(200);
expect(JSON.parse(response.text)).toEqual(data.output);
});
+
test('(pinterest_tag) failure router transform(partial failure)', async () => {
const data = getDataFromPath('./data_scenarios/destination/router/failure_test.json');
const response = await request(server)
diff --git a/test/integrations/common/criteo/network.ts b/test/integrations/common/criteo/network.ts
new file mode 100644
index 00000000000..cd5e1ca1e81
--- /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 00000000000..95b76f8da81
--- /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 00000000000..a6ab202a4ec
--- /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 ec4fb02dc19..388c283c615 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 2c208d0d084..e28a25cf59d 100644
--- a/test/integrations/destinations/adj/processor/data.ts
+++ b/test/integrations/destinations/adj/processor/data.ts
@@ -2179,7 +2179,8 @@ export const data = [
status: 200,
body: [
{
- error: 'App Token is not present. Please configure your app token from config dashbaord',
+ error:
+ 'App Token is not present. Please configure your app token from config dashbaord',
statTags: {
destType: 'ADJ',
errorCategory: 'dataValidation',
@@ -2205,24 +2206,24 @@ export const data = [
body: [
{
message: {
- "type": "track",
- "event": "Application Installed",
- "sentAt": "2022-09-28T20:14:44.995Z",
- "userId": "sample_user_id",
- "context": {
- "device": {
- "id": "sample_device_id",
- "type": "android",
- "advertisingId": "_sample"
+ type: 'track',
+ event: 'Application Installed',
+ sentAt: '2022-09-28T20:14:44.995Z',
+ userId: 'sample_user_id',
+ context: {
+ device: {
+ id: 'sample_device_id',
+ type: 'android',
+ advertisingId: '_sample',
+ },
+ traits: {
+ userId: '_sample_uid',
+ anonymousId: '_sample_anonid',
},
- "traits": {
- "userId": "_sample_uid",
- "anonymousId": "_sample_anonid"
- }
- },
- "timestamp": "2022-09-28T20:14:43.314Z",
- "request_ip": "71.189.106.156",
- "originalTimestamp": "2022-09-28T20:14:44.995Z"
+ },
+ timestamp: '2022-09-28T20:14:43.314Z',
+ request_ip: '71.189.106.156',
+ originalTimestamp: '2022-09-28T20:14:44.995Z',
},
destination: {
ID: '1i3Em7GMU9xVEiDlZUN8c88BMS9',
@@ -2245,8 +2246,7 @@ export const data = [
},
Config: {
appToken: 'testAppToken',
- customMappings: [
- { from: 'Application Installed', to: '3fdmll' }],
+ customMappings: [{ from: 'Application Installed', to: '3fdmll' }],
partnerParamsKeys: [
{ from: 'key1', to: 'partnerParamKey-1' },
{ from: 'key2', to: 'partnerParamKey-2' },
@@ -2277,10 +2277,10 @@ export const data = [
endpoint: 'https://s2s.adjust.com/event',
headers: { Accept: '*/*' },
params: {
- event_token: "3fdmll",
- ip_address: "71.189.106.156",
+ event_token: '3fdmll',
+ ip_address: '71.189.106.156',
android_id: 'sample_device_id',
- gps_adid: "_sample",
+ gps_adid: '_sample',
s2s: 1,
app_token: 'testAppToken',
environment: 'production',
@@ -2294,4 +2294,5 @@ export const data = [
],
},
},
- },];
+ },
+];
diff --git a/test/integrations/destinations/adobe_analytics/dataDelivery/business.ts b/test/integrations/destinations/adobe_analytics/dataDelivery/business.ts
new file mode 100644
index 00000000000..76e07690cf0
--- /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 182969da73d..2535a0639e9 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 2fe4f0204ed..7e32c5f10b4 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 8b639f45c02..d0fd29b0890 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 00000000000..8ba964e2ddb
--- /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 00000000000..e8d0817a7fe
--- /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 00000000000..feb4eb46c50
--- /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 00000000000..f5ccc703378
--- /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 00000000000..84e932bbdbf
--- /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 0cbdd8b31b2..7c37c9642ae 100644
--- a/test/integrations/destinations/algolia/processor/data.ts
+++ b/test/integrations/destinations/algolia/processor/data.ts
@@ -380,7 +380,7 @@ export const data = [
body: [
{
error:
- 'Either filters or objectIds is required.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required.',
+ 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.',
statTags: {
destType: 'ALGOLIA',
errorCategory: 'dataValidation',
@@ -1417,4 +1417,214 @@ export const data = [
},
},
},
+ {
+ name: 'algolia',
+ description: 'Eventype must be one of click, conversion pr view',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ channel: 'web',
+ context: {
+ app: {
+ build: '1.0.0',
+ name: 'RudderLabs JavaScript SDK',
+ namespace: 'com.rudderlabs.javascript',
+ version: '1.0.0',
+ },
+ traits: {
+ email: 'testone@gmail.com',
+ firstName: 'test',
+ lastName: 'one',
+ },
+ library: {
+ name: 'RudderLabs JavaScript SDK',
+ version: '1.0.0',
+ },
+ userAgent:
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
+ locale: 'en-US',
+ ip: '0.0.0.0',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ page: {
+ path: '/destinations/ometria',
+ referrer: '',
+ search: '',
+ title: '',
+ url: 'https://docs.rudderstack.com/destinations/ometria',
+ category: 'destination',
+ initial_referrer: 'https://docs.rudderstack.com',
+ initial_referring_domain: 'docs.rudderstack.com',
+ },
+ },
+ type: 'track',
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ anonymousId: '123456',
+ event: 'product clicked',
+ userId: 'testuserId1',
+ properties: {
+ index: 'products',
+ filters: ['field1:hello', 'val1:val2'],
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ excludeKeys: [],
+ includeKeys: [],
+ },
+ },
+ Config: {
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'product clicked',
+ to: 'abcd',
+ },
+ ],
+ },
+ },
+ metadata: {
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'eventType can be either click, view or conversion: Workflow: procWorkflow, Step: preparePayload, ChildStep: undefined, OriginalError: eventType can be either click, view or conversion',
+ statTags: {
+ destType: 'ALGOLIA',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ statusCode: 400,
+ metadata: {
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'algolia',
+ description: 'filters or objectIds must be non empty',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ type: 'track',
+ event: 'product clicked',
+ sentAt: '2024-02-25T17:55:36.882Z',
+ userId: '12345',
+ channel: 'web',
+ properties: {
+ index: 'products',
+ list_id: 'search_results_page',
+ queryId: '8e737',
+ products: [],
+ eventName: 'productListView',
+ list_name: 'Search Results Page',
+ objectIds: [],
+ positions: [],
+ userToken: 'e494',
+ additional_attributes: {},
+ },
+ receivedAt: '2024-02-25T17:55:38.089Z',
+ request_ip: '107.130.37.100',
+ anonymousId: '68e9f4b8-fd4d-4c56-8ca4-858de2fd1df8',
+ integrations: {
+ All: true,
+ },
+ originalTimestamp: '2024-02-25T17:55:36.880Z',
+ },
+ destination: {
+ DestinationDefinition: {
+ Config: {
+ cdkV2Enabled: true,
+ excludeKeys: [],
+ includeKeys: [],
+ },
+ },
+ Config: {
+ apiKey: 'dummyApiKey',
+ applicationId: 'O2YARRI15I',
+ eventTypeSettings: [
+ {
+ from: 'product clicked',
+ to: 'cLick ',
+ },
+ ],
+ },
+ },
+ metadata: {
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'Either filters or objectIds is required and must be non empty.: Workflow: procWorkflow, Step: validateDestPayload, ChildStep: undefined, OriginalError: Either filters or objectIds is required and must be non empty.',
+ statTags: {
+ destType: 'ALGOLIA',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'cdkV2',
+ module: 'destination',
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ statusCode: 400,
+ metadata: {
+ destinationId: 'destId',
+ workspaceId: 'wspId',
+ },
+ },
+ ],
+ },
+ },
+ },
];
diff --git a/test/integrations/destinations/algolia/router/data.ts b/test/integrations/destinations/algolia/router/data.ts
index 65c74342dc8..dca899693e5 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/batch/data.ts b/test/integrations/destinations/am/batch/data.ts
index aa67df06c7c..91a17606a90 100644
--- a/test/integrations/destinations/am/batch/data.ts
+++ b/test/integrations/destinations/am/batch/data.ts
@@ -61,7 +61,7 @@ export const data = [
endpoint: 'https://api.eu.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
destination: {
@@ -83,16 +83,24 @@ export const data = [
{
batched: false,
error: 'Both userId and deviceId cannot be undefined',
- //TODO fix this
- metadata: {
- job_id: 1,
- userId: 'u1',
- },
+ metadata: [
+ {
+ jobId: 1,
+ userId: 'u1',
+ },
+ ],
statTags: {
errorCategory: 'dataValidation',
errorType: 'instrumentation',
},
statusCode: 400,
+ destination: {
+ ID: 'a',
+ url: 'a',
+ Config: {
+ residencyServer: 'EU',
+ },
+ },
},
],
},
@@ -163,7 +171,7 @@ export const data = [
endpoint: 'https://api.eu.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
destination: {
@@ -218,7 +226,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
destination: {
@@ -273,7 +281,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
destination: {
@@ -331,7 +339,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
destination: {
@@ -389,7 +397,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
destination: {
@@ -423,7 +431,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/groupidentify',
},
metadata: {
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
destination: {
@@ -455,7 +463,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/usermap',
},
metadata: {
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
destination: {
@@ -529,7 +537,7 @@ export const data = [
},
metadata: [
{
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
],
@@ -565,7 +573,7 @@ export const data = [
},
metadata: [
{
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
],
@@ -599,7 +607,7 @@ export const data = [
},
metadata: [
{
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
],
@@ -710,19 +718,19 @@ export const data = [
},
metadata: [
{
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
{
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
{
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
{
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
],
@@ -801,7 +809,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
destination: {
@@ -854,7 +862,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
destination: {
@@ -907,7 +915,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
destination: {
@@ -963,7 +971,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
destination: {
@@ -1019,7 +1027,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
destination: {
@@ -1053,7 +1061,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/groupidentify',
},
metadata: {
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
destination: {
@@ -1085,7 +1093,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/usermap',
},
metadata: {
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
destination: {
@@ -1157,7 +1165,7 @@ export const data = [
},
metadata: [
{
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
],
@@ -1193,7 +1201,7 @@ export const data = [
},
metadata: [
{
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
],
@@ -1227,7 +1235,7 @@ export const data = [
},
metadata: [
{
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
],
@@ -1338,19 +1346,19 @@ export const data = [
},
metadata: [
{
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
{
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
{
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
{
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
],
@@ -2117,7 +2125,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
destination: {
@@ -2172,7 +2180,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
destination: {
@@ -2227,7 +2235,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
destination: {
@@ -2285,7 +2293,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
destination: {
@@ -2343,7 +2351,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
destination: {
@@ -2377,7 +2385,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/groupidentify',
},
metadata: {
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
destination: {
@@ -2409,7 +2417,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/usermap',
},
metadata: {
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
destination: {
@@ -2483,7 +2491,7 @@ export const data = [
},
metadata: [
{
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
],
@@ -2519,7 +2527,7 @@ export const data = [
},
metadata: [
{
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
],
@@ -2553,7 +2561,7 @@ export const data = [
},
metadata: [
{
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
],
@@ -2664,19 +2672,19 @@ export const data = [
},
metadata: [
{
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
{
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
{
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
{
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
],
@@ -2756,7 +2764,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
destination: {
@@ -2811,7 +2819,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
destination: {
@@ -2866,7 +2874,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
destination: {
@@ -2924,7 +2932,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
destination: {
@@ -2982,7 +2990,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/2/httpapi',
},
metadata: {
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
destination: {
@@ -3016,7 +3024,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/groupidentify',
},
metadata: {
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
destination: {
@@ -3048,7 +3056,7 @@ export const data = [
endpoint: 'https://api2.amplitude.com/usermap',
},
metadata: {
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
destination: {
@@ -3121,7 +3129,7 @@ export const data = [
},
metadata: [
{
- job_id: 1,
+ jobId: 1,
userId: 'u1',
},
],
@@ -3157,7 +3165,7 @@ export const data = [
},
metadata: [
{
- job_id: 6,
+ jobId: 6,
userId: 'u1',
},
],
@@ -3191,7 +3199,7 @@ export const data = [
},
metadata: [
{
- job_id: 7,
+ jobId: 7,
userId: 'u1',
},
],
@@ -3302,19 +3310,19 @@ export const data = [
},
metadata: [
{
- job_id: 2,
+ jobId: 2,
userId: 'u1',
},
{
- job_id: 3,
+ jobId: 3,
userId: 'u1',
},
{
- job_id: 4,
+ jobId: 4,
userId: 'u1',
},
{
- job_id: 5,
+ jobId: 5,
userId: 'u1',
},
],
diff --git a/test/integrations/destinations/am/processor/data.ts b/test/integrations/destinations/am/processor/data.ts
index f28606da0cd..01f9feb44a3 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 00000000000..c2205c25a18
--- /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 00000000000..de7584df781
--- /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 00000000000..660e335bc69
--- /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 00000000000..72d48bf93dc
--- /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 00000000000..5b81f8c95a8
--- /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 9fed3542358..dc1bdd33bca 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 00000000000..4997c5ffaeb
--- /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 8162e75720a..2596a4b9592 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 00000000000..9353899a654
--- /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 40d75c9d343..ae093ce1f48 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 00000000000..e663f3212a8
--- /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 601ad56401f..0373ca99926 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 00000000000..288a06bfe6f
--- /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 00000000000..1c0c45728c0
--- /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 ddecbaf8fa0..b7c23012481 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 00000000000..d9f83f52f35
--- /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 13a70d38c93..57e0d0ceea1 100644
--- a/test/integrations/destinations/clevertap/dataDelivery/data.ts
+++ b/test/integrations/destinations/clevertap/dataDelivery/data.ts
@@ -1,227 +1,233 @@
-export const data = [
+import { V1BusinessTestScenarion } from './business';
+const oldV0TestCases = [
{
- "name": "clevertap",
- "description": "Test 0",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.clevertap.com/1/upload/test1",
- "headers": {
- "X-CleverTap-Account-Id": "476550467",
- "X-CleverTap-Passcode": "fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1",
- "Content-Type": "application/json"
+ name: 'clevertap',
+ description: 'Test 0',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.clevertap.com/1/upload/test1',
+ headers: {
+ 'X-CleverTap-Account-Id': '476550467',
+ 'X-CleverTap-Passcode':
+ 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1',
+ 'Content-Type': 'application/json',
},
- "body": {
- "JSON": {
- "d": [
+ body: {
+ 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\":\"\"}"
+ 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"
- }
- ]
+ identity: 'anon_id',
+ },
+ ],
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'clevertap',
},
- "files": {},
- "params": {
- "destination": "clevertap"
- }
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 200,
- "body": {
- "output": {
- "status": 200,
- "message": "Request Processed Successfully",
- "destinationResponse": {
- "response": {
- "status": "success",
- "processed": 1,
- "unprocessed": []
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ destinationResponse: {
+ response: {
+ status: 'success',
+ processed: 1,
+ unprocessed: [],
},
- "status": 200
- }
- }
- }
- }
- }
+ status: 200,
+ },
+ },
+ },
+ },
+ },
},
{
- "name": "clevertap",
- "description": "Test 1",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "d": [
+ name: 'clevertap',
+ description: 'Test 1',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ d: [
{
- "identity": "anon-id-new",
- "type": "event",
- "evtName": "Web Page Viewed: Rudder",
- "evtData": {
- "title": "Home",
- "path": "/"
- }
- }
- ]
- }
+ identity: 'anon-id-new',
+ type: 'event',
+ evtName: 'Web Page Viewed: Rudder',
+ evtData: {
+ title: 'Home',
+ path: '/',
+ },
+ },
+ ],
+ },
+ },
+ type: 'REST',
+ files: {},
+ method: 'POST',
+ headers: {
+ 'X-CleverTap-Account-Id': 'fakeId123',
+ 'X-CleverTap-Passcode': 'fakePasscode123',
+ 'Content-Type': 'application/json',
},
- "type": "REST",
- "files": {},
- "method": "POST",
- "headers": {
- "X-CleverTap-Account-Id": "fakeId123",
- "X-CleverTap-Passcode": "fakePasscode123",
- "Content-Type": "application/json"
+ version: '1',
+ endpoint: 'https://api.clevertap.com/1/upload/test2',
+ params: {
+ destination: 'clevertap',
},
- "version": "1",
- "endpoint": "https://api.clevertap.com/1/upload/test2",
- "params": {
- "destination": "clevertap"
- }
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 401,
- "body": {
- "output": {
- "status": 401,
- "message": "Request failed with status: 401",
- "destinationResponse": {
- "response": {
- "status": "fail",
- "error": "Invalid Credentials",
- "code": 401
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ status: 401,
+ message: 'Request failed with status: 401',
+ destinationResponse: {
+ response: {
+ status: 'fail',
+ error: 'Invalid Credentials',
+ code: 401,
},
- "status": 401
+ status: 401,
},
- "statTags": {
- "destType": "CLEVERTAP",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
- }
- }
- }
- }
- }
+ statTags: {
+ destType: 'CLEVERTAP',
+ errorCategory: 'network',
+ destinationId: 'Non-determininable',
+ workspaceId: 'Non-determininable',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
},
{
- "name": "clevertap",
- "description": "Test 2",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "d": [
+ name: 'clevertap',
+ description: 'Test 2',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ d: [
{
- "identity": "anon-id-new",
- "type": "event",
- "evtData": {
- "title": "Home",
- "path": "/"
- }
- }
- ]
- }
+ identity: 'anon-id-new',
+ type: 'event',
+ evtData: {
+ title: 'Home',
+ path: '/',
+ },
+ },
+ ],
+ },
+ },
+ type: 'REST',
+ files: {},
+ method: 'POST',
+ headers: {
+ 'X-CleverTap-Account-Id': '476550467',
+ 'X-CleverTap-Passcode':
+ 'fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1',
+ 'Content-Type': 'application/json',
},
- "type": "REST",
- "files": {},
- "method": "POST",
- "headers": {
- "X-CleverTap-Account-Id": "476550467",
- "X-CleverTap-Passcode": "fbee74a147828e2932c701d19dc1f2dcfa4ac0048be3aa3a88d427090a59dc1c0fa002f1",
- "Content-Type": "application/json"
+ version: '1',
+ endpoint: 'https://api.clevertap.com/1/upload/test3',
+ params: {
+ destination: 'clevertap',
},
- "version": "1",
- "endpoint": "https://api.clevertap.com/1/upload/test3",
- "params": {
- "destination": "clevertap"
- }
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 400,
- "body": {
- "output": {
- "status": 400,
- "message": "Request failed with status: 200",
- "destinationResponse": {
- "response": {
- "status": "fail",
- "processed": 0,
- "unprocessed": []
+ output: {
+ response: {
+ status: 400,
+ body: {
+ output: {
+ status: 400,
+ message: 'Request failed with status: 200',
+ destinationResponse: {
+ response: {
+ status: 'fail',
+ processed: 0,
+ unprocessed: [],
},
- "status": 200
+ status: 200,
},
- "statTags": {
- "destType": "CLEVERTAP",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
- }
- }
- }
- }
- }
- }
-]
+ statTags: {
+ destType: 'CLEVERTAP',
+ errorCategory: 'network',
+ destinationId: 'Non-determininable',
+ workspaceId: 'Non-determininable',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
+ },
+];
+
+export const data = [...oldV0TestCases, ...V1BusinessTestScenarion];
diff --git a/test/integrations/destinations/clevertap/network.ts b/test/integrations/destinations/clevertap/network.ts
index c4eb23ee398..9122ba11293 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 d79ebaa8da5..1d7bdd7e782 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 4f1723a7da4..5f25bbe83e7 100644
--- a/test/integrations/destinations/clevertap/router/data.ts
+++ b/test/integrations/destinations/clevertap/router/data.ts
@@ -162,10 +162,10 @@ export const data = [
msgSms: true,
msgemail: true,
msgwhatsapp: false,
- custom_tags: '["Test_User","Interested_User","DIY_Hobby"]',
- custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}',
address:
'{"city":"kolkata","country":"India","postalCode":789223,"state":"WB","street":""}',
+ custom_mappings: '{"Office":"Trastkiv","Country":"Russia"}',
+ custom_tags: '["Test_User","Interested_User","DIY_Hobby"]',
},
objectId: 'anon_id',
},
diff --git a/test/integrations/destinations/clickup/network.ts b/test/integrations/destinations/clickup/network.ts
index 1a26209923e..2cb7cde34fc 100644
--- a/test/integrations/destinations/clickup/network.ts
+++ b/test/integrations/destinations/clickup/network.ts
@@ -1,247 +1,247 @@
export const networkCallsData = [
- {
- httpReq: {
- url: 'https://api.clickup.com/api/v2/list/correctListId123/field',
- method: 'GET',
- },
- httpRes: {
- data: {
- "fields": [
- {
- "id": "19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e",
- "name": "Labels",
- "type": "labels",
- "type_config": {
- "options": [
- {
- "id": "32c81c1c-cf53-4829-92f5-0f0270d27a45",
- "label": "Option 1",
- "color": {}
- },
- {
- "id": "7e24f329-9dd9-4e68-b426-2c70af6f9347",
- "label": "Option 2",
- "color": {}
- }
- ]
- },
- "date_created": "1661964865880",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "22eaffee-ffec-4c3b-bdae-56e69d55eecd",
- "name": "Payment Status",
- "type": "drop_down",
- "type_config": {
- "default": 0,
- "placeholder": {},
- "new_drop_down": true,
- "options": [
- {
- "id": "e109e36b-a052-4a31-af16-25da7324990f",
- "name": "Sent Request",
- "color": "#FF7FAB",
- "orderindex": 0
- },
- {
- "id": "3a3b4512-2896-44f7-8075-2ff37777fe24",
- "name": "Quote sent",
- "color": "#EA80FC",
- "orderindex": 1
- },
- {
- "id": "7afcb6fb-cec8-41d8-bf0c-039a9db28460",
- "name": "Pending",
- "color": "#ff7800",
- "orderindex": 2
- },
- {
- "id": "890ecf28-bdd4-4f53-92cc-bc4edb696fcd",
- "name": "Payment Recieved",
- "color": "#2ecd6f",
- "orderindex": 3
- },
- {
- "id": "e89f7dd7-fd24-4b32-ac4d-f174d8ca914f",
- "name": "n/a",
- "color": "#b5bcc2",
- "orderindex": 4
- }
- ]
- },
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "4b7a29be-e261-4340-8f3f-e6de838473e5",
- "name": "Plan",
- "type": "drop_down",
- "type_config": {
- "default": 0,
- "placeholder": {},
- "new_drop_down": true,
- "options": [
- {
- "id": "4b9366a7-2592-4b7a-909a-ed4af705e27c",
- "name": "Unlimited",
- "color": "#02BCD4",
- "orderindex": 0
- },
- {
- "id": "c5032049-8c05-44e9-a000-3a071d457b8f",
- "name": "Business",
- "color": "#1bbc9c",
- "orderindex": 1
- },
- {
- "id": "9fb08801-1130-4650-8e2e-28578344ff3c",
- "name": "Enterprise",
- "color": "#2ecd6f",
- "orderindex": 2
- }
- ]
- },
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "4bfebc00-9d4a-40d1-aef8-5a87b610186c",
- "name": "Contact Title",
- "type": "text",
- "type_config": {},
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "666f74bf-6d87-41f3-8735-ccf0efe066dd",
- "name": "Date",
- "type": "date",
- "type_config": {},
- "date_created": "1662379321069",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c",
- "name": "Industry",
- "type": "drop_down",
- "type_config": {
- "default": 0,
- "placeholder": {},
- "options": [
- {
- "id": "75173398-257f-42b6-8bae-4cf767fa99ab",
- "name": "Engineering",
- "color": "#04A9F4",
- "orderindex": 0
- },
- {
- "id": "c7f9b6f5-cd98-4609-af10-68a8710cc1bf",
- "name": "Retail",
- "color": "#ff7800",
- "orderindex": 1
- },
- {
- "id": "dbe84940-b4e8-4a29-8491-e1aa5f2be4e2",
- "name": "Hospitality",
- "color": "#2ecd6f",
- "orderindex": 2
- }
- ]
- },
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "b01b32fd-94d3-43e6-9f31-2c855ff169cd",
- "name": "Url",
- "type": "url",
- "type_config": {},
- "date_created": "1661970432587",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6",
- "name": "Phone Number",
- "type": "phone",
- "type_config": {},
- "date_created": "1661970795061",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "d0201829-ddcd-4b97-b71f-0f9e672488f2",
- "name": "Account Size",
- "type": "number",
- "type_config": {},
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "ea6c1e48-2abf-4328-b228-79c213e147c8",
- "name": "Location",
- "type": "location",
- "type_config": {},
- "date_created": "1662229589329",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "ebe825fb-92de-41ce-a29c-25018da039b4",
- "name": "Email",
- "type": "email",
- "type_config": {},
- "date_created": "1660124553414",
- "hide_from_guests": false,
- "required": {}
- },
- {
- "id": "f431cda3-a575-4a05-ba8d-583d9b6cb2df",
- "name": "Rating",
- "type": "emoji",
- "type_config": {
- "count": 5,
- "code_point": "2b50"
- },
- "date_created": "1661963909454",
- "hide_from_guests": false,
- "required": false
- },
- {
- "id": "ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb",
- "name": "Money In INR",
- "type": "currency",
- "type_config": {
- "default": {},
- "precision": 2,
- "currency_type": "INR"
- },
- "date_created": "1661428276019",
- "hide_from_guests": false,
- "required": false
- }
- ]
- },
- status: 200
- },
+ {
+ httpReq: {
+ url: 'https://api.clickup.com/api/v2/list/correctListId123/field',
+ method: 'GET',
},
- {
- httpReq: {
- url: 'https://api.clickup.com/api/v2/list/correctListId456/field',
- method: 'GET',
- },
- httpRes: {
- data: {
- "fields": []
+ httpRes: {
+ data: {
+ fields: [
+ {
+ id: '19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e',
+ name: 'Labels',
+ type: 'labels',
+ type_config: {
+ options: [
+ {
+ id: '32c81c1c-cf53-4829-92f5-0f0270d27a45',
+ label: 'Option 1',
+ color: {},
+ },
+ {
+ id: '7e24f329-9dd9-4e68-b426-2c70af6f9347',
+ label: 'Option 2',
+ color: {},
+ },
+ ],
+ },
+ date_created: '1661964865880',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: '22eaffee-ffec-4c3b-bdae-56e69d55eecd',
+ name: 'Payment Status',
+ type: 'drop_down',
+ type_config: {
+ default: 0,
+ placeholder: {},
+ new_drop_down: true,
+ options: [
+ {
+ id: 'e109e36b-a052-4a31-af16-25da7324990f',
+ name: 'Sent Request',
+ color: '#FF7FAB',
+ orderindex: 0,
+ },
+ {
+ id: '3a3b4512-2896-44f7-8075-2ff37777fe24',
+ name: 'Quote sent',
+ color: '#EA80FC',
+ orderindex: 1,
+ },
+ {
+ id: '7afcb6fb-cec8-41d8-bf0c-039a9db28460',
+ name: 'Pending',
+ color: '#ff7800',
+ orderindex: 2,
+ },
+ {
+ id: '890ecf28-bdd4-4f53-92cc-bc4edb696fcd',
+ name: 'Payment Recieved',
+ color: '#2ecd6f',
+ orderindex: 3,
+ },
+ {
+ id: 'e89f7dd7-fd24-4b32-ac4d-f174d8ca914f',
+ name: 'n/a',
+ color: '#b5bcc2',
+ orderindex: 4,
+ },
+ ],
+ },
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: '4b7a29be-e261-4340-8f3f-e6de838473e5',
+ name: 'Plan',
+ type: 'drop_down',
+ type_config: {
+ default: 0,
+ placeholder: {},
+ new_drop_down: true,
+ options: [
+ {
+ id: '4b9366a7-2592-4b7a-909a-ed4af705e27c',
+ name: 'Unlimited',
+ color: '#02BCD4',
+ orderindex: 0,
+ },
+ {
+ id: 'c5032049-8c05-44e9-a000-3a071d457b8f',
+ name: 'Business',
+ color: '#1bbc9c',
+ orderindex: 1,
+ },
+ {
+ id: '9fb08801-1130-4650-8e2e-28578344ff3c',
+ name: 'Enterprise',
+ color: '#2ecd6f',
+ orderindex: 2,
+ },
+ ],
+ },
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: '4bfebc00-9d4a-40d1-aef8-5a87b610186c',
+ name: 'Contact Title',
+ type: 'text',
+ type_config: {},
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: '666f74bf-6d87-41f3-8735-ccf0efe066dd',
+ name: 'Date',
+ type: 'date',
+ type_config: {},
+ date_created: '1662379321069',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: 'a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c',
+ name: 'Industry',
+ type: 'drop_down',
+ type_config: {
+ default: 0,
+ placeholder: {},
+ options: [
+ {
+ id: '75173398-257f-42b6-8bae-4cf767fa99ab',
+ name: 'Engineering',
+ color: '#04A9F4',
+ orderindex: 0,
+ },
+ {
+ id: 'c7f9b6f5-cd98-4609-af10-68a8710cc1bf',
+ name: 'Retail',
+ color: '#ff7800',
+ orderindex: 1,
+ },
+ {
+ id: 'dbe84940-b4e8-4a29-8491-e1aa5f2be4e2',
+ name: 'Hospitality',
+ color: '#2ecd6f',
+ orderindex: 2,
+ },
+ ],
+ },
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: 'b01b32fd-94d3-43e6-9f31-2c855ff169cd',
+ name: 'Url',
+ type: 'url',
+ type_config: {},
+ date_created: '1661970432587',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: 'c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6',
+ name: 'Phone Number',
+ type: 'phone',
+ type_config: {},
+ date_created: '1661970795061',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: 'd0201829-ddcd-4b97-b71f-0f9e672488f2',
+ name: 'Account Size',
+ type: 'number',
+ type_config: {},
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: 'ea6c1e48-2abf-4328-b228-79c213e147c8',
+ name: 'Location',
+ type: 'location',
+ type_config: {},
+ date_created: '1662229589329',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: 'ebe825fb-92de-41ce-a29c-25018da039b4',
+ name: 'Email',
+ type: 'email',
+ type_config: {},
+ date_created: '1660124553414',
+ hide_from_guests: false,
+ required: {},
+ },
+ {
+ id: 'f431cda3-a575-4a05-ba8d-583d9b6cb2df',
+ name: 'Rating',
+ type: 'emoji',
+ type_config: {
+ count: 5,
+ code_point: '2b50',
},
- status: 200
- },
- }
+ date_created: '1661963909454',
+ hide_from_guests: false,
+ required: false,
+ },
+ {
+ id: 'ffbe4f03-cbc3-4077-8fea-9e5d08b4dceb',
+ name: 'Money In INR',
+ type: 'currency',
+ type_config: {
+ default: {},
+ precision: 2,
+ currency_type: 'INR',
+ },
+ date_created: '1661428276019',
+ hide_from_guests: false,
+ required: false,
+ },
+ ],
+ },
+ status: 200,
+ },
+ },
+ {
+ httpReq: {
+ url: 'https://api.clickup.com/api/v2/list/correctListId456/field',
+ method: 'GET',
+ },
+ httpRes: {
+ data: {
+ fields: [],
+ },
+ status: 200,
+ },
+ },
];
diff --git a/test/integrations/destinations/clickup/processor/data.ts b/test/integrations/destinations/clickup/processor/data.ts
index 4a79df28627..686bf670e2e 100644
--- a/test/integrations/destinations/clickup/processor/data.ts
+++ b/test/integrations/destinations/clickup/processor/data.ts
@@ -1,829 +1,818 @@
export const data = [
- {
- "name": "clickup",
- "description": "Invalid priority",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123"
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "priority": 0
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Invalid value specified for priority. Value must be Integer and in range \"[1,4]\"",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Custom field: Invalid phone number",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "phone",
- "to": "Phone Number"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "phone": "9999999999"
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "The provided phone number is invalid",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Custom field: Invalid email",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "email",
- "to": "Email"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "email": "test.com"
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "The provided email is invalid",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Custom field: Invalid url",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "url",
- "to": "Url"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "url": "www.test.com"
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "The provided url is invalid",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Custom field: Invalid location latitude",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "location",
- "to": "Location"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "location": {
- "lat": -100,
- "lng": 124,
- "formattedAddress": "Gold Coast QLD, Australia"
- }
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Invalid value specified for latitude. Latitude must be in range \"[-90, 90]\"",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Custom field: Invalid rating",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "rating",
- "to": "Rating"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "rating": "7"
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Invalid value specified for rating. Value must be in range \"[0,5]\"",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Creating task with valid custom fields values",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "keyToCustomFieldName": [
- {
- "from": "industry",
- "to": "Industry"
- },
- {
- "from": "Payment Status",
- "to": "Payment Status"
- },
- {
- "from": "labelKey",
- "to": "Labels"
- },
- {
- "from": "locationKey",
- "to": "Location"
- },
- {
- "from": "phone",
- "to": "Phone Number"
- },
- {
- "from": "email",
- "to": "Email"
- },
- {
- "from": "url",
- "to": "Url"
- },
- {
- "from": "rating",
- "to": "Rating"
- },
- {
- "from": "plan",
- "to": "Plan"
- },
- {
- "from": "contactTitle",
- "to": "Contact Title"
- },
- {
- "from": "date",
- "to": "Date"
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "description": "Creating task with valid custom fields values",
- "tags": [
- "testing",
- "custom fields"
- ],
- "timeEstimate": 10800000,
- "status": "Ready",
- "priority": 1,
- "dueDate": "2022-02-25T13:39:21.032Z",
- "includeDueDateTime": true,
- "startDate": "2022-01-20T13:39:21.032Z",
- "includeStartDateTime": "true",
- "notifyAll": false,
- "industry": "Retail",
- "Payment Status": "Pending",
- "labelKey": [
- "option 1",
- "option 2",
- "option 3"
- ],
- "locationKey": {
- "lat": -20,
- "lng": 124,
- "formattedAddress": "Gold Coast QLD, Australia"
- },
- "phone": "+12233445567",
- "email": "test123@example.com",
- "url": "https://www.rudderstack.com/",
- "rating": 3,
- "plan": "Business",
- "contactTitle": "VP of Operations",
- "date": "2022-02-25T13:39:21.032Z"
- },
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.clickup.com/api/v2/list/correctListId123/task",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "pk_123"
- },
- "params": {},
- "body": {
- "JSON": {
- "name": "Product Viewed",
- "description": "Creating task with valid custom fields values",
- "tags": [
- "testing",
- "custom fields"
- ],
- "time_estimate": 10800000,
- "status": "Ready",
- "priority": 1,
- "due_date": 1645796361032,
- "due_date_time": true,
- "start_date": 1642685961032,
- "start_date_time": "true",
- "notify_all": false,
- "custom_fields": [
- {
- "id": "a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c",
- "value": "c7f9b6f5-cd98-4609-af10-68a8710cc1bf"
- },
- {
- "id": "22eaffee-ffec-4c3b-bdae-56e69d55eecd",
- "value": "7afcb6fb-cec8-41d8-bf0c-039a9db28460"
- },
- {
- "id": "19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e",
- "value": [
- "32c81c1c-cf53-4829-92f5-0f0270d27a45",
- "7e24f329-9dd9-4e68-b426-2c70af6f9347"
- ]
- },
- {
- "id": "ea6c1e48-2abf-4328-b228-79c213e147c8",
- "value": {
- "location": {
- "lat": -20,
- "lng": 124
- },
- "formatted_address": "Gold Coast QLD, Australia"
- }
- },
- {
- "id": "c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6",
- "value": "+12233445567"
- },
- {
- "id": "ebe825fb-92de-41ce-a29c-25018da039b4",
- "value": "test123@example.com"
- },
- {
- "id": "b01b32fd-94d3-43e6-9f31-2c855ff169cd",
- "value": "https://www.rudderstack.com/"
- },
- {
- "id": "f431cda3-a575-4a05-ba8d-583d9b6cb2df",
- "value": 3
- },
- {
- "id": "4b7a29be-e261-4340-8f3f-e6de838473e5",
- "value": "c5032049-8c05-44e9-a000-3a071d457b8f"
- },
- {
- "id": "4bfebc00-9d4a-40d1-aef8-5a87b610186c",
- "value": "VP of Operations"
- },
- {
- "id": "666f74bf-6d87-41f3-8735-ccf0efe066dd",
- "value": 1645796361032
- }
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Creating task with assignees",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123"
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Product Viewed",
- "properties": {
- "taskName": "Transformer Testing"
- },
- "context": {
- "externalId": [
- {
- "type": "clickUpAssigneeId",
- "id": 61205104
- },
- {
- "type": "clickUpAssigneeId",
- "id": 61217234
- },
- {
- "type": "clickUpAssigneeId",
- "id": 61228575
- }
- ]
- },
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.clickup.com/api/v2/list/correctListId123/task",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "pk_123"
- },
- "params": {},
- "body": {
- "JSON": {
- "name": "Transformer Testing",
- "assignees": [
- 61205104,
- 61217234,
- 61228575
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Event filtering : Sending non whitelisted event when some events are whitelisted",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "whitelistedEvents": [
- {
- "eventName": "Anonymous Page Visit"
- },
- {
- "eventName": "Product Viewed"
- },
- {
- "eventName": ""
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Credit Card Added",
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "The event was discarded as it was not allow listed in the destination configuration",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Event filtering : No event is whitelisted",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123",
- "whitelistedEvents": [
- {
- "eventName": ""
- },
- {
- "eventName": ""
- },
- {
- "eventName": ""
- }
- ]
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "Credit Card Added",
- "context": {},
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "The event was discarded as it was not allow listed in the destination configuration",
- "statTags": {
- "destType": "CLICKUP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "clickup",
- "description": "Creating task using listId from externalId array",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiToken": "pk_123",
- "listId": "correctListId123"
- },
- "ID": "clickup-1234"
- },
- "message": {
- "type": "track",
- "event": "anonymous page visit",
- "context": {
- "externalId": [
- {
- "type": "clickUpListId",
- "id": "correctListId456"
- }
- ]
- },
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.clickup.com/api/v2/list/correctListId456/task",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "pk_123"
- },
- "params": {},
- "body": {
- "JSON": {
- "name": "anonymous page visit"
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- }
-]
+ {
+ name: 'clickup',
+ description: 'Invalid priority',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ priority: 0,
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'Invalid value specified for priority. Value must be Integer and in range "[1,4]"',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Custom field: Invalid phone number',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'phone',
+ to: 'Phone Number',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ phone: '9999999999',
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'The provided phone number is invalid',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Custom field: Invalid email',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'email',
+ to: 'Email',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ email: 'test.com',
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'The provided email is invalid',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Custom field: Invalid url',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'url',
+ to: 'Url',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ url: 'www.test.com',
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'The provided url is invalid',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Custom field: Invalid location latitude',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'location',
+ to: 'Location',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ location: {
+ lat: -100,
+ lng: 124,
+ formattedAddress: 'Gold Coast QLD, Australia',
+ },
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Invalid value specified for latitude. Latitude must be in range "[-90, 90]"',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Custom field: Invalid rating',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'rating',
+ to: 'Rating',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ rating: '7',
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Invalid value specified for rating. Value must be in range "[0,5]"',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Creating task with valid custom fields values',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ keyToCustomFieldName: [
+ {
+ from: 'industry',
+ to: 'Industry',
+ },
+ {
+ from: 'Payment Status',
+ to: 'Payment Status',
+ },
+ {
+ from: 'labelKey',
+ to: 'Labels',
+ },
+ {
+ from: 'locationKey',
+ to: 'Location',
+ },
+ {
+ from: 'phone',
+ to: 'Phone Number',
+ },
+ {
+ from: 'email',
+ to: 'Email',
+ },
+ {
+ from: 'url',
+ to: 'Url',
+ },
+ {
+ from: 'rating',
+ to: 'Rating',
+ },
+ {
+ from: 'plan',
+ to: 'Plan',
+ },
+ {
+ from: 'contactTitle',
+ to: 'Contact Title',
+ },
+ {
+ from: 'date',
+ to: 'Date',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ description: 'Creating task with valid custom fields values',
+ tags: ['testing', 'custom fields'],
+ timeEstimate: 10800000,
+ status: 'Ready',
+ priority: 1,
+ dueDate: '2022-02-25T13:39:21.032Z',
+ includeDueDateTime: true,
+ startDate: '2022-01-20T13:39:21.032Z',
+ includeStartDateTime: 'true',
+ notifyAll: false,
+ industry: 'Retail',
+ 'Payment Status': 'Pending',
+ labelKey: ['option 1', 'option 2', 'option 3'],
+ locationKey: {
+ lat: -20,
+ lng: 124,
+ formattedAddress: 'Gold Coast QLD, Australia',
+ },
+ phone: '+12233445567',
+ email: 'test123@example.com',
+ url: 'https://www.rudderstack.com/',
+ rating: 3,
+ plan: 'Business',
+ contactTitle: 'VP of Operations',
+ date: '2022-02-25T13:39:21.032Z',
+ },
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.clickup.com/api/v2/list/correctListId123/task',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'pk_123',
+ },
+ params: {},
+ body: {
+ JSON: {
+ name: 'Product Viewed',
+ description: 'Creating task with valid custom fields values',
+ tags: ['testing', 'custom fields'],
+ time_estimate: 10800000,
+ status: 'Ready',
+ priority: 1,
+ due_date: 1645796361032,
+ due_date_time: true,
+ start_date: 1642685961032,
+ start_date_time: 'true',
+ notify_all: false,
+ custom_fields: [
+ {
+ id: 'a5f5044a-cbad-4caf-bcbb-4cd32bd8db7c',
+ value: 'c7f9b6f5-cd98-4609-af10-68a8710cc1bf',
+ },
+ {
+ id: '22eaffee-ffec-4c3b-bdae-56e69d55eecd',
+ value: '7afcb6fb-cec8-41d8-bf0c-039a9db28460',
+ },
+ {
+ id: '19d3ac4e-2b1e-4569-b33e-ff86c7d94d6e',
+ value: [
+ '32c81c1c-cf53-4829-92f5-0f0270d27a45',
+ '7e24f329-9dd9-4e68-b426-2c70af6f9347',
+ ],
+ },
+ {
+ id: 'ea6c1e48-2abf-4328-b228-79c213e147c8',
+ value: {
+ location: {
+ lat: -20,
+ lng: 124,
+ },
+ formatted_address: 'Gold Coast QLD, Australia',
+ },
+ },
+ {
+ id: 'c9b83d91-b979-4b34-b4bd-88bf9cf2b9a6',
+ value: '+12233445567',
+ },
+ {
+ id: 'ebe825fb-92de-41ce-a29c-25018da039b4',
+ value: 'test123@example.com',
+ },
+ {
+ id: 'b01b32fd-94d3-43e6-9f31-2c855ff169cd',
+ value: 'https://www.rudderstack.com/',
+ },
+ {
+ id: 'f431cda3-a575-4a05-ba8d-583d9b6cb2df',
+ value: 3,
+ },
+ {
+ id: '4b7a29be-e261-4340-8f3f-e6de838473e5',
+ value: 'c5032049-8c05-44e9-a000-3a071d457b8f',
+ },
+ {
+ id: '4bfebc00-9d4a-40d1-aef8-5a87b610186c',
+ value: 'VP of Operations',
+ },
+ {
+ id: '666f74bf-6d87-41f3-8735-ccf0efe066dd',
+ value: 1645796361032,
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Creating task with assignees',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Product Viewed',
+ properties: {
+ taskName: 'Transformer Testing',
+ },
+ context: {
+ externalId: [
+ {
+ type: 'clickUpAssigneeId',
+ id: 61205104,
+ },
+ {
+ type: 'clickUpAssigneeId',
+ id: 61217234,
+ },
+ {
+ type: 'clickUpAssigneeId',
+ id: 61228575,
+ },
+ ],
+ },
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.clickup.com/api/v2/list/correctListId123/task',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'pk_123',
+ },
+ params: {},
+ body: {
+ JSON: {
+ name: 'Transformer Testing',
+ assignees: [61205104, 61217234, 61228575],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Event filtering : Sending non whitelisted event when some events are whitelisted',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ whitelistedEvents: [
+ {
+ eventName: 'Anonymous Page Visit',
+ },
+ {
+ eventName: 'Product Viewed',
+ },
+ {
+ eventName: '',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Credit Card Added',
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'The event was discarded as it was not allow listed in the destination configuration',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Event filtering : No event is whitelisted',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ whitelistedEvents: [
+ {
+ eventName: '',
+ },
+ {
+ eventName: '',
+ },
+ {
+ eventName: '',
+ },
+ ],
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Credit Card Added',
+ context: {},
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error:
+ 'The event was discarded as it was not allow listed in the destination configuration',
+ statTags: {
+ destType: 'CLICKUP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'clickup',
+ description: 'Creating task using listId from externalId array',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiToken: 'pk_123',
+ listId: 'correctListId123',
+ },
+ ID: 'clickup-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'anonymous page visit',
+ context: {
+ externalId: [
+ {
+ type: 'clickUpListId',
+ id: 'correctListId456',
+ },
+ ],
+ },
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.clickup.com/api/v2/list/correctListId456/task',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'pk_123',
+ },
+ params: {},
+ body: {
+ JSON: {
+ name: 'anonymous page visit',
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 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 00000000000..f30bf73d7a9
--- /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 162e5663659..c603ef66648 100644
--- a/test/integrations/destinations/criteo_audience/dataDelivery/data.ts
+++ b/test/integrations/destinations/criteo_audience/dataDelivery/data.ts
@@ -1,530 +1,541 @@
-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",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34894/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 0',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34894/contactlist',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "remove",
- "identifierType": "gum",
- "identifiers": [
- "sample_gum3"
- ],
- "internalIdentifiers": false,
- "gumCallerId": "329739"
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'remove',
+ identifierType: 'gum',
+ identifiers: ['sample_gum3'],
+ internalIdentifiers: false,
+ gumCallerId: '329739',
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(1),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ destinationResponse: {
+ response: '',
+ status: 200,
+ },
+ },
+ },
+ },
},
- "output": {
- "response": {
- "status": 200,
- "body": {
- "output": {
- "status": 200,
- "message": "Request Processed Successfully",
- "destinationResponse": {
- "response": "",
- "status": 200
- }
- }
- }
- }
- }
},
{
- "name": "criteo_audience",
- "description": "Test 1",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/3485/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 1',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/3485/contactlist/expiredAccessToken',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(2),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 401,
- "body": {
- "output": {
- "status": 401,
- "authErrorCategory": "REFRESH_TOKEN",
- "destinationResponse": {
- "errors": [
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ status: 401,
+ authErrorCategory: 'REFRESH_TOKEN',
+ destinationResponse: {
+ errors: [
{
- "traceIdentifier": "80a1a0ba3981b04da847d05700752c77",
- "type": "authorization",
- "code": "authorization-token-expired",
- "instance": "/2022-10/audiences/123/contactlist",
- "title": "The authorization token has expired"
- }
- ]
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ code: 'authorization-token-expired',
+ instance: '/2022-10/audiences/123/contactlist',
+ title: 'The authorization token has expired',
+ },
+ ],
},
- "message": "The authorization token has expired during criteo_audience response transformation",
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
- }
- }
- }
- }
- }
+ message:
+ 'The authorization token has expired during criteo_audience response transformation',
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+ },
+ },
+ },
+ },
+ },
},
{
- "name": "criteo_audience",
- "description": "Test 2",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34895/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 2',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34895/contactlist/invalidAccessToken',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ files: {},
+ params: {
+ destination: 'criteo_audience',
+ },
+ userId: '1234',
+ metadata: generateMetadata(3),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 401,
- "body": {
- "output": {
- "status": 401,
- "authErrorCategory": "REFRESH_TOKEN",
- "destinationResponse": {
- "errors": [
+ output: {
+ response: {
+ status: 401,
+ body: {
+ output: {
+ status: 401,
+ authErrorCategory: 'REFRESH_TOKEN',
+ destinationResponse: {
+ errors: [
{
- "traceIdentifier": "80a1a0ba3981b04da847d05700752c77",
- "type": "authorization",
- "code": "authorization-token-invalid",
- "instance": "/2022-10/audiences/123/contactlist",
- "title": "The authorization header is invalid"
- }
- ]
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ code: 'authorization-token-invalid',
+ instance: '/2022-10/audiences/123/contactlist',
+ title: 'The authorization header is invalid',
+ },
+ ],
+ },
+ message:
+ 'The authorization header is invalid during criteo_audience response transformation',
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
},
- "message": "The authorization header is invalid during criteo_audience response transformation",
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
- }
- }
- }
- }
- }
+ },
+ },
+ },
+ },
},
{
- "name": "criteo_audience",
- "description": "Test 3",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34896/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 3',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34896/contactlist',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(4),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 400,
- "body": {
- "output": {
- "message": "AudienceId is Invalid. Please Provide Valid AudienceId",
- "destinationResponse": {
- "response": {
- "errors": [
+ output: {
+ response: {
+ status: 400,
+ body: {
+ output: {
+ message: 'AudienceId is Invalid. Please Provide Valid AudienceId',
+ destinationResponse: {
+ response: {
+ errors: [
{
- "code": "audience-invalid",
- "traceIdentifier": "80a1a0ba3981b04da847d05700752c77",
- "type": "authorization"
- }
- ]
+ code: 'audience-invalid',
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ },
+ ],
},
- "status": 404
+ status: 404,
},
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "meta": "instrumentation",
- "module": "destination"
+ statTags: {
+ destType: 'CRITEO_AUDIENCE',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ meta: 'instrumentation',
+ module: 'destination',
},
- "status": 400
- }
- }
- }
- }
+ status: 400,
+ },
+ },
+ },
+ },
},
{
- "name": "criteo_audience",
- "description": "Test 4",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34897/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 4',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34897/contactlist',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(5),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 500,
- "body": {
- "output": {
- "destinationResponse": {
- "response": {
- "errors": [
+ output: {
+ response: {
+ status: 500,
+ body: {
+ output: {
+ destinationResponse: {
+ response: {
+ errors: [
{
- "code": "audience-invalid",
- "traceIdentifier": "80a1a0ba3981b04da847d05700752c77",
- "type": "authorization"
- }
- ]
+ code: 'audience-invalid',
+ traceIdentifier: '80a1a0ba3981b04da847d05700752c77',
+ type: 'authorization',
+ },
+ ],
},
- "status": 503
+ status: 503,
},
- "message": "Request Failed: during criteo_audience response transformation (Retryable)",
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "feature": "dataDelivery",
- "implementation": "native",
- "errorType": "retryable",
- "module": "destination"
+ 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',
},
- "status": 500
- }
- }
- }
- }
+ status: 500,
+ },
+ },
+ },
+ },
},
{
- "name": "criteo_audience",
- "description": "Test 5",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34898/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 5',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34898/contactlist',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(6),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 429,
- "body": {
- "output": {
- "destinationResponse": {
- "response": {},
- "status": 429
+ output: {
+ response: {
+ status: 429,
+ body: {
+ output: {
+ destinationResponse: {
+ response: {},
+ status: 429,
},
- "message": "Request Failed: during criteo_audience response transformation - due to Request Limit exceeded, (Throttled)",
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "throttled",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
+ 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',
+ errorType: 'throttled',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
},
- "status": 429
- }
- }
- }
- }
+ status: 429,
+ },
+ },
+ },
+ },
},
{
- "name": "criteo_audience",
- "description": "Test 6",
- "feature": "dataDelivery",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": {
- "version": "1",
- "type": "REST",
- "method": "PATCH",
- "endpoint": "https://api.criteo.com/2022-10/audiences/34899/contactlist",
- "headers": {
- "Authorization": "Bearer success_access_token",
- "Content-Type": "application/json",
- "Accept": "application/json"
+ name: 'criteo_audience',
+ description: 'Test 6',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: {
+ version: '1',
+ type: 'REST',
+ method: 'PATCH',
+ endpoint: 'https://api.criteo.com/2022-10/audiences/34899/contactlist',
+ headers: {
+ Authorization: 'Bearer success_access_token',
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
},
- "body": {
- "JSON": {
- "data": {
- "type": "ContactlistAmendment",
- "attributes": {
- "operation": "add",
- "identifierType": "madid",
- "identifiers": [
- "sample_madid",
- "sample_madid_1",
- "sample_madid_2"
- ],
- "internalIdentifiers": false
- }
- }
+ body: {
+ JSON: {
+ data: {
+ type: 'ContactlistAmendment',
+ attributes: {
+ operation: 'add',
+ identifierType: 'madid',
+ identifiers: ['sample_madid', 'sample_madid_1', 'sample_madid_2'],
+ internalIdentifiers: false,
+ },
+ },
},
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ params: {
+ destination: 'criteo_audience',
},
- "files": {},
- "params": {
- "destination": "criteo_audience"
- }
+ userId: '1234',
+ metadata: generateMetadata(7),
+ destinationConfig: {},
},
- "method": "POST"
- }
+ method: 'POST',
+ },
},
- "output": {
- "response": {
- "status": 400,
- "body": {
- "output": {
- "destinationResponse": {
- "response": {
- "message": "unknown error"
+ output: {
+ response: {
+ status: 400,
+ body: {
+ output: {
+ destinationResponse: {
+ response: {
+ message: 'unknown error',
},
- "status": 410
+ status: 410,
},
- "message": "Request Failed: during criteo_audience response transformation with status \"410\" due to \"{\"message\":\"unknown error\"}\", (Aborted) ",
- "statTags": {
- "destType": "CRITEO_AUDIENCE",
- "errorCategory": "network",
- "destinationId": "Non-determininable",
- "workspaceId": "Non-determininable",
- "errorType": "aborted",
- "feature": "dataDelivery",
- "implementation": "native",
- "module": "destination"
+ 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',
},
- "status": 400
- }
- }
- }
- }
- }
-]
+ status: 400,
+ },
+ },
+ },
+ },
+ },
+];
+
+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 00000000000..982397f7c33
--- /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 00000000000..145be62528a
--- /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 959e8a21127..7ccf649e2a0 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 00000000000..22a120770a1
--- /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 4af6545f9fa..242f54c97b2 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/custify/processor/data.ts b/test/integrations/destinations/custify/processor/data.ts
index 8b6fc934f4c..b5bd8bd753f 100644
--- a/test/integrations/destinations/custify/processor/data.ts
+++ b/test/integrations/destinations/custify/processor/data.ts
@@ -1,695 +1,672 @@
export const data = [
- {
- "name": "custify",
- "description": "Identify Call with all traits and adding to company",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "identify",
- "userId": "user_1234",
- "context": {
- "traits": {
- "email": "user111@gmail.com",
- "firstName": "New",
- "lastName": "User",
- "phone": 9830311522,
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z",
- "company": {
- "id": "company_123"
- }
- }
- },
- "timestamp": "2022-04-27T13:56:13.012Z",
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.custify.com/people",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "Bearer apiKey_key_test_001",
- "Accept": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "user_id": "user_1234",
- "email": "user111@gmail.com",
- "phone": 9830311522,
- "session_count": 23,
- "unsubscribed_from_emails": false,
- "unsubscribed_from_calls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_attributes": {
- "firstName": "New",
- "lastName": "User",
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "createdAt": "2022-04-27T13:56:13.012Z"
- },
- "name": "New User",
- "companies": [
- {
- "company_id": "company_123",
- "remove": false
- }
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": "user_1234"
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Identify Call removing the user from company",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "identify",
- "userId": "user_1234",
- "context": {
- "traits": {
- "email": "user111@gmail.com",
- "firstName": "New",
- "lastName": "User",
- "phone": 9830311522,
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z",
- "company": {
- "id": "company_123",
- "remove": true
- }
- }
- },
- "timestamp": "2022-04-27T13:56:13.012Z",
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04",
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.custify.com/people",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "Bearer apiKey_key_test_001",
- "Accept": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "user_id": "user_1234",
- "email": "user111@gmail.com",
- "phone": 9830311522,
- "session_count": 23,
- "unsubscribed_from_emails": false,
- "unsubscribed_from_calls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_attributes": {
- "firstName": "New",
- "lastName": "User",
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "createdAt": "2022-04-27T13:56:13.012Z"
- },
- "name": "New User",
- "companies": [
- {
- "company_id": "company_123",
- "remove": true
- }
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": "user_1234"
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Identify Call without userId and email and anoymousId",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "identify",
- "context": {
- "traits": {
- "firstName": "New",
- "lastName": "User",
- "phone": 9830311522,
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z",
- "company": {
- "id": "company_123",
- "remove": true
- }
- }
- },
- "timestamp": "2022-04-27T13:56:13.012Z",
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Email or userId is mandatory",
- "statTags": {
- "destType": "CUSTIFY",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Identify Call without userId and email and sendAnonymous is false",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "identify",
- "context": {
- "traits": {
- "firstName": "New",
- "lastName": "User",
- "phone": 9830311522,
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z",
- "company": {
- "id": "company_123",
- "remove": true
- }
- }
- },
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c",
- "timestamp": "2022-04-27T13:56:13.012Z",
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Email or userId is mandatory",
- "statTags": {
- "destType": "CUSTIFY",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Identify Call without userId and email and sendAnonymous is true",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": true
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "identify",
- "context": {
- "traits": {
- "firstName": "New",
- "lastName": "User",
- "phone": 9830311522,
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z",
- "company": {
- "id": "company_123",
- "remove": true
- }
- }
- },
- "anonymousId": "bf412108-0357-4330-b119-7305e767823c",
- "timestamp": "2022-04-27T13:56:13.012Z",
- "rudderId": "553b5522-c575-40a7-8072-9741c5f9a647",
- "messageId": "831f1fa5-de84-4f22-880a-4c3f23fc3f04"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.custify.com/people",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "Bearer apiKey_key_test_001",
- "Accept": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "phone": 9830311522,
- "session_count": 23,
- "unsubscribed_from_emails": false,
- "unsubscribed_from_calls": false,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_attributes": {
- "firstName": "New",
- "lastName": "User",
- "sessionCount": 23,
- "unsubscribedFromEmails": false,
- "unsubscribedFromCalls": false,
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "createdAt": "2022-04-27T13:56:13.012Z"
- },
- "user_id": "bf412108-0357-4330-b119-7305e767823c",
- "name": "New User",
- "companies": [
- {
- "company_id": "company_123",
- "remove": true
- }
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": "bf412108-0357-4330-b119-7305e767823c"
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Track call with all properties",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "track",
- "event": "Order Completed Version 2",
- "sentAt": "2021-08-26T14:16:47.321Z",
- "userId": "user_111",
- "context": {
- "library": {
- "name": "analytics-node",
- "version": "1.0.3"
- },
- "traits": {
- "email": "user111@gmail.com"
- },
- "page": {
- "url": "https://www.website.com/product/path"
- }
- },
- "rudderId": "70612f39-0607-45bb-8236-bf0995fde4fa",
- "_metadata": {
- "nodeVersion": "10.24.1"
- },
- "messageId": "node-84952e4eb9c6debbda735c49d08a8b31-fcbfed6a-38cf-42c5-881c-f590f59311b1",
- "properties": {
- "product": "Cube",
- "revenue": 9002,
- "organization_id": "company_123"
- },
- "originalTimestamp": "2021-08-26T14:16:47.317Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.custify.com/event",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "Bearer apiKey_key_test_001",
- "Accept": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "user_id": "user_111",
- "email": "user111@gmail.com",
- "name": "Order Completed Version 2",
- "created_at": "2021-08-26T14:16:47.317Z",
- "company_id": "company_123",
- "metadata": {
- "product": "Cube",
- "revenue": 9002,
- "organization_id": "company_123",
- "user_id": "user_111"
- }
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": "user_111"
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "custify",
- "description": "Group call with all fields success scenario",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "apiKey_key_test_001",
- "sendAnonymousId": false
- },
- "ID": "custify-1234"
- },
- "message": {
- "type": "group",
- "userId": "user_111",
- "groupId": "company_222",
- "traits": {
- "name": "Absolute Company",
- "industry": " Absolute",
- "employees": 121,
- "size": 100,
- "website": "www.rudder.com",
- "plan": "GOLD",
- "monthly_revenue": 8000,
- "churned": false,
- "test_att1": "test_att_val1"
- },
- "context": {
- "traits": {
- "firstName": "Absolute",
- "lastName": "User",
- "phone": 9830311522,
- "session_count": 23,
- "signed_up_at": "2022-04-27T13:56:13.012Z",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "custom_prop4": {
- "test": "test"
- },
- "custom_prop5": [
- 1,
- 3,
- 4
- ],
- "createdAt": "2022-04-27T13:56:13.012Z"
- },
- "ip": "14.5.67.21",
- "library": {
- "name": "http"
- }
- },
- "timestamp": "2020-01-21T00:21:34.208Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.custify.com/people",
- "headers": {
- "Content-Type": "application/json",
- "Authorization": "Bearer apiKey_key_test_001",
- "Accept": "application/json"
- },
- "params": {},
- "body": {
- "JSON": {
- "user_id": "user_111",
- "phone": 9830311522,
- "signed_up_at": "2020-01-21T00:21:34.208Z",
- "custom_attributes": {
- "firstName": "Absolute",
- "lastName": "User",
- "custom_prop1": "custom_value1",
- "custom_prop2": 123,
- "custom_prop3": false,
- "createdAt": "2022-04-27T13:56:13.012Z"
- },
- "name": "Absolute User",
- "companies": [
- {
- "company_id": "company_222",
- "remove": false
- }
- ]
- },
- "JSON_ARRAY": {},
- "XML": {},
- "FORM": {}
- },
- "files": {},
- "userId": "user_111"
- },
- "statusCode": 200
- }
- ]
- }
- }
- }
-]
+ {
+ name: 'custify',
+ description: 'Identify Call with all traits and adding to company',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'identify',
+ userId: 'user_1234',
+ context: {
+ traits: {
+ email: 'user111@gmail.com',
+ firstName: 'New',
+ lastName: 'User',
+ phone: 9830311522,
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ company: {
+ id: 'company_123',
+ },
+ },
+ },
+ timestamp: '2022-04-27T13:56:13.012Z',
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.custify.com/people',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer apiKey_key_test_001',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ user_id: 'user_1234',
+ email: 'user111@gmail.com',
+ phone: 9830311522,
+ session_count: 23,
+ unsubscribed_from_emails: false,
+ unsubscribed_from_calls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_attributes: {
+ firstName: 'New',
+ lastName: 'User',
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ createdAt: '2022-04-27T13:56:13.012Z',
+ },
+ name: 'New User',
+ companies: [
+ {
+ company_id: 'company_123',
+ remove: false,
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'user_1234',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Identify Call removing the user from company',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'identify',
+ userId: 'user_1234',
+ context: {
+ traits: {
+ email: 'user111@gmail.com',
+ firstName: 'New',
+ lastName: 'User',
+ phone: 9830311522,
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ company: {
+ id: 'company_123',
+ remove: true,
+ },
+ },
+ },
+ timestamp: '2022-04-27T13:56:13.012Z',
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.custify.com/people',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer apiKey_key_test_001',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ user_id: 'user_1234',
+ email: 'user111@gmail.com',
+ phone: 9830311522,
+ session_count: 23,
+ unsubscribed_from_emails: false,
+ unsubscribed_from_calls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_attributes: {
+ firstName: 'New',
+ lastName: 'User',
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ createdAt: '2022-04-27T13:56:13.012Z',
+ },
+ name: 'New User',
+ companies: [
+ {
+ company_id: 'company_123',
+ remove: true,
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'user_1234',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Identify Call without userId and email and anoymousId',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'identify',
+ context: {
+ traits: {
+ firstName: 'New',
+ lastName: 'User',
+ phone: 9830311522,
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ company: {
+ id: 'company_123',
+ remove: true,
+ },
+ },
+ },
+ timestamp: '2022-04-27T13:56:13.012Z',
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Email or userId is mandatory',
+ statTags: {
+ destType: 'CUSTIFY',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Identify Call without userId and email and sendAnonymous is false',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'identify',
+ context: {
+ traits: {
+ firstName: 'New',
+ lastName: 'User',
+ phone: 9830311522,
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ company: {
+ id: 'company_123',
+ remove: true,
+ },
+ },
+ },
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ timestamp: '2022-04-27T13:56:13.012Z',
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Email or userId is mandatory',
+ statTags: {
+ destType: 'CUSTIFY',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Identify Call without userId and email and sendAnonymous is true',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: true,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'identify',
+ context: {
+ traits: {
+ firstName: 'New',
+ lastName: 'User',
+ phone: 9830311522,
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ company: {
+ id: 'company_123',
+ remove: true,
+ },
+ },
+ },
+ anonymousId: 'bf412108-0357-4330-b119-7305e767823c',
+ timestamp: '2022-04-27T13:56:13.012Z',
+ rudderId: '553b5522-c575-40a7-8072-9741c5f9a647',
+ messageId: '831f1fa5-de84-4f22-880a-4c3f23fc3f04',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.custify.com/people',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer apiKey_key_test_001',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ phone: 9830311522,
+ session_count: 23,
+ unsubscribed_from_emails: false,
+ unsubscribed_from_calls: false,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_attributes: {
+ firstName: 'New',
+ lastName: 'User',
+ sessionCount: 23,
+ unsubscribedFromEmails: false,
+ unsubscribedFromCalls: false,
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ createdAt: '2022-04-27T13:56:13.012Z',
+ },
+ user_id: 'bf412108-0357-4330-b119-7305e767823c',
+ name: 'New User',
+ companies: [
+ {
+ company_id: 'company_123',
+ remove: true,
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'bf412108-0357-4330-b119-7305e767823c',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Track call with all properties',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'track',
+ event: 'Order Completed Version 2',
+ sentAt: '2021-08-26T14:16:47.321Z',
+ userId: 'user_111',
+ context: {
+ library: {
+ name: 'analytics-node',
+ version: '1.0.3',
+ },
+ traits: {
+ email: 'user111@gmail.com',
+ },
+ page: {
+ url: 'https://www.website.com/product/path',
+ },
+ },
+ rudderId: '70612f39-0607-45bb-8236-bf0995fde4fa',
+ _metadata: {
+ nodeVersion: '10.24.1',
+ },
+ messageId:
+ 'node-84952e4eb9c6debbda735c49d08a8b31-fcbfed6a-38cf-42c5-881c-f590f59311b1',
+ properties: {
+ product: 'Cube',
+ revenue: 9002,
+ organization_id: 'company_123',
+ },
+ originalTimestamp: '2021-08-26T14:16:47.317Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.custify.com/event',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer apiKey_key_test_001',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ user_id: 'user_111',
+ email: 'user111@gmail.com',
+ name: 'Order Completed Version 2',
+ created_at: '2021-08-26T14:16:47.317Z',
+ company_id: 'company_123',
+ metadata: {
+ product: 'Cube',
+ revenue: 9002,
+ organization_id: 'company_123',
+ user_id: 'user_111',
+ },
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'user_111',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'custify',
+ description: 'Group call with all fields success scenario',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'apiKey_key_test_001',
+ sendAnonymousId: false,
+ },
+ ID: 'custify-1234',
+ },
+ message: {
+ type: 'group',
+ userId: 'user_111',
+ groupId: 'company_222',
+ traits: {
+ name: 'Absolute Company',
+ industry: ' Absolute',
+ employees: 121,
+ size: 100,
+ website: 'www.rudder.com',
+ plan: 'GOLD',
+ monthly_revenue: 8000,
+ churned: false,
+ test_att1: 'test_att_val1',
+ },
+ context: {
+ traits: {
+ firstName: 'Absolute',
+ lastName: 'User',
+ phone: 9830311522,
+ session_count: 23,
+ signed_up_at: '2022-04-27T13:56:13.012Z',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ custom_prop4: {
+ test: 'test',
+ },
+ custom_prop5: [1, 3, 4],
+ createdAt: '2022-04-27T13:56:13.012Z',
+ },
+ ip: '14.5.67.21',
+ library: {
+ name: 'http',
+ },
+ },
+ timestamp: '2020-01-21T00:21:34.208Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.custify.com/people',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer apiKey_key_test_001',
+ Accept: 'application/json',
+ },
+ params: {},
+ body: {
+ JSON: {
+ user_id: 'user_111',
+ phone: 9830311522,
+ signed_up_at: '2020-01-21T00:21:34.208Z',
+ custom_attributes: {
+ firstName: 'Absolute',
+ lastName: 'User',
+ custom_prop1: 'custom_value1',
+ custom_prop2: 123,
+ custom_prop3: false,
+ createdAt: '2022-04-27T13:56:13.012Z',
+ },
+ name: 'Absolute User',
+ companies: [
+ {
+ company_id: 'company_222',
+ remove: false,
+ },
+ ],
+ },
+ JSON_ARRAY: {},
+ XML: {},
+ FORM: {},
+ },
+ files: {},
+ userId: 'user_111',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/delighted/network.ts b/test/integrations/destinations/delighted/network.ts
index 15b0a414e6b..d9896a25e81 100644
--- a/test/integrations/destinations/delighted/network.ts
+++ b/test/integrations/destinations/delighted/network.ts
@@ -1,30 +1,30 @@
export const networkCallsData = [
- {
- httpReq: {
- url: 'https://api.delighted.com/v1/people.json',
- method: 'GET',
- headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` },
- params: {
- email: "identified_user@email.com"
- }
- },
- httpRes: {
- data: ["user data"],
- status: 200
- },
+ {
+ httpReq: {
+ url: 'https://api.delighted.com/v1/people.json',
+ method: 'GET',
+ headers: { Authorization: `Basic ZHVtbXlBcGlLZXk=` },
+ params: {
+ email: 'identified_user@email.com',
+ },
},
- {
- httpReq: {
- url: 'https://api.delighted.com/v1/people.json',
- method: 'GET',
- headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` },
- params: {
- email: "unidentified_user@email.com"
- }
- },
- httpRes: {
- data: [],
- status: 200
- },
- }
+ httpRes: {
+ data: ['user data'],
+ status: 200,
+ },
+ },
+ {
+ httpReq: {
+ url: 'https://api.delighted.com/v1/people.json',
+ method: 'GET',
+ headers: { Authorization: `Basic ZHVtbXlBcGlLZXlmb3JmYWlsdXJl` },
+ params: {
+ email: 'unidentified_user@email.com',
+ },
+ },
+ httpRes: {
+ data: [],
+ status: 200,
+ },
+ },
];
diff --git a/test/integrations/destinations/delighted/processor/data.ts b/test/integrations/destinations/delighted/processor/data.ts
index 3596d7ef80d..7a5ad7de9d4 100644
--- a/test/integrations/destinations/delighted/processor/data.ts
+++ b/test/integrations/destinations/delighted/processor/data.ts
@@ -1,937 +1,947 @@
export const data = [
- {
- "name": "delighted",
- "description": "Test 0",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "userId": "abc@123.com",
- "traits": {
- "firstName": "James",
- "lastName": "Doe",
- "phone": "+91237416221",
- "last_sent_at": "1626698350"
- },
- "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.delighted.com/v1/people.json",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "email": "abc@123.com",
- "send": false,
- "channel": "email",
- "delay": 0,
- "name": "James Doe",
- "phone_number": "+91237416221",
- "last_sent_at": "1626698350"
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 1",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "alias",
- "previousId": "123@abc.com",
- "userId": "abc@123.com",
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "email": "123@abc.com",
- "email_update": "abc@123.com"
- }
- },
- "type": "REST",
- "files": {},
- "method": "POST",
- "params": {},
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "version": "1",
- "endpoint": "https://api.delighted.com/v1/people.json",
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 2",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": "Product Reviewed"
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "userId": "identified_user@email.com",
- "event": "Product Reviewed",
- "properties": {
- "review_id": "12345",
- "product_id": "123",
- "rating": 3,
- "review_body": "Average product, expected much more."
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "properties": {
- "review_id": "12345",
- "product_id": "123",
- "rating": 3,
- "review_body": "Average product, expected much more."
- },
- "send": true,
- "channel": "email",
- "delay": 0,
- "email": "identified_user@email.com"
- }
- },
- "type": "REST",
- "files": {},
- "method": "POST",
- "params": {},
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "version": "1",
- "endpoint": "https://api.delighted.com/v1/people.json",
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 3",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKeyforfailure",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": "Product Reviewed"
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "userId": "unidentified_user@email.com",
- "event": "Product Reviewed",
- "properties": {
- "review_id": "12345",
- "product_id": "123",
- "rating": 3,
- "review_body": "Average product, expected much more."
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "user unidentified_user@email.com doesn't exist",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "network",
- "errorType": "aborted",
- "feature": "processor",
- "implementation": "native",
- "meta": "instrumentation",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 4",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "userId": "identified_user@email.com",
- "event": "Product Reviewed",
- "properties": {
- "review_id": "12345",
- "product_id": "123",
- "rating": 3,
- "review_body": "Average product, expected much more."
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Event is not configured on your Rudderstack Dashboard",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "dataValidation",
- "errorType": "configuration",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 5",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": "Product Reviewed"
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "anonymousId": "identified_user@email.com",
- "event": "Product Reviewed",
- "properties": {
- "review_id": "12345",
- "product_id": "123",
- "rating": 3,
- "review_body": "Average product, expected much more."
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "userId is required.",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 6",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "alias",
- "previousId": "123@abc.com",
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "userId is required.",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 7",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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",
- "externalId": [
- {
- "id": "sms",
- "type": "delightedChannelType"
- }
- ],
- "locale": "en-US",
- "ip": "0.0.0.0",
- "os": {
- "name": "",
- "version": ""
- },
- "screen": {
- "density": 2
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "alias",
- "userId": "abc@123.com",
- "previousId": "123@abc.com",
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "User Id and Previous Id should be of same type i.e. phone/sms",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 8",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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",
- "externalId": [
- {
- "id": "sms",
- "type": "delightedChannelType"
- }
- ],
- "locale": "en-US",
- "ip": "0.0.0.0",
- "os": {
- "name": "",
- "version": ""
- },
- "screen": {
- "density": 2
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "userId": "abc@123.com",
- "traits": {
- "firstName": "James",
- "lastName": "Doe",
- "phone": "+91237416221",
- "last_sent_at": "1626698350"
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Channel is set to sms. Enter correct phone number i.e. E.164",
- "statTags": {
- "destType": "DELIGHTED",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "delighted",
- "description": "Test 9",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "channel": "email",
- "delay": 0,
- "eventNamesSettings": [
- {
- "event": ""
- }
- ]
- }
- },
- "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",
- "externalId": [
- {
- "id": "sms",
- "type": "delightedChannelType"
- }
- ],
- "locale": "en-US",
- "ip": "0.0.0.0",
- "os": {
- "name": "",
- "version": ""
- },
- "screen": {
- "density": 2
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "userId": "+911234567890",
- "traits": {
- "firstName": "James",
- "lastName": "Doe",
- "last_sent_at": "1626698350"
- },
- "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.delighted.com/v1/people.json",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "send": false,
- "channel": "sms",
- "delay": 0,
- "name": "James Doe",
- "phone_number": "+911234567890",
- "last_sent_at": "1626698350"
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- }
-]
+ {
+ name: 'delighted',
+ description: 'Test 0',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ userId: 'abc@123.com',
+ traits: {
+ firstName: 'James',
+ lastName: 'Doe',
+ phone: '+91237416221',
+ last_sent_at: '1626698350',
+ },
+ 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.delighted.com/v1/people.json',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ email: 'abc@123.com',
+ send: false,
+ channel: 'email',
+ delay: 0,
+ name: 'James Doe',
+ phone_number: '+91237416221',
+ last_sent_at: '1626698350',
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 1',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'alias',
+ previousId: '123@abc.com',
+ userId: 'abc@123.com',
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ email: '123@abc.com',
+ email_update: 'abc@123.com',
+ },
+ },
+ type: 'REST',
+ files: {},
+ method: 'POST',
+ params: {},
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ version: '1',
+ endpoint: 'https://api.delighted.com/v1/people.json',
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 2',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: 'Product Reviewed',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ userId: 'identified_user@email.com',
+ event: 'Product Reviewed',
+ properties: {
+ review_id: '12345',
+ product_id: '123',
+ rating: 3,
+ review_body: 'Average product, expected much more.',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ properties: {
+ review_id: '12345',
+ product_id: '123',
+ rating: 3,
+ review_body: 'Average product, expected much more.',
+ },
+ send: true,
+ channel: 'email',
+ delay: 0,
+ email: 'identified_user@email.com',
+ },
+ },
+ type: 'REST',
+ files: {},
+ method: 'POST',
+ params: {},
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ version: '1',
+ endpoint: 'https://api.delighted.com/v1/people.json',
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 3',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKeyforfailure',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: 'Product Reviewed',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ userId: 'unidentified_user@email.com',
+ event: 'Product Reviewed',
+ properties: {
+ review_id: '12345',
+ product_id: '123',
+ rating: 3,
+ review_body: 'Average product, expected much more.',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: "user unidentified_user@email.com doesn't exist",
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'network',
+ errorType: 'aborted',
+ feature: 'processor',
+ implementation: 'native',
+ meta: 'instrumentation',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 4',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ userId: 'identified_user@email.com',
+ event: 'Product Reviewed',
+ properties: {
+ review_id: '12345',
+ product_id: '123',
+ rating: 3,
+ review_body: 'Average product, expected much more.',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Event is not configured on your Rudderstack Dashboard',
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'dataValidation',
+ errorType: 'configuration',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 5',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: 'Product Reviewed',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ anonymousId: 'identified_user@email.com',
+ event: 'Product Reviewed',
+ properties: {
+ review_id: '12345',
+ product_id: '123',
+ rating: 3,
+ review_body: 'Average product, expected much more.',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'userId is required.',
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 6',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'alias',
+ previousId: '123@abc.com',
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'userId is required.',
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 7',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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',
+ externalId: [
+ {
+ id: 'sms',
+ type: 'delightedChannelType',
+ },
+ ],
+ locale: 'en-US',
+ ip: '0.0.0.0',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'alias',
+ userId: 'abc@123.com',
+ previousId: '123@abc.com',
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'User Id and Previous Id should be of same type i.e. phone/sms',
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 8',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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',
+ externalId: [
+ {
+ id: 'sms',
+ type: 'delightedChannelType',
+ },
+ ],
+ locale: 'en-US',
+ ip: '0.0.0.0',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ userId: 'abc@123.com',
+ traits: {
+ firstName: 'James',
+ lastName: 'Doe',
+ phone: '+91237416221',
+ last_sent_at: '1626698350',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Channel is set to sms. Enter correct phone number i.e. E.164',
+ statTags: {
+ destType: 'DELIGHTED',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'delighted',
+ description: 'Test 9',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ channel: 'email',
+ delay: 0,
+ eventNamesSettings: [
+ {
+ event: '',
+ },
+ ],
+ },
+ },
+ 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',
+ externalId: [
+ {
+ id: 'sms',
+ type: 'delightedChannelType',
+ },
+ ],
+ locale: 'en-US',
+ ip: '0.0.0.0',
+ os: {
+ name: '',
+ version: '',
+ },
+ screen: {
+ density: 2,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ userId: '+911234567890',
+ traits: {
+ firstName: 'James',
+ lastName: 'Doe',
+ last_sent_at: '1626698350',
+ },
+ 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.delighted.com/v1/people.json',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ send: false,
+ channel: 'sms',
+ delay: 0,
+ name: 'James Doe',
+ phone_number: '+911234567890',
+ last_sent_at: '1626698350',
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/drip/processor/data.ts b/test/integrations/destinations/drip/processor/data.ts
index 0159959d13b..1874f932fa1 100644
--- a/test/integrations/destinations/drip/processor/data.ts
+++ b/test/integrations/destinations/drip/processor/data.ts
@@ -1,1487 +1,1498 @@
export const data = [
- {
- "name": "drip",
- "description": "Test 0",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "traits": {
- "email": "test1@gmail.com",
- "firstName": "James",
- "lastName": "Doe",
- "phone": "237416221",
- "customFields": {
- "filter1": "filterval1"
- }
- },
- "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.getdrip.com/v2/1809802/subscribers",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "subscribers": [
- {
- "email": "test1@gmail.com",
- "first_name": "James",
- "last_name": "Doe",
- "phone": "237416221",
- "ip_address": "0.0.0.0",
- "custom_fields": {
- "filter1": "filterval1"
- }
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 1",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "traits": {
- "email": "12324adfgs",
- "firstName": "James",
- "lastName": "Doe",
- "phone": "237416221",
- "customFields": {
- "filter1": "filterval1"
- }
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "dripId or email is required for the call",
- "statTags": {
- "destType": "DRIP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 2",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "traits": {
- "email": "test1@gmail.com",
- "name": "James Doe",
- "phone": "237416221",
- "filter1": "filterval1",
- "filter2": "filterval2"
- },
- "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.getdrip.com/v2/1809802/subscribers",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "subscribers": [
- {
- "email": "test1@gmail.com",
- "first_name": "James",
- "last_name": "Doe",
- "phone": "237416221",
- "ip_address": "0.0.0.0",
- "custom_fields": {
- "filter1": "filterval1",
- "filter2": "filterval2"
- }
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 3",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "identify",
- "traits": {
- "email": "test1@gmail.com",
- "name": "James Doe",
- "phone": "237416221",
- "filter1": "filterval1",
- "filter2": "filterval2",
- "tags": [
- "tag1",
- "tag2"
- ],
- "startingEmailIndex": 1
- },
- "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.getdrip.com/v2/1809802/campaigns/915194776/subscribers",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "subscribers": [
- {
- "email": "test1@gmail.com",
- "starting_email_index": 1
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 4",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "testing",
- "properties": {
- "email": "user1@gmail.com",
- "customFields": {
- "field1": "val1"
- }
- },
- "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.getdrip.com/v2/1809802/events",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "events": [
- {
- "email": "user1@gmail.com",
- "properties": {
- "field1": "val1"
- },
- "action": "testing",
- "occurred_at": "2019-10-14T09:03:17.562Z"
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 5",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": true
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "",
- "properties": {
- "email": "user1@gmail.com",
- "custom_fields": {
- "field1": "val1"
- }
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Event name is required",
- "statTags": {
- "destType": "DRIP",
- "errorCategory": "dataValidation",
- "errorType": "instrumentation",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 6",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "testing",
- "properties": {
- "email": "identified_user@gmail.com",
- "customFields": {
- "field1": "val1"
- }
- },
- "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.getdrip.com/v2/1809802/events",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "events": [
- {
- "email": "identified_user@gmail.com",
- "properties": {
- "field1": "val1"
- },
- "action": "testing",
- "occurred_at": "2019-10-14T09:03:17.562Z"
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 7",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "testing",
- "properties": {
- "email": "identified_user@gmail.com",
- "field1": "val1"
- },
- "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.getdrip.com/v2/1809802/events",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "events": [
- {
- "email": "identified_user@gmail.com",
- "properties": {
- "field1": "val1"
- },
- "action": "testing",
- "occurred_at": "2019-10-14T09:03:17.562Z"
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 8",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "testing",
- "properties": {
- "email": "unidentified_user@gmail.com",
- "field1": "val1"
- },
- "integrations": {
- "All": true
- },
- "sentAt": "2019-10-14T09:03:22.563Z"
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "error": "Error occurred while checking user : error response not found",
- "statTags": {
- "destType": "DRIP",
- "errorCategory": "network",
- "errorType": "aborted",
- "feature": "processor",
- "implementation": "native",
- "module": "destination"
- },
- "statusCode": 400
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 9",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "checkout started",
- "properties": {
- "email": "identified_user@gmail.com",
- "field1": "val1",
- "affiliation": "my_custom_order",
- "order_id": "456445746"
- },
- "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.getdrip.com/v3/1809802/shopper_activity/order",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "action": "placed",
- "email": "identified_user@gmail.com",
- "occurred_at": "2019-10-14T09:03:17.562Z",
- "order_id": "456445746",
- "provider": "my_custom_order"
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 10",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "checkout started",
- "properties": {
- "email": "identified_user@gmail.com",
- "field1": "val1",
- "affiliation": "my_custom_order",
- "order_id": "456445746",
- "products": [
- {
- "name": "shirt",
- "price": 11.16
- }
- ]
- },
- "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.getdrip.com/v3/1809802/shopper_activity/order",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "action": "placed",
- "email": "identified_user@gmail.com",
- "occurred_at": "2019-10-14T09:03:17.562Z",
- "order_id": "456445746",
- "provider": "my_custom_order",
- "items": [
- {
- "name": "shirt",
- "price": 11.16
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 11",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "915194776",
- "enableUserCreation": false
- }
- },
- "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
- }
- },
- "messageId": "84e26acc-56a5-4835-8233-591137fca468",
- "session_id": "3049dc4c-5a95-4ccd-a3e7-d74a7e411f22",
- "originalTimestamp": "2019-10-14T09:03:17.562Z",
- "type": "track",
- "event": "checkout",
- "properties": {
- "email": "identified_user@gmail.com",
- "field1": "val1",
- "customFields": {
- "field2": "val2"
- }
- },
- "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.getdrip.com/v2/1809802/events",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "events": [
- {
- "action": "checkout",
- "email": "identified_user@gmail.com",
- "occurred_at": "2019-10-14T09:03:17.562Z",
- "properties": {
- "field2": "val2"
- }
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- },
- {
- "name": "drip",
- "description": "Test 12",
- "feature": "processor",
- "module": "destination",
- "version": "v0",
- "input": {
- "request": {
- "body": [
- {
- "destination": {
- "Config": {
- "apiKey": "dummyApiKey",
- "accountId": "1809802",
- "campaignId": "",
- "enableUserCreation": true
- }
- },
- "message": {
- "type": "identify",
- "event": "identify",
- "userId": "user@1",
- "channel": "mobile",
- "context": {
- "os": {
- "name": "Android",
- "version": "13"
- },
- "app": {
- "name": "rudderstack",
- "build": "5425",
- "version": "2.4.1"
- },
- "device": {
- "id": "8b048b94cbec4fcf",
- "name": "o1q",
- "type": "Android",
- "model": "SM-G991U",
- "manufacturer": "samsung"
- },
- "locale": "en-US",
- "traits": {
- "id": "ruddertest@gmail.com",
- "email": "ruddertest@gmail.com",
- "title": "Social Impact Program Manager",
- "skills": [
- {
- "id": 134,
- "name": "Business Development",
- "tagGroupId": 2,
- "parentTagId": 134,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 96,
- "name": "Communications",
- "tagGroupId": 2,
- "parentTagId": 96,
- "huddleVisible": true,
- "onboardVisible": true
- },
- {
- "id": 489,
- "name": "Construction",
- "tagGroupId": 2,
- "parentTagId": 489,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 671,
- "name": "Data Analysis",
- "tagGroupId": 2,
- "parentTagId": 671,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 676,
- "name": "Engineering: Mechanical",
- "tagGroupId": 2,
- "parentTagId": 676,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 189,
- "name": "Operations",
- "tagGroupId": 2,
- "parentTagId": 189,
- "huddleVisible": true,
- "onboardVisible": true
- },
- {
- "id": 194,
- "name": "Product Management",
- "tagGroupId": 2,
- "parentTagId": 194,
- "huddleVisible": true,
- "onboardVisible": true
- },
- {
- "id": 195,
- "name": "Program Management",
- "tagGroupId": 2,
- "parentTagId": 195,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 199,
- "name": "R&D",
- "tagGroupId": 2,
- "parentTagId": 199,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 102,
- "name": "Sales",
- "tagGroupId": 2,
- "parentTagId": 102,
- "huddleVisible": true,
- "onboardVisible": true
- }
- ],
- "userId": "ruddertest@gmail.com",
- "address": "Chicago, IL, USA",
- "industry": "Education",
- "lastName": "test",
- "lastname": "test",
- "verified": false,
- "firstName": "rudder",
- "firstname": "rudder",
- "interests": [
- {
- "id": 649,
- "name": "Adaptation",
- "tagGroupId": 4,
- "parentTagId": 745,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 663,
- "name": "Carbon removal and sequestration",
- "tagGroupId": 4,
- "parentTagId": 761,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 664,
- "name": "Clean Energy and Energy Efficiency",
- "tagGroupId": 4,
- "parentTagId": 259,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 654,
- "name": "Climate Science and Earth Systems",
- "tagGroupId": 4,
- "parentTagId": 744,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 650,
- "name": "Corporate Sustainability",
- "tagGroupId": 4,
- "parentTagId": 650,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 651,
- "name": "Finance & Risk",
- "tagGroupId": 4,
- "parentTagId": 651,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 652,
- "name": "Food and Agriculture",
- "tagGroupId": 4,
- "parentTagId": 652,
- "huddleVisible": false,
- "onboardVisible": false
- },
- {
- "id": 665,
- "name": "Transport",
- "tagGroupId": 4,
- "parentTagId": 665,
- "huddleVisible": false,
- "onboardVisible": false
- }
- ],
- "isDeleted": false,
- "anonymousId": "8b048b94cbec4fcf",
- "jobFunction": "Program manager",
- "reminderType": 1,
- "jobPreferences": {
- "motivationStr": "I am looking for a job in climate change.,I want to learn about climate change.,I’d like to connect with other climate enthusiasts.",
- "searchUrgency": "As soon as possible",
- "maxCompensation": 0,
- "minCompensation": 0
- },
- "visibilityType": "public",
- "Linkedin-Signin": true,
- "hubbleOnboarded": false,
- "sharePreference": false,
- "notificationType": 0,
- "shortDescription": "Social Impact Program Manager",
- "jobProfileComplete": false,
- "noOfMonthExperience": 0,
- "onboarding_completed": "Yes"
- },
- "library": {
- "name": "com.rudderstack.android.sdk.core",
- "version": "1.8.1"
- },
- "timezone": "America/Chicago",
- "sessionId": 1681096824,
- "userAgent": "Dalvik/2.1.0 (Linux; U; Android 13; SM-G991U Build/TP1A.220624.014)"
- },
- "rudderId": "f701966c-5568-4500-92ba-4e9023c8fe31",
- "messageId": "77b53247-177f-4ca3-a6b4-aa7558fec280",
- "request_ip": "75.209.176.135",
- "anonymousId": "8b048b94cbec4fcf",
- "integrations": {
- "All": true
- }
- }
- }
- ]
- }
- },
- "output": {
- "response": {
- "status": 200,
- "body": [
- {
- "output": {
- "version": "1",
- "type": "REST",
- "method": "POST",
- "endpoint": "https://api.getdrip.com/v2/1809802/subscribers",
- "headers": {
- "Authorization": "Basic ZHVtbXlBcGlLZXk=",
- "Content-Type": "application/json"
- },
- "params": {},
- "body": {
- "XML": {},
- "JSON_ARRAY": {},
- "FORM": {},
- "JSON": {
- "subscribers": [
- {
- "email": "ruddertest@gmail.com",
- "user_id": "user@1",
- "address1": "Chicago, IL, USA",
- "last_name": "test",
- "first_name": "rudder",
- "custom_fields": {
- "title": "Social Impact Program Manager",
- "industry": "Education",
- "verified": false,
- "isDeleted": false,
- "jobFunction": "Program manager",
- "reminderType": 1,
- "jobPreferences": {
- "motivationStr": "I am looking for a job in climate change.,I want to learn about climate change.,I’d like to connect with other climate enthusiasts.",
- "searchUrgency": "As soon as possible",
- "maxCompensation": 0,
- "minCompensation": 0
- },
- "visibilityType": "public",
- "hubbleOnboarded": false,
- "sharePreference": false,
- "notificationType": 0,
- "shortDescription": "Social Impact Program Manager",
- "jobProfileComplete": false,
- "noOfMonthExperience": 0,
- "onboarding_completed": "Yes"
- }
- }
- ]
- }
- },
- "files": {},
- "userId": ""
- },
- "statusCode": 200
- }
- ]
- }
- }
- }
-]
+ {
+ name: 'drip',
+ description: 'Test 0',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ traits: {
+ email: 'test1@gmail.com',
+ firstName: 'James',
+ lastName: 'Doe',
+ phone: '237416221',
+ customFields: {
+ filter1: 'filterval1',
+ },
+ },
+ 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.getdrip.com/v2/1809802/subscribers',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ subscribers: [
+ {
+ email: 'test1@gmail.com',
+ first_name: 'James',
+ last_name: 'Doe',
+ phone: '237416221',
+ ip_address: '0.0.0.0',
+ custom_fields: {
+ filter1: 'filterval1',
+ },
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 1',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ traits: {
+ email: '12324adfgs',
+ firstName: 'James',
+ lastName: 'Doe',
+ phone: '237416221',
+ customFields: {
+ filter1: 'filterval1',
+ },
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'dripId or email is required for the call',
+ statTags: {
+ destType: 'DRIP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 2',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ traits: {
+ email: 'test1@gmail.com',
+ name: 'James Doe',
+ phone: '237416221',
+ filter1: 'filterval1',
+ filter2: 'filterval2',
+ },
+ 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.getdrip.com/v2/1809802/subscribers',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ subscribers: [
+ {
+ email: 'test1@gmail.com',
+ first_name: 'James',
+ last_name: 'Doe',
+ phone: '237416221',
+ ip_address: '0.0.0.0',
+ custom_fields: {
+ filter1: 'filterval1',
+ filter2: 'filterval2',
+ },
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 3',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'identify',
+ traits: {
+ email: 'test1@gmail.com',
+ name: 'James Doe',
+ phone: '237416221',
+ filter1: 'filterval1',
+ filter2: 'filterval2',
+ tags: ['tag1', 'tag2'],
+ startingEmailIndex: 1,
+ },
+ 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.getdrip.com/v2/1809802/campaigns/915194776/subscribers',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ subscribers: [
+ {
+ email: 'test1@gmail.com',
+ starting_email_index: 1,
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 4',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'testing',
+ properties: {
+ email: 'user1@gmail.com',
+ customFields: {
+ field1: 'val1',
+ },
+ },
+ 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.getdrip.com/v2/1809802/events',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ events: [
+ {
+ email: 'user1@gmail.com',
+ properties: {
+ field1: 'val1',
+ },
+ action: 'testing',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 5',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: true,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: '',
+ properties: {
+ email: 'user1@gmail.com',
+ custom_fields: {
+ field1: 'val1',
+ },
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Event name is required',
+ statTags: {
+ destType: 'DRIP',
+ errorCategory: 'dataValidation',
+ errorType: 'instrumentation',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 6',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'testing',
+ properties: {
+ email: 'identified_user@gmail.com',
+ customFields: {
+ field1: 'val1',
+ },
+ },
+ 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.getdrip.com/v2/1809802/events',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ events: [
+ {
+ email: 'identified_user@gmail.com',
+ properties: {
+ field1: 'val1',
+ },
+ action: 'testing',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 7',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'testing',
+ properties: {
+ email: 'identified_user@gmail.com',
+ field1: 'val1',
+ },
+ 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.getdrip.com/v2/1809802/events',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ events: [
+ {
+ email: 'identified_user@gmail.com',
+ properties: {
+ field1: 'val1',
+ },
+ action: 'testing',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 8',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'testing',
+ properties: {
+ email: 'unidentified_user@gmail.com',
+ field1: 'val1',
+ },
+ integrations: {
+ All: true,
+ },
+ sentAt: '2019-10-14T09:03:22.563Z',
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ error: 'Error occurred while checking user : error response not found',
+ statTags: {
+ destType: 'DRIP',
+ errorCategory: 'network',
+ errorType: 'aborted',
+ feature: 'processor',
+ implementation: 'native',
+ module: 'destination',
+ },
+ statusCode: 400,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 9',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'checkout started',
+ properties: {
+ email: 'identified_user@gmail.com',
+ field1: 'val1',
+ affiliation: 'my_custom_order',
+ order_id: '456445746',
+ },
+ 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.getdrip.com/v3/1809802/shopper_activity/order',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ action: 'placed',
+ email: 'identified_user@gmail.com',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ order_id: '456445746',
+ provider: 'my_custom_order',
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 10',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'checkout started',
+ properties: {
+ email: 'identified_user@gmail.com',
+ field1: 'val1',
+ affiliation: 'my_custom_order',
+ order_id: '456445746',
+ products: [
+ {
+ name: 'shirt',
+ price: 11.16,
+ },
+ ],
+ },
+ 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.getdrip.com/v3/1809802/shopper_activity/order',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ action: 'placed',
+ email: 'identified_user@gmail.com',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ order_id: '456445746',
+ provider: 'my_custom_order',
+ items: [
+ {
+ name: 'shirt',
+ price: 11.16,
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 11',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '915194776',
+ enableUserCreation: false,
+ },
+ },
+ 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,
+ },
+ },
+ messageId: '84e26acc-56a5-4835-8233-591137fca468',
+ session_id: '3049dc4c-5a95-4ccd-a3e7-d74a7e411f22',
+ originalTimestamp: '2019-10-14T09:03:17.562Z',
+ type: 'track',
+ event: 'checkout',
+ properties: {
+ email: 'identified_user@gmail.com',
+ field1: 'val1',
+ customFields: {
+ field2: 'val2',
+ },
+ },
+ 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.getdrip.com/v2/1809802/events',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ events: [
+ {
+ action: 'checkout',
+ email: 'identified_user@gmail.com',
+ occurred_at: '2019-10-14T09:03:17.562Z',
+ properties: {
+ field2: 'val2',
+ },
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+ {
+ name: 'drip',
+ description: 'Test 12',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ destination: {
+ Config: {
+ apiKey: 'dummyApiKey',
+ accountId: '1809802',
+ campaignId: '',
+ enableUserCreation: true,
+ },
+ },
+ message: {
+ type: 'identify',
+ event: 'identify',
+ userId: 'user@1',
+ channel: 'mobile',
+ context: {
+ os: {
+ name: 'Android',
+ version: '13',
+ },
+ app: {
+ name: 'rudderstack',
+ build: '5425',
+ version: '2.4.1',
+ },
+ device: {
+ id: '8b048b94cbec4fcf',
+ name: 'o1q',
+ type: 'Android',
+ model: 'SM-G991U',
+ manufacturer: 'samsung',
+ },
+ locale: 'en-US',
+ traits: {
+ id: 'ruddertest@gmail.com',
+ email: 'ruddertest@gmail.com',
+ title: 'Social Impact Program Manager',
+ skills: [
+ {
+ id: 134,
+ name: 'Business Development',
+ tagGroupId: 2,
+ parentTagId: 134,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 96,
+ name: 'Communications',
+ tagGroupId: 2,
+ parentTagId: 96,
+ huddleVisible: true,
+ onboardVisible: true,
+ },
+ {
+ id: 489,
+ name: 'Construction',
+ tagGroupId: 2,
+ parentTagId: 489,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 671,
+ name: 'Data Analysis',
+ tagGroupId: 2,
+ parentTagId: 671,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 676,
+ name: 'Engineering: Mechanical',
+ tagGroupId: 2,
+ parentTagId: 676,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 189,
+ name: 'Operations',
+ tagGroupId: 2,
+ parentTagId: 189,
+ huddleVisible: true,
+ onboardVisible: true,
+ },
+ {
+ id: 194,
+ name: 'Product Management',
+ tagGroupId: 2,
+ parentTagId: 194,
+ huddleVisible: true,
+ onboardVisible: true,
+ },
+ {
+ id: 195,
+ name: 'Program Management',
+ tagGroupId: 2,
+ parentTagId: 195,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 199,
+ name: 'R&D',
+ tagGroupId: 2,
+ parentTagId: 199,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 102,
+ name: 'Sales',
+ tagGroupId: 2,
+ parentTagId: 102,
+ huddleVisible: true,
+ onboardVisible: true,
+ },
+ ],
+ userId: 'ruddertest@gmail.com',
+ address: 'Chicago, IL, USA',
+ industry: 'Education',
+ lastName: 'test',
+ lastname: 'test',
+ verified: false,
+ firstName: 'rudder',
+ firstname: 'rudder',
+ interests: [
+ {
+ id: 649,
+ name: 'Adaptation',
+ tagGroupId: 4,
+ parentTagId: 745,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 663,
+ name: 'Carbon removal and sequestration',
+ tagGroupId: 4,
+ parentTagId: 761,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 664,
+ name: 'Clean Energy and Energy Efficiency',
+ tagGroupId: 4,
+ parentTagId: 259,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 654,
+ name: 'Climate Science and Earth Systems',
+ tagGroupId: 4,
+ parentTagId: 744,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 650,
+ name: 'Corporate Sustainability',
+ tagGroupId: 4,
+ parentTagId: 650,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 651,
+ name: 'Finance & Risk',
+ tagGroupId: 4,
+ parentTagId: 651,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 652,
+ name: 'Food and Agriculture',
+ tagGroupId: 4,
+ parentTagId: 652,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ {
+ id: 665,
+ name: 'Transport',
+ tagGroupId: 4,
+ parentTagId: 665,
+ huddleVisible: false,
+ onboardVisible: false,
+ },
+ ],
+ isDeleted: false,
+ anonymousId: '8b048b94cbec4fcf',
+ jobFunction: 'Program manager',
+ reminderType: 1,
+ jobPreferences: {
+ motivationStr:
+ 'I am looking for a job in climate change.,I want to learn about climate change.,I’d like to connect with other climate enthusiasts.',
+ searchUrgency: 'As soon as possible',
+ maxCompensation: 0,
+ minCompensation: 0,
+ },
+ visibilityType: 'public',
+ 'Linkedin-Signin': true,
+ hubbleOnboarded: false,
+ sharePreference: false,
+ notificationType: 0,
+ shortDescription: 'Social Impact Program Manager',
+ jobProfileComplete: false,
+ noOfMonthExperience: 0,
+ onboarding_completed: 'Yes',
+ },
+ library: {
+ name: 'com.rudderstack.android.sdk.core',
+ version: '1.8.1',
+ },
+ timezone: 'America/Chicago',
+ sessionId: 1681096824,
+ userAgent: 'Dalvik/2.1.0 (Linux; U; Android 13; SM-G991U Build/TP1A.220624.014)',
+ },
+ rudderId: 'f701966c-5568-4500-92ba-4e9023c8fe31',
+ messageId: '77b53247-177f-4ca3-a6b4-aa7558fec280',
+ request_ip: '75.209.176.135',
+ anonymousId: '8b048b94cbec4fcf',
+ integrations: {
+ All: true,
+ },
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://api.getdrip.com/v2/1809802/subscribers',
+ headers: {
+ Authorization: 'Basic ZHVtbXlBcGlLZXk=',
+ 'Content-Type': 'application/json',
+ },
+ params: {},
+ body: {
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {},
+ JSON: {
+ subscribers: [
+ {
+ email: 'ruddertest@gmail.com',
+ user_id: 'user@1',
+ address1: 'Chicago, IL, USA',
+ last_name: 'test',
+ first_name: 'rudder',
+ custom_fields: {
+ title: 'Social Impact Program Manager',
+ industry: 'Education',
+ verified: false,
+ isDeleted: false,
+ jobFunction: 'Program manager',
+ reminderType: 1,
+ jobPreferences: {
+ motivationStr:
+ 'I am looking for a job in climate change.,I want to learn about climate change.,I’d like to connect with other climate enthusiasts.',
+ searchUrgency: 'As soon as possible',
+ maxCompensation: 0,
+ minCompensation: 0,
+ },
+ visibilityType: 'public',
+ hubbleOnboarded: false,
+ sharePreference: false,
+ notificationType: 0,
+ shortDescription: 'Social Impact Program Manager',
+ jobProfileComplete: false,
+ noOfMonthExperience: 0,
+ onboarding_completed: 'Yes',
+ },
+ },
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/facebook_conversions/processor/data.ts b/test/integrations/destinations/facebook_conversions/processor/data.ts
index beb7eb32aa0..6eb90942a77 100644
--- a/test/integrations/destinations/facebook_conversions/processor/data.ts
+++ b/test/integrations/destinations/facebook_conversions/processor/data.ts
@@ -1434,4 +1434,104 @@ export const data = [
},
mockFns: defaultMockFns,
},
+ {
+ name: 'facebook_conversions',
+ description: 'Track event with standard event order completed with content_type in properties',
+ feature: 'processor',
+ module: 'destination',
+ version: 'v0',
+ input: {
+ request: {
+ body: [
+ {
+ message: {
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ channel: 'web',
+ context: {
+ traits: {
+ email: ' aBc@gmail.com ',
+ address: {
+ zip: 1234,
+ },
+ anonymousId: 'c82cbdff-e5be-4009-ac78-cdeea09ab4b1',
+ },
+ },
+ event: 'order completed',
+ integrations: {
+ All: true,
+ },
+ message_id: 'a80f82be-9bdc-4a9f-b2a5-15621ee41df8',
+ properties: {
+ content_type: 'product_group',
+ revenue: 400,
+ additional_bet_index: 0,
+ products: [
+ {
+ product_id: 1234,
+ quantity: 5,
+ price: 55,
+ },
+ ],
+ },
+ timestamp: '2023-11-12T15:46:51.693229+05:30',
+ type: 'track',
+ },
+ destination: {
+ Config: {
+ limitedDataUsage: true,
+ blacklistPiiProperties: [
+ {
+ blacklistPiiProperties: '',
+ blacklistPiiHash: false,
+ },
+ ],
+ accessToken: '09876',
+ datasetId: 'dummyID',
+ eventsToEvents: [
+ {
+ from: '',
+ to: '',
+ },
+ ],
+ removeExternalId: true,
+ actionSource: 'website',
+ },
+ Enabled: true,
+ },
+ },
+ ],
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: [
+ {
+ output: {
+ version: '1',
+ type: 'REST',
+ method: 'POST',
+ endpoint: 'https://graph.facebook.com/v18.0/dummyID/events?access_token=09876',
+ headers: {},
+ params: {},
+ body: {
+ JSON: {},
+ XML: {},
+ JSON_ARRAY: {},
+ FORM: {
+ data: [
+ '{"user_data":{"em":"48ddb93f0b30c475423fe177832912c5bcdce3cc72872f8051627967ef278e08","zp":"03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4"},"event_name":"Purchase","event_time":1699784211,"action_source":"website","custom_data":{"content_type":"product_group","revenue":400,"additional_bet_index":0,"products":[{"product_id":1234,"quantity":5,"price":55}],"content_ids":[1234],"contents":[{"id":1234,"quantity":5,"item_price":55}],"currency":"USD","value":400,"num_items":1}}',
+ ],
+ },
+ },
+ files: {},
+ userId: '',
+ },
+ statusCode: 200,
+ },
+ ],
+ },
+ },
+ mockFns: defaultMockFns,
+ },
];
diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts
new file mode 100644
index 00000000000..9ac709978d6
--- /dev/null
+++ b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts
@@ -0,0 +1,258 @@
+import { generateMetadata, generateProxyV1Payload } from '../../../testUtils';
+import { ProxyV1TestData } from '../../../testTypes';
+import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+
+export const testFormData = {
+ data: [
+ '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654772112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}',
+ ],
+};
+export const statTags = {
+ destType: 'FACEBOOK_PIXEL',
+ errorCategory: 'network',
+ destinationId: 'default-destinationId',
+ workspaceId: 'default-workspaceId',
+ errorType: 'aborted',
+ feature: 'dataDelivery',
+ implementation: 'native',
+ module: 'destination',
+};
+export const testScenariosForV1API: ProxyV1TestData[] = [
+ {
+ id: 'facebook_pixel_v1_scenario_1',
+ name: 'facebook_pixel',
+ description: 'app event fails due to access token error',
+ successCriteria: 'Should return 400 with invalid access token error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Invalid OAuth 2.0 access token',
+ statTags: {
+ ...statTags,
+ errorCategory: 'dataValidation',
+ errorType: 'configuration',
+ meta: 'accessTokenExpired',
+ },
+ response: [
+ {
+ error: 'Invalid OAuth 2.0 access token',
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_scenario_2',
+ name: 'facebook_pixel',
+ description: 'app event sent successfully',
+ successCriteria: 'Should return 200',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`,
+ params: {
+ destination: 'facebook_pixel',
+ },
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 200,
+ message: 'Request Processed Successfully',
+ response: [
+ {
+ error: '{"events_received":1,"fbtrace_id":"facebook_trace_id"}',
+ statusCode: 200,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_scenario_3',
+ name: 'facebook_pixel',
+ description: 'app event fails due to invalid timestamp',
+ successCriteria: 'Should return 400 with invalid timestamp error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Event Timestamp Too Old',
+ statTags,
+ response: [
+ {
+ error: 'Event Timestamp Too Old',
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_scenario_4',
+ name: 'facebook_pixel',
+ description: 'app event fails due to missing permissions',
+ successCriteria: 'Should return 400 with missing permissions error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message:
+ "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation",
+ statTags,
+ response: [
+ {
+ error:
+ "Object with ID 'PIXEL_ID' / 'DATASET_ID' / 'AUDIENCE_ID' does not exist, cannot be loaded due to missing permissions, or does not support this operation",
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_scenario_5',
+ name: 'facebook_pixel',
+ description: 'app event fails due to invalid parameter',
+ successCriteria: 'Should return 400 with Invalid parameter error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Invalid Parameter',
+ statTags,
+ response: [
+ {
+ error: 'Invalid Parameter',
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_scenario_6',
+ name: 'facebook_pixel',
+ description: 'app event fails due to invalid parameter',
+ successCriteria: 'Should return 400 with Invalid parameter error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Invalid Parameter',
+ statTags,
+ response: [
+ {
+ error: 'Invalid Parameter',
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts
index eb9ce344e0a..dcc633e1a85 100644
--- a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts
+++ b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts
@@ -1,6 +1,15 @@
import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+import { testScenariosForV1API, testFormData, statTags as baseStatTags } from './business';
+import { otherScenariosV1 } from './other';
+import { oauthScenariosV1 } from './oauth';
-export const data = [
+const statTags = {
+ ...baseStatTags,
+ destinationId: 'Non-determininable',
+ workspaceId: 'Non-determininable',
+};
+
+export const v0TestData = [
{
name: 'facebook_pixel',
description: 'Test 0',
@@ -12,11 +21,7 @@ export const data = [
body: {
body: {
XML: {},
- FORM: {
- data: [
- '{"user_data":{"external_id":"c58f05b5e3cc4796f3181cf07349d306547c00b20841a175b179c6860e6a34ab","client_ip_address":"32.122.223.26","client_user_agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1"},"event_name":"Checkout Step Viewed","event_time":1654773112,"event_source_url":"https://www.my.kaiser.com/checkout","event_id":"4f002656-a7b2-4c17-b9bd-8caa5a29190a","custom_data":{"checkout_id":"26SF29B","site":"www.my.kaiser.com","step":1}}',
- ],
- },
+ FORM: testFormData,
JSON: {},
JSON_ARRAY: {},
},
@@ -43,22 +48,18 @@ export const data = [
message: 'Invalid OAuth 2.0 access token',
destinationResponse: {
error: {
- message: 'The access token could not be decrypted',
- type: 'OAuthException',
code: 190,
fbtrace_id: 'fbpixel_trace_id',
+ message: 'The access token could not be decrypted',
+ type: 'OAuthException',
},
status: 500,
},
statTags: {
- destType: 'FACEBOOK_PIXEL',
- errorCategory: 'network',
- destinationId: 'Non-determininable',
- workspaceId: 'Non-determininable',
- errorType: 'aborted',
- feature: 'dataDelivery',
- implementation: 'native',
- module: 'destination',
+ ...statTags,
+ errorCategory: 'dataValidation',
+ errorType: 'configuration',
+ meta: 'accessTokenExpired',
},
},
},
@@ -76,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: {},
},
@@ -125,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: {},
},
@@ -168,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,
},
},
},
@@ -194,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: {},
},
@@ -233,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',
},
},
},
@@ -258,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: {},
},
@@ -299,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,
},
},
},
@@ -325,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: {},
},
@@ -364,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,
},
},
},
@@ -390,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: {},
},
@@ -429,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,
},
},
},
@@ -455,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: {},
},
@@ -494,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,
},
},
},
@@ -520,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: {},
},
@@ -560,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',
},
},
@@ -576,3 +488,10 @@ export const data = [
},
},
];
+
+export const data = [
+ ...v0TestData,
+ ...testScenariosForV1API,
+ ...otherScenariosV1,
+ ...oauthScenariosV1,
+];
diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts
new file mode 100644
index 00000000000..c6d938c627b
--- /dev/null
+++ b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts
@@ -0,0 +1,45 @@
+import { testFormData, statTags } from './business';
+import { generateProxyV1Payload, generateMetadata } from '../../../testUtils';
+import { ProxyV1TestData } from '../../../testTypes';
+import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+
+export const oauthScenariosV1: ProxyV1TestData[] = [
+ {
+ id: 'facebook_pixel_v1_oauth_scenario_1',
+ name: 'facebook_pixel',
+ description: 'app event fails due to missing permissions',
+ successCriteria: 'Should return 400 with missing permissions error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 400,
+ message: 'Capability or permissions issue.',
+ statTags,
+ response: [
+ {
+ error: 'Capability or permissions issue.',
+ statusCode: 400,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts
new file mode 100644
index 00000000000..e25cc8e07c4
--- /dev/null
+++ b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts
@@ -0,0 +1,93 @@
+import { generateMetadata, generateProxyV1Payload } from '../../../testUtils';
+import { ProxyV1TestData } from '../../../testTypes';
+import { VERSION } from '../../../../../src/v0/destinations/facebook_pixel/config';
+import { testFormData, statTags } from './business';
+
+export const otherScenariosV1: ProxyV1TestData[] = [
+ {
+ id: 'facebook_pixel_v1_other_scenario_1',
+ name: 'facebook_pixel',
+ description: 'user update request is throttled due to too many calls',
+ successCriteria: 'Should return 429 with message there have been too many calls',
+ scenario: 'Framework',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`,
+ params: {
+ destination: 'facebook_pixel',
+ },
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 429,
+ message: 'API User Too Many Calls',
+ statTags: {
+ ...statTags,
+ errorType: 'throttled',
+ },
+ response: [
+ {
+ error: 'API User Too Many Calls',
+ statusCode: 429,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: 'facebook_pixel_v1_other_scenario_2',
+ name: 'facebook_pixel',
+ description: 'app event fails due to Unhandled random error',
+ successCriteria: 'Should return 500 with Unhandled random error',
+ scenario: 'Business',
+ feature: 'dataDelivery',
+ module: 'destination',
+ version: 'v1',
+ input: {
+ request: {
+ body: generateProxyV1Payload({
+ endpoint: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`,
+ FORM: testFormData,
+ }),
+ method: 'POST',
+ },
+ },
+ output: {
+ response: {
+ status: 200,
+ body: {
+ output: {
+ status: 500,
+ message: 'Unhandled random error',
+ statTags: {
+ ...statTags,
+ errorType: 'retryable',
+ meta: 'unhandledStatusCode',
+ },
+ response: [
+ {
+ error: 'Unhandled random error',
+ statusCode: 500,
+ metadata: generateMetadata(1),
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+];
diff --git a/test/integrations/destinations/facebook_pixel/network.ts b/test/integrations/destinations/facebook_pixel/network.ts
index 05b3a05fd0b..a61fa44eab8 100644
--- a/test/integrations/destinations/facebook_pixel/network.ts
+++ b/test/integrations/destinations/facebook_pixel/network.ts
@@ -1,4 +1,4 @@
-import { data } from './dataDelivery/data';
+import { testFormData } from './dataDelivery/business';
import { getFormData } from '../../../../src/adapters/network';
import { VERSION } from '../../../../src/v0/destinations/facebook_pixel/config';
@@ -6,7 +6,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_access_token`,
- data: getFormData(data[0].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -26,7 +26,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_timestamp_correct_access_token`,
- data: getFormData(data[2].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -51,7 +51,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=throttled_valid_access_token`,
- data: getFormData(data[3].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -71,7 +71,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=invalid_account_id_valid_access_token`,
- data: getFormData(data[4].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -93,7 +93,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=not_found_access_token`,
- data: getFormData(data[5].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -114,7 +114,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234570/events?access_token=valid_access_token`,
- data: getFormData(data[6].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -135,7 +135,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234571/events?access_token=valid_access_token`,
- data: getFormData(data[7].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -156,7 +156,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234572/events?access_token=valid_access_token_unhandled_response`,
- data: getFormData(data[8].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
@@ -177,7 +177,7 @@ export const networkCallsData = [
{
httpReq: {
url: `https://graph.facebook.com/${VERSION}/1234567891234567/events?access_token=my_access_token`,
- data: getFormData(data[1].input.request.body.body.FORM).toString(),
+ data: getFormData(testFormData).toString(),
params: { destination: 'facebook_pixel' },
headers: { 'User-Agent': 'RudderLabs' },
method: 'POST',
diff --git a/test/integrations/destinations/facebook_pixel/processor/configLevelFeaturesTestData.ts b/test/integrations/destinations/facebook_pixel/processor/configLevelFeaturesTestData.ts
new file mode 100644
index 00000000000..5a7beb41740
--- /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 557bc7066c3..6af7e3cd9ba 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