use std::process::Command;
fn bin() -> Command {
Command::new(env!("CARGO_BIN_EXE_apimock"))
}
fn clean_config_dir() -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("apimock.toml"),
"[service]\nrule_sets = [\"rules.toml\"]\nfallback_respond_dir = \".\"\n",
)
.unwrap();
std::fs::write(
dir.path().join("rules.toml"),
"[[rules]]\nwhen.request.url_path = \"/ok\"\nrespond.text = \"ok\"\n",
)
.unwrap();
dir
}
fn structurally_invalid_config_dir() -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("apimock.toml"),
"[service]\nrule_sets = [\"rules.toml\"]\nfallback_respond_dir = \".\"\n",
)
.unwrap();
std::fs::write(
dir.path().join("rules.toml"),
"[[rules]]\nwhen.request.url_path = \"/a\"\nrespond = {}\n",
)
.unwrap();
dir
}
#[test]
fn json_flag_stdout_is_unaffected_by_the_warning() {
let dir = clean_config_dir();
let with_json = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--json"])
.output()
.expect("failed to run apimock validate");
let stdout = String::from_utf8_lossy(&with_json.stdout);
assert_eq!(with_json.status.code(), Some(0));
assert!(stdout.contains("[]"), "stdout was:\n{stdout}");
assert!(
stdout.contains("Validation passed (1 rules across 1 rule set(s))."),
"stdout was:\n{stdout}"
);
let stderr = String::from_utf8_lossy(&with_json.stderr);
assert!(
stderr.contains("--json is deprecated and will be removed in 6.0.0."),
"stderr was:\n{stderr}"
);
assert!(
stderr.contains("Use --format json"),
"stderr was:\n{stderr}"
);
assert!(
!stdout.contains("deprecated"),
"stdout was:\n{stdout} (warning text must never reach stdout)"
);
}
#[test]
fn json_flag_warning_appears_exactly_once_per_run_including_on_load_failure() {
for (dir, expected_exit) in [
(clean_config_dir(), 0),
(structurally_invalid_config_dir(), 2),
] {
let output = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--json"])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(expected_exit));
let stderr = String::from_utf8_lossy(&output.stderr);
let occurrences = stderr
.matches("is deprecated and will be removed in 6.0.0")
.count();
assert_eq!(
occurrences, 1,
"exit {expected_exit} case: stderr was:\n{stderr}"
);
}
}
#[test]
fn format_json_emits_a_valid_envelope_on_a_clean_config() {
let dir = clean_config_dir();
let output = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--format", "json"])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(0));
assert!(
output.stderr.is_empty(),
"no --json, so no deprecation warning; stderr was: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!("stdout must be a single parseable JSON value: {e}\nstdout was:\n{stdout}")
});
assert!(v.is_object(), "envelope must be an object, not an array");
assert_eq!(v["schema"], 1);
assert!(v["apimock"].is_string());
assert!(v.get("result").is_some(), "v was: {v}");
assert!(v.get("error").is_none(), "v was: {v}");
assert_eq!(v["result"]["summary"]["errors"], 0);
assert_eq!(v["result"]["diagnostics"].as_array().unwrap().len(), 0);
}
#[test]
fn format_json_reports_config_load_failure_as_an_error_envelope() {
let output = bin()
.args([
"validate",
"--config",
"/nonexistent/apimock.toml",
"--format",
"json",
])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(2));
let stdout = String::from_utf8_lossy(&output.stdout);
let v: serde_json::Value =
serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{e}\nstdout was:\n{stdout}"));
assert!(v.get("error").is_some(), "v was: {v}");
assert!(v.get("result").is_none(), "v was: {v}");
assert_eq!(v["error"]["kind"], "config_unreadable");
}
#[test]
fn format_json_distinguishes_invalid_from_unreadable() {
let dir = structurally_invalid_config_dir();
let output = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--format", "json"])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(2));
let stdout = String::from_utf8_lossy(&output.stdout);
let v: serde_json::Value =
serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{e}\nstdout was:\n{stdout}"));
assert_eq!(v["error"]["kind"], "config_invalid");
}
#[test]
fn format_text_matches_the_implicit_default() {
let dir = clean_config_dir();
let explicit = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--format", "text"])
.output()
.expect("failed to run apimock validate");
let implicit = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml"])
.output()
.expect("failed to run apimock validate");
assert_eq!(explicit.status.code(), implicit.status.code());
assert_eq!(explicit.stdout, implicit.stdout);
assert_eq!(explicit.stderr, implicit.stderr);
}
#[test]
fn json_and_format_together_is_a_usage_error() {
let dir = clean_config_dir();
let output = bin()
.current_dir(dir.path())
.args([
"validate",
"--config",
"./apimock.toml",
"--json",
"--format",
"json",
])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(2));
assert!(
output.stdout.is_empty(),
"stdout was: {}",
String::from_utf8_lossy(&output.stdout)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--json and --format cannot be used together"),
"stderr was:\n{stderr}"
);
}
#[test]
fn invalid_format_value_is_a_usage_error() {
let dir = clean_config_dir();
let output = bin()
.current_dir(dir.path())
.args(["validate", "--config", "./apimock.toml", "--format", "xml"])
.output()
.expect("failed to run apimock validate");
assert_eq!(output.status.code(), Some(2));
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("invalid value for --format"),
"stderr was:\n{stderr}"
);
}
#[test]
fn exit_codes_unchanged_with_strict_and_quiet() {
let clean = clean_config_dir();
let invalid = structurally_invalid_config_dir();
let cases: &[(&std::path::Path, &[&str], i32)] = &[
(clean.path(), &["--config", "./apimock.toml"], 0),
(clean.path(), &["--config", "./apimock.toml", "--strict"], 0),
(clean.path(), &["--config", "./apimock.toml", "--quiet"], 0),
(invalid.path(), &["--config", "./apimock.toml"], 2),
(
invalid.path(),
&["--config", "./apimock.toml", "--strict"],
2,
),
(
invalid.path(),
&["--config", "./apimock.toml", "--quiet"],
2,
),
];
for (dir, extra_args, expected) in cases {
let mut args = vec!["validate"];
args.extend_from_slice(extra_args);
let output = bin()
.current_dir(dir)
.args(&args)
.output()
.expect("failed to run apimock validate");
assert_eq!(
output.status.code(),
Some(*expected),
"args {:?} in {:?}",
args,
dir
);
}
}