use assert_cmd::Command;
use predicates::prelude::*;
use std::collections::HashMap;
fn valid_plan_json(base_url: &str) -> String {
format!(
r#"{{
"name": "integration-test",
"base_url": "{base_url}",
"default_headers": {{ "Accept": "application/json" }},
"steps": [
{{
"type": "request",
"name": "health",
"method": "GET",
"url": "/health",
"assert": [
{{ "status": 200 }},
{{ "content_type": "application/json" }},
{{ "valid_json": null }}
]
}}
]
}}"#
)
}
async fn start_mock_server() -> (momus_mock::MockServer, String) {
let mut routes = HashMap::new();
routes.insert(
"GET /health".into(),
momus_mock::MockResponse::json(200, serde_json::json!({"status": "ok"})),
);
let server = momus_mock::MockServer::start(routes).await;
let url = server.addr.clone();
(server, url)
}
#[test]
fn test_help_exits_with_0() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.arg("--help");
cmd.assert()
.success()
.stdout(predicate::str::contains("Usage:"))
.stdout(predicate::str::contains("run"))
.stdout(predicate::str::contains("validate"))
.stdout(predicate::str::contains("mock"))
.stdout(predicate::str::contains("bench"))
.stdout(predicate::str::contains("fuzz"))
.stdout(predicate::str::contains("chaos"))
.stdout(predicate::str::contains("convert"))
.stdout(predicate::str::contains("contract"))
.stdout(predicate::str::contains("guard"))
.stdout(predicate::str::contains("diff"))
.stdout(predicate::str::contains("init"))
.stdout(predicate::str::contains("plan"))
.stdout(predicate::str::contains("fhir"));
}
#[test]
fn test_run_help_shows_run_flags() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["run", "--help"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("--base-url"))
.stdout(predicate::str::contains("--output"))
.stdout(predicate::str::contains("--format"));
}
#[test]
fn test_version_exits_with_0() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.arg("--version");
cmd.assert()
.success()
.stdout(predicate::str::contains("momus"));
}
#[test]
fn test_unknown_subcommand_exits_nonzero() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.arg("nonexistent-subcommand");
cmd.assert()
.failure()
.stderr(predicate::str::contains("error"));
}
#[test]
fn test_validate_valid_plan() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("test-plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "test",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["validate", plan_path.to_str().unwrap()]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Valid test plan"))
.stdout(predicate::str::contains("test"));
}
#[test]
fn test_validate_invalid_json() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("bad-plan.json");
std::fs::write(&plan_path, "this is not json").unwrap();
cmd.args(["validate", plan_path.to_str().unwrap()]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Failed to parse test plan"));
}
#[test]
fn test_validate_malformed_plan() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("bad-plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "bad",
"base_url": "http://localhost",
"steps": [
{ "type": "request" }
]
}"#,
)
.unwrap();
cmd.args(["validate", plan_path.to_str().unwrap()]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Failed to parse test plan"));
}
#[tokio::test]
async fn test_run_health_check_plan() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args(["run", plan_path.to_str().unwrap()]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("Passed"))
.stdout(predicate::str::contains("health"));
}
#[tokio::test]
async fn test_run_with_base_url_override() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_json = r#"{
"name": "override-test",
"base_url": "http://localhost:1",
"steps": [
{
"type": "request",
"name": "health",
"method": "GET",
"url": "/health",
"assert": [
{ "status": 200 },
{ "valid_json": null }
]
}
]
}"#
.to_string();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, &plan_json).unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--base-url", &base_url]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success().stdout(predicate::str::contains("Passed"));
}
#[tokio::test]
async fn test_run_with_output_dir() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let out_dir = dir.path().join("run-output");
cmd.args([
"run",
plan_path.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
let results_dir = out_dir.join("results");
assert!(
results_dir.join("integration-test.json").exists(),
"Expected integration-test.json in {:?}. Contents: {:?}",
results_dir,
std::fs::read_dir(&results_dir)
.map(|e| e.map(|e| e.unwrap().path()).collect::<Vec<_>>())
.unwrap_or_default()
);
}
#[test]
fn test_convert_curl() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args([
"convert",
"curl",
"curl -X GET https://api.example.com/health -H 'Accept: application/json'",
]);
cmd.assert()
.success()
.stdout(predicate::str::contains("GET"))
.stdout(predicate::str::contains("/health"));
}
#[test]
fn test_convert_curl_to_file() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let out_path = dir.path().join("converted.json");
cmd.args([
"convert",
"curl",
"curl -X POST https://api.example.com/data -d '{\"key\":\"value\"}'",
"--output",
out_path.to_str().unwrap(),
]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(plan["name"].as_str().unwrap().contains("cURL"));
}
#[test]
fn test_convert_unknown_format() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["convert", "unknown-format", "input.txt"]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("invalid value"))
.stderr(predicate::str::contains("unknown-format"));
}
#[tokio::test]
async fn test_bench_steady_mode() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"steady",
"--concurrency",
"1",
"--duration",
"1",
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[tokio::test]
async fn test_fuzz_basic() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
format!(
r#"{{
"name": "fuzz-test",
"base_url": "{base_url}",
"steps": [
{{
"type": "request",
"name": "create",
"method": "POST",
"url": "/data",
"body": {{ "key": "value" }},
"assert": [{{ "status": 200 }}]
}}
]
}}"#
),
)
.unwrap();
cmd.args([
"fuzz",
plan_path.to_str().unwrap(),
"--iterations",
"5",
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[tokio::test]
async fn test_guard_basic() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"guard",
plan_path.to_str().unwrap(),
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[tokio::test]
async fn test_diff_basic() {
let (_server1, base_url1) = start_mock_server().await;
let (_server2, base_url2) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "diff-test",
"base_url": "http://localhost:1",
"steps": [
{
"type": "request",
"name": "health",
"method": "GET",
"url": "/health",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args([
"diff",
plan_path.to_str().unwrap(),
"--baseline",
&base_url1,
"--target",
&base_url2,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[test]
fn test_init_plan() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let out_path = dir.path().join("test-plan.json");
cmd.args(["init", "plan", "--output", out_path.to_str().unwrap()]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(plan["name"].is_string());
assert!(plan["steps"].is_array());
}
#[test]
fn test_plan_display() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "display-test",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["plan", plan_path.to_str().unwrap()]);
cmd.assert().success();
let output_path = std::path::Path::new("./output/plan/plan.txt");
if output_path.exists() {
let content = std::fs::read_to_string(output_path).unwrap();
assert!(
content.contains("display-test"),
"Expected plan name in output"
);
assert!(content.contains("ping"), "Expected step name in output");
}
}
#[test]
fn test_run_dry_run_does_not_send_requests() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "dry-run-test",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--dry-run"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Dry-run"))
.stdout(predicate::str::contains("GET /ping"))
.stdout(predicate::str::contains("Total requests: 1"))
.stdout(predicate::str::contains("Dry-run OK"));
}
#[test]
fn test_run_dry_run_redacts_sensitive_headers() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "dry-run-redact",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"headers": {
"Authorization": "Bearer super-secret-token",
"Accept": "application/json"
},
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--dry-run"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("[REDACTED]"))
.stdout(predicate::str::contains("Authorization: [REDACTED]"))
.stdout(predicate::str::contains("Bearer super-secret-token").not())
.stdout(predicate::str::contains("Accept: application/json"));
}
#[test]
fn test_run_dry_run_fails_without_base_url() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "dry-run-nobase",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--dry-run"]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("no base_url is set"));
}
#[test]
fn test_completions_bash() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["completions", "bash"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("_momus"));
}
#[test]
fn test_completions_zsh() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["completions", "zsh"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("compdef momus"));
}
#[test]
fn test_completions_invalid_shell() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["completions", "not-a-shell"]);
cmd.assert().failure();
}
#[test]
fn test_docs_prints_url() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args(["docs"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("https://docs.rs/momus"));
}
fn write_fhir_ndjson(data_dir: &std::path::Path) {
std::fs::create_dir_all(data_dir).unwrap();
std::fs::write(
data_dir.join("Organization.ndjson"),
r#"{"resourceType":"Organization","id":"org-1","name":"Test Org"}"#.to_string() + "\n",
)
.unwrap();
}
#[tokio::test]
async fn test_fhir_upload_puts_resources() {
let base_url = start_fhir_mock_server().await;
let dir = tempfile::TempDir::new().unwrap();
write_fhir_ndjson(dir.path());
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args([
"fhir",
"upload",
"--data-dir",
dir.path().to_str().unwrap(),
"--endpoint",
&base_url,
"--method",
"PUT",
"--concurrency",
"2",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("Uploaded 1 resources"));
}
#[tokio::test]
async fn test_fhir_delete_cleans_up() {
let base_url = start_fhir_mock_server().await;
let dir = tempfile::TempDir::new().unwrap();
write_fhir_ndjson(dir.path());
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args([
"fhir",
"delete",
"--data-dir",
dir.path().to_str().unwrap(),
"--endpoint",
&base_url,
"--concurrency",
"2",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("Cleanup complete"));
}
async fn start_fhir_mock_server() -> String {
let addr = momus_mock::fhir::start_fhir_mock_server(0).await.unwrap();
format!("http://{addr}/fhir")
}
#[test]
fn test_run_missing_plan_file() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let missing = dir.path().join("no-such-plan.json");
cmd.args(["run", missing.to_str().unwrap()]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Failed to read plan file"));
}
#[test]
fn test_run_invalid_json() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, "this is not json{{").unwrap();
cmd.args(["run", plan_path.to_str().unwrap()]);
cmd.assert()
.failure()
.stderr(predicate::str::contains("Failed to parse test plan"));
}
#[test]
fn test_validate_missing_plan_file() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let missing = dir.path().join("no-such-plan.json");
cmd.args(["validate", missing.to_str().unwrap()]);
cmd.assert().failure();
}
#[tokio::test]
async fn test_run_unreachable_server() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "unreachable",
"base_url": "http://127.0.0.1:1",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--timeout", "2"]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.code(1);
}
#[test]
fn test_convert_missing_file() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let missing = dir.path().join("no-such.yaml");
cmd.args(["convert", "openapi", missing.to_str().unwrap()]);
cmd.assert().failure();
}
#[tokio::test]
async fn test_run_html_report_output() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let out_path = dir.path().join("report.html");
cmd.args([
"run",
plan_path.to_str().unwrap(),
"--output",
out_path.to_str().unwrap(),
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
let html = std::fs::read_to_string(&out_path).unwrap();
assert!(html.contains("<!DOCTYPE html>"), "expected HTML report");
}
#[tokio::test]
async fn test_run_junit_xml_output() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let out_dir = dir.path().join("junit-out");
cmd.args([
"run",
plan_path.to_str().unwrap(),
"--output",
out_dir.to_str().unwrap(),
"--format",
"junit",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
let junit_path = out_dir.join("junit.xml");
assert!(junit_path.exists(), "expected junit.xml to be written");
let content = std::fs::read_to_string(&junit_path).unwrap();
assert!(content.contains("testsuite"), "expected JUnit XML");
}
#[tokio::test]
async fn test_bench_max_throughput_mode() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"max-throughput",
"--min-concurrency",
"1",
"--max-concurrency",
"4",
"--step",
"1",
"--step-duration",
"1",
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("Max sustainable concurrency"));
}
#[tokio::test]
async fn test_bench_soak_mode() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"soak",
"--concurrency",
"1",
"--duration",
"1",
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[tokio::test]
async fn test_bench_json_output() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let out_path = dir.path().join("bench-report.json");
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"steady",
"--concurrency",
"1",
"--duration",
"1",
"--base-url",
&base_url,
"--output",
out_path.to_str().unwrap(),
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
let content = std::fs::read_to_string(&out_path).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["schema_version"], 1);
assert_eq!(parsed["mode"], "steady");
assert!(parsed["total_requests"].as_u64().unwrap() > 0);
}
#[tokio::test]
async fn test_chaos_basic() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let experiment = r#"{"NetworkLatency":{"endpoint":"/health","delay_ms":10,"duration_secs":0}}"#;
cmd.args([
"chaos",
plan_path.to_str().unwrap(),
"--base-url",
&base_url,
"--experiment",
experiment,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[test]
fn test_chaos_invalid_experiment_json() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{"name":"t","base_url":"http://localhost","steps":[]}"#,
)
.unwrap();
cmd.args([
"chaos",
plan_path.to_str().unwrap(),
"--experiment",
"not-json",
]);
cmd.assert().failure();
}
#[tokio::test]
async fn test_contract_against_openapi_spec() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
let spec_path = dir.path().join("spec.yaml");
std::fs::write(
&spec_path,
r#"openapi: "3.0.0"
info:
title: Test API
version: "1.0"
paths:
/health:
get:
responses:
"200":
description: OK
"#,
)
.unwrap();
cmd.args([
"contract",
plan_path.to_str().unwrap(),
"--spec",
spec_path.to_str().unwrap(),
"--base-url",
&base_url,
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success();
}
#[test]
fn test_contract_missing_spec() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{"name":"t","base_url":"http://localhost","steps":[]}"#,
)
.unwrap();
cmd.args([
"contract",
plan_path.to_str().unwrap(),
"--spec",
dir.path().join("no-spec.yaml").to_str().unwrap(),
]);
cmd.assert().failure();
}
#[test]
fn test_convert_openapi() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let spec_path = dir.path().join("spec.yaml");
std::fs::write(
&spec_path,
r#"openapi: "3.0.0"
info:
title: Test API
version: "1.0"
servers:
- url: http://localhost:8080
paths:
/health:
get:
operationId: healthCheck
responses:
"200":
description: OK
"#,
)
.unwrap();
let out_path = dir.path().join("plan.json");
cmd.args([
"convert",
"openapi",
spec_path.to_str().unwrap(),
"--output",
out_path.to_str().unwrap(),
]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(plan["base_url"], "http://localhost:8080");
assert!(!plan["steps"].as_array().unwrap().is_empty());
}
#[test]
fn test_convert_har() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let har_path = dir.path().join("traffic.har");
std::fs::write(
&har_path,
r#"{
"log": {
"version": "1.2",
"entries": [
{
"request": {
"method": "GET",
"url": "https://api.example.com/health",
"headers": []
},
"response": { "status": 200, "headers": [] }
}
]
}
}"#,
)
.unwrap();
let out_path = dir.path().join("plan.json");
cmd.args([
"convert",
"har",
har_path.to_str().unwrap(),
"--output",
out_path.to_str().unwrap(),
]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(!plan["steps"].as_array().unwrap().is_empty());
}
#[test]
fn test_convert_postman() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let col_path = dir.path().join("collection.json");
std::fs::write(
&col_path,
r#"{
"info": {
"name": "Test API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Health Check",
"request": {
"method": "GET",
"url": {
"raw": "https://api.example.com/health",
"protocol": "https",
"host": ["api", "example", "com"],
"path": ["health"]
}
},
"response": [{"code": 200, "body": "{\"status\":\"ok\"}"}]
}
]
}"#,
)
.unwrap();
let out_path = dir.path().join("plan.json");
cmd.args([
"convert",
"postman",
col_path.to_str().unwrap(),
"--output",
out_path.to_str().unwrap(),
]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(!plan["steps"].as_array().unwrap().is_empty());
}
#[test]
fn test_convert_graphql() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let sdl_path = dir.path().join("schema.graphql");
std::fs::write(
&sdl_path,
r#"type Query {
health: String
}
"#,
)
.unwrap();
let out_path = dir.path().join("plan.json");
cmd.args([
"convert",
"graphql",
sdl_path.to_str().unwrap(),
"--output",
out_path.to_str().unwrap(),
]);
cmd.assert().success();
let content = std::fs::read_to_string(&out_path).unwrap();
let plan: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(!plan["steps"].as_array().unwrap().is_empty());
}
#[tokio::test]
async fn test_fhir_mock_serves_requests() {
let addr = momus_mock::fhir::start_fhir_mock_server(0).await.unwrap();
let url = format!("http://{addr}/fhir/Patient");
let client = reqwest::Client::new();
let resp = client.get(&url).send().await.unwrap();
assert!(resp.status().is_success());
}
#[test]
fn test_verbose_flag_sets_debug_log() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.env_remove("RUST_LOG");
cmd.args(["--verbose", "docs"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Documentation:"));
}
#[test]
fn test_config_file_loading_with_env() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let config_path = dir.path().join("momus.toml");
std::fs::write(
&config_path,
r#"[global]
timeout_secs = 7
[env.staging]
base_url = "http://staging.local"
"#,
)
.unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "config-test",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args([
"--config",
config_path.to_str().unwrap(),
"validate",
plan_path.to_str().unwrap(),
]);
cmd.env("MOMUS_ENV", "staging");
cmd.assert()
.success()
.stdout(predicate::str::contains("Valid test plan"));
}
#[test]
fn test_validate_plan_from_stdin() {
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.arg("validate").arg("-");
cmd.write_stdin(
r#"{
"name": "stdin-test",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
);
cmd.assert()
.success()
.stdout(predicate::str::contains("Valid test plan"))
.stdout(predicate::str::contains("stdin-test"));
}
#[test]
fn test_validate_plan_with_setup_teardown() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "setup-teardown-test",
"base_url": "http://localhost:9999",
"setup": [
{
"type": "request",
"name": "setup-req",
"method": "GET",
"url": "/setup",
"assert": [{ "status": 200 }]
}
],
"teardown": [
{
"type": "request",
"name": "teardown-req",
"method": "GET",
"url": "/teardown",
"assert": [{ "status": 200 }]
}
],
"steps": [
{
"type": "request",
"name": "ping",
"method": "GET",
"url": "/ping",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["validate", plan_path.to_str().unwrap()]);
cmd.assert()
.success()
.stdout(predicate::str::contains("Setup steps: 1"))
.stdout(predicate::str::contains("Teardown steps: 1"));
}
#[test]
fn test_run_dry_run_with_body_and_save_as() {
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(
&plan_path,
r#"{
"name": "dry-run-body",
"base_url": "http://localhost:9999",
"steps": [
{
"type": "request",
"name": "create",
"method": "POST",
"url": "/data",
"body": { "key": "value" },
"save_as": "created",
"assert": [{ "status": 200 }]
}
]
}"#,
)
.unwrap();
cmd.args(["run", plan_path.to_str().unwrap(), "--dry-run"]);
cmd.assert()
.success()
.stdout(predicate::str::contains("body="))
.stdout(predicate::str::contains("save_as: created"));
}
#[tokio::test]
async fn test_bench_json_stdout() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"steady",
"--concurrency",
"1",
"--duration",
"1",
"--base-url",
&base_url,
"--format",
"json",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("schema_version"));
}
#[tokio::test]
async fn test_bench_html_stdout() {
let (_server, base_url) = start_mock_server().await;
let mut cmd = Command::cargo_bin("momus").unwrap();
let dir = tempfile::TempDir::new().unwrap();
let plan_path = dir.path().join("plan.json");
std::fs::write(&plan_path, valid_plan_json(&base_url)).unwrap();
cmd.args([
"bench",
plan_path.to_str().unwrap(),
"--mode",
"steady",
"--concurrency",
"1",
"--duration",
"1",
"--base-url",
&base_url,
"--format",
"html",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result.success().stdout(predicate::str::contains("<html"));
}
#[tokio::test]
async fn test_fhir_upload_with_basic_auth() {
let base_url = start_fhir_mock_server().await;
let dir = tempfile::TempDir::new().unwrap();
write_fhir_ndjson(dir.path());
let mut cmd = Command::cargo_bin("momus").unwrap();
cmd.args([
"fhir",
"upload",
"--data-dir",
dir.path().to_str().unwrap(),
"--endpoint",
&base_url,
"--method",
"PUT",
"--username",
"admin",
"--password",
"secret",
"--concurrency",
"2",
]);
let result = tokio::task::spawn_blocking(move || cmd.assert())
.await
.unwrap();
result
.success()
.stdout(predicate::str::contains("Uploaded 1 resources"));
}