#[derive(Debug, serde::Deserialize)]
struct PyJsonOutput {
profiles: Vec<String>,
summary: PyJsonSummary,
diagnostics: Vec<PyJsonDiagnostic>,
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct PyJsonSummary {
total_issues: u32,
errors: u32,
warnings: u32,
schemas_checked: u32,
}
#[derive(Debug, serde::Deserialize)]
struct PyJsonDiagnostic {
code: String,
severity: String,
#[serde(default)]
pointer: String,
#[serde(default)]
profile: String,
}
fn fixtures_dir() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
#[test]
fn real_sidecar_pydantic_fixture_triggers_oai_violations() {
let fixtures = fixtures_dir();
assert!(
fixtures.join("pydantic_fixture").exists(),
"fixture directory missing: {}",
fixtures.join("pydantic_fixture").display()
);
let mut cmd = Command::cargo_bin("schemalint").unwrap();
cmd.env("PYTHONPATH", &fixtures);
let output = cmd
.args([
"check-python",
"--package",
"pydantic_fixture",
"--profile",
"openai.so.2026-04-30",
"--format",
"json",
])
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let out: PyJsonOutput = serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!(
"JSON parse failed: {e}\n\
stdout:\n{stdout}\n\
stderr:\n{stderr}\n\
\n\
If stderr shows 'No module named schemalint_pydantic', install the sidecar:\n\
pip install pydantic && pip install ./python/schemalint-pydantic"
)
});
assert!(
out.profiles.iter().any(|p| p == "openai.so.2026-04-30"),
"expected openai.so.2026-04-30 in profiles, got: {:?}",
out.profiles
);
assert_eq!(
out.summary.schemas_checked, 1,
"expected 1 schema checked, got {}",
out.summary.schemas_checked
);
assert!(
out.diagnostics
.iter()
.any(|d| d.code == "OAI-K-format-restricted"),
"expected OAI-K-format-restricted diagnostic, got codes: {:?}",
out.diagnostics.iter().map(|d| &d.code).collect::<Vec<_>>()
);
let format_diag = out
.diagnostics
.iter()
.find(|d| d.code == "OAI-K-format-restricted")
.unwrap();
assert_eq!(
format_diag.pointer, "/properties/website",
"OAI-K-format-restricted should point to /properties/website"
);
assert_eq!(format_diag.severity, "error");
assert_eq!(format_diag.profile, "openai.so.2026-04-30");
assert!(
out.diagnostics
.iter()
.any(|d| d.code == "OAI-S-additional-properties-false"),
"expected OAI-S-additional-properties-false diagnostic"
);
assert!(out.summary.errors > 0, "expected at least 1 error");
assert!(
!output.status.success(),
"process should exit 1 when there are errors"
);
}
#[test]
fn real_sidecar_nonexistent_package_discover_failed() {
let mut cmd = Command::cargo_bin("schemalint").unwrap();
let output = cmd
.args([
"check-python",
"--package",
"nonexistent_zzz_package_schemalint",
"--profile",
"openai.so.2026-04-30",
])
.output()
.unwrap();
assert!(
!output.status.success(),
"exit code should be 1 when discovery fails"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("discovery failed for package"),
"expected 'discovery failed for package' framing in stderr, got:\n{stderr}"
);
}