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