use std::path::Path;
use tracing_test::traced_test;
use crate::extract::extractor::extract;
fn extract_ordered(sources: &[(&str, &str)]) -> crate::core::ir::ApiSurface {
let dir = tempfile::tempdir().expect("tempdir");
let paths: Vec<_> = sources
.iter()
.map(|(name, content)| {
let path = dir.path().join(name);
std::fs::write(&path, content).expect("write source file");
path
})
.collect();
let path_refs: Vec<&Path> = paths.iter().map(|p| p.as_path()).collect();
extract(&path_refs, "sample_crate", "0.0.0", None).expect("extract failed")
}
#[test]
#[traced_test]
fn enum_field_agreement_is_not_reported_when_the_enum_is_extracted_after_the_struct() {
let config_rs = r#"
pub struct ExtractionConfig {
#[serde(default)]
pub ocr_strategy: OcrStrategy,
}
impl Default for ExtractionConfig {
fn default() -> Self {
Self {
ocr_strategy: OcrStrategy::Auto,
}
}
}
"#;
let ocr_rs = r#"
#[derive(Clone, Copy, Default)]
pub enum OcrStrategy {
Never,
#[default]
Auto,
Always,
}
"#;
let surface = extract_ordered(&[("config.rs", config_rs), ("ocr.rs", ocr_rs)]);
let config = surface
.types
.iter()
.find(|typ| typ.name == "ExtractionConfig")
.expect("ExtractionConfig must be extracted");
let ocr_strategy_field = config
.fields
.iter()
.find(|field| field.name == "ocr_strategy")
.expect("ocr_strategy field must be extracted");
assert_eq!(
ocr_strategy_field.typed_default,
Some(crate::core::ir::DefaultValue::EnumVariant("Auto".to_string())),
"the field itself must still resolve to the concrete variant regardless of file order"
);
assert!(
!logs_contain("disagrees"),
"a genuine agreement (the field default names the enum's own #[default] variant) must \
not be reported as a disagreement just because the enum's source file was parsed after \
the struct's"
);
}
#[test]
#[traced_test]
fn enum_field_agreement_is_not_reported_when_the_enum_is_extracted_before_the_struct() {
let config_rs = r#"
pub struct ExtractionConfig {
#[serde(default)]
pub ocr_strategy: OcrStrategy,
}
impl Default for ExtractionConfig {
fn default() -> Self {
Self {
ocr_strategy: OcrStrategy::Auto,
}
}
}
"#;
let ocr_rs = r#"
#[derive(Clone, Copy, Default)]
pub enum OcrStrategy {
Never,
#[default]
Auto,
Always,
}
"#;
let surface = extract_ordered(&[("ocr.rs", ocr_rs), ("config.rs", config_rs)]);
let config = surface
.types
.iter()
.find(|typ| typ.name == "ExtractionConfig")
.expect("ExtractionConfig must be extracted");
let ocr_strategy_field = config
.fields
.iter()
.find(|field| field.name == "ocr_strategy")
.expect("ocr_strategy field must be extracted");
assert_eq!(
ocr_strategy_field.typed_default,
Some(crate::core::ir::DefaultValue::EnumVariant("Auto".to_string()))
);
assert!(!logs_contain("disagrees"));
}
#[test]
#[traced_test]
fn genuine_numeric_disagreement_is_still_reported_after_deferring_the_check() {
let source = r#"
pub struct EmbeddingConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_embed_duration_secs: Option<u64>,
}
impl Default for EmbeddingConfig {
fn default() -> Self {
Self {
max_embed_duration_secs: Some(60),
}
}
}
"#;
let surface = extract_ordered(&[("processing.rs", source)]);
let config = surface
.types
.iter()
.find(|typ| typ.name == "EmbeddingConfig")
.expect("EmbeddingConfig must be extracted");
let field = config
.fields
.iter()
.find(|field| field.name == "max_embed_duration_secs")
.expect("max_embed_duration_secs field must be extracted");
assert_eq!(
field.typed_default,
Some(crate::core::ir::DefaultValue::IntLiteral(60)),
"the field's own resolved default must still be the real Some(60), not suppressed"
);
assert!(
logs_contain("disagrees"),
"Option<u64>::default() is None, genuinely different from the manual impl's Some(60); \
this real disagreement must still be reported"
);
}