#![allow(clippy::missing_panics_doc)]
#![allow(clippy::result_large_err)]
use std::path::PathBuf;
use serde_json::Value;
use apcore::errors::ErrorCode;
use apcore::registry::registry::MODULE_ID_PATTERN;
use apcore::registry::{
class_name_to_segment, derive_module_ids, DiscoveredClass, DiscoveryConfig,
};
fn find_fixtures_root() -> PathBuf {
if let Ok(spec_repo) = std::env::var("APCORE_SPEC_REPO") {
let p = PathBuf::from(&spec_repo)
.join("conformance")
.join("fixtures");
if p.is_dir() {
return p;
}
panic!("APCORE_SPEC_REPO={spec_repo} does not contain conformance/fixtures/");
}
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let sibling = manifest_dir
.parent()
.unwrap()
.join("apcore")
.join("conformance")
.join("fixtures");
if sibling.is_dir() {
return sibling;
}
panic!(
"Cannot find apcore conformance fixtures.\n\
Fix one of:\n\
1. Set APCORE_SPEC_REPO to the apcore spec repo path\n\
2. Clone apcore as a sibling: git clone <apcore-url> {}\n",
manifest_dir.parent().unwrap().join("apcore").display()
);
}
fn load_fixture(name: &str) -> Value {
let path = find_fixtures_root().join(format!("{name}.json"));
let content = std::fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("Failed to read fixture: {}", path.display()));
serde_json::from_str(&content).unwrap_or_else(|e| panic!("Invalid JSON in {name}: {e}"))
}
fn fixture_case<'a>(fixture: &'a Value, id: &str) -> &'a Value {
fixture["test_cases"]
.as_array()
.expect("test_cases must be an array")
.iter()
.find(|c| c["id"].as_str() == Some(id))
.unwrap_or_else(|| panic!("fixture case '{id}' not present"))
}
fn parse_classes(input: &Value) -> Vec<DiscoveredClass> {
input["classes"]
.as_array()
.map(|arr| {
arr.iter()
.map(|c| DiscoveredClass {
name: c["name"].as_str().unwrap().to_string(),
implements_module: c["implements_module"].as_bool().unwrap_or(true),
})
.collect()
})
.unwrap_or_default()
}
fn run_derive(case: &Value) -> Result<Vec<String>, apcore::errors::ModuleError> {
let input = &case["input"];
let file_path = PathBuf::from(input["file_path"].as_str().unwrap());
let extensions_root = input["extensions_root"].as_str().unwrap_or("extensions");
let classes = parse_classes(input);
let config = DiscoveryConfig {
multi_class: input["multi_class_enabled"].as_bool().unwrap_or(false),
};
derive_module_ids(&file_path, extensions_root, &classes, &config)
}
#[test]
fn conformance_single_class_id_unchanged() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "single_class_id_unchanged");
let ids = run_derive(case).expect("expected success");
let expected: Vec<String> = case["expected"]["module_ids"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
assert_eq!(ids, expected, "single-class identity guarantee");
}
#[test]
fn conformance_two_classes_distinct_ids() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "two_classes_distinct_ids");
let ids = run_derive(case).expect("expected success");
let expected: Vec<String> = case["expected"]["module_ids"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
assert_eq!(ids, expected, "two-class ID derivation");
}
#[test]
fn conformance_class_name_snake_case_addition() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "class_name_snake_case_addition");
let class_name = case["input"]["class_name"].as_str().unwrap();
let expected = case["expected"]["class_segment"].as_str().unwrap();
assert_eq!(class_name_to_segment(class_name), expected);
}
#[test]
fn conformance_class_name_snake_case_math_ops() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "class_name_snake_case_math_ops");
let class_name = case["input"]["class_name"].as_str().unwrap();
let expected = case["expected"]["class_segment"].as_str().unwrap();
assert_eq!(class_name_to_segment(class_name), expected);
}
#[test]
fn conformance_class_name_snake_case_https_sender() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "class_name_snake_case_https_sender");
let class_name = case["input"]["class_name"].as_str().unwrap();
let expected = case["expected"]["class_segment"].as_str().unwrap();
assert_eq!(class_name_to_segment(class_name), expected);
}
#[test]
fn conformance_conflict_same_segment() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "conflict_same_segment");
let err = run_derive(case).expect_err("expected MODULE_ID_CONFLICT");
assert_eq!(
err.code,
ErrorCode::ModuleIdConflict,
"expected MODULE_ID_CONFLICT"
);
let expected_segment = case["expected"]["error"]["conflicting_segment"]
.as_str()
.unwrap();
let actual_segment = err
.details
.get("conflicting_segment")
.and_then(|v| v.as_str())
.expect("conflicting_segment must appear in error details");
assert_eq!(actual_segment, expected_segment);
}
#[test]
fn conformance_full_id_grammar_valid() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "full_id_grammar_valid");
let pattern = regex::Regex::new(MODULE_ID_PATTERN).unwrap();
let ids = run_derive(case).expect("expected success");
for id in &ids {
assert!(
pattern.is_match(id),
"single-class derived ID '{id}' must match canonical grammar"
);
}
let file_path = PathBuf::from(case["input"]["file_path"].as_str().unwrap());
let extensions_root = case["input"]["extensions_root"].as_str().unwrap();
let two_classes = vec![
DiscoveredClass {
name: "Addition".to_string(),
implements_module: true,
},
DiscoveredClass {
name: "Subtraction".to_string(),
implements_module: true,
},
];
let config = DiscoveryConfig::with_multi_class();
let multi_ids = derive_module_ids(&file_path, extensions_root, &two_classes, &config).unwrap();
for id in &multi_ids {
assert!(
pattern.is_match(id),
"multi-class derived ID '{id}' must match canonical grammar"
);
}
assert_eq!(
multi_ids,
vec![
"executor.math.arithmetic.addition".to_string(),
"executor.math.arithmetic.subtraction".to_string(),
]
);
}
#[test]
fn conformance_disabled_by_default() {
let fixture = load_fixture("multi_module_discovery");
let case = fixture_case(&fixture, "disabled_by_default");
let ids = run_derive(case).expect("expected success");
let expected: Vec<String> = case["expected"]["module_ids"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
assert_eq!(ids, expected, "disabled-by-default returns base_id only");
}