@@ -2935,6 +2935,213 @@ def test_derive_client_error_sampling_rate_invalid_range(self) -> None:
29352935 # Check that sample_rate was not set due to invalid range
29362936 assert "sample_rate" not in event .data
29372937
2938+ def test_times_seen_new_group_default_behavior (self ) -> None :
2939+ """Test that new groups start with times_seen=1 when no sample rate is provided"""
2940+ manager = EventManager (make_event (message = "test message" ))
2941+ manager .normalize ()
2942+
2943+ with self .tasks ():
2944+ event = manager .save (self .project .id )
2945+
2946+ group = event .group
2947+ assert group is not None
2948+ assert group .times_seen == 1
2949+
2950+ def test_times_seen_existing_group_increment (self ) -> None :
2951+ """Test that existing groups have their times_seen incremented"""
2952+ # Create first event to establish the group
2953+ manager1 = EventManager (make_event (message = "test message" , fingerprint = ["group1" ]))
2954+ manager1 .normalize ()
2955+
2956+ with self .tasks ():
2957+ event1 = manager1 .save (self .project .id )
2958+
2959+ group = event1 .group
2960+ assert group is not None
2961+ initial_times_seen = group .times_seen
2962+ assert initial_times_seen == 1
2963+
2964+ # Create second event for the same group
2965+ manager2 = EventManager (make_event (message = "test message 2" , fingerprint = ["group1" ]))
2966+ manager2 .normalize ()
2967+
2968+ with self .tasks ():
2969+ event2 = manager2 .save (self .project .id )
2970+
2971+ # Should be the same group
2972+ assert event2 .group_id == event1 .group_id
2973+
2974+ # Refresh group from database to get updated times_seen
2975+ group .refresh_from_db ()
2976+ assert group .times_seen == initial_times_seen + 1
2977+
2978+ def test_times_seen_weighted_with_sample_rate_option_enabled (self ) -> None :
2979+ """Test that times_seen is weighted by 1/sample_rate when the project is in the allowlist"""
2980+
2981+ with self .options ({"issues.client_error_sampling.project_allowlist" : [self .project .id ]}):
2982+ # Create event with a sample rate of 0.5 (50%)
2983+ event_data = make_event (
2984+ message = "sampled event" , contexts = {"error_sampling" : {"client_sample_rate" : 0.5 }}
2985+ )
2986+
2987+ manager = EventManager (event_data )
2988+ manager .normalize ()
2989+
2990+ with self .tasks ():
2991+ event = manager .save (self .project .id )
2992+
2993+ group = event .group
2994+ assert group is not None
2995+ # With sample rate 0.5, times_seen should be 1/0.5 = 2
2996+ assert group .times_seen == 2
2997+
2998+ def test_times_seen_weighted_with_sample_rate_option_disabled (self ) -> None :
2999+ """Test that times_seen is not weighted when the project is not in the allowlist"""
3000+
3001+ # Create event with a sample rate of 0.5 (50%) but project not in allowlist
3002+ event_data = make_event (
3003+ message = "sampled event" , contexts = {"error_sampling" : {"client_sample_rate" : 0.5 }}
3004+ )
3005+
3006+ manager = EventManager (event_data )
3007+ manager .normalize ()
3008+
3009+ with self .tasks ():
3010+ event = manager .save (self .project .id )
3011+
3012+ group = event .group
3013+ assert group is not None
3014+ # With the project not in allowlist, times_seen should remain 1 regardless of sample rate
3015+ assert group .times_seen == 1
3016+
3017+ def test_times_seen_weighted_existing_group_with_sample_rate (self ) -> None :
3018+ """Test that existing groups are incremented by weighted amount when project is in allowlist"""
3019+
3020+ # Create first event to establish the group
3021+ manager1 = EventManager (make_event (message = "test message" , fingerprint = ["group1" ]))
3022+ manager1 .normalize ()
3023+
3024+ with self .tasks ():
3025+ event1 = manager1 .save (self .project .id )
3026+
3027+ group = event1 .group
3028+ assert group is not None
3029+ initial_times_seen = group .times_seen
3030+ assert initial_times_seen == 1
3031+
3032+ with self .options ({"issues.client_error_sampling.project_allowlist" : [self .project .id ]}):
3033+ # Create second event for the same group with sample rate 0.25 (25%)
3034+ event_data = make_event (
3035+ message = "test message 2" ,
3036+ fingerprint = ["group1" ],
3037+ contexts = {"error_sampling" : {"client_sample_rate" : 0.25 }},
3038+ )
3039+
3040+ manager2 = EventManager (event_data )
3041+ manager2 .normalize ()
3042+
3043+ with self .tasks ():
3044+ event2 = manager2 .save (self .project .id )
3045+
3046+ # Should be the same group
3047+ assert event2 .group_id == event1 .group_id
3048+
3049+ # Refresh group from database to get updated times_seen
3050+ group .refresh_from_db ()
3051+ # Should be incremented by 1/0.25 = 4
3052+ assert group .times_seen == initial_times_seen + 4
3053+
3054+ def test_times_seen_no_sample_rate_meta (self ) -> None :
3055+ """Test that times_seen defaults to 1 when no sample rate meta exists"""
3056+ with self .options ({"issues.client_error_sampling.project_allowlist" : [self .project .id ]}):
3057+ # Create event with no error_sampling context
3058+ manager = EventManager (make_event (fingerprint = ["no_context" ]))
3059+ manager .normalize ()
3060+
3061+ with self .tasks ():
3062+ event = manager .save (self .project .id )
3063+ assert event .group is not None
3064+ assert event .group .times_seen == 1
3065+
3066+ # Create event with empty error_sampling context
3067+ manager = EventManager (
3068+ make_event (fingerprint = ["empty_context" ], contexts = {"error_sampling" : {}})
3069+ )
3070+ manager .normalize ()
3071+
3072+ with self .tasks ():
3073+ event = manager .save (self .project .id )
3074+ assert event .group is not None
3075+ assert event .group .times_seen == 1
3076+
3077+ # Create event with null client_sample_rate
3078+ manager = EventManager (
3079+ make_event (
3080+ fingerprint = ["null_client_sample_rate" ],
3081+ contexts = {"error_sampling" : {"client_sample_rate" : None }},
3082+ )
3083+ )
3084+ manager .normalize ()
3085+
3086+ with self .tasks ():
3087+ event = manager .save (self .project .id )
3088+ assert event .group is not None
3089+ assert event .group .times_seen == 1
3090+
3091+ def test_times_seen_invalid_sample_rate (self ) -> None :
3092+ """Test times_seen calculation with invalid sample rates (null, 0, negative, > 1)"""
3093+ with self .options ({"issues.client_error_sampling.project_allowlist" : [self .project .id ]}):
3094+ # Test null sample rate
3095+ manager = EventManager (make_event (fingerprint = ["null_sample_rate" ]))
3096+ manager .normalize ()
3097+
3098+ with self .tasks ():
3099+ event = manager .save (self .project .id )
3100+ assert event .group is not None
3101+ assert event .group .times_seen == 1
3102+
3103+ # Test sample rate of 0 (should result in times_seen = 1)
3104+ manager = EventManager (
3105+ make_event (
3106+ fingerprint = ["zero_sample_rate" ],
3107+ contexts = {"error_sampling" : {"client_sample_rate" : 0 }},
3108+ )
3109+ )
3110+ manager .normalize ()
3111+
3112+ with self .tasks ():
3113+ event = manager .save (self .project .id )
3114+ assert event .group is not None
3115+ assert event .group .times_seen == 1
3116+
3117+ # Test negative sample rate (should result in times_seen = 1)
3118+ manager = EventManager (
3119+ make_event (
3120+ fingerprint = ["negative_sample_rate" ],
3121+ contexts = {"error_sampling" : {"client_sample_rate" : - 0.5 }},
3122+ )
3123+ )
3124+ manager .normalize ()
3125+
3126+ with self .tasks ():
3127+ event = manager .save (self .project .id )
3128+ assert event .group is not None
3129+ assert event .group .times_seen == 1
3130+
3131+ # Test sample rate > 1 (should result in times_seen = 1)
3132+ manager = EventManager (
3133+ make_event (
3134+ fingerprint = ["high_sample_rate" ],
3135+ contexts = {"error_sampling" : {"client_sample_rate" : 1.5 }},
3136+ )
3137+ )
3138+ manager .normalize ()
3139+
3140+ with self .tasks ():
3141+ event = manager .save (self .project .id )
3142+ assert event .group is not None
3143+ assert event .group .times_seen == 1
3144+
29383145
29393146class ReleaseIssueTest (TestCase ):
29403147 def setUp (self ) -> None :
0 commit comments