diff --git a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/__tests__/sendEvent.test.ts b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/__tests__/sendEvent.test.ts index cf201f941b3..5fcc63ca592 100644 --- a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/__tests__/sendEvent.test.ts +++ b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/__tests__/sendEvent.test.ts @@ -392,6 +392,108 @@ describe('Microsoft Bing CAPI (Actions) - sendEvent (updated)', () => { expect(scope.isDone()).toBe(true) }) + test('batch: warning-only detail (isWarning:true) at an index is treated as SUCCESS, not failure', async () => { + const events = [buildTrackEvent({ messageId: 'm1' }), buildTrackEvent({ messageId: 'm2' })] + // Microsoft returns HTTP 200 and, because continueOnValidationError:true, reports + // non-fatal issues as warnings while still accepting the event. + const scope = nock('https://capi.uet.microsoft.com') + .post(`/v1/${settings.UetTag}/events`) + .reply(200, { + eventsReceived: 2, + error: { + details: [ + { + errorCode: 'Empty', + errorMessage: "'price' must not be empty.", + index: 0, + isWarning: true, + propertyName: 'data[0].customData.items[0].price' + } + ] + } + }) + const responses: any = await testDestination.executeBatch('sendEvent', { + events, + settings, + mapping: { + enable_batching: true, + data: { eventType: 'custom' }, + userData: { anonymousId: 'anon-1' }, + timestamp: { '@path': '$.timestamp' } + } + }) + expect(responses.length).toBe(2) + // Warning-only event was accepted by Microsoft -> must be a success, not a 400. + expect(responses[0].status).toBe(200) + expect(responses[1].status).toBe(200) + expect(scope.isDone()).toBe(true) + }) + + test('batch: real error (isWarning:false) is still marked 400 while warning at another index stays 200', async () => { + const events = [buildTrackEvent({ messageId: 'm1' }), buildTrackEvent({ messageId: 'm2' })] + const scope = nock('https://capi.uet.microsoft.com') + .post(`/v1/${settings.UetTag}/events`) + .reply(200, { + eventsReceived: 1, + error: { + details: [ + { + errorCode: 'Empty', + errorMessage: "'price' must not be empty.", + index: 0, + isWarning: true, + propertyName: 'data[0].customData.items[0].price' + }, + { + errorCode: 'Invalid', + errorMessage: 'Second failed', + index: 1, + isWarning: false, + propertyName: 'data[1].eventName' + } + ] + } + }) + const responses: any = await testDestination.executeBatch('sendEvent', { + events, + settings, + mapping: { + enable_batching: true, + data: { eventType: 'custom' }, + userData: { anonymousId: 'anon-1' }, + timestamp: { '@path': '$.timestamp' } + } + }) + expect(responses.length).toBe(2) + // index 0 had only a warning -> success; index 1 had a real error -> failure. + expect(responses[0].status).toBe(200) + expect(responses[1].status).toBe(400) + expect(responses[1].errormessage).toContain('Second failed') + expect(scope.isDone()).toBe(true) + }) + + test('batch: error detail without isWarning field defaults to a real failure (400)', async () => { + const events = [buildTrackEvent({ messageId: 'm1' }), buildTrackEvent({ messageId: 'm2' })] + const scope = nock('https://capi.uet.microsoft.com') + .post(`/v1/${settings.UetTag}/events`) + .reply(200, { error: { details: [{ index: 1, errorMessage: 'Second failed' }] } }) + const responses: any = await testDestination.executeBatch('sendEvent', { + events, + settings, + mapping: { + enable_batching: true, + data: { eventType: 'custom' }, + userData: { anonymousId: 'anon-1' }, + timestamp: { '@path': '$.timestamp' } + } + }) + expect(responses.length).toBe(2) + expect(responses[0].status).toBe(200) + expect(responses[1].status).toBe(400) + expect(responses[1].errormessage).toContain('Second failed') + expect(scope.isDone()).toBe(true) + }) + test('phone digits normalized before hashing', async () => { const rawPhone = '+1 (555) 123-4567 ext.89' const event = buildTrackEvent({ context: { traits: { phone: rawPhone, email: 'norm@example.com' } } }) diff --git a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/index.ts b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/index.ts index 6c1bd66b51a..48279b305b4 100644 --- a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/index.ts +++ b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/index.ts @@ -72,7 +72,7 @@ async function send(request: RequestClient, payloads: Payload[], settings: Setti const details = response.data?.error?.details ?? [] payloads.forEach((payload, index) => { - const error = details.find((detail) => detail.index === index) + const error = details.find((detail) => detail.index === index && !detail.isWarning) if (error) { multiStatusResponse.setErrorResponseAtIndex(index, { status: 400, diff --git a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/types.ts b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/types.ts index dbd69b456ab..59690b29ef3 100644 --- a/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/types.ts +++ b/packages/destination-actions/src/destinations/ms-bing-capi/sendEvent/types.ts @@ -61,6 +61,8 @@ export interface MSMultiStatusResponse { propertyName: string attemptedValue: unknown errorMessage: string + errorCode?: string + isWarning?: boolean }> } traceId: string