@@ -815,4 +815,288 @@ describe('Hubspot.customEvent', () => {
815815 )
816816 } )
817817 } )
818+
819+ describe ( 'numeric string handling' , ( ) => {
820+ const numericStringPayload = {
821+ timestamp : timestamp ,
822+ event : 'Custom Event 2' ,
823+ messageId : 'aaa-bbb-ccc' ,
824+ type : 'track' ,
825+ userId : 'user_id_1' ,
826+ properties : {
827+ custom_prop_str : 'Hello String!' ,
828+ custom_prop_numeric_string : '123' // will be inferred as number but should be string
829+ }
830+ } as Partial < SegmentEvent >
831+
832+ const expectedNumericStringPayload = {
833+ eventName : 'pe23132826_custom_event_2' ,
834+ objectId : undefined ,
835+ 836+ utk : undefined ,
837+ occurredAt : timestamp ,
838+ properties : {
839+ custom_prop_str : 'Hello String!' ,
840+ custom_prop_numeric_string : '123' // converted to string
841+ }
842+ }
843+
844+ const multipleNumericStringsPayload = {
845+ timestamp : timestamp ,
846+ event : 'Custom Event 3' ,
847+ messageId : 'aaa-bbb-ccc' ,
848+ type : 'track' ,
849+ userId : 'user_id_1' ,
850+ properties : {
851+ prop1 : 123 ,
852+ prop2 : 456.78 ,
853+ prop3 : 0 ,
854+ prop4 : 'regular string'
855+ }
856+ } as Partial < SegmentEvent >
857+
858+ const expectedMultipleNumericStringsPayload = {
859+ eventName : 'pe23132826_custom_event_3' ,
860+ objectId : undefined ,
861+ 862+ utk : undefined ,
863+ occurredAt : timestamp ,
864+ properties : {
865+ prop1 : '123' ,
866+ prop2 : '456.78' ,
867+ prop3 : '0' ,
868+ prop4 : 'regular string'
869+ }
870+ }
871+
872+ const numericTypePayload = {
873+ timestamp : timestamp ,
874+ event : 'Custom Event 6' ,
875+ messageId : 'aaa-bbb-ccc' ,
876+ type : 'track' ,
877+ userId : 'user_id_1' ,
878+ properties : {
879+ numeric_prop : 123 ,
880+ string_prop : 'test'
881+ }
882+ } as Partial < SegmentEvent >
883+
884+ const expectedNumericTypePayload = {
885+ eventName : 'pe23132826_custom_event_6' ,
886+ objectId : undefined ,
887+ 888+ utk : undefined ,
889+ occurredAt : timestamp ,
890+ properties : {
891+ numeric_prop : 123 , // should stay as number
892+ string_prop : 'test'
893+ }
894+ }
895+
896+ it ( 'should convert numeric string from cache and send event without hitting HubSpot' , async ( ) => {
897+ const event = createTestEvent ( numericStringPayload )
898+
899+ // fetches the event definition from Hubspot
900+ nock ( 'https://api.hubapi.com' )
901+ . get ( '/events/v3/event-definitions/custom_event_2/?includeProperties=true' )
902+ . reply ( 200 , {
903+ name : 'custom_event_2' ,
904+ fullyQualifiedName : 'pe23132826_custom_event_2' ,
905+ properties : [
906+ {
907+ name : 'custom_prop_str' ,
908+ type : 'string' ,
909+ archived : false
910+ } ,
911+ {
912+ name : 'custom_prop_numeric_string' ,
913+ type : 'string' , // HubSpot has this as string, not number
914+ archived : false
915+ }
916+ ]
917+ } )
918+
919+ // sends an event completion to Hubspot
920+ nock ( 'https://api.hubapi.com' ) . post ( '/events/v3/send' , expectedNumericStringPayload ) . reply ( 200 , { } )
921+
922+ const responses = await testDestination . testAction ( 'customEvent' , {
923+ event,
924+ settings,
925+ useDefaultMappings : true ,
926+ mapping : upsertMapping ,
927+ subscriptionMetadata
928+ } )
929+
930+ expect ( responses . length ) . toBe ( 2 )
931+ expect ( responses [ 1 ] . status ) . toBe ( 200 )
932+
933+ // sends an event completion to Hubspot without first fetching the event definition
934+ nock ( 'https://api.hubapi.com' ) . post ( '/events/v3/send' , expectedNumericStringPayload ) . reply ( 200 , { } )
935+
936+ const responses2 = await testDestination . testAction ( 'customEvent' , {
937+ event : createTestEvent ( numericStringPayload ) ,
938+ settings,
939+ useDefaultMappings : true ,
940+ mapping : upsertMapping ,
941+ subscriptionMetadata
942+ } )
943+
944+ expect ( responses2 . length ) . toBe ( 1 )
945+ expect ( responses2 [ 0 ] . status ) . toBe ( 200 )
946+ } )
947+
948+ it ( 'should handle multiple numeric strings in a single event' , async ( ) => {
949+ const event = createTestEvent ( multipleNumericStringsPayload )
950+
951+ // fetches the event definition from Hubspot
952+ nock ( 'https://api.hubapi.com' )
953+ . get ( '/events/v3/event-definitions/custom_event_3/?includeProperties=true' )
954+ . reply ( 200 , {
955+ name : 'custom_event_3' ,
956+ fullyQualifiedName : 'pe23132826_custom_event_3' ,
957+ properties : [
958+ {
959+ name : 'prop1' ,
960+ type : 'string' ,
961+ archived : false
962+ } ,
963+ {
964+ name : 'prop2' ,
965+ type : 'string' ,
966+ archived : false
967+ } ,
968+ {
969+ name : 'prop3' ,
970+ type : 'string' ,
971+ archived : false
972+ } ,
973+ {
974+ name : 'prop4' ,
975+ type : 'string' ,
976+ archived : false
977+ }
978+ ]
979+ } )
980+
981+ // sends an event completion to Hubspot
982+ nock ( 'https://api.hubapi.com' ) . post ( '/events/v3/send' , expectedMultipleNumericStringsPayload ) . reply ( 200 , { } )
983+
984+ const responses = await testDestination . testAction ( 'customEvent' , {
985+ event,
986+ settings,
987+ useDefaultMappings : true ,
988+ mapping : upsertMapping ,
989+ subscriptionMetadata
990+ } )
991+
992+ expect ( responses . length ) . toBe ( 2 )
993+ expect ( responses [ 1 ] . status ) . toBe ( 200 )
994+ } )
995+
996+ it ( 'should handle numeric strings with partial property match' , async ( ) => {
997+ const event = createTestEvent ( {
998+ timestamp : timestamp ,
999+ event : 'Custom Event 5' ,
1000+ messageId : 'aaa-bbb-ccc' ,
1001+ type : 'track' ,
1002+ userId : 'user_id_1' ,
1003+ properties : {
1004+ existing_prop : 123 ,
1005+ new_prop : 'new value'
1006+ }
1007+ } as Partial < SegmentEvent > )
1008+
1009+ // fetches the event definition from Hubspot
1010+ nock ( 'https://api.hubapi.com' )
1011+ . get ( '/events/v3/event-definitions/custom_event_5/?includeProperties=true' )
1012+ . reply ( 200 , {
1013+ name : 'custom_event_5' ,
1014+ fullyQualifiedName : 'pe23132826_custom_event_5' ,
1015+ properties : [
1016+ {
1017+ name : 'existing_prop' ,
1018+ type : 'string' , // numeric string
1019+ archived : false
1020+ }
1021+ // new_prop doesn't exist yet
1022+ ]
1023+ } )
1024+
1025+ const expectedHubspotCreatePropertyPayload = {
1026+ name : 'new_prop' ,
1027+ label : 'new_prop' ,
1028+ type : 'string' ,
1029+ description : 'new_prop - (created by Segment)'
1030+ }
1031+
1032+ // creates property on Hubspot
1033+ nock ( 'https://api.hubapi.com' )
1034+ . post ( '/events/v3/event-definitions/pe23132826_custom_event_5/property' , expectedHubspotCreatePropertyPayload )
1035+ . reply ( 200 , { } )
1036+
1037+ // sends an event completion to Hubspot
1038+ nock ( 'https://api.hubapi.com' )
1039+ . post ( '/events/v3/send' , {
1040+ eventName : 'pe23132826_custom_event_5' ,
1041+ objectId : undefined ,
1042+ 1043+ utk : undefined ,
1044+ occurredAt : timestamp ,
1045+ properties : {
1046+ existing_prop : '123' ,
1047+ new_prop : 'new value'
1048+ }
1049+ } )
1050+ . reply ( 200 , { } )
1051+
1052+ const responses = await testDestination . testAction ( 'customEvent' , {
1053+ event,
1054+ settings,
1055+ useDefaultMappings : true ,
1056+ mapping : upsertMapping ,
1057+ subscriptionMetadata
1058+ } )
1059+
1060+ expect ( responses . length ) . toBe ( 3 )
1061+ expect ( responses [ 2 ] . status ) . toBe ( 200 )
1062+ } )
1063+
1064+ it ( 'should not convert numeric values when HubSpot schema expects number type' , async ( ) => {
1065+ const event = createTestEvent ( numericTypePayload )
1066+
1067+ // fetches the event definition from Hubspot
1068+ nock ( 'https://api.hubapi.com' )
1069+ . get ( '/events/v3/event-definitions/custom_event_6/?includeProperties=true' )
1070+ . reply ( 200 , {
1071+ name : 'custom_event_6' ,
1072+ fullyQualifiedName : 'pe23132826_custom_event_6' ,
1073+ properties : [
1074+ {
1075+ name : 'numeric_prop' ,
1076+ type : 'number' , // HubSpot expects number, not string
1077+ archived : false
1078+ } ,
1079+ {
1080+ name : 'string_prop' ,
1081+ type : 'string' ,
1082+ archived : false
1083+ }
1084+ ]
1085+ } )
1086+
1087+ // sends an event completion to Hubspot
1088+ nock ( 'https://api.hubapi.com' ) . post ( '/events/v3/send' , expectedNumericTypePayload ) . reply ( 200 , { } )
1089+
1090+ const responses = await testDestination . testAction ( 'customEvent' , {
1091+ event,
1092+ settings,
1093+ useDefaultMappings : true ,
1094+ mapping : upsertMapping ,
1095+ subscriptionMetadata
1096+ } )
1097+
1098+ expect ( responses . length ) . toBe ( 2 )
1099+ expect ( responses [ 1 ] . status ) . toBe ( 200 )
1100+ } )
1101+ } )
8181102} )
0 commit comments