@@ -381,3 +381,303 @@ fn header_field_path(
381381 _ => builder. index ( header_index) . finish ( ) ,
382382 }
383383}
384+
385+ #[ cfg( test) ]
386+ mod tests {
387+ use super :: * ;
388+ use crate :: config:: types:: {
389+ BackoffStrategyDTO , HeaderMatcherDTO , HeaderPredicateLegacy , RetryPolicy ,
390+ } ;
391+ use pavis_core:: Timeout ;
392+
393+ #[ test]
394+ fn test_parse_http_method_valid ( ) {
395+ assert ! ( parse_http_method( "GET" , "test" . to_string( ) ) . is_ok( ) ) ;
396+ assert ! ( parse_http_method( "post" , "test" . to_string( ) ) . is_ok( ) ) ;
397+ assert ! ( parse_http_method( "PUT" , "test" . to_string( ) ) . is_ok( ) ) ;
398+ }
399+
400+ #[ test]
401+ fn test_parse_http_method_invalid ( ) {
402+ let result = parse_http_method ( "INVALID" , "test" . to_string ( ) ) ;
403+ assert ! ( result. is_err( ) ) ;
404+ let err = result. unwrap_err ( ) ;
405+ assert ! ( err. to_string( ) . contains( "invalid HTTP method" ) ) ;
406+ }
407+
408+ #[ test]
409+ fn test_validate_header_name_empty ( ) {
410+ let result = validate_header_name ( "" , 0 , 0 , 0 ) ;
411+ assert ! ( result. is_err( ) ) ;
412+ let err = result. unwrap_err ( ) ;
413+ assert ! ( err. to_string( ) . contains( "cannot be empty" ) ) ;
414+ }
415+
416+ #[ test]
417+ fn test_validate_header_name_invalid_characters ( ) {
418+ let result = validate_header_name ( "x-header!" , 0 , 0 , 0 ) ;
419+ assert ! ( result. is_err( ) ) ;
420+ let err = result. unwrap_err ( ) ;
421+ assert ! ( err. to_string( ) . contains( "invalid header name" ) ) ;
422+ }
423+
424+ #[ test]
425+ fn test_validate_header_name_valid ( ) {
426+ assert ! ( validate_header_name( "x-my-header" , 0 , 0 , 0 ) . is_ok( ) ) ;
427+ assert ! ( validate_header_name( "content-type" , 0 , 0 , 0 ) . is_ok( ) ) ;
428+ assert ! ( validate_header_name( "X_Custom_Header" , 0 , 0 , 0 ) . is_ok( ) ) ;
429+ }
430+
431+ #[ test]
432+ fn test_header_predicate_dto_regex_too_long ( ) {
433+ let long_pattern = "a" . repeat ( 257 ) ;
434+ let dto = HeaderMatcherDTO :: Regex {
435+ name : "x-test" . to_string ( ) ,
436+ pattern : long_pattern,
437+ } ;
438+
439+ let result = to_runtime_header_predicate_dto ( dto, 0 , 0 , 0 ) ;
440+ assert ! ( result. is_err( ) ) ;
441+ let err = result. unwrap_err ( ) ;
442+ assert ! ( err. to_string( ) . contains( "exceeds 256 bytes" ) ) ;
443+ }
444+
445+ #[ test]
446+ fn test_header_predicate_dto_regex_invalid_syntax ( ) {
447+ let dto = HeaderMatcherDTO :: Regex {
448+ name : "x-test" . to_string ( ) ,
449+ pattern : "[invalid" . to_string ( ) ,
450+ } ;
451+
452+ let result = to_runtime_header_predicate_dto ( dto, 0 , 0 , 0 ) ;
453+ assert ! ( result. is_err( ) ) ;
454+ let err = result. unwrap_err ( ) ;
455+ assert ! ( err. to_string( ) . contains( "invalid regex pattern" ) ) ;
456+ }
457+
458+ #[ test]
459+ fn test_header_predicate_dto_regex_valid ( ) {
460+ let dto = HeaderMatcherDTO :: Regex {
461+ name : "x-version" . to_string ( ) ,
462+ pattern : "^v[0-9]+$" . to_string ( ) ,
463+ } ;
464+
465+ let result = to_runtime_header_predicate_dto ( dto, 0 , 0 , 0 ) ;
466+ assert ! ( result. is_ok( ) ) ;
467+ }
468+
469+ #[ test]
470+ fn test_header_predicate_legacy_absent_with_value_conflict ( ) {
471+ let pred = HeaderPredicateLegacy {
472+ name : "x-test" . to_string ( ) ,
473+ value : Some ( "test" . to_string ( ) ) ,
474+ regex : false ,
475+ prefix : false ,
476+ absent : true ,
477+ } ;
478+
479+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
480+ assert ! ( result. is_err( ) ) ;
481+ let err = result. unwrap_err ( ) ;
482+ assert ! ( err. to_string( ) . contains( "absent=true is incompatible" ) ) ;
483+ }
484+
485+ #[ test]
486+ fn test_header_predicate_legacy_regex_without_value ( ) {
487+ let pred = HeaderPredicateLegacy {
488+ name : "x-test" . to_string ( ) ,
489+ value : None ,
490+ regex : true ,
491+ prefix : false ,
492+ absent : false ,
493+ } ;
494+
495+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
496+ assert ! ( result. is_err( ) ) ;
497+ let err = result. unwrap_err ( ) ;
498+ assert ! ( err. to_string( ) . contains( "regex=true requires a value" ) ) ;
499+ }
500+
501+ #[ test]
502+ fn test_header_predicate_legacy_prefix_without_value ( ) {
503+ let pred = HeaderPredicateLegacy {
504+ name : "x-test" . to_string ( ) ,
505+ value : None ,
506+ regex : false ,
507+ prefix : true ,
508+ absent : false ,
509+ } ;
510+
511+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
512+ assert ! ( result. is_err( ) ) ;
513+ let err = result. unwrap_err ( ) ;
514+ assert ! ( err. to_string( ) . contains( "prefix=true requires a value" ) ) ;
515+ }
516+
517+ #[ test]
518+ fn test_header_predicate_legacy_regex_and_prefix_exclusive ( ) {
519+ let pred = HeaderPredicateLegacy {
520+ name : "x-test" . to_string ( ) ,
521+ value : Some ( "test" . to_string ( ) ) ,
522+ regex : true ,
523+ prefix : true ,
524+ absent : false ,
525+ } ;
526+
527+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
528+ assert ! ( result. is_err( ) ) ;
529+ let err = result. unwrap_err ( ) ;
530+ assert ! ( err. to_string( ) . contains( "mutually exclusive" ) ) ;
531+ }
532+
533+ #[ test]
534+ fn test_header_predicate_legacy_regex_too_long ( ) {
535+ let long_pattern = "a" . repeat ( 257 ) ;
536+ let pred = HeaderPredicateLegacy {
537+ name : "x-test" . to_string ( ) ,
538+ value : Some ( long_pattern) ,
539+ regex : true ,
540+ prefix : false ,
541+ absent : false ,
542+ } ;
543+
544+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
545+ assert ! ( result. is_err( ) ) ;
546+ let err = result. unwrap_err ( ) ;
547+ assert ! ( err. to_string( ) . contains( "exceeds 256 bytes" ) ) ;
548+ }
549+
550+ #[ test]
551+ fn test_header_predicate_legacy_regex_invalid_syntax ( ) {
552+ let pred = HeaderPredicateLegacy {
553+ name : "x-test" . to_string ( ) ,
554+ value : Some ( "[invalid" . to_string ( ) ) ,
555+ regex : true ,
556+ prefix : false ,
557+ absent : false ,
558+ } ;
559+
560+ let result = to_runtime_header_predicate_legacy ( pred, 0 , 0 , 0 ) ;
561+ assert ! ( result. is_err( ) ) ;
562+ let err = result. unwrap_err ( ) ;
563+ assert ! ( err. to_string( ) . contains( "invalid regex pattern" ) ) ;
564+ }
565+
566+ #[ test]
567+ fn test_convert_retry_policy_max_attempts_zero ( ) {
568+ let dto = RetryPolicy {
569+ max_attempts : 0 ,
570+ per_try : None ,
571+ retryable_reasons : vec ! [ ] ,
572+ retryable_status_codes : None ,
573+ backoff : BackoffStrategyDTO :: Fixed { base_ms : 100 } ,
574+ retry_non_idempotent : false ,
575+ fail_on_non_replayable_retry : false ,
576+ max_request_body_buffer_bytes : 1_048_576 ,
577+ } ;
578+
579+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
580+ assert ! ( result. is_err( ) ) ;
581+ let err = result. unwrap_err ( ) ;
582+ assert ! ( err. to_string( ) . contains( "must be >= 1" ) ) ;
583+ }
584+
585+ #[ test]
586+ fn test_convert_retry_policy_max_attempts_exceeds_limit ( ) {
587+ let dto = RetryPolicy {
588+ max_attempts : 11 ,
589+ per_try : None ,
590+ retryable_reasons : vec ! [ ] ,
591+ retryable_status_codes : None ,
592+ backoff : BackoffStrategyDTO :: Fixed { base_ms : 100 } ,
593+ retry_non_idempotent : false ,
594+ fail_on_non_replayable_retry : false ,
595+ max_request_body_buffer_bytes : 1_048_576 ,
596+ } ;
597+
598+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
599+ assert ! ( result. is_err( ) ) ;
600+ let err = result. unwrap_err ( ) ;
601+ assert ! ( err. to_string( ) . contains( "exceeds maximum of 10" ) ) ;
602+ }
603+
604+ #[ test]
605+ fn test_convert_retry_policy_unknown_reason ( ) {
606+ let dto = RetryPolicy {
607+ max_attempts : 3 ,
608+ per_try : None ,
609+ retryable_reasons : vec ! [ "unknown_reason" . to_string( ) ] ,
610+ retryable_status_codes : None ,
611+ backoff : BackoffStrategyDTO :: Fixed { base_ms : 100 } ,
612+ retry_non_idempotent : false ,
613+ fail_on_non_replayable_retry : false ,
614+ max_request_body_buffer_bytes : 1_048_576 ,
615+ } ;
616+
617+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
618+ assert ! ( result. is_err( ) ) ;
619+ let err = result. unwrap_err ( ) ;
620+ assert ! ( err. to_string( ) . contains( "unknown retryable reason" ) ) ;
621+ }
622+
623+ #[ test]
624+ fn test_convert_retry_policy_status_code_missing_codes ( ) {
625+ let dto = RetryPolicy {
626+ max_attempts : 3 ,
627+ per_try : None ,
628+ retryable_reasons : vec ! [ "status_code" . to_string( ) ] ,
629+ retryable_status_codes : None ,
630+ backoff : BackoffStrategyDTO :: Fixed { base_ms : 100 } ,
631+ retry_non_idempotent : false ,
632+ fail_on_non_replayable_retry : false ,
633+ max_request_body_buffer_bytes : 1_048_576 ,
634+ } ;
635+
636+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
637+ assert ! ( result. is_err( ) ) ;
638+ let err = result. unwrap_err ( ) ;
639+ assert ! (
640+ err. to_string( )
641+ . contains( "retryable_status_codes is required" )
642+ ) ;
643+ }
644+
645+ #[ test]
646+ fn test_convert_retry_policy_status_code_empty_codes ( ) {
647+ let dto = RetryPolicy {
648+ max_attempts : 3 ,
649+ per_try : None ,
650+ retryable_reasons : vec ! [ "status_code" . to_string( ) ] ,
651+ retryable_status_codes : Some ( vec ! [ ] ) ,
652+ backoff : BackoffStrategyDTO :: Fixed { base_ms : 100 } ,
653+ retry_non_idempotent : false ,
654+ fail_on_non_replayable_retry : false ,
655+ max_request_body_buffer_bytes : 1_048_576 ,
656+ } ;
657+
658+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
659+ assert ! ( result. is_err( ) ) ;
660+ let err = result. unwrap_err ( ) ;
661+ assert ! ( err. to_string( ) . contains( "cannot be empty" ) ) ;
662+ }
663+
664+ #[ test]
665+ fn test_convert_retry_policy_valid ( ) {
666+ let dto = RetryPolicy {
667+ max_attempts : 3 ,
668+ per_try : None ,
669+ retryable_reasons : vec ! [ "status_code" . to_string( ) ] ,
670+ retryable_status_codes : Some ( vec ! [ 502 , 503 , 504 ] ) ,
671+ backoff : BackoffStrategyDTO :: Exponential {
672+ base_ms : 100 ,
673+ max_ms : 5000 ,
674+ } ,
675+ retry_non_idempotent : false ,
676+ fail_on_non_replayable_retry : false ,
677+ max_request_body_buffer_bytes : 1_048_576 ,
678+ } ;
679+
680+ let result = convert_retry_policy ( dto, & Timeout :: Disabled , 0 , 0 ) ;
681+ assert ! ( result. is_ok( ) ) ;
682+ }
683+ }
0 commit comments