use std::fs;
use std::path::PathBuf;
use camel_cli::commands::test::document::parse_test_document;
use camel_cli::commands::test::run_tests;
use camel_cli::commands::test::runner::{TestDocResult, run_test_doc};
fn temp_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"camel-test-repo-stubs-{tag}-{}",
std::process::id()
));
fs::create_dir_all(&dir).expect("create temp dir"); dir
}
fn assert_green(result: &TestDocResult, expected_endpoints: usize) {
assert!(
result.doc_error.is_none(),
"doc_error: {:?}",
result.doc_error
);
assert_eq!(result.endpoint_results.len(), expected_endpoints);
for er in &result.endpoint_results {
assert!(
er.outcome.is_ok(),
"endpoint {} failed: {:?}",
er.endpoint,
er.outcome
);
}
}
#[tokio::test(flavor = "multi_thread")]
async fn cache_stub_miss_then_hit() {
let dir = temp_dir("miss-then-hit");
let yaml = r#"
routes:
- id: r1
from: "direct:in"
steps:
- cache:
repository: persistent
key: k
on_miss:
- to: "mock:miss"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
- to: "direct:in"
body: "second"
repositories:
cache:
persistent: memory
expects:
mock:miss:
count: 1
mock:out:
count: 2
"#;
let doc = parse_test_document(yaml).expect("document should parse"); let (result, _) = run_test_doc(&doc, &dir).await;
assert_green(&result, 2);
}
#[tokio::test(flavor = "multi_thread")]
async fn empty_cache_map_no_warning() {
let dir = temp_dir("empty-cache-map");
let path = dir.join("empty.test.yaml");
fs::write(
&path,
r#"
routes:
- id: r1
from: "direct:in"
steps:
- cache:
repository: memory
key: k
on_miss:
- to: "mock:miss"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
repositories:
cache: {}
expects:
mock:miss:
count: 1
mock:out:
count: 1
"#,
)
.expect("write empty-cache doc"); let mut out = Vec::new();
let mut err = Vec::new();
let summary = run_tests(&[path], &mut out, &mut err).await;
assert_eq!(summary.exit_code, 0, "document must run green");
let err = String::from_utf8(err).expect("stderr utf-8"); assert!(
!err.contains("R-REPOSITORY-STUB"),
"err must NOT carry the stub warning: {err}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn undeclared_repository_name_fails_route_load() {
let dir = temp_dir("undeclared");
let yaml = r#"
routes:
- id: r1
from: "direct:in"
steps:
- cache:
repository: persistant
key: k
on_miss:
- to: "mock:miss"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
repositories:
cache:
persistent: memory
expects:
mock:miss:
count: 1
mock:out:
count: 1
"#;
let doc = parse_test_document(yaml).expect("document should parse"); let (result, _) = run_test_doc(&doc, &dir).await;
let err = result.doc_error.expect("doc_error must be Some"); assert!(
err.contains("cache: repository 'persistant' is not registered"),
"doc_error must name the unknown repository, got: {err}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn idempotent_stub_filters_duplicates() {
let dir = temp_dir("idempotent");
let yaml = r#"
routes:
- id: r1
from: "direct:in"
steps:
- idempotent_consumer:
repository: redis
expression: "${header.messageId}"
steps:
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
headers:
messageId: "dup-1"
- to: "direct:in"
body: "second"
headers:
messageId: "dup-1"
repositories:
idempotent:
redis: memory
expects:
mock:out:
count: 1
"#;
let doc = parse_test_document(yaml).expect("document should parse"); let (result, _) = run_test_doc(&doc, &dir).await;
assert_green(&result, 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn stub_warning_emitted_per_run() {
let dir = temp_dir("stub-warning");
let path = dir.join("stub.test.yaml");
fs::write(
&path,
r#"
routes:
- id: r1
from: "direct:in"
steps:
- cache:
repository: persistent
key: k
on_miss:
- to: "mock:miss"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
repositories:
cache:
persistent: memory
expects:
mock:miss:
count: 1
mock:out:
count: 1
"#,
)
.expect("write stub doc"); let mut out = Vec::new();
let mut err = Vec::new();
let summary = run_tests(&[path], &mut out, &mut err).await;
assert_eq!(summary.exit_code, 0, "document must run green");
let err = String::from_utf8(err).expect("stderr utf-8"); assert!(
err.contains("R-REPOSITORY-STUB"),
"err must carry the stub warning: {err}"
);
assert!(
err.contains("cache=persistent"),
"err must name the stubbed registry and repository: {err}"
);
assert!(
err.contains("persistent"),
"err must name the stubbed repository: {err}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn no_stub_no_warning() {
let dir = temp_dir("no-stub");
let path = dir.join("nostub.test.yaml");
fs::write(
&path,
r#"
routes:
- id: r1
from: "direct:in"
steps:
- cache:
repository: memory
key: k
on_miss:
- to: "mock:miss"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "first"
expects:
mock:miss:
count: 1
mock:out:
count: 1
"#,
)
.expect("write no-stub doc"); let mut out = Vec::new();
let mut err = Vec::new();
let summary = run_tests(&[path], &mut out, &mut err).await;
assert_eq!(summary.exit_code, 0, "document must run green");
let err = String::from_utf8(err).expect("stderr utf-8"); assert!(
!err.contains("R-REPOSITORY-STUB"),
"err must NOT carry the stub warning: {err}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn claimcheck_stub_roundtrip() {
let dir = temp_dir("claimcheck");
let yaml = r#"
routes:
- id: r1
from: "direct:in"
steps:
- claim_check:
repository: redb
operation: set
key: "${header.claimKey}"
- claim_check:
repository: redb
operation: get
key: "${header.claimKey}"
- to: "mock:out"
inputs:
- to: "direct:in"
body: "payload"
headers:
claimKey: "k1"
repositories:
claimCheck:
redb: memory
expects:
mock:out:
count: 1
bodies:
- "payload"
"#;
let doc = parse_test_document(yaml).expect("document should parse"); let (result, _) = run_test_doc(&doc, &dir).await;
assert_green(&result, 1);
}