diff --git a/posthog/src/main/java/com/posthog/PostHogStateless.kt b/posthog/src/main/java/com/posthog/PostHogStateless.kt index 5628b166..ae2cba28 100644 --- a/posthog/src/main/java/com/posthog/PostHogStateless.kt +++ b/posthog/src/main/java/com/posthog/PostHogStateless.kt @@ -454,7 +454,16 @@ public open class PostHogStateless protected constructor( groupProperties, )?.let { props["\$feature_flag_error"] = it } - captureStateless(PostHogEventName.FEATURE_FLAG_CALLED.event, distinctId, properties = props) + val userProps = personProperties + ?.filterValues { it != null } + ?.mapValues { it.value!! } + captureStateless( + PostHogEventName.FEATURE_FLAG_CALLED.event, + distinctId, + properties = props, + userProperties = userProps, + groups = groups, + ) } } } diff --git a/posthog/src/test/java/com/posthog/PostHogStatelessTest.kt b/posthog/src/test/java/com/posthog/PostHogStatelessTest.kt index dea8871c..b3561792 100644 --- a/posthog/src/test/java/com/posthog/PostHogStatelessTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogStatelessTest.kt @@ -811,6 +811,122 @@ internal class PostHogStatelessTest { assertEquals(true, event.properties!!["\$feature_flag_response"]) } + @Test + fun `feature flag called events propagate userProperties and groups`() { + val mockQueue = MockQueue() + val mockFeatureFlags = MockFeatureFlags() + mockFeatureFlags.setFlag("test_flag", "variant_a") + + sut = createStatelessInstance() + config = createConfig(sendFeatureFlagEvent = true) + + sut.setup(config) + sut.setMockQueue(mockQueue) + sut.setMockFeatureFlags(mockFeatureFlags) + + val groups = mapOf("organization" to "org_123") + val personProperties = mapOf("plan" to "premium", "role" to "admin") + + // Access feature flag with groups and person properties + sut.getFeatureFlagStateless( + "user123", + "test_flag", + null, + groups, + personProperties, + null, + ) + + // Should generate feature flag called event with propagated properties + assertEquals(1, mockQueue.events.size) + val event = mockQueue.events.first() + assertEquals("\$feature_flag_called", event.event) + assertEquals("user123", event.distinctId) + assertEquals("test_flag", event.properties!!["\$feature_flag"]) + assertEquals("variant_a", event.properties!!["\$feature_flag_response"]) + + // Check that groups are propagated + assertEquals(mapOf("organization" to "org_123"), event.properties!!["\$groups"]) + + // Check that userProperties are propagated (as $set) + @Suppress("UNCHECKED_CAST") + val setProps = event.properties!!["\$set"] as? Map + assertNotNull(setProps) + assertEquals("premium", setProps["plan"]) + assertEquals("admin", setProps["role"]) + } + + @Test + fun `feature flag called events filter out null values from personProperties`() { + val mockQueue = MockQueue() + val mockFeatureFlags = MockFeatureFlags() + mockFeatureFlags.setFlag("test_flag", true) + + sut = createStatelessInstance() + config = createConfig(sendFeatureFlagEvent = true) + + sut.setup(config) + sut.setMockQueue(mockQueue) + sut.setMockFeatureFlags(mockFeatureFlags) + + val personProperties = mapOf("plan" to "premium", "nullable" to null) + + // Access feature flag with person properties containing null + sut.isFeatureEnabledStateless( + "user123", + "test_flag", + false, + null, + personProperties, + null, + ) + + assertEquals(1, mockQueue.events.size) + val event = mockQueue.events.first() + + // Check that userProperties are propagated without null values + @Suppress("UNCHECKED_CAST") + val setProps = event.properties!!["\$set"] as? Map + assertNotNull(setProps) + assertEquals("premium", setProps["plan"]) + assertFalse(setProps.containsKey("nullable")) + } + + @Test + fun `feature flag called events handle null personProperties gracefully`() { + val mockQueue = MockQueue() + val mockFeatureFlags = MockFeatureFlags() + mockFeatureFlags.setFlag("test_flag", true) + + sut = createStatelessInstance() + config = createConfig(sendFeatureFlagEvent = true) + + sut.setup(config) + sut.setMockQueue(mockQueue) + sut.setMockFeatureFlags(mockFeatureFlags) + + val groups = mapOf("organization" to "org_123") + + // Access feature flag with null person properties + sut.isFeatureEnabledStateless( + "user123", + "test_flag", + false, + groups, + null, + null, + ) + + assertEquals(1, mockQueue.events.size) + val event = mockQueue.events.first() + + // Check that groups are still propagated + assertEquals(mapOf("organization" to "org_123"), event.properties!!["\$groups"]) + + // Check that $set is not present when personProperties is null + assertNull(event.properties!!["\$set"]) + } + @Test fun `feature flag called events not sent when disabled`() { val mockQueue = MockQueue()