use datasynth_group::{validate::validate, GroupConfig};
fn parse(yaml: &str) -> GroupConfig {
serde_yaml::from_str(yaml).expect("YAML must parse")
}
fn base_yaml(extra_entities: &str, extra_sections: &str) -> String {
format!(
r#"
id: T
presentation_currency: USD
period: {{ start_date: "2024-01-01", length: quarterly }}
seed: 1
scoping_profiles:
std: {{}}
ownership:
parent_entity_code: P
entities:
- {{ code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }}
{extra_entities}
fx:
base_currency: USD
rate_source: inline
rates: {{}}
policy: {{ balance_sheet: closing, income_statement: average, equity: historical }}
{extra_sections}
"#
)
}
#[test]
fn test_unknown_parent_code_fails() {
let yaml = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: UNKNOWN
entities:
- { code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg = parse(yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("parent_entity_code"),
"error should mention parent_entity_code, got: {msg}"
);
assert!(
msg.contains("UNKNOWN"),
"error should mention the bad code, got: {msg}"
);
}
#[test]
fn test_unknown_scoping_profile_fails() {
let yaml = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: P
entities:
- { code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }
- { code: Q, country: US, functional_currency: USD, scoping_profile: nonexistent, consolidation_method: full, ownership_percent: 1.0, parent_code: P }
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg = parse(yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("scoping_profile"),
"error should mention scoping_profile, got: {msg}"
);
assert!(
msg.contains("nonexistent"),
"error should mention the bad profile name, got: {msg}"
);
assert!(
msg.contains('Q'),
"error should mention the entity code, got: {msg}"
);
}
#[test]
fn test_unknown_parent_code_on_entity_fails() {
let yaml = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: P
entities:
- { code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }
- { code: Q, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: full, ownership_percent: 1.0, parent_code: GHOST }
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg = parse(yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("GHOST"),
"error should mention the missing parent_code, got: {msg}"
);
assert!(
msg.contains('Q'),
"error should mention the entity with bad parent_code, got: {msg}"
);
}
#[test]
fn test_ownership_percent_out_of_range_fails() {
let yaml_above = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: P
entities:
- { code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }
- { code: Q, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: full, ownership_percent: 1.5, parent_code: P }
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg = parse(yaml_above);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("ownership_percent"),
"error should mention ownership_percent, got: {msg}"
);
assert!(
msg.contains('Q'),
"error should mention the entity, got: {msg}"
);
let yaml_below = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: P
entities:
- { code: P, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: parent }
- { code: R, country: US, functional_currency: USD, scoping_profile: std, consolidation_method: full, ownership_percent: -0.1, parent_code: P }
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg2 = parse(yaml_below);
let err2 = validate(&cfg2).unwrap_err();
let msg2 = err2.to_string();
assert!(
msg2.contains("ownership_percent"),
"error should mention ownership_percent, got: {msg2}"
);
}
#[test]
fn test_ic_relationship_unknown_entity_fails() {
let extra_sections = r#"
intercompany:
relationships:
- { seller: P, buyer: GHOST_ENTITY, types: [goods_sale], annual_volume: 1000000 }
"#;
let yaml = base_yaml("", extra_sections);
let cfg = parse(&yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("GHOST_ENTITY"),
"error should mention the unknown entity, got: {msg}"
);
}
#[test]
fn test_ic_relationship_self_pair_fails() {
let extra_sections = r#"
intercompany:
relationships:
- { seller: P, buyer: P, types: [management_fee], annual_volume: 500000 }
"#;
let yaml = base_yaml("", extra_sections);
let cfg = parse(&yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("seller == buyer") || msg.contains("self"),
"error should flag self-pair, got: {msg}"
);
assert!(
msg.contains('P'),
"error should mention the entity code, got: {msg}"
);
}
#[test]
fn test_ic_pattern_unknown_entity_fails() {
let extra_sections = r#"
intercompany:
relationships:
- pattern: { seller: NONEXISTENT, buyer_scoping_profile: std }
types: [management_fee]
per_pair_volume: 100000
"#;
let yaml = base_yaml("", extra_sections);
let cfg = parse(&yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("NONEXISTENT"),
"error should mention the unknown entity in pattern, got: {msg}"
);
}
#[test]
fn test_entity_overrides_typo_detection() {
let yaml = r#"
id: T
presentation_currency: USD
period: { start_date: "2024-01-01", length: quarterly }
seed: 1
scoping_profiles: { std: {} }
ownership:
parent_entity_code: P
entities:
- code: P
country: US
functional_currency: USD
scoping_profile: std
consolidation_method: parent
acconting_framework: us_gaap
fx:
base_currency: USD
rate_source: inline
rates: {}
policy: { balance_sheet: closing, income_statement: average, equity: historical }
"#;
let cfg = parse(yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("acconting_framework"),
"error should mention the typo key, got: {msg}"
);
assert!(
msg.contains("accounting_framework"),
"error should suggest the correct field, got: {msg}"
);
}
#[test]
fn test_valid_config_passes() {
let yaml = include_str!("fixtures/mini_acme.yaml");
let cfg: GroupConfig = serde_yaml::from_str(yaml).expect("fixture must parse");
validate(&cfg).expect("mini_acme.yaml must pass validation");
}
#[test]
fn test_ic_pattern_unknown_scoping_profile_fails() {
let extra_sections = r#"
intercompany:
relationships:
- pattern: { seller_scoping_profile: std, buyer_scoping_profile: does_not_exist }
types: [management_fee]
per_pair_volume: 100000
"#;
let yaml = base_yaml("", extra_sections);
let cfg = parse(&yaml);
let err = validate(&cfg).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("does_not_exist"),
"error should mention the unknown scoping_profile in pattern, got: {msg}"
);
}