Skip to content

Commit 2733bd9

Browse files
All List Tests
1 parent 754f6f5 commit 2733bd9

File tree

5 files changed

+122
-81
lines changed

5 files changed

+122
-81
lines changed

packages/destination-actions/src/destinations/hubspot/upsertObject/__tests__/lists.test.ts

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -597,65 +597,6 @@ describe('Hubspot.upsertObject', () => {
597597

598598
expect(responses.length).toBe(4)
599599
})
600-
601-
it('should error if more than 1 list is included in the same batch', async () => {
602-
// To simplify this test properties and sensitive_properties are excluded
603-
const modifiedPayload = {
604-
...payload,
605-
properties: {
606-
contact_list_2: true,
607-
608-
}
609-
} as SegmentEvent
610-
611-
const modifiedPayload2 = {
612-
...payload,
613-
properties: {
614-
contact_list_3: true,
615-
616-
}
617-
} as SegmentEvent
618-
619-
nock(HUBSPOT_BASE_URL)
620-
.get(`/crm/v3/lists/object-type-id/contact/name/contact_list_2`)
621-
.reply(400, {
622-
status: 'error',
623-
message: 'List does not exist with name contact_list_2 and object type ID 0-2.'
624-
})
625-
626-
nock(HUBSPOT_BASE_URL)
627-
.post('/crm/v3/lists', {name: "contact_list_2", objectTypeId: "contact", processingType: "MANUAL"})
628-
.reply(200, {
629-
list: {
630-
listId: "21",
631-
objectTypeId: "0-2",
632-
name: "contact_list_2"
633-
}
634-
})
635-
636-
nock(HUBSPOT_BASE_URL)
637-
.put(`/crm/v3/lists/21/memberships/add-and-remove`, {
638-
recordIdsToAdd: ['62102303560', '999999898989'],
639-
recordIdsToRemove: []
640-
})
641-
.reply(200)
642-
643-
nock(HUBSPOT_BASE_URL)
644-
.post('/crm/v3/objects/contact/batch/upsert',
645-
{inputs:[{idProperty:"email",id:"[email protected]",properties:{email:"[email protected]"}}, {idProperty:"email",id:"[email protected]",properties:{email:"[email protected]"}}]})
646-
.reply(200, modifiedUpsertObjectResp)
647-
648-
const responses = await testDestination.testBatchAction('upsertObject', {
649-
events: [modifiedPayload, modifiedPayload2],
650-
settings,
651-
useDefaultMappings: true,
652-
mapping,
653-
features: { 'actions-hubspot-lists-association-support': true }
654-
})
655-
656-
expect(responses.length).toBe(4)
657-
})
658-
659600
})
660601

661602
describe('Not an Engage Audience', () => {
@@ -876,5 +817,71 @@ describe('Hubspot.upsertObject', () => {
876817
expect(responses.length).toBe(6)
877818
})
878819

820+
it('should error if more than 1 list is included in the same batch', async () => {
821+
// To simplify this test properties and sensitive_properties are excluded
822+
const modifiedPayload = {
823+
...payload,
824+
context: {},
825+
properties: {
826+
827+
list_details: {
828+
connected_to_engage_audience: false,
829+
should_create_list: true,
830+
list_name: 'contact_list_2',
831+
list_action: true
832+
}
833+
}
834+
} as SegmentEvent
835+
836+
const modifiedPayload2 = {
837+
...payload,
838+
context: {},
839+
properties: {
840+
841+
list_details: {
842+
connected_to_engage_audience: false,
843+
should_create_list: true,
844+
list_name: 'contact_list_3', // different list name
845+
list_action: true
846+
}
847+
}
848+
} as SegmentEvent
849+
850+
const modifiedPayload3 = {
851+
...payload,
852+
context: {},
853+
properties: {
854+
855+
list_details: {
856+
connected_to_engage_audience: false,
857+
should_create_list: true,
858+
list_name: '', // empty string list name
859+
list_action: true
860+
}
861+
}
862+
} as SegmentEvent
863+
864+
const modifiedMapping: JSONObject = {
865+
...mapping,
866+
list_details: {
867+
connected_to_engage_audience: { '@path': '$.properties.list_details.connected_to_engage_audience' },
868+
should_create_list: { '@path': '$.properties.list_details.should_create_list' },
869+
list_name: { '@path': '$.properties.list_details.list_name' },
870+
list_action: { '@path': '$.properties.list_details.list_action' }
871+
}
872+
}
873+
874+
try{
875+
await testDestination.testBatchAction('upsertObject', {
876+
events: [modifiedPayload, modifiedPayload2, modifiedPayload3],
877+
settings,
878+
useDefaultMappings: true,
879+
mapping: modifiedMapping,
880+
features: { 'actions-hubspot-lists-association-support': true }
881+
})
882+
} catch (err) {
883+
expect((err as Error).message).toBe('When updating List membership, all payloads must reference the same list. Found multiple lists in the batch: contact_list_2, contact_list_3')
884+
}
885+
})
879886
})
880887
})

packages/destination-actions/src/destinations/hubspot/upsertObject/functions/hubspot-list-functions.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Client } from '../client'
2-
import { CachableList, PayloadWithFromId, AddRemoveFromListReq, EngageAudiencePayload } from '../types'
2+
import { CachableList, PayloadWithFromId, AddRemoveFromListReq, EngageAudiencePayload, PayloadListType } from '../types'
33
import { StatsContext, RetryableError, PayloadValidationError } from '@segment/actions-core'
44
import { SubscriptionMetadata } from '@segment/actions-core/destination-kit'
55
import { getListFromCache, saveListToCache } from '../functions/cache-functions'
@@ -8,35 +8,58 @@ import { Payload } from '../generated-types'
88
import { ENGAGE_AUDIENCE_COMPUTATION_CLASSES } from '../constants'
99

1010
export function getListName(payload: Payload): string | undefined {
11-
if(isEngageAudiencePayload(payload)) {
12-
const { computation_key } = payload as EngageAudiencePayload
13-
return (payload?.list_details?.list_name || computation_key) as string | undefined
14-
} else {
15-
return payload?.list_details?.list_name
11+
const listPayloadType = getListPayloadType(payload)
12+
switch(listPayloadType) {
13+
case 'is_not_audience_payload':
14+
return undefined
15+
case 'is_non_engage_audience_payload': {
16+
const listName = payload?.list_details?.list_name?.trim() || undefined
17+
return listName
18+
}
19+
case 'is_engage_audience_payload': {
20+
const { computation_key } = payload as EngageAudiencePayload
21+
return (payload?.list_details?.list_name || computation_key) as string | undefined
22+
}
1623
}
1724
}
1825

19-
export function isEngageAudiencePayload(payload: Payload): boolean {
26+
export function getListPayloadType(payload: Payload): PayloadListType {
2027
const {
21-
list_details: { connected_to_engage_audience } = {},
28+
list_details: { connected_to_engage_audience, list_action, list_name, should_create_list } = {},
2229
traits_or_props,
2330
computation_class = '',
2431
computation_key = ''
2532
} = payload
2633

27-
return connected_to_engage_audience === true
34+
if (connected_to_engage_audience === true
2835
&& computation_key!== ''
2936
&& ENGAGE_AUDIENCE_COMPUTATION_CLASSES.includes(computation_class)
3037
&& typeof traits_or_props === 'object'
31-
&& typeof traits_or_props[computation_key] === 'boolean'
38+
&& typeof traits_or_props[computation_key] === 'boolean') {
39+
return 'is_engage_audience_payload'
40+
} else if (connected_to_engage_audience === false
41+
&& typeof list_action === 'boolean'
42+
&& typeof list_name === 'string'
43+
&& list_name.trim().length > 0
44+
&& typeof should_create_list === 'boolean'
45+
) {
46+
return 'is_non_engage_audience_payload'
47+
}
48+
return 'is_not_audience_payload'
3249
}
3350

3451
function getListAction(payload: PayloadWithFromId): boolean | undefined{
35-
if(isEngageAudiencePayload(payload)) {
36-
const action = (payload as EngageAudiencePayload)?.traits_or_props[payload.computation_key as string] as boolean
37-
return typeof action === 'boolean' ? action : undefined
38-
} else {
39-
return payload?.list_details?.list_action
52+
const listPayloadType = getListPayloadType(payload)
53+
54+
switch(listPayloadType) {
55+
case 'is_not_audience_payload':
56+
return undefined
57+
case 'is_non_engage_audience_payload':
58+
return payload?.list_details?.list_action
59+
case 'is_engage_audience_payload': {
60+
const action = (payload as EngageAudiencePayload)?.traits_or_props[payload.computation_key as string] as boolean
61+
return typeof action === 'boolean' ? action : undefined
62+
}
4063
}
4164
}
4265

packages/destination-actions/src/destinations/hubspot/upsertObject/functions/validation-functions.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { PayloadValidationError, StatsContext } from '@segment/actions-core'
22
import { Payload } from '../generated-types'
33
import { Association } from '../types'
4-
import { isEngageAudiencePayload } from '../functions/hubspot-list-functions'
4+
import { getListPayloadType, getListName } from '../functions/hubspot-list-functions'
55

66
export function validate(payloads: Payload[], flag?: boolean): Payload[] {
77
const length = payloads.length
@@ -22,15 +22,24 @@ export function validate(payloads: Payload[], flag?: boolean): Payload[] {
2222
}
2323

2424
if(flag === true){
25-
// check that all payloads are either engage audience or not
26-
const allEngageAudience = cleaned.every(isEngageAudiencePayload)
27-
const noneEngageAudience = cleaned.every((p) => !isEngageAudiencePayload(p))
25+
const hasEngageAudience = cleaned.some((p) => getListPayloadType(p) === 'is_engage_audience_payload')
26+
const hasNonEngageAudience = cleaned.some((p) => getListPayloadType(p) === 'is_non_engage_audience_payload')
2827

29-
if(!(allEngageAudience || noneEngageAudience)){
28+
if(hasEngageAudience && hasNonEngageAudience){
3029
throw new PayloadValidationError(
3130
'Engage and non Engage payloads cannot be mixed in the same batch.'
3231
)
3332
}
33+
34+
const listNames = Array.from(
35+
new Set(cleaned.map((p) => getListName(p)).filter(Boolean))
36+
)
37+
38+
if(listNames.length > 1){
39+
throw new PayloadValidationError(
40+
`When updating List membership, all payloads must reference the same list. Found multiple lists in the batch: ${listNames.slice(0, 3).join(', ')}`
41+
)
42+
}
3443
}
3544

3645
cleaned.forEach((payload) => {
@@ -57,6 +66,7 @@ export function validate(payloads: Payload[], flag?: boolean): Payload[] {
5766
return fieldsToCheck.every((field) => field !== null && field !== '')
5867
})
5968
})
69+
6070
return cleaned
6171
}
6272

packages/destination-actions/src/destinations/hubspot/upsertObject/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ const send = async (
7676
let shouldCreateList: boolean | undefined = false
7777

7878
const client = new Client(request, objectType)
79-
8079
const validPayloads = validate(payloads, flag)
8180

8281
if(flag){

packages/destination-actions/src/destinations/hubspot/upsertObject/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,6 @@ export type EngageAudiencePayload = Payload & {
285285
[k : string]: unknown
286286
}
287287
computation_key: string
288-
}
288+
}
289+
290+
export type PayloadListType = 'is_engage_audience_payload' | 'is_non_engage_audience_payload' | 'is_not_audience_payload'

0 commit comments

Comments
 (0)