use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ParityClass {
TestOnly,
Mixed,
#[allow(dead_code)] ProductionUsed,
}
impl ParityClass {
fn header_tag(self) -> &'static str {
match self {
ParityClass::TestOnly => "Parity status: test-only",
ParityClass::Mixed => "Parity status: mixed",
ParityClass::ProductionUsed => "Parity status: production-used",
}
}
}
const PARITY_MODULES: &[(&str, ParityClass)] = &[
("parity_config_validation.rs", ParityClass::TestOnly),
("parity_feedback_tools.rs", ParityClass::TestOnly),
("parity_metadata.rs", ParityClass::TestOnly),
("parity_notifier_resolution.rs", ParityClass::TestOnly),
("parity_observability.rs", ParityClass::TestOnly),
("parity_plugin_registry.rs", ParityClass::TestOnly),
("parity_session_strategy.rs", ParityClass::Mixed),
("parity_utils.rs", ParityClass::TestOnly),
];
fn src_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("src")
}
fn discovered_parity_files() -> BTreeSet<String> {
fs::read_dir(src_dir())
.expect("read src dir")
.filter_map(|e| e.ok())
.filter_map(|e| {
let name = e.file_name().to_string_lossy().to_string();
if name.starts_with("parity_") && name.ends_with(".rs") {
Some(name)
} else {
None
}
})
.collect()
}
#[test]
fn parity_module_set_matches_expected() {
let on_disk = discovered_parity_files();
let expected: BTreeSet<String> = PARITY_MODULES
.iter()
.map(|(name, _)| (*name).to_string())
.collect();
let missing: Vec<_> = expected.difference(&on_disk).collect();
let unexpected: Vec<_> = on_disk.difference(&expected).collect();
assert!(
missing.is_empty() && unexpected.is_empty(),
"Parity module set drift.\n\
Missing (listed in `PARITY_MODULES` but not on disk): {missing:?}\n\
Unexpected (on disk but not listed): {unexpected:?}\n\
\n\
To fix: update `PARITY_MODULES` in this test AND the table in\n\
`docs/ts-core-parity-report.md` → \"Parity-only modules\"."
);
}
#[test]
fn every_parity_module_has_status_header() {
let src = src_dir();
for (name, class) in PARITY_MODULES {
let path = src.join(name);
let content =
fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
assert!(
content.contains(class.header_tag()),
"{name} is missing required header line `{}`. \
Every `parity_*` module must carry a `//!` `Parity status:` tag \
matching its classification in `PARITY_MODULES`.",
class.header_tag(),
);
}
}
#[test]
fn session_strategy_enums_still_reexported_from_lib() {
let lib = fs::read_to_string(src_dir().join("lib.rs")).expect("read lib.rs");
let has_reexport = lib.contains(
"pub use parity_session_strategy::{OpencodeIssueSessionStrategy, \
OrchestratorSessionStrategy}",
) || (lib.contains("pub use parity_session_strategy::")
&& lib.contains("OrchestratorSessionStrategy")
&& lib.contains("OpencodeIssueSessionStrategy"));
assert!(
has_reexport,
"parity_session_strategy is classified as Mixed because its enums are \
re-exported from `ao_core::lib`. That re-export is gone. Either \
restore it, or reclassify the module in `PARITY_MODULES` and the \
docs table."
);
}