use std::collections::BTreeSet;
use datasynth_runtime::{EnhancedGenerationResult, EnhancedOrchestrator, PhaseConfig};
use datasynth_test_utils::fixtures::minimal_config;
fn build_runtime(inject_anomalies: bool) -> EnhancedOrchestrator {
let mut config = minimal_config();
config.global.seed = Some(424242);
config.global.period_months = 2;
config.fraud.enabled = false;
config.balance.reconcile_subledgers = true;
let mut phase_config = PhaseConfig::from_config(&config);
phase_config.show_progress = false;
phase_config.generate_journal_entries = true;
phase_config.generate_document_flows = true;
phase_config.generate_period_close = true;
phase_config.generate_accounting_standards = true;
phase_config.generate_tax = true;
phase_config.generate_hr = true;
phase_config.generate_treasury = true;
phase_config.generate_manufacturing = true;
phase_config.inject_anomalies = inject_anomalies;
phase_config.generate_intercompany = false;
phase_config.generate_banking = false;
phase_config.generate_graph_export = false;
phase_config.generate_ocpm_events = false;
phase_config.generate_audit = false;
phase_config.inject_data_quality = false;
phase_config.generate_evolution_events = false;
phase_config.generate_sourcing = false;
phase_config.generate_sales_kpi_budgets = false;
phase_config.generate_esg = false;
phase_config.generate_project_accounting = false;
phase_config.generate_compliance_regulations = false;
phase_config.generate_financial_statements = false;
phase_config.generate_bank_reconciliation = false;
phase_config.validate_balances = false;
EnhancedOrchestrator::new(config, phase_config).expect("build orchestrator")
}
fn collect_missing(result: &EnhancedGenerationResult) -> (BTreeSet<String>, usize, usize) {
let coa: BTreeSet<&str> = result
.chart_of_accounts
.accounts
.iter()
.map(|a| a.account_number.as_str())
.collect();
let mut missing: BTreeSet<String> = BTreeSet::new();
for je in &result.journal_entries {
for line in je.lines.iter() {
if !coa.contains(line.gl_account.as_str()) {
missing.insert(line.gl_account.clone());
}
}
}
(missing, coa.len(), result.journal_entries.len())
}
fn assert_full_coverage(label: &str, result: &EnhancedGenerationResult) {
let (missing, coa_size, je_count) = collect_missing(result);
assert!(
missing.is_empty(),
"[{label}] JEs reference {} gl_account values that are not in the chart of accounts: {:?}\n\
(COA has {} accounts; total JEs: {}). Each missing value indicates either\n\
a generator using a raw string instead of an `accounts.rs` constant, or a\n\
constant whose module is not seeded by `seed_canonical_accounts`.",
missing.len(),
missing,
coa_size,
je_count,
);
}
#[test]
fn every_je_gl_account_exists_in_coa() {
let mut orch = build_runtime(false);
let result = orch.generate().expect("generate");
assert_full_coverage("base", &result);
}
#[test]
fn every_je_gl_account_exists_in_coa_with_anomalies() {
let mut orch = build_runtime(true);
let result = orch.generate().expect("generate");
let any_anomaly = result.journal_entries.iter().any(|je| je.header.is_anomaly);
assert!(
any_anomaly,
"anomaly-injection branch produced zero anomaly entries — \
test fixture probably needs a higher injection rate"
);
assert_full_coverage("with-anomalies", &result);
}