use std::collections::BTreeMap;
use std::path::Path;
use std::sync::Arc;
use camel_integration_test::DocumentOutcome;
use camel_integration_test::{
DirectStimulus, LayeredEnv, PartnerAdapter, PartnerRouter, ScenarioDocument, ScenarioFailure,
ScenarioVerdict, ambient_std, boot_scenario, parse_scenario_document, run_scenario_document,
};
use tokio::sync::Mutex;
fn project(route: &str, doc: &str) -> (tempfile::TempDir, ScenarioDocument) {
let dir = tempfile::tempdir().expect("temp dir");
std::fs::write(dir.path().join("Camel.toml"), "# minimal\n").expect("write Camel.toml");
std::fs::write(dir.path().join("routes.yaml"), route).expect("write route file");
let doc_path = dir.path().join("case.test.yaml");
std::fs::write(&doc_path, doc).expect("write document");
let document = parse_scenario_document(&doc_path).expect("document parses");
(dir, document)
}
async fn run_direct(doc: &ScenarioDocument, root: &Path) -> DocumentOutcome {
let env = LayeredEnv::new(BTreeMap::new(), BTreeMap::new(), Vec::new(), ambient_std());
let run = boot_scenario(doc, root, &env)
.await
.expect("scenario boots");
let ctx = Arc::new(Mutex::new(run.ctx));
let router = PartnerRouter::new(BTreeMap::from([(
"direct:echo".to_string(),
Box::new(DirectStimulus::new(Arc::clone(&ctx))) as Box<dyn PartnerAdapter>,
)]));
let mut vars = camel_integration_test::ScenarioVars::new();
let mut outcome = run_scenario_document(doc, &router, &mut vars, None).await;
outcome.inbound_bound = run.inbound_bound;
if let Err(e) = run.boot.shutdown(&mut *ctx.lock().await).await {
outcome.final_failure = Some(ScenarioFailure::ShutdownFailure {
message: e.to_string(),
});
}
outcome
}
#[tokio::test]
async fn expect_reply_matches_direct_body() {
let (dir, doc) = project(
r#"
routes:
- id: echo-route
from: direct:echo
steps:
- set_body: "ack-7f3a"
"#,
r#"
routeFiles: [routes.yaml]
scenario:
- send:
to: direct:echo
body: ping
expectReply:
contains: ack
"#,
);
let outcome = run_direct(&doc, dir.path()).await;
assert_eq!(
outcome.verdict,
Some(ScenarioVerdict::Pass),
"matching expectReply must pass, got {outcome:?}"
);
}
#[tokio::test]
async fn expect_reply_mismatch_is_verdict_failure() {
let (dir, doc) = project(
r#"
routes:
- id: echo-route
from: direct:echo
steps:
- set_body: "ack-7f3a"
"#,
r#"
routeFiles: [routes.yaml]
scenario:
- send:
to: direct:echo
body: ping
expectReply:
equals:
wrong: true
"#,
);
let outcome = run_direct(&doc, dir.path()).await;
assert_eq!(outcome.verdict, None, "mismatched expectReply must fail");
assert_eq!(outcome.per_action.len(), 1, "the send is the only action");
let failure = outcome
.per_action
.first()
.and_then(|result| result.as_ref().err().cloned())
.expect("the send action must carry the failure");
assert!(
matches!(failure, ScenarioFailure::ValidationMismatch { .. }),
"mismatch must be verdict-class ValidationMismatch, got {failure}"
);
let rendered = failure.to_string();
assert!(
rendered.contains("validation-mismatch"),
"failure must name the verdict class: {rendered}"
);
assert!(
rendered.contains("{\"wrong\":true}"),
"failure must name the rendered expectation: {rendered}"
);
assert!(
rendered.contains("ack-7f3a"),
"failure must name the actual reply body: {rendered}"
);
}
#[tokio::test]
async fn expect_reply_json_subset_on_direct_body() {
let (dir, doc) = project(
r#"
routes:
- id: echo-json-route
from: direct:echo
steps:
- set_body:
status: ok
seq: 7
"#,
r#"
routeFiles: [routes.yaml]
scenario:
- send:
to: direct:echo
body: ping
expectReply:
jsonSubset:
status: ok
"#,
);
let outcome = run_direct(&doc, dir.path()).await;
assert_eq!(
outcome.verdict,
Some(ScenarioVerdict::Pass),
"jsonSubset must match the JSON reply body, got {outcome:?}"
);
}