11import json
2+ import logging
23import time
34import typing
45
@@ -1020,6 +1021,27 @@ def test_track_exposure_event_raises_without_config(api_key: str) -> None:
10201021 flagsmith .track_exposure_event ("checkout_v2" )
10211022
10221023
1024+ def test_track_exposure_event__no_identifier__exposure_not_sent (
1025+ mocker : MockerFixture ,
1026+ api_key : str ,
1027+ caplog : pytest .LogCaptureFixture ,
1028+ ) -> None :
1029+ # Given
1030+ config = EventProcessorConfig (events_api_url = "http://test/" )
1031+ flagsmith = Flagsmith (
1032+ environment_key = api_key , enable_events = True , event_processor_config = config
1033+ )
1034+
1035+ mock_track = mocker .patch .object (flagsmith ._event_processor , "track_exposure_event" )
1036+
1037+ # When
1038+ flagsmith .track_exposure_event ("checkout_v2" , value = "variant_b" )
1039+
1040+ # Then
1041+ mock_track .assert_not_called ()
1042+ assert "an exposure requires an identifier" in caplog .text
1043+
1044+
10231045def test_track_exposure_event_delegates_to_event_processor (
10241046 mocker : MockerFixture , api_key : str
10251047) -> None :
@@ -1054,9 +1076,10 @@ def test_get_experiment_flag_raises_without_events_enabled(api_key: str) -> None
10541076
10551077
10561078@responses .activate ()
1057- def test_get_experiment_flag_returns_flag_and_tracks_exposure (
1079+ def test_get_experiment_flag__variant__returns_flag_and_tracks_exposure (
10581080 mocker : MockerFixture , api_key : str , identities_json : str
10591081) -> None :
1082+ # Given
10601083 config = EventProcessorConfig (events_api_url = "http://test/" )
10611084 flagsmith = Flagsmith (
10621085 environment_key = api_key , enable_events = True , event_processor_config = config
@@ -1065,29 +1088,33 @@ def test_get_experiment_flag_returns_flag_and_tracks_exposure(
10651088 mock_track = mocker .patch .object (flagsmith ._event_processor , "track_exposure_event" )
10661089 responses .add (method = "POST" , url = flagsmith .identities_url , body = identities_json )
10671090
1091+ # When
10681092 result = flagsmith .get_experiment_flag (
10691093 feature_name = "some_feature" ,
10701094 identifier = "user1" ,
10711095 traits = {"plan" : "premium" },
10721096 )
10731097
1098+ # Then
10741099 assert isinstance (result , Flag )
10751100 assert result .is_default is False
10761101 assert result .feature_name == "some_feature"
10771102 assert result .value == "some-value"
1103+ assert result .variant == "treatment"
10781104 mock_track .assert_called_once_with (
10791105 feature_name = "some_feature" ,
10801106 identifier = "user1" ,
1081- value = "some-value " ,
1107+ value = "treatment " ,
10821108 traits = {"plan" : "premium" },
10831109 metadata = None ,
10841110 )
10851111
10861112
10871113@responses .activate ()
1088- def test_get_experiment_flag_skips_exposure_for_default_flag (
1089- mocker : MockerFixture , api_key : str
1114+ def test_get_experiment_flag__default_flag__skips_exposure (
1115+ mocker : MockerFixture , api_key : str , caplog : pytest . LogCaptureFixture
10901116) -> None :
1117+ # Given
10911118 config = EventProcessorConfig (events_api_url = "http://test/" )
10921119
10931120 def default_flag_handler (feature_name : str ) -> DefaultFlag :
@@ -1106,20 +1133,27 @@ def default_flag_handler(feature_name: str) -> DefaultFlag:
11061133 body = json .dumps ({"flags" : [], "traits" : []}),
11071134 )
11081135
1109- result = flagsmith .get_experiment_flag (
1110- feature_name = "missing_feature" , identifier = "user1"
1111- )
1136+ # When
1137+ with caplog .at_level (logging .DEBUG , logger = "flagsmith.flagsmith" ):
1138+ result = flagsmith .get_experiment_flag (
1139+ feature_name = "missing_feature" , identifier = "user1"
1140+ )
11121141
1142+ # Then
11131143 assert isinstance (result , DefaultFlag )
11141144 assert result .is_default is True
11151145 assert result .value == "default-variant"
11161146 mock_track .assert_not_called ()
1147+ assert (
1148+ "Not sending $flag_exposure for feature missing_feature: feature not found."
1149+ in caplog .messages
1150+ )
11171151
11181152
1119- def test_get_experiment_flag_uses_variant_as_exposure_value (
1153+ def test_get_experiment_flag__variant__used_as_exposure_value (
11201154 mocker : MockerFixture , api_key : str
11211155) -> None :
1122- # Given - a resolved flag carrying a variant
1156+ # Given
11231157 config = EventProcessorConfig (events_api_url = "http://test/" )
11241158 flagsmith = Flagsmith (
11251159 environment_key = api_key , enable_events = True , event_processor_config = config
@@ -1141,7 +1175,7 @@ def test_get_experiment_flag_uses_variant_as_exposure_value(
11411175 # When
11421176 flagsmith .get_experiment_flag (feature_name = "checkout_v2" , identifier = "user1" )
11431177
1144- # Then - the exposure value is the variant, not the flag value
1178+ # Then
11451179 mock_track .assert_called_once_with (
11461180 feature_name = "checkout_v2" ,
11471181 identifier = "user1" ,
@@ -1151,10 +1185,10 @@ def test_get_experiment_flag_uses_variant_as_exposure_value(
11511185 )
11521186
11531187
1154- def test_get_experiment_flag_falls_back_to_value_without_variant (
1155- mocker : MockerFixture , api_key : str
1188+ def test_get_experiment_flag__no_variant__skips_exposure (
1189+ mocker : MockerFixture , api_key : str , caplog : pytest . LogCaptureFixture
11561190) -> None :
1157- # Given - a resolved flag with no variant
1191+ # Given
11581192 config = EventProcessorConfig (events_api_url = "http://test/" )
11591193 flagsmith = Flagsmith (
11601194 environment_key = api_key , enable_events = True , event_processor_config = config
@@ -1174,15 +1208,17 @@ def test_get_experiment_flag_falls_back_to_value_without_variant(
11741208 mock_track = mocker .patch .object (flagsmith ._event_processor , "track_exposure_event" )
11751209
11761210 # When
1177- flagsmith .get_experiment_flag (feature_name = "checkout_v2" , identifier = "user1" )
1211+ with caplog .at_level (logging .DEBUG , logger = "flagsmith.flagsmith" ):
1212+ result = flagsmith .get_experiment_flag (
1213+ feature_name = "checkout_v2" , identifier = "user1"
1214+ )
11781215
1179- # Then - the exposure value falls back to the flag value
1180- mock_track .assert_called_once_with (
1181- feature_name = "checkout_v2" ,
1182- identifier = "user1" ,
1183- value = "blue" ,
1184- traits = None ,
1185- metadata = None ,
1216+ # Then
1217+ assert result is flag
1218+ mock_track .assert_not_called ()
1219+ assert (
1220+ "Not sending $flag_exposure for feature checkout_v2: flag has no variant."
1221+ in caplog .messages
11861222 )
11871223
11881224
@@ -1260,10 +1296,10 @@ def test_flagsmith_posts_analytics_to_api_url_when_analytics_url_unset(
12601296 assert len (analytics_posts ) == 1
12611297
12621298
1263- def test_get_experiment_flag_skips_exposure_for_disabled_feature (
1264- mocker : MockerFixture , api_key : str
1299+ def test_get_experiment_flag__disabled_feature__skips_exposure (
1300+ mocker : MockerFixture , api_key : str , caplog : pytest . LogCaptureFixture
12651301) -> None :
1266- # Given - a resolved flag for a disabled feature
1302+ # Given
12671303 config = EventProcessorConfig (events_api_url = "http://test/" )
12681304 flagsmith = Flagsmith (
12691305 environment_key = api_key , enable_events = True , event_processor_config = config
@@ -1283,10 +1319,15 @@ def test_get_experiment_flag_skips_exposure_for_disabled_feature(
12831319 mock_track = mocker .patch .object (flagsmith ._event_processor , "track_exposure_event" )
12841320
12851321 # When
1286- result = flagsmith .get_experiment_flag (
1287- feature_name = "checkout_v2" , identifier = "user1"
1288- )
1322+ with caplog .at_level (logging .DEBUG , logger = "flagsmith.flagsmith" ):
1323+ result = flagsmith .get_experiment_flag (
1324+ feature_name = "checkout_v2" , identifier = "user1"
1325+ )
12891326
1290- # Then - the flag is returned but no exposure event is tracked
1327+ # Then
12911328 assert result is flag
12921329 mock_track .assert_not_called ()
1330+ assert (
1331+ "Not sending $flag_exposure for feature checkout_v2: feature is disabled."
1332+ in caplog .messages
1333+ )
0 commit comments