use std::collections::BTreeSet;
use super::prose_act_reference_tests::{CITED_NOT_MODELLED, act_refs, cites_article_or_annex};
use crate::instrument::InstrumentCatalog;
use crate::schemas::VersionedSchemaRegistry;
#[test]
fn an_article_or_annex_citation_names_its_act() {
let mut unanchored: Vec<String> = Vec::new();
for schema in schema_prose() {
let anchored_by_root = !act_refs(&schema.root_description).is_empty();
if anchored_by_root {
continue;
}
for description in &schema.descriptions {
if cites_article_or_annex(description) && act_refs(description).is_empty() {
unanchored.push(format!(
"{} v{}: cites an article or annex, and neither this \
description nor the schema's root description names an act \
by number — {}",
schema.product_group,
schema.version,
truncate(description)
));
}
}
}
assert!(
unanchored.is_empty(),
"{} citation(s) rest on an act nothing names. Name it by number, in the \
root description or in the citing description:\n{}",
unanchored.len(),
unanchored.join("\n")
);
}
#[test]
fn every_act_cited_in_prose_is_known() {
let catalog = InstrumentCatalog::new();
let modelled: BTreeSet<&str> = catalog
.all()
.iter()
.filter_map(|i| i.celex.as_deref())
.collect();
let inventoried: BTreeSet<&str> = CITED_NOT_MODELLED.iter().map(|(celex, _)| *celex).collect();
let mut unknown: Vec<String> = Vec::new();
let mut seen: BTreeSet<String> = BTreeSet::new();
for schema in schema_prose() {
for description in &schema.descriptions {
for act in act_refs(description) {
if modelled.contains(act.celex.as_str()) || inventoried.contains(act.celex.as_str())
{
seen.insert(act.celex);
continue;
}
unknown.push(format!(
"{} v{}: '{}' resolves to CELEX {} which is \
neither in the instrument catalog nor in CITED_NOT_MODELLED",
schema.product_group, schema.version, act.text, act.celex
));
}
}
}
assert!(
unknown.is_empty(),
"{} act reference(s) point at nothing this crate holds. Either model the \
instrument or add it to CITED_NOT_MODELLED with a reason:\n{}",
unknown.len(),
unknown.join("\n")
);
let stale: Vec<&str> = inventoried
.iter()
.filter(|celex| !seen.contains(**celex))
.copied()
.collect();
assert!(
stale.is_empty(),
"CITED_NOT_MODELLED lists {} act(s) no schema cites any more — remove \
them: {stale:?}",
stale.len()
);
}
#[test]
fn schema_prose_states_no_passport_applicability_date() {
const CLAIMS: &[&str] = &[
"dpp mandate",
"passport mandate",
"dpp applies",
"dpp requirements",
"delegated-act adoption",
"mandate expected",
"mandate is expected",
];
let mut asserted: Vec<String> = Vec::new();
for schema in schema_prose() {
for description in &schema.descriptions {
let lowered = description.to_lowercase();
for claim in CLAIMS {
if lowered.contains(claim) {
asserted.push(format!(
"{} v{}: '{claim}' — {}",
schema.product_group,
schema.version,
truncate(description)
));
}
}
}
}
assert!(
asserted.is_empty(),
"{} description(s) assert a passport applicability date. That belongs in \
the instrument catalog, where it carries a basis:\n{}",
asserted.len(),
asserted.join("\n")
);
}
#[test]
fn a_date_claim_names_its_act_in_the_same_description() {
const DATE_CLAIMS: &[&str] = &[
"applies from",
"applying from",
"applicable from",
"in force since",
"entered into force",
"effective ",
"phased",
];
let mut unanchored: Vec<String> = Vec::new();
for schema in schema_prose() {
for description in &schema.descriptions {
let lowered = description.to_lowercase();
let makes_claim = DATE_CLAIMS.iter().any(|c| lowered.contains(c));
if makes_claim && act_refs(description).is_empty() {
unanchored.push(format!(
"{} v{}: {}",
schema.product_group,
schema.version,
truncate(description)
));
}
}
}
assert!(
unanchored.is_empty(),
"{} date claim(s) name no act. State which instrument applies from that \
date:\n{}",
unanchored.len(),
unanchored.join("\n")
);
}
struct SchemaProse {
product_group: String,
version: String,
root_description: String,
descriptions: Vec<String>,
}
fn schema_prose() -> Vec<SchemaProse> {
let registry = VersionedSchemaRegistry::new();
let mut out = Vec::new();
for product_group in registry.product_groups() {
for version in registry.versions_for(product_group) {
let json_text = registry
.get(product_group, version)
.expect("the registry listed it");
let schema: serde_json::Value =
serde_json::from_str(json_text).expect("schema is valid JSON");
let mut descriptions = Vec::new();
collect_descriptions(&schema, &mut descriptions);
out.push(SchemaProse {
product_group: product_group.to_owned(),
version: version.to_string(),
root_description: schema
.get("description")
.and_then(|d| d.as_str())
.unwrap_or_default()
.to_owned(),
descriptions,
});
}
}
assert!(!out.is_empty(), "no schemas were read at all");
out
}
fn collect_descriptions(node: &serde_json::Value, out: &mut Vec<String>) {
match node {
serde_json::Value::Object(map) => {
if let Some(description) = map.get("description").and_then(|d| d.as_str()) {
out.push(description.to_owned());
}
for value in map.values() {
collect_descriptions(value, out);
}
}
serde_json::Value::Array(items) => {
for item in items {
collect_descriptions(item, out);
}
}
_ => {}
}
}
fn truncate(description: &str) -> String {
let cut = description
.char_indices()
.nth(120)
.map_or(description.len(), |(i, _)| i);
if cut == description.len() {
description.to_owned()
} else {
format!("{}…", &description[..cut])
}
}