#[test]
fn parse_check_python_with_output_flag() {
let cli = Cli::parse_from([
"schemalint",
"check-python",
"-P",
"myapp.models",
"-p",
"openai.so.2026-04-30",
"-o",
"results.json",
]);
match cli.command {
Commands::CheckPython(args) => {
assert_eq!(args.output, Some(std::path::PathBuf::from("results.json")));
}
_ => unreachable!(),
}
}
#[test]
fn parse_check_python_with_output_and_format() {
let cli = Cli::parse_from([
"schemalint",
"check-python",
"-P",
"myapp.models",
"-p",
"openai.so.2026-04-30",
"-f",
"sarif",
"-o",
"results.sarif",
]);
match cli.command {
Commands::CheckPython(args) => {
assert_eq!(args.format, Some(OutputFormat::Sarif));
assert_eq!(args.output, Some(std::path::PathBuf::from("results.sarif")));
}
_ => unreachable!(),
}
}
#[test]
fn parse_check_python_empty_package_list() {
let cli = Cli::parse_from(["schemalint", "check-python", "-p", "openai.so.2026-04-30"]);
match cli.command {
Commands::CheckPython(args) => {
assert!(args.packages.is_empty());
assert!(!args.profiles.is_empty());
}
_ => unreachable!(),
}
}
#[test]
fn parse_check_python_minimal_args() {
let cli = Cli::parse_from([
"schemalint",
"check-python",
"-P",
"myapp",
"-p",
"openai.so.2026-04-30",
]);
match cli.command {
Commands::CheckPython(args) => {
assert_eq!(args.packages, vec!["myapp"]);
assert_eq!(args.profiles.len(), 1);
assert!(args.format.is_none());
assert!(args.config.is_none());
assert!(args.python_path.is_none());
assert!(args.output.is_none());
}
_ => unreachable!(),
}
}
#[test]
fn check_python_missing_required_package_flag() {
let mut cmd = Command::cargo_bin("schemalint").unwrap();
let output = cmd
.args(["check-python", "-p", "openai.so.2026-04-30"])
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("no packages specified."));
}
#[test]
fn check_python_invalid_profile_name() {
let tmp = TempDir::new().unwrap();
let mut cmd = Command::cargo_bin("schemalint").unwrap();
cmd.current_dir(tmp.path());
let output = cmd
.args([
"check-python",
"-P",
"myapp.models",
"-p",
"nonexistent-zzz-profile",
])
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("unknown built-in profile"));
}
#[test]
fn check_python_invalid_format_cli_rejects() {
let mut cmd = Command::cargo_bin("schemalint").unwrap();
let output = cmd
.args([
"check-python",
"-P",
"myapp.models",
"-p",
"openai.so.2026-04-30",
"-f",
"invalidfmt",
])
.output()
.unwrap();
assert!(!output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("invalidfmt"));
}
#[test]
fn check_python_pyproject_schemalint_no_packages_errors() {
let tmp = TempDir::new().unwrap();
let pyproject = tmp.path().join("pyproject.toml");
fs::write(
&pyproject,
r#"
[tool.schemalint]
profiles = ["openai.so.2026-04-30"]
"#,
)
.unwrap();
let mut cmd = Command::cargo_bin("schemalint").unwrap();
cmd.current_dir(tmp.path());
let output = cmd.args(["check-python"]).output().unwrap();
assert!(
!output.status.success(),
"exit code should be 1 when packages is empty"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("no packages specified."),
"expected 'no packages specified.' in stderr, got:\n{stderr}"
);
}
#[test]
fn check_python_explicit_config_invalid_toml_errors() {
let tmp = TempDir::new().unwrap();
let custom_config = tmp.path().join("custom-pyproject.toml");
fs::write(&custom_config, "this is definitely not valid toml {{{").unwrap();
let mut cmd = Command::cargo_bin("schemalint").unwrap();
cmd.current_dir(tmp.path());
let output = cmd
.args([
"check-python",
"--config",
custom_config.to_str().unwrap(),
"--profile",
"openai.so.2026-04-30",
"--package",
"myapp.models",
])
.output()
.unwrap();
assert!(
!output.status.success(),
"exit code should be 1 for malformed --config TOML"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("invalid TOML in"),
"expected 'invalid TOML in' in stderr, got:\n{stderr}"
);
assert!(
stderr.contains("custom-pyproject.toml"),
"expected config filename in error message, got:\n{stderr}"
);
}
#[test]
fn check_python_explicit_config_nonexistent_falls_through() {
let tmp = TempDir::new().unwrap();
let nonexistent_config = tmp.path().join("does-not-exist.toml");
assert!(!nonexistent_config.exists());
let mut cmd = Command::cargo_bin("schemalint").unwrap();
cmd.current_dir(tmp.path());
let output = cmd
.args([
"check-python",
"--config",
nonexistent_config.to_str().unwrap(),
"--profile",
"openai.so.2026-04-30",
])
.output()
.unwrap();
assert!(
!output.status.success(),
"exit code should be 1 (no packages)"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("no packages specified."),
"expected 'no packages specified.' (not a read error), got:\n{stderr}"
);
assert!(
!stderr.contains("failed to read"),
"nonexistent --config should NOT produce a read error, got:\n{stderr}"
);
}