use crate::adapters::analyzers::architecture::call_parity_rule::{
self, RULE_MISSING_ADAPTER, RULE_NO_DELEGATION,
};
use crate::adapters::analyzers::architecture::compiled::compile_architecture;
use crate::config::Config;
use crate::ports::{AnalysisContext, ParsedFile};
use std::fs;
use std::path::{Path, PathBuf};
fn example_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("examples")
.join("architecture")
.join("call_parity")
}
fn load_parsed_file(root: &Path, rel: &str) -> ParsedFile {
let abs = root.join(rel);
let content = fs::read_to_string(&abs)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", abs.display()));
let ast: syn::File = syn::parse_str(&content)
.unwrap_or_else(|e| panic!("failed to parse {}: {e}", abs.display()));
ParsedFile {
path: rel.to_string(),
content,
ast,
}
}
fn load_golden_config(root: &Path) -> Config {
let toml_str = fs::read_to_string(root.join("rustqual.toml"))
.expect("failed to read example rustqual.toml");
let mut config: Config = toml::from_str(&toml_str).expect("failed to parse example config");
config.compile();
config
}
fn parsed_files(root: &Path) -> Vec<ParsedFile> {
[
"src/application/stats.rs",
"src/application/list.rs",
"src/application/mod.rs",
"src/cli/handlers.rs",
"src/cli/mod.rs",
"src/mcp/handlers.rs",
"src/mcp/mod.rs",
"src/rest/handlers.rs",
"src/rest/mod.rs",
]
.iter()
.map(|rel| load_parsed_file(root, rel))
.collect()
}
#[test]
fn call_parity_golden_example_produces_expected_findings() {
let root = example_root();
let files = parsed_files(&root);
let config = load_golden_config(&root);
let ctx = AnalysisContext {
files: &files,
config: &config,
};
let compiled = compile_architecture(&config.architecture).expect("compile architecture config");
let findings = call_parity_rule::collect_findings(&ctx, &compiled);
let no_delegation: Vec<_> = findings
.iter()
.filter(|f| f.rule_id == RULE_NO_DELEGATION)
.collect();
let missing_adapter: Vec<_> = findings
.iter()
.filter(|f| f.rule_id == RULE_MISSING_ADAPTER)
.collect();
let no_delegation_fns: std::collections::HashSet<String> =
no_delegation.iter().map(|f| f.message.clone()).collect();
assert!(
no_delegation_fns.iter().any(|m| m.contains("post_list")),
"expected no_delegation for post_list, got {:?}",
no_delegation
);
assert!(
no_delegation_fns.iter().any(|m| m.contains("cmd_debug")),
"expected raw no_delegation for cmd_debug (suppressed by \
architecture suppression pipeline later), got {:?}",
no_delegation
);
assert_eq!(
missing_adapter.len(),
1,
"expected exactly one missing_adapter finding, got {:?}",
missing_adapter
);
let ma = missing_adapter[0];
assert!(
ma.message.contains("list_items"),
"missing_adapter must target list_items, got message = {}",
ma.message
);
assert!(
ma.message.contains("rest"),
"missing_adapter must mention rest as missing, got message = {}",
ma.message
);
}