Skip to content

Commit 9b16411

Browse files
committed
Add integration tests for proxy service I/O error handling and enhance existing tests for upstream peer functionality
1 parent 6c3804a commit 9b16411

File tree

6 files changed

+1824
-89
lines changed

6 files changed

+1824
-89
lines changed

crates/pavis-codec-serde/src/config/convert/routes/semantic_validate.rs

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)