@@ -658,4 +658,204 @@ class DataPipelinesInteractionTests : JUnitTest() {
658658 }
659659
660660 // endregion
661+
662+ // region Nested Null Handling Tests
663+
664+ @Test
665+ fun track_givenNestedNullInCustomAttributes_expectSuccessfulEventProcessing () {
666+ val givenEvent = String .random
667+ val givenProperties = mapOf (
668+ " level1" to mapOf (
669+ " key1" to " value1" ,
670+ " key2" to null ,
671+ " nested" to mapOf (
672+ " deepKey" to " deepValue" ,
673+ " nullValue" to null
674+ )
675+ ),
676+ " nullObject" to null ,
677+ " regularValue" to " test"
678+ )
679+
680+ sdkInstance.track(givenEvent, givenProperties)
681+
682+ outputReaderPlugin.trackEvents.size shouldBeEqualTo 1
683+ val trackEvent = outputReaderPlugin.trackEvents.lastOrNull()
684+ trackEvent.shouldNotBeNull()
685+
686+ trackEvent.event shouldBeEqualTo givenEvent
687+
688+ // Verify event was processed successfully and properties match
689+ // The outputReaderPlugin should have the properties with JsonNull replacing nulls
690+ val level1Map = trackEvent.properties[" level1" ]
691+ level1Map.shouldNotBeNull()
692+
693+ // Ensure we can access the properties without errors, which would happen if nulls weren't properly handled
694+ val nestedMap = (level1Map as Map <* , * >)[" nested" ]
695+ nestedMap.shouldNotBeNull()
696+ }
697+
698+ @Test
699+ fun track_givenNullInDeepNestedStructure_expectSuccessfulEventProcessing () {
700+ val givenEvent = String .random
701+ val userData: CustomAttributes = mapOf (
702+ " name" to " John Doe" ,
703+ " contact" to mapOf (
704+ " email" to " john@example.com" ,
705+ " phone" to null , // Null phone
706+ " address" to mapOf (
707+ " street" to " 123 Main St" ,
708+ " city" to null , // Null city
709+ " zipCode" to " 12345"
710+ )
711+ ),
712+ " preferences" to mapOf (
713+ " notifications" to mapOf (
714+ " email" to true ,
715+ " push" to null , // Null push preference
716+ " frequency" to mapOf (
717+ " daily" to true ,
718+ " weekly" to null // Nested null
719+ )
720+ ),
721+ " theme" to null // Null theme
722+ )
723+ )
724+
725+ sdkInstance.track(givenEvent, userData)
726+
727+ outputReaderPlugin.trackEvents.size shouldBeEqualTo 1
728+ val trackEvent = outputReaderPlugin.trackEvents.lastOrNull()
729+ trackEvent.shouldNotBeNull()
730+
731+ trackEvent.event shouldBeEqualTo givenEvent
732+
733+ // Verify the nested structure with nulls was processed correctly
734+ val contact = trackEvent.properties[" contact" ] as ? Map <* , * >
735+ contact.shouldNotBeNull()
736+
737+ val address = contact[" address" ] as ? Map <* , * >
738+ address.shouldNotBeNull()
739+
740+ val preferences = trackEvent.properties[" preferences" ] as ? Map <* , * >
741+ preferences.shouldNotBeNull()
742+
743+ val notifications = preferences[" notifications" ] as ? Map <* , * >
744+ notifications.shouldNotBeNull()
745+
746+ val frequency = notifications[" frequency" ] as ? Map <* , * >
747+ frequency.shouldNotBeNull()
748+ }
749+
750+ @Test
751+ fun track_givenArrayWithNestedNulls_expectSuccessfulEventProcessing () {
752+ val givenEvent = String .random
753+ val givenProperties: CustomAttributes = mapOf (
754+ " items" to listOf (
755+ mapOf (" id" to 1 , " value" to " first" , " optional" to null ),
756+ mapOf (" id" to 2 , " value" to null , " details" to mapOf (" key" to null )),
757+ null
758+ )
759+ )
760+
761+ sdkInstance.track(givenEvent, givenProperties)
762+
763+ outputReaderPlugin.trackEvents.size shouldBeEqualTo 1
764+ val trackEvent = outputReaderPlugin.trackEvents.lastOrNull()
765+ trackEvent.shouldNotBeNull()
766+
767+ trackEvent.event shouldBeEqualTo givenEvent
768+
769+ // Verify the event was processed successfully
770+ val items = trackEvent.properties[" items" ]
771+ items.shouldNotBeNull()
772+ (items as List <* >).size shouldBeEqualTo 3
773+ }
774+
775+ @Test
776+ fun identify_givenNestedNullInAttributes_expectSuccessfulProfileCreation () {
777+ val givenIdentifier = String .random
778+ val givenTraits: CustomAttributes = mapOf (
779+ " profile" to mapOf (
780+ " firstName" to " Jane" ,
781+ " lastName" to " Doe" ,
782+ " settings" to mapOf (
783+ " language" to " en" ,
784+ " timezone" to null ,
785+ " preferences" to mapOf (
786+ " notifications" to true ,
787+ " marketing" to null
788+ )
789+ ),
790+ " company" to null
791+ )
792+ )
793+
794+ sdkInstance.identify(givenIdentifier, givenTraits)
795+
796+ analytics.userId() shouldBeEqualTo givenIdentifier
797+
798+ outputReaderPlugin.identifyEvents.size shouldBeEqualTo 1
799+ val identifyEvent = outputReaderPlugin.identifyEvents.lastOrNull()
800+ identifyEvent.shouldNotBeNull()
801+
802+ identifyEvent.userId shouldBeEqualTo givenIdentifier
803+
804+ // If this passes, it means the SDK correctly handled the nested nulls
805+ val profile = identifyEvent.traits[" profile" ] as ? Map <* , * >
806+ profile.shouldNotBeNull()
807+
808+ val settings = profile[" settings" ] as ? Map <* , * >
809+ settings.shouldNotBeNull()
810+
811+ val preferences = settings[" preferences" ] as ? Map <* , * >
812+ preferences.shouldNotBeNull()
813+ }
814+
815+ @Test
816+ fun screen_givenNestedNullInProperties_expectSuccessfulScreenView () {
817+ val givenTitle = String .random
818+ val givenProperties: CustomAttributes = mapOf (
819+ " view" to mapOf (
820+ " id" to " home_screen" ,
821+ " elements" to listOf (
822+ mapOf (" type" to " button" , " visible" to true ),
823+ mapOf (" type" to " input" , " value" to null ),
824+ null
825+ ),
826+ " metadata" to mapOf (
827+ " version" to " 1.0" ,
828+ " debug" to null ,
829+ " nested" to mapOf (
830+ " key" to " value" ,
831+ " optional" to null
832+ )
833+ )
834+ )
835+ )
836+
837+ sdkInstance.screen(givenTitle, givenProperties)
838+
839+ outputReaderPlugin.screenEvents.size shouldBeEqualTo 1
840+ val screenEvent = outputReaderPlugin.screenEvents.lastOrNull()
841+ screenEvent.shouldNotBeNull()
842+
843+ screenEvent.name shouldBeEqualTo givenTitle
844+
845+ // Verify the event properties were correctly processed with null handling
846+ val view = screenEvent.properties[" view" ] as ? Map <* , * >
847+ view.shouldNotBeNull()
848+
849+ val elements = view[" elements" ] as ? List <* >
850+ elements.shouldNotBeNull()
851+ elements.size shouldBeEqualTo 3
852+
853+ val metadata = view[" metadata" ] as ? Map <* , * >
854+ metadata.shouldNotBeNull()
855+
856+ val nested = metadata[" nested" ] as ? Map <* , * >
857+ nested.shouldNotBeNull()
858+ }
859+
860+ // endregion
661861}
0 commit comments