diff --git a/.github/workflows/dt-test-and-report-code-coverage.yml b/.github/workflows/dt-test-and-report-code-coverage.yml index 2c76898882..12bd9ac78b 100644 --- a/.github/workflows/dt-test-and-report-code-coverage.yml +++ b/.github/workflows/dt-test-and-report-code-coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Checkout uses: actions/checkout@v4.2.1 with: - fetch-depth: 1 + fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4.0.4 diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index 8eeafc376e..cdf27f7832 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -78,6 +78,7 @@ jobs: runs-on: ubuntu-latest needs: [generate-tag-names, build-transformer-image, build-user-transformer-image] env: + TF_IMAGE_REPOSITORY: rudderstack/rudder-transformer TAG_NAME: ${{ needs.generate-tag-names.outputs.tag_name }} UT_TAG_NAME: ${{ needs.generate-tag-names.outputs.tag_name_ut }} steps: @@ -113,6 +114,7 @@ jobs: git checkout -b $BRANCH_NAME cd helm-charts/shared-services/per-az/environment/staging + yq eval -i ".rudder-transformer.image.repository=\"$TF_IMAGE_REPOSITORY\"" staging.yaml yq eval -i ".rudder-transformer.image.tag=\"$TAG_NAME\"" staging.yaml yq eval -i ".user-transformer.image.tag=\"$TAG_NAME\"" staging.yaml git add staging.yaml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 4bd66285bd..604ff1eaee 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -35,8 +35,8 @@ jobs: - name: Filter JS/TS Files run: | - echo "${{ steps.files.outputs.added_modified }}" | tr ' ' '\n' | grep -E '\.(js|ts|jsx|tsx)$' > changed_files.txt - if [ ! -s changed_files.txt ]; then + changed_files=$(echo "${{ steps.files.outputs.added_modified }}" | tr ' ' '\n' | grep -E '\.(js|ts|jsx|tsx)$' || true) + if [ -z "$changed_files" ]; then echo "No JS/TS files to format or lint." exit 0 fi diff --git a/src/util/prometheus.js b/src/util/prometheus.js index 1585f0b3a6..2a3a1fb22a 100644 --- a/src/util/prometheus.js +++ b/src/util/prometheus.js @@ -532,6 +532,12 @@ class Prometheus { type: 'gauge', labelNames: ['destination_id'], }, + { + name: 'mailjet_packing_size', + help: 'mailjet_packing_size', + type: 'gauge', + labelNames: ['group'], + }, { name: 'hs_batch_size', help: 'hs_batch_size', diff --git a/src/v0/destinations/google_adwords_offline_conversions/transform.js b/src/v0/destinations/google_adwords_offline_conversions/transform.js index 76b12587cd..2648f03e8a 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/transform.js +++ b/src/v0/destinations/google_adwords_offline_conversions/transform.js @@ -18,6 +18,7 @@ const { getClickConversionPayloadAndEndpoint, getConsentsDataFromIntegrationObj, getCallConversionPayload, + updateConversion, } = require('./utils'); const helper = require('./helper'); @@ -48,6 +49,9 @@ const getConversions = (message, metadata, { Config }, event, conversionType) => filteredCustomerId, eventLevelConsentsData, ); + convertedPayload.payload.conversions[0] = updateConversion( + convertedPayload.payload.conversions[0], + ); payload = convertedPayload.payload; endpoint = convertedPayload.endpoint; } else if (conversionType === 'store') { diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 89fe609df9..2d47095eea 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -317,6 +317,35 @@ const getStoreConversionPayload = (message, Config, event) => { return payload; }; +const hasClickId = (conversion) => { + const { gbraid, wbraid, gclid } = conversion; + return gclid || wbraid || gbraid; +}; +const populateUserIdentifier = ({ email, phone, properties, payload, UserIdentifierSource }) => { + const copiedPayload = cloneDeep(payload); + // userIdentifierSource + // if userIdentifierSource doesn't exist in properties + // then it is taken from the webapp config + if (!properties.userIdentifierSource && UserIdentifierSource !== 'none') { + set( + copiedPayload, + 'conversions[0].userIdentifiers[0].userIdentifierSource', + UserIdentifierSource, + ); + // one of email or phone must be provided when none of gclid, wbraid and gbraid provided + } + if (!email && !phone) { + if (!hasClickId(copiedPayload.conversions[0])) { + throw new InstrumentationError( + `Either an email address or a phone number is required for user identification when none of gclid, wbraid, or gbraid is provided.`, + ); + } else { + // we are deleting userIdentifiers if any one of gclid, wbraid and gbraid is there but email or phone is not present + delete copiedPayload.conversions[0].userIdentifiers; + } + } + return copiedPayload; +}; const getClickConversionPayloadAndEndpoint = ( message, Config, @@ -335,7 +364,7 @@ const getClickConversionPayloadAndEndpoint = ( updatedClickMapping = removeHashToSha256TypeFromMappingJson(updatedClickMapping); } - const payload = constructPayload(message, updatedClickMapping); + let payload = constructPayload(message, updatedClickMapping); const endpoint = CLICK_CONVERSION.replace(':customerId', filteredCustomerId); @@ -353,17 +382,8 @@ const getClickConversionPayloadAndEndpoint = ( set(payload, 'conversions[0].cartData.items', itemList); } - // userIdentifierSource - // if userIdentifierSource doesn't exist in properties - // then it is taken from the webapp config - if (!properties.userIdentifierSource && UserIdentifierSource !== 'none') { - set(payload, 'conversions[0].userIdentifiers[0].userIdentifierSource', UserIdentifierSource); + payload = populateUserIdentifier({ email, phone, properties, payload, UserIdentifierSource }); - // one of email or phone must be provided - if (!email && !phone) { - throw new InstrumentationError(`Either of email or phone is required for user identifier`); - } - } // either of email or phone should be passed // defaultUserIdentifier depends on the webapp configuration // Ref - https://developers.google.com/google-ads/api/rest/reference/rest/v11/customers/uploadClickConversions#ClickConversion @@ -411,6 +431,25 @@ const getConsentsDataFromIntegrationObj = (message) => { return integrationObj?.consents || {}; }; +/** + * remove redundant ids + * @param {*} conversionCopy + */ +const updateConversion = (conversion) => { + const conversionCopy = cloneDeep(conversion); + if (conversionCopy.gclid) { + delete conversionCopy.wbraid; + delete conversionCopy.gbraid; + } + if (conversionCopy.wbraid && conversionCopy.gbraid) { + throw new InstrumentationError(`You can't use both wbraid and gbraid.`); + } + if (conversionCopy.wbraid || conversionCopy.gbraid) { + delete conversionCopy.userIdentifiers; + } + return conversionCopy; +}; + module.exports = { validateDestinationConfig, generateItemListFromProducts, @@ -423,4 +462,5 @@ module.exports = { getExisitingUserIdentifier, getConsentsDataFromIntegrationObj, getCallConversionPayload, + updateConversion, }; 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 2d1863413c..b6c6653782 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.test.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.test.js @@ -244,7 +244,9 @@ describe('getClickConversionPayloadAndEndpoint util tests', () => { }; expect(() => getClickConversionPayloadAndEndpoint(fittingPayload, config, '9625812972'), - ).toThrow('Either of email or phone is required for user identifier'); + ).toThrow( + 'Either an email address or a phone number is required for user identification when none of gclid, wbraid, or gbraid is provided.', + ); }); it('finaliseConsent', () => { diff --git a/src/v0/destinations/mailjet/transform.js b/src/v0/destinations/mailjet/transform.js index 78b4f766d1..0a742b4cf7 100644 --- a/src/v0/destinations/mailjet/transform.js +++ b/src/v0/destinations/mailjet/transform.js @@ -1,5 +1,6 @@ const lodash = require('lodash'); const { TransformationError, InstrumentationError } = require('@rudderstack/integrations-lib'); +const stats = require('../../../util/stats'); const { getSuccessRespEvents, defaultRequestConfig, @@ -105,6 +106,9 @@ const batchEvents = (successRespList) => { const eventChunks = lodash.chunk(eventGroups[combination], MAX_BATCH_SIZE); // eventChunks = [[e1,e2,e3,..batchSize],[e1,e2,e3,..batchSize]..] eventChunks.forEach((chunk) => { + stats.gauge('mailjet_packing_size', chunk.length, { + group: combination, + }); const batchEventResponse = generateBatchedPaylaodForArray(chunk, combination); batchedResponseList.push( getSuccessRespEvents( diff --git a/src/v0/util/facebookUtils/networkHandler.js b/src/v0/util/facebookUtils/networkHandler.js index fbb7899efe..46ac59a07a 100644 --- a/src/v0/util/facebookUtils/networkHandler.js +++ b/src/v0/util/facebookUtils/networkHandler.js @@ -85,7 +85,10 @@ const errorDetailsMap = { "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", ) .build(), - default: new ErrorDetailsExtractorBuilder().setStatus(400).setMessageField('message').build(), + default: new ErrorDetailsExtractorBuilder() + .setStatus(400) + .setMessageField('error_user_msg') + .build(), }, 1: { // An unknown error occurred. @@ -107,9 +110,7 @@ const errorDetailsMap = { .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', - ) + .setMessageField('error_user_msg') .build(), 463: new ErrorDetailsExtractorBuilder() @@ -117,56 +118,56 @@ const errorDetailsMap = { .setStat({ [TAG_NAMES.ERROR_TYPE]: ERROR_TYPES.AUTH, }) - .setMessageField('message') + .setMessageField('error_user_msg') .build(), default: new ErrorDetailsExtractorBuilder() .setStatus(400) .setStat({ [TAG_NAMES.ERROR_TYPE]: ERROR_TYPES.AUTH, }) - .setMessage('Invalid OAuth 2.0 access token') + .setMessageField('error_user_msg') .build(), }, 3: { default: new ErrorDetailsExtractorBuilder() .setStatus(400) - .setMessage('Capability or permissions issue.') + .setMessageField('error_user_msg') .build(), }, 2: { default: new ErrorDetailsExtractorBuilder() .setStatus(500) - .setMessage('Temporary issue due to downtime.') + .setMessageField('error_user_msg') .build(), }, 341: { default: new ErrorDetailsExtractorBuilder() .setStatus(500) - .setMessage('Application limit reached: Temporary issue due to downtime or throttling') + .setMessageField('error_user_msg') .build(), }, 368: { default: new ErrorDetailsExtractorBuilder() .setStatus(500) - .setMessage('Temporarily blocked for policies violations.') + .setMessageField('error_user_msg') .build(), }, 5000: { default: new ErrorDetailsExtractorBuilder() .setStatus(500) - .setMessage('Unknown Error Code') + .setMessageField('error_user_msg') .build(), }, 4: { default: new ErrorDetailsExtractorBuilder() .setStatus(429) - .setMessage('API Too Many Calls') + .setMessageField('error_user_msg') .build(), }, 17: { default: new ErrorDetailsExtractorBuilder() .setStatus(429) - .setMessage('API User Too Many Calls') + .setMessageField('error_user_msg') .build(), }, // facebook custom audience related error codes @@ -176,9 +177,7 @@ const errorDetailsMap = { 294: { default: new ErrorDetailsExtractorBuilder() .setStatus(400) - .setMessage( - 'Missing permission. Please make sure you have ads_management permission and the application is included in the allowlist', - ) + .setMessageField('error_user_msg') .build(), }, 1487301: { @@ -214,7 +213,10 @@ const errorDetailsMap = { .build(), }, 200: { - default: new ErrorDetailsExtractorBuilder().setStatus(403).setMessageField('message').build(), + default: new ErrorDetailsExtractorBuilder() + .setStatus(403) + .setMessageField('error_user_msg') + .build(), }, 21009: { default: new ErrorDetailsExtractorBuilder().setStatus(500).setMessageField('message').build(), @@ -236,9 +238,11 @@ const getStatus = (error) => { const isErrorDetailEmpty = isEmpty(errorDetail); if (isErrorDetailEmpty) { // Unhandled error response + const errorMessage = get(error?.error || error, 'error_user_msg'); return { status: errorStatus, stats: { [TAG_NAMES.META]: METADATA.UNHANDLED_STATUS_CODE }, + errorMessage: errorMessage || JSON.stringify(error), }; } errorStatus = errorDetail.status; diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js index c31bc74bf1..292a980a93 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideTransform.js @@ -1,18 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ const lodash = require('lodash'); const get = require('get-value'); -// const { RedisError } = require('@rudderstack/integrations-lib'); const stats = require('../../../../util/stats'); -const { - getShopifyTopic, - // createPropertiesForEcomEvent, - extractEmailFromPayload, - getAnonymousIdAndSessionId, - // getHashLineItems, -} = require('../../../../v0/sources/shopify/util'); -// const logger = require('../../../logger'); +const { getShopifyTopic, extractEmailFromPayload } = require('../../../../v0/sources/shopify/util'); const { removeUndefinedAndNullValues, isDefinedAndNotNull } = require('../../../../v0/util'); -// const { RedisDB } = require('../../../util/redis/redisConnector'); const Message = require('../../../../v0/sources/message'); const { EventType } = require('../../../../constants'); const { @@ -28,6 +19,7 @@ const { const { createPropertiesForEcomEventFromWebhook, getProductsFromLineItems, + getAnonymousIdFromAttributes, } = require('./serverSideUtlis'); const NO_OPERATION_SUCCESS = { @@ -128,12 +120,9 @@ const processEvent = async (inputEvent, metricMetadata) => { message.setProperty('traits.email', email); } } + // attach anonymousId if the event is track event using note_attributes if (message.type !== EventType.IDENTIFY) { - const { anonymousId } = await getAnonymousIdAndSessionId( - message, - { shopifyTopic, ...metricMetadata }, - null, - ); + const anonymousId = getAnonymousIdFromAttributes(event); if (isDefinedAndNotNull(anonymousId)) { message.setProperty('anonymousId', anonymousId); } diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js index a611d1d8dc..4a2839103b 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtils.test.js @@ -1,6 +1,7 @@ const { getProductsFromLineItems, createPropertiesForEcomEventFromWebhook, + getAnonymousIdFromAttributes, } = require('./serverSideUtlis'); const { constructPayload } = require('../../../../v0/util'); @@ -109,4 +110,21 @@ describe('serverSideUtils.js', () => { }); }); }); + + describe('getAnonymousIdFromAttributes', () => { + // Handles empty note_attributes array gracefully + it('should return null when note_attributes is an empty array', async () => { + const event = { note_attributes: [] }; + const result = await getAnonymousIdFromAttributes(event); + expect(result).toBeNull(); + }); + + it('get anonymousId from noteAttributes', async () => { + const event = { + note_attributes: [{ name: 'rudderAnonymousId', value: '123456' }], + }; + const result = await getAnonymousIdFromAttributes(event); + expect(result).toEqual('123456'); + }); + }); }); diff --git a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js index eed03de71f..951fa479e4 100644 --- a/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js +++ b/src/v1/sources/shopify/webhookTransformations/serverSideUtlis.js @@ -1,5 +1,5 @@ +const { isDefinedAndNotNull } = require('@rudderstack/integrations-lib'); const { constructPayload } = require('../../../../v0/util'); - const { lineItemsMappingJSON, productMappingJSON, @@ -17,7 +17,6 @@ const getProductsFromLineItems = (lineItems, mapping) => { } const products = []; lineItems.forEach((lineItem) => { - // const product = constructPayload(lineItem, lineItemsMappingJSON); const product = constructPayload(lineItem, mapping); products.push(product); }); @@ -39,7 +38,24 @@ const createPropertiesForEcomEventFromWebhook = (message) => { return mappedPayload; }; +/** + * Returns the anonymousId from the noteAttributes array in the webhook event + * @param {Object} event + * @returns {String} anonymousId + */ +const getAnonymousIdFromAttributes = (event) => { + if (!isDefinedAndNotNull(event) || !isDefinedAndNotNull(event.note_attributes)) { + return null; // Return early if event or note_attributes is invalid + } + + const noteAttributes = event.note_attributes; + const rudderAnonymousIdObject = noteAttributes.find((attr) => attr.name === 'rudderAnonymousId'); + + return rudderAnonymousIdObject ? rudderAnonymousIdObject.value : null; +}; + module.exports = { createPropertiesForEcomEventFromWebhook, getProductsFromLineItems, + getAnonymousIdFromAttributes, }; diff --git a/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js b/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js index 0c1007f311..46ae59e0cf 100644 --- a/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js +++ b/src/v1/sources/shopify/webpixelTransformations/pixelUtils.js @@ -14,7 +14,7 @@ const { function getNestedValue(object, path) { const keys = path.split('.'); - return keys.reduce((nestedObject, key) => nestedObject && nestedObject[key], object); + return keys.reduce((nestedObject, key) => nestedObject?.[key], object); } function setNestedValue(object, path, value) { diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts index 1b425ac5fa..2b4af61ac3 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/business.ts @@ -42,7 +42,7 @@ export const testScenariosForV1API: ProxyV1TestData[] = [ body: { output: { status: 400, - message: 'Invalid OAuth 2.0 access token', + message: 'The access token could not be decrypted', statTags: { ...statTags, errorCategory: 'dataValidation', @@ -51,7 +51,7 @@ export const testScenariosForV1API: ProxyV1TestData[] = [ }, response: [ { - error: 'Invalid OAuth 2.0 access token', + error: 'The access token could not be decrypted', 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 3366e62c5a..0e004c1183 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/data.ts @@ -45,12 +45,13 @@ export const v0TestData = [ body: { output: { status: 400, - message: 'Invalid OAuth 2.0 access token', + message: 'The access token could not be decrypted', destinationResponse: { error: { code: 190, fbtrace_id: 'fbpixel_trace_id', message: 'The access token could not be decrypted', + error_user_msg: 'The access token could not be decrypted', type: 'OAuthException', }, status: 500, @@ -202,7 +203,7 @@ export const v0TestData = [ body: { output: { status: 429, - message: 'API User Too Many Calls', + message: 'User request limit reached', destinationResponse: { error: { message: 'User request limit reached', @@ -414,7 +415,7 @@ export const v0TestData = [ body: { output: { status: 400, - message: 'Capability or permissions issue.', + message: 'Some error in permission', destinationResponse: { error: { message: 'Some error in permission', @@ -466,7 +467,8 @@ export const v0TestData = [ body: { output: { status: 500, - message: 'Unhandled random error', + message: + '{"message":"Unhandled random error","type":"RandomException","code":5,"error_subcode":12,"fbtrace_id":"facebook_px_trace_id_10"}', destinationResponse: { error: { message: 'Unhandled random error', @@ -488,6 +490,66 @@ export const v0TestData = [ }, }, + { + name: 'facebook_pixel', + description: 'Test 10: should handle error with code: 100 & subcode: 2804009', + feature: 'dataDelivery', + module: 'destination', + version: 'v0', + input: { + request: { + body: { + body: { + XML: {}, + FORM: testFormData, + JSON: {}, + JSON_ARRAY: {}, + }, + type: 'REST', + files: {}, + method: 'POST', + userId: '', + headers: {}, + version: '1', + endpoint: `https://graph.facebook.com/${VERSION}/12345678912804009/events?access_token=2804009_valid_access_token`, + params: { + destination: 'facebook_pixel', + }, + }, + method: 'POST', + }, + }, + output: { + response: { + status: 400, + body: { + output: { + status: 400, + message: + 'Your purchase event doesn’t include a value parameter. Enter a value. For example: 1.99', + destinationResponse: { + error: { + message: 'Invalid parameter', + type: 'OAuthException', + code: 100, + error_user_msg: + 'Your purchase event doesn’t include a value parameter. Enter a value. For example: 1.99', + error_user_title: 'Missing Value for Purchase Event', + error_subcode: 2804009, + is_transient: false, + fbtrace_id: 'AP4G-xxxxxxxx', + }, + status: 400, + }, + statTags: { + ...statTags, + errorType: 'aborted', + }, + }, + }, + }, + }, + }, { name: 'facebook_pixel', description: 'Test 9: should handle error with code: 21009', diff --git a/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts index c6d938c627..bb4fd582f0 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/oauth.ts @@ -28,11 +28,11 @@ export const oauthScenariosV1: ProxyV1TestData[] = [ body: { output: { status: 400, - message: 'Capability or permissions issue.', + message: 'Some error in permission', statTags, response: [ { - error: 'Capability or permissions issue.', + error: 'Some error in permission', 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 index e25cc8e07c..154c6f75ad 100644 --- a/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts +++ b/test/integrations/destinations/facebook_pixel/dataDelivery/other.ts @@ -31,14 +31,14 @@ export const otherScenariosV1: ProxyV1TestData[] = [ body: { output: { status: 429, - message: 'API User Too Many Calls', + message: 'User request limit reached', statTags: { ...statTags, errorType: 'throttled', }, response: [ { - error: 'API User Too Many Calls', + error: 'User request limit reached', statusCode: 429, metadata: generateMetadata(1), }, @@ -72,7 +72,8 @@ export const otherScenariosV1: ProxyV1TestData[] = [ body: { output: { status: 500, - message: 'Unhandled random error', + message: + '{"message":"Unhandled random error","type":"RandomException","code":5,"error_subcode":12,"fbtrace_id":"facebook_px_trace_id_10"}', statTags: { ...statTags, errorType: 'retryable', @@ -80,7 +81,8 @@ export const otherScenariosV1: ProxyV1TestData[] = [ }, response: [ { - error: 'Unhandled random error', + error: + '{"message":"Unhandled random error","type":"RandomException","code":5,"error_subcode":12,"fbtrace_id":"facebook_px_trace_id_10"}', statusCode: 500, metadata: generateMetadata(1), }, diff --git a/test/integrations/destinations/facebook_pixel/network.ts b/test/integrations/destinations/facebook_pixel/network.ts index 411d36cf19..a6ff50623b 100644 --- a/test/integrations/destinations/facebook_pixel/network.ts +++ b/test/integrations/destinations/facebook_pixel/network.ts @@ -15,6 +15,7 @@ export const networkCallsData = [ data: { error: { message: 'The access token could not be decrypted', + error_user_msg: 'The access token could not be decrypted', type: 'OAuthException', code: 190, fbtrace_id: 'fbpixel_trace_id', @@ -208,4 +209,29 @@ export const networkCallsData = [ statusText: 'OK', }, }, + { + httpReq: { + url: `https://graph.facebook.com/${VERSION}/12345678912804009/events?access_token=2804009_valid_access_token`, + data: getFormData(testFormData).toString(), + params: { destination: 'facebook_pixel' }, + headers: { 'User-Agent': 'RudderLabs' }, + method: 'POST', + }, + httpRes: { + status: 400, + data: { + error: { + code: 100, + error_subcode: 2804009, + error_user_msg: + 'Your purchase event doesn’t include a value parameter. Enter a value. For example: 1.99', + error_user_title: 'Missing Value for Purchase Event', + fbtrace_id: 'AP4G-xxxxxxxx', + is_transient: false, + message: 'Invalid parameter', + type: 'OAuthException', + }, + }, + }, + }, ]; diff --git a/test/integrations/destinations/fb/dataDelivery/data.ts b/test/integrations/destinations/fb/dataDelivery/data.ts index 9ee19af265..dfa5dbc65e 100644 --- a/test/integrations/destinations/fb/dataDelivery/data.ts +++ b/test/integrations/destinations/fb/dataDelivery/data.ts @@ -59,6 +59,7 @@ export const existingTestData = [ message: 'Invalid OAuth 2.0 access token', destinationResponse: { error: { + error_user_msg: 'Invalid OAuth 2.0 access token', code: 190, fbtrace_id: 'fbpixel_trace_id', message: 'The access token could not be decrypted', @@ -274,7 +275,7 @@ export const existingTestData = [ body: { output: { status: 429, - message: 'API User Too Many Calls', + message: 'User request limit reached', destinationResponse: { error: { message: 'User request limit reached', diff --git a/test/integrations/destinations/fb/dataDelivery/other.ts b/test/integrations/destinations/fb/dataDelivery/other.ts index 9ac3f14fb5..36d3e45f53 100644 --- a/test/integrations/destinations/fb/dataDelivery/other.ts +++ b/test/integrations/destinations/fb/dataDelivery/other.ts @@ -31,14 +31,14 @@ export const otherScenariosV1: ProxyV1TestData[] = [ body: { output: { status: 429, - message: 'API User Too Many Calls', + message: 'User request limit reached', statTags: { ...statTags, errorType: 'throttled', }, response: [ { - error: 'API User Too Many Calls', + error: 'User request limit reached', statusCode: 429, metadata: generateMetadata(1), }, diff --git a/test/integrations/destinations/fb/network.ts b/test/integrations/destinations/fb/network.ts index 31bbaf0b6e..1aed48136c 100644 --- a/test/integrations/destinations/fb/network.ts +++ b/test/integrations/destinations/fb/network.ts @@ -30,6 +30,7 @@ export const networkCallsData = [ data: { error: { message: 'The access token could not be decrypted', + error_user_msg: 'Invalid OAuth 2.0 access token', type: 'OAuthException', code: 190, fbtrace_id: 'fbpixel_trace_id', diff --git a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts index b41c656d9f..cd440aaa37 100644 --- a/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts +++ b/test/integrations/destinations/fb_custom_audience/dataDelivery/data.ts @@ -623,6 +623,8 @@ export const existingTestData = [ fbtrace_id: 'A3b8C6PpI-kdIOwPwV4PANi', message: 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + error_user_msg: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', type: 'OAuthException', }, status: 400, diff --git a/test/integrations/destinations/fb_custom_audience/network.ts b/test/integrations/destinations/fb_custom_audience/network.ts index 369c27afa9..ba14d537ad 100644 --- a/test/integrations/destinations/fb_custom_audience/network.ts +++ b/test/integrations/destinations/fb_custom_audience/network.ts @@ -514,6 +514,8 @@ export const networkCallsData = [ error: { message: 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', + error_user_msg: + 'Error validating access token: Session has expired on Tuesday, 01-Aug-23 10:12:14 PDT. The current time is Sunday, 28-Jan-24 16:01:17 PST.', type: 'OAuthException', code: 190, error_subcode: 463, diff --git a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts index ab3e19dc2f..3ecae2b92d 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/processor/data.ts @@ -231,8 +231,6 @@ export const data = [ JSON: { conversions: [ { - gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionData: { externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', @@ -528,8 +526,6 @@ export const data = [ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', }, - gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionData: { externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', @@ -821,8 +817,6 @@ export const data = [ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', }, - gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionData: { externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', @@ -1911,7 +1905,6 @@ export const data = [ userId: '12345', properties: { gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', conversionCustomVariable: 'conversionCustomVariable', @@ -1935,7 +1928,6 @@ export const data = [ ], userIdentifierSource: 'FIRST_PARTY', conversionEnvironment: 'WEB', - gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', conversionValue: '1', currency: 'GBP', @@ -2052,7 +2044,6 @@ export const data = [ ], properties: { gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', conversionCustomVariable: 'conversionCustomVariable', @@ -2076,7 +2067,6 @@ export const data = [ ], userIdentifierSource: 'FIRST_PARTY', conversionEnvironment: 'WEB', - gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', conversionValue: '1', currency: 'GBP', @@ -2093,7 +2083,6 @@ export const data = [ adUserData: 'UNSPECIFIED', }, gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionData: { externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', @@ -2110,15 +2099,7 @@ export const data = [ }, ], }, - userIdentifiers: [ - { - userIdentifierSource: 'FIRST_PARTY', - hashedPhoneNumber: - '04e1dabb7c1348b72bfa87da179c9697c69af74827649266a5da8cdbb367abcd', - }, - ], conversionEnvironment: 'WEB', - gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', conversionValue: 1, currencyCode: 'GBP', @@ -2169,7 +2150,6 @@ export const data = [ ], properties: { gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', conversionCustomVariable: 'conversionCustomVariable', @@ -2193,7 +2173,6 @@ export const data = [ ], userIdentifierSource: 'FIRST_PARTY', conversionEnvironment: 'WEB', - gclid: 'gclid', conversionDateTime: '2022-01-01 12:32:45-08:00', conversionValue: '1', currency: 'GBP', @@ -3751,20 +3730,73 @@ export const data = [ metadata: { secret: { access_token: 'abcd1234', - refresh_token: 'efgh5678', developer_token: 'ijkl91011', + refresh_token: 'efgh5678', }, }, - statusCode: 400, - error: 'Either of email or phone is required for user identifier', - statTags: { - errorCategory: 'dataValidation', - errorType: 'instrumentation', - destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', - module: 'destination', - implementation: 'native', - feature: 'processor', + output: { + body: { + FORM: {}, + JSON: { + conversions: [ + { + cartData: { + items: [ + { + productId: '123445', + quantity: 123, + }, + ], + }, + consent: { + adPersonalization: 'UNSPECIFIED', + adUserData: 'UNSPECIFIED', + }, + conversionDateTime: '2022-01-01 12:32:45-08:00', + conversionEnvironment: 'WEB', + gclid: 'gclid', + }, + ], + partialFailure: true, + }, + JSON_ARRAY: {}, + XML: {}, + }, + endpoint: + 'https://googleads.googleapis.com/v16/customers/9625812972:uploadClickConversions', + files: {}, + headers: { + Authorization: 'Bearer abcd1234', + 'Content-Type': 'application/json', + 'developer-token': 'ijkl91011', + 'login-customer-id': '8617859087', + }, + method: 'POST', + params: { + customVariables: [ + { + from: 'value', + to: 'revenue', + }, + { + from: 'total', + to: 'cost', + }, + ], + customerId: '9625812972', + event: 'Sign-up - click', + properties: { + conversionDateTime: '2022-01-01 12:32:45-08:00', + gclid: 'gclid', + product_id: '123445', + quantity: 123, + }, + }, + type: 'REST', + userId: '', + version: '1', }, + statusCode: 200, }, ], }, @@ -5704,4 +5736,197 @@ export const data = [ }, mockFns: timestampMock, }, + { + name: 'google_adwords_offline_conversions', + description: 'Test 28 : when both gbraid and wbraid are available', + 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', + }, + device: { + id: '0572f78fa49c648e', + name: 'generic_x86_arm', + type: 'Android', + model: 'AOSP on IA Emulator', + manufacturer: 'Google', + adTrackingEnabled: true, + advertisingId: '44c97318-9040-4361-8bc7-4eb30f665ca8', + }, + traits: { + phone: 'alex@example.com', + firstName: 'John', + lastName: 'Gomes', + city: 'London', + state: 'England', + countryCode: 'GB', + postalCode: 'EC3M', + streetAddress: '71 Cherry Court SOUTHAMPTON SO53 5PD UK', + }, + library: { + name: 'RudderLabs JavaScript SDK', + version: '1.0.0', + }, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36', + locale: 'en-US', + ip: '0.0.0.0', + os: { + name: '', + version: '', + }, + screen: { + density: 2, + }, + }, + event: 'Promotion Clicked', + type: 'track', + messageId: '5e10d13a-bf9a-44bf-b884-43a9e591ea71', + originalTimestamp: '2019-10-14T11:15:18.299Z', + anonymousId: '00000000000000000000000000', + userId: '12345', + properties: { + gbraid: 'gbraid', + wbraid: 'wbraid', + externalAttributionCredit: 10, + externalAttributionModel: 'externalAttributionModel', + conversionCustomVariable: 'conversionCustomVariable', + value: 'value', + merchantId: '9876merchantId', + feedCountryCode: 'feedCountryCode', + feedLanguageCode: 'feedLanguageCode', + localTransactionCost: 20, + products: [ + { + product_id: '507f1f77bcf86cd799439011', + quantity: '2', + price: '50', + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + position: '1', + category: 'cars', + url: 'https://www.example.com/product/path', + image_url: 'https://www.example.com/product/path.jpg', + }, + ], + userIdentifierSource: 'FIRST_PARTY', + conversionEnvironment: 'WEB', + conversionValue: '1', + currency: 'GBP', + orderId: 'PL-123QR', + }, + integrations: { + All: true, + }, + name: 'ApplicationLoaded', + sentAt: '2019-10-14T11:15:53.296Z', + }, + metadata: { + secret: { + access_token: 'abcd1234', + refresh_token: 'efgh5678', + developer_token: 'ijkl91011', + }, + }, + destination: { + Config: { + customerId: '962-581-2972', + subAccount: false, + eventsToOfflineConversionsTypeMapping: [ + { + from: 'Sign up completed', + to: 'click', + }, + { + from: 'Download', + to: 'call', + }, + { + from: 'Promotion Clicked', + to: 'click', + }, + { + from: 'Product Searched', + to: 'call', + }, + ], + eventsToConversionsNamesMapping: [ + { + from: 'Sign up completed', + to: 'Sign-up - click', + }, + { + from: 'Download', + to: 'Page view', + }, + { + from: 'Promotion Clicked', + to: 'Sign-up - click', + }, + { + from: 'Product Searched', + to: 'search', + }, + ], + customVariables: [ + { + from: 'value', + to: 'revenue', + }, + { + from: 'total', + to: 'cost', + }, + ], + UserIdentifierSource: 'THIRD_PARTY', + conversionEnvironment: 'WEB', + hashUserIdentifier: true, + defaultUserIdentifier: 'email', + validateOnly: false, + rudderAccountId: '2EOknn1JNH7WK1MfNku4fGYKkRK', + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: "You can't use both wbraid and gbraid.", + metadata: { + secret: { + access_token: 'abcd1234', + developer_token: 'ijkl91011', + refresh_token: 'efgh5678', + }, + }, + statTags: { + destType: 'GOOGLE_ADWORDS_OFFLINE_CONVERSIONS', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'native', + module: 'destination', + }, + statusCode: 400, + }, + ], + }, + }, + mockFns: timestampMock, + }, ]; diff --git a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts index 9d1ba220c8..82c8e9b3ff 100644 --- a/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts +++ b/test/integrations/destinations/google_adwords_offline_conversions/router/data.ts @@ -738,8 +738,6 @@ export const data = [ adPersonalization: 'UNSPECIFIED', adUserData: 'UNSPECIFIED', }, - gbraid: 'gbraid', - wbraid: 'wbraid', externalAttributionData: { externalAttributionCredit: 10, externalAttributionModel: 'externalAttributionModel', diff --git a/test/integrations/sources/shopify/constants.ts b/test/integrations/sources/shopify/constants.ts index f9df305841..af53a3180e 100644 --- a/test/integrations/sources/shopify/constants.ts +++ b/test/integrations/sources/shopify/constants.ts @@ -83,6 +83,21 @@ export const dummyContext = { }, }; +export const note_attributes = [ + { + name: 'cartId', + value: '9c623f099fc8819aa4d6a958b65dfe7d', + }, + { + name: 'cartToken', + value: 'Z2NwLXVzLWVhc3QxOjAxSkQzNUFXVEI4VkVUNUpTTk1LSzBCMzlF', + }, + { + name: 'rudderAnonymousId', + value: '50ead33e-d763-4854-b0ab-765859ef05cb', + }, +]; + export const responseDummyContext = { document: { location: { diff --git a/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts b/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts index ade496efb7..a154ccb890 100644 --- a/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts +++ b/test/integrations/sources/shopify/webhookTestScenarios/CheckoutEventsTests.ts @@ -1,7 +1,6 @@ // This file contains the test scenarios for the server-side events from the Shopify GraphQL API for // the v1 transformation flow -import { mockFns } from '../mocks'; -import { dummySourceConfig } from '../constants'; +import { dummySourceConfig, note_attributes } from '../constants'; export const checkoutEventsTestScenarios = [ { @@ -27,7 +26,7 @@ export const checkoutEventsTestScenarios = [ updated_at: '2024-11-05T21:22:02-05:00', landing_site: '/', note: '', - note_attributes: [], + note_attributes, referring_site: '', shipping_lines: [], shipping_address: [], @@ -145,7 +144,7 @@ export const checkoutEventsTestScenarios = [ updated_at: '2024-11-05T21:22:02-05:00', landing_site: '/', note: '', - note_attributes: [], + note_attributes, referring_site: '', shipping_lines: [], shipping_address: [], @@ -237,7 +236,7 @@ export const checkoutEventsTestScenarios = [ traits: { shippingAddress: [], }, - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -269,7 +268,7 @@ export const checkoutEventsTestScenarios = [ created_at: '2024-09-16T03:50:1500:00', updated_at: '2024-09-17T03:29:02-04:00', note: '', - note_attributes: [], + note_attributes, shipping_address: { first_name: 'testuser', address1: 'oakwood bridge', @@ -396,7 +395,7 @@ export const checkoutEventsTestScenarios = [ output: { batch: [ { - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', context: { cart_token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', integration: { @@ -415,7 +414,7 @@ export const checkoutEventsTestScenarios = [ created_at: '2024-09-16T03:50:1500:00', updated_at: '2024-09-17T03:29:02-04:00', note: '', - note_attributes: [], + note_attributes, shipping_address: { first_name: 'testuser', address1: 'oakwood bridge', @@ -680,7 +679,7 @@ export const checkoutEventsTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, number: 17, order_number: 1017, order_status_url: @@ -988,7 +987,7 @@ export const checkoutEventsTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, number: 17, order_number: 1017, order_status_url: @@ -1289,7 +1288,7 @@ export const checkoutEventsTestScenarios = [ }, }, timestamp: '2024-11-06T02:54:50.000Z', - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -1326,6 +1325,7 @@ export const checkoutEventsTestScenarios = [ current_total_tax: '0.00', email: 'henry@wfls.com', name: '#1017', + note_attributes, order_number: 1017, order_status_url: 'https://pixel-testing-rs.myshopify.com/59026964593/orders/676613a0027fc8240e16d67fdc9f5ac8/authenticate?key=a70bbe7ec8abcc46b77e4331e4df8c60', @@ -1486,6 +1486,7 @@ export const checkoutEventsTestScenarios = [ current_total_tax: '0.00', email: 'henry@wfls.com', name: '#1017', + note_attributes, order_number: 1017, order_status_url: 'https://pixel-testing-rs.myshopify.com/59026964593/orders/676613a0027fc8240e16d67fdc9f5ac8/authenticate?key=a70bbe7ec8abcc46b77e4331e4df8c60', @@ -1675,7 +1676,7 @@ export const checkoutEventsTestScenarios = [ }, }, timestamp: '2024-11-06T02:54:50.000Z', - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], }, @@ -1684,4 +1685,4 @@ export const checkoutEventsTestScenarios = [ }, }, }, -].map((d1) => ({ ...d1, mockFns })); +]; diff --git a/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts b/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts index f04fd7e08e..d68d0a8f59 100644 --- a/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts +++ b/test/integrations/sources/shopify/webhookTestScenarios/GenericTrackTests.ts @@ -1,7 +1,7 @@ // This file contains the test scenarios for the server-side events from the Shopify GraphQL API for // the v1 transformation flow import { mockFns } from '../mocks'; -import { dummySourceConfig } from '../constants'; +import { dummySourceConfig, note_attributes } from '../constants'; export const genericTrackTestScenarios = [ { @@ -24,6 +24,7 @@ export const genericTrackTestScenarios = [ token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', line_items: [], note: '', + note_attributes, updated_at: '2024-09-17T08:15:13.280Z', created_at: '2024-09-16T03:50:15.478Z', }, @@ -43,7 +44,7 @@ export const genericTrackTestScenarios = [ output: { batch: [ { - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', context: { integration: { name: 'SHOPIFY', @@ -58,6 +59,7 @@ export const genericTrackTestScenarios = [ id: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', line_items: [], note: '', + note_attributes, token: 'Z2NwLXVzLWVhc3QxOjAxSjdXRjdOQjY0NlFFNFdQVEg0MTRFM1E2', updated_at: '2024-09-17T08:15:13.280Z', }, @@ -149,7 +151,7 @@ export const genericTrackTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, order_number: 1017, original_total_additional_fees_set: null, original_total_duties_set: null, @@ -363,7 +365,7 @@ export const genericTrackTestScenarios = [ merchant_of_record_app_id: null, name: '#1017', note: null, - note_attributes: [], + note_attributes, order_number: 1017, original_total_additional_fees_set: null, original_total_duties_set: null, @@ -545,7 +547,7 @@ export const genericTrackTestScenarios = [ traits: { email: 'henry@wfls.com', }, - anonymousId: '5d3e2cb6-4011-5c9c-b7ee-11bc1e905097', + anonymousId: '50ead33e-d763-4854-b0ab-765859ef05cb', }, ], },