@@ -374,6 +374,48 @@ def make_split_fixture(flag_key: str):
374374 }
375375
376376
377+ def make_invalid_regex_fixture (flag_key : str , invalid_regex : str = "[invalid" ):
378+ """Create a UFC fixture with an invalid regex pattern in a MATCHES condition.
379+
380+ This tests the PARSE_ERROR scenario where the configuration contains
381+ a syntactically invalid regex pattern that fails during evaluation.
382+ """
383+ return {
384+ "createdAt" : "2024-04-17T19:40:53.716Z" ,
385+ "format" : "SERVER" ,
386+ "environment" : {"name" : "Test" },
387+ "flags" : {
388+ flag_key : {
389+ "key" : flag_key ,
390+ "enabled" : True ,
391+ "variationType" : "STRING" ,
392+ "variations" : {
393+ "on" : {"key" : "on" , "value" : "on-value" },
394+ "off" : {"key" : "off" , "value" : "off-value" },
395+ },
396+ "allocations" : [
397+ {
398+ "key" : "regex-allocation" ,
399+ "rules" : [
400+ {
401+ "conditions" : [
402+ {
403+ "operator" : "MATCHES" ,
404+ "attribute" : "email" ,
405+ "value" : invalid_regex , # Invalid regex pattern
406+ }
407+ ]
408+ }
409+ ],
410+ "splits" : [{"variationKey" : "on" , "shards" : []}],
411+ "doLog" : True ,
412+ }
413+ ],
414+ }
415+ },
416+ }
417+
418+
377419@scenarios .feature_flagging_and_experimentation
378420@features .feature_flags_eval_metrics
379421class Test_FFE_Eval_Reason_Targeting :
@@ -543,7 +585,7 @@ def test_ffe_eval_reason_disabled(self):
543585# -----------------------|---------------------------------------------
544586# FLAG_NOT_FOUND | Test_FFE_Eval_Config_Exists_Flag_Missing
545587# TYPE_MISMATCH | Test_FFE_Eval_Metric_Type_Mismatch, Test_FFE_Eval_Metric_Numeric_To_Integer
546- # PARSE_ERROR | (not tested - no cross-SDK consistent scenario)
588+ # PARSE_ERROR | Test_FFE_Eval_Metric_Parse_Error
547589# GENERAL | (not tested - catch-all error code)
548590# TARGETING_KEY_MISSING | Test_FFE_Eval_Targeting_Key_Optional (verifies it's NOT returned; JS excluded)
549591# INVALID_CONTEXT | Test_FFE_Eval_Invalid_Context_Nested_Attribute (Python only)
@@ -712,6 +754,63 @@ def test_ffe_eval_metric_numeric_to_integer(self):
712754 )
713755
714756
757+ @scenarios .feature_flagging_and_experimentation
758+ @features .feature_flags_eval_metrics
759+ @irrelevant (
760+ context .library == "golang" ,
761+ reason = "Go validates regex at config load time and rejects invalid patterns upfront" ,
762+ )
763+ class Test_FFE_Eval_Metric_Parse_Error :
764+ """Test that an invalid regex pattern produces error.type=parse_error.
765+
766+ This configures a flag with a MATCHES condition containing an invalid regex pattern
767+ (e.g., "[invalid" which has an unclosed bracket). When the condition is evaluated,
768+ the regex compilation fails and produces a parse_error.
769+
770+ Behavioral differences across SDKs:
771+ - Python (libdatadog): Returns parse_error during evaluation
772+ - Go: Validates regex at config load time, rejects config with invalid regex
773+ """
774+
775+ def setup_ffe_eval_metric_parse_error (self ):
776+ rc .tracer_rc_state .reset ().apply ()
777+
778+ config_id = "ffe-eval-metric-parse-error"
779+ self .flag_key = "eval-metric-parse-error-flag"
780+ rc .tracer_rc_state .set_config (
781+ f"{ RC_PATH } /{ config_id } /config" , make_invalid_regex_fixture (self .flag_key )
782+ ).apply ()
783+
784+ # Evaluate the flag with an attribute that triggers the invalid regex condition
785+ self .r = weblog .post (
786+ "/ffe" ,
787+ json = {
788+ "flag" : self .flag_key ,
789+ "variationType" : "STRING" ,
790+ "defaultValue" : "default" ,
791+ "targetingKey" : "user-1" ,
792+ "attributes" : {"email" : "test@example.com" }, # Triggers MATCHES condition
793+ },
794+ )
795+
796+ def test_ffe_eval_metric_parse_error (self ):
797+ """Test that invalid regex produces error.type:parse_error."""
798+ assert self .r .status_code == 200 , f"Flag evaluation request failed: { self .r .text } "
799+
800+ metrics = find_eval_metrics (self .flag_key )
801+ assert len (metrics ) > 0 , f"Expected metric for flag '{ self .flag_key } ', found none. All: { find_eval_metrics ()} "
802+
803+ point = metrics [0 ]
804+ tags = point .get ("tags" , [])
805+
806+ assert get_tag_value (tags , "feature_flag.result.reason" ) == "error" , (
807+ f"Expected reason 'error' for parse error, got tags: { tags } "
808+ )
809+ assert get_tag_value (tags , "error.type" ) == "parse_error" , (
810+ f"Expected error.type 'parse_error', got tags: { tags } "
811+ )
812+
813+
715814@scenarios .feature_flagging_and_experimentation
716815@features .feature_flags_eval_metrics
717816class Test_FFE_Eval_No_Config_Loaded :
0 commit comments