@@ -442,3 +442,216 @@ def test_get_feature_flag_result_unknown_flag(self, patch_capture, patch_flags):
442442 groups = {},
443443 disable_geoip = None ,
444444 )
445+
446+ @mock .patch ("posthog.client.flags" )
447+ @mock .patch .object (Client , "capture" )
448+ def test_get_feature_flag_result_with_errors_while_computing_flags (
449+ self , patch_capture , patch_flags
450+ ):
451+ """Test that errors_while_computing_flags is included in the $feature_flag_called event.
452+
453+ When the server returns errorsWhileComputingFlags=true, it indicates that there
454+ was an error computing one or more flags. We include this in the event so users
455+ can identify and debug flag evaluation issues.
456+ """
457+ patch_flags .return_value = {
458+ "flags" : {
459+ "my-flag" : {
460+ "key" : "my-flag" ,
461+ "enabled" : True ,
462+ "variant" : None ,
463+ "reason" : {"description" : "Matched condition set 1" },
464+ "metadata" : {"id" : 1 , "version" : 1 , "payload" : None },
465+ },
466+ },
467+ "requestId" : "test-request-id-789" ,
468+ "errorsWhileComputingFlags" : True ,
469+ }
470+
471+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
472+
473+ self .assertEqual (flag_result .enabled , True )
474+ patch_capture .assert_called_with (
475+ "$feature_flag_called" ,
476+ distinct_id = "some-distinct-id" ,
477+ properties = {
478+ "$feature_flag" : "my-flag" ,
479+ "$feature_flag_response" : True ,
480+ "locally_evaluated" : False ,
481+ "$feature/my-flag" : True ,
482+ "$feature_flag_request_id" : "test-request-id-789" ,
483+ "$feature_flag_reason" : "Matched condition set 1" ,
484+ "$feature_flag_id" : 1 ,
485+ "$feature_flag_version" : 1 ,
486+ "$feature_flag_error" : "errors_while_computing_flags" ,
487+ },
488+ groups = {},
489+ disable_geoip = None ,
490+ )
491+
492+ @mock .patch ("posthog.client.flags" )
493+ @mock .patch .object (Client , "capture" )
494+ def test_get_feature_flag_result_flag_not_in_response (
495+ self , patch_capture , patch_flags
496+ ):
497+ """Test that when a flag is not in the API response, we capture flag_missing error.
498+
499+ This happens when a flag doesn't exist or the user doesn't match any conditions.
500+ """
501+ patch_flags .return_value = {
502+ "flags" : {
503+ "other-flag" : {
504+ "key" : "other-flag" ,
505+ "enabled" : True ,
506+ "variant" : None ,
507+ "reason" : {"description" : "Matched condition set 1" },
508+ "metadata" : {"id" : 1 , "version" : 1 , "payload" : None },
509+ },
510+ },
511+ "requestId" : "test-request-id-456" ,
512+ }
513+
514+ flag_result = self .client .get_feature_flag_result (
515+ "missing-flag" , "some-distinct-id"
516+ )
517+
518+ self .assertIsNone (flag_result )
519+ patch_capture .assert_called_with (
520+ "$feature_flag_called" ,
521+ distinct_id = "some-distinct-id" ,
522+ properties = {
523+ "$feature_flag" : "missing-flag" ,
524+ "$feature_flag_response" : None ,
525+ "locally_evaluated" : False ,
526+ "$feature/missing-flag" : None ,
527+ "$feature_flag_request_id" : "test-request-id-456" ,
528+ "$feature_flag_error" : "flag_missing" ,
529+ },
530+ groups = {},
531+ disable_geoip = None ,
532+ )
533+
534+ @mock .patch ("posthog.client.flags" )
535+ @mock .patch .object (Client , "capture" )
536+ def test_get_feature_flag_result_unknown_error (self , patch_capture , patch_flags ):
537+ """Test that unexpected exceptions are captured as unknown_error."""
538+ patch_flags .side_effect = Exception ("Unexpected error" )
539+
540+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
541+
542+ self .assertIsNone (flag_result )
543+ patch_capture .assert_called_with (
544+ "$feature_flag_called" ,
545+ distinct_id = "some-distinct-id" ,
546+ properties = {
547+ "$feature_flag" : "my-flag" ,
548+ "$feature_flag_response" : None ,
549+ "locally_evaluated" : False ,
550+ "$feature/my-flag" : None ,
551+ "$feature_flag_error" : "unknown_error" ,
552+ },
553+ groups = {},
554+ disable_geoip = None ,
555+ )
556+
557+ @mock .patch ("posthog.client.flags" )
558+ @mock .patch .object (Client , "capture" )
559+ def test_get_feature_flag_result_timeout_error (self , patch_capture , patch_flags ):
560+ """Test that timeout errors are captured specifically."""
561+ import requests .exceptions
562+
563+ patch_flags .side_effect = requests .exceptions .Timeout ("Request timed out" )
564+
565+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
566+
567+ self .assertIsNone (flag_result )
568+ patch_capture .assert_called_with (
569+ "$feature_flag_called" ,
570+ distinct_id = "some-distinct-id" ,
571+ properties = {
572+ "$feature_flag" : "my-flag" ,
573+ "$feature_flag_response" : None ,
574+ "locally_evaluated" : False ,
575+ "$feature/my-flag" : None ,
576+ "$feature_flag_error" : "timeout" ,
577+ },
578+ groups = {},
579+ disable_geoip = None ,
580+ )
581+
582+ @mock .patch ("posthog.client.flags" )
583+ @mock .patch .object (Client , "capture" )
584+ def test_get_feature_flag_result_connection_error (self , patch_capture , patch_flags ):
585+ """Test that connection errors are captured specifically."""
586+ import requests .exceptions
587+
588+ patch_flags .side_effect = requests .exceptions .ConnectionError (
589+ "Connection refused"
590+ )
591+
592+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
593+
594+ self .assertIsNone (flag_result )
595+ patch_capture .assert_called_with (
596+ "$feature_flag_called" ,
597+ distinct_id = "some-distinct-id" ,
598+ properties = {
599+ "$feature_flag" : "my-flag" ,
600+ "$feature_flag_response" : None ,
601+ "locally_evaluated" : False ,
602+ "$feature/my-flag" : None ,
603+ "$feature_flag_error" : "connection_error" ,
604+ },
605+ groups = {},
606+ disable_geoip = None ,
607+ )
608+
609+ @mock .patch ("posthog.client.flags" )
610+ @mock .patch .object (Client , "capture" )
611+ def test_get_feature_flag_result_api_error (self , patch_capture , patch_flags ):
612+ """Test that API errors include the status code."""
613+ from posthog .request import APIError
614+
615+ patch_flags .side_effect = APIError (500 , "Internal server error" )
616+
617+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
618+
619+ self .assertIsNone (flag_result )
620+ patch_capture .assert_called_with (
621+ "$feature_flag_called" ,
622+ distinct_id = "some-distinct-id" ,
623+ properties = {
624+ "$feature_flag" : "my-flag" ,
625+ "$feature_flag_response" : None ,
626+ "locally_evaluated" : False ,
627+ "$feature/my-flag" : None ,
628+ "$feature_flag_error" : "api_error_500" ,
629+ },
630+ groups = {},
631+ disable_geoip = None ,
632+ )
633+
634+ @mock .patch ("posthog.client.flags" )
635+ @mock .patch .object (Client , "capture" )
636+ def test_get_feature_flag_result_quota_limited (self , patch_capture , patch_flags ):
637+ """Test that quota limit errors are captured specifically."""
638+ from posthog .request import QuotaLimitError
639+
640+ patch_flags .side_effect = QuotaLimitError (429 , "Rate limit exceeded" )
641+
642+ flag_result = self .client .get_feature_flag_result ("my-flag" , "some-distinct-id" )
643+
644+ self .assertIsNone (flag_result )
645+ patch_capture .assert_called_with (
646+ "$feature_flag_called" ,
647+ distinct_id = "some-distinct-id" ,
648+ properties = {
649+ "$feature_flag" : "my-flag" ,
650+ "$feature_flag_response" : None ,
651+ "locally_evaluated" : False ,
652+ "$feature/my-flag" : None ,
653+ "$feature_flag_error" : "quota_limited" ,
654+ },
655+ groups = {},
656+ disable_geoip = None ,
657+ )
0 commit comments