#![allow(clippy::missing_panics_doc, reason = "tests are allowed to panic")]
#![allow(clippy::panic, reason = "tests are allowed panic")]
use std::{
collections::{BTreeMap, BTreeSet},
path::Path,
};
use super::Report;
use crate::{
json, tariff,
test::{self, Expectation},
warning, Version,
};
#[test_each::file(
glob = "ocpi-tariffs/test_data/v221/lint/*/test*.json",
name(segments = 2)
)]
fn should_lint_v221_tariff(test_json: &str, path: &Path) {
test::setup();
run_lint(test_json, path, Version::V221);
}
#[test_each::file(
glob = "ocpi-tariffs/test_data/v211/lint/*/test*.json",
name(segments = 2)
)]
fn should_lint_v211_tariff(test_json: &str, path: &Path) {
test::setup();
run_lint(test_json, path, Version::V211);
}
#[derive(serde::Deserialize)]
struct TestRun<'buf> {
#[serde(borrow)]
tariff: &'buf serde_json::value::RawValue,
expect: Expect,
}
#[derive(serde::Deserialize)]
struct Expect {
#[serde(default)]
unexpected_fields: Expectation<Vec<json::test::PathGlob>>,
#[serde(default)]
warnings: Expectation<test::WarningMap>,
#[serde(default)]
schema: Expectation<test::WarningMap>,
#[serde(default)]
pending: Expectation<BTreeMap<String, Vec<String>>>,
}
#[track_caller]
pub fn run_lint(test_json: &str, test_file_path: &Path, expected_version: Version) {
let mut test_json = test_json.to_owned();
let test_run = {
json_strip_comments::strip(&mut test_json).unwrap_or_else(|err| {
panic!(
"Unable to strip comments from {}:\n{:#?}",
test_file_path.display(),
err
);
});
serde_json::from_str::<TestRun<'_>>(&test_json).unwrap_or_else(|err| {
panic!("Unable to parse {}:\n{:#?}", test_file_path.display(), err);
})
};
let TestRun { tariff, expect } = test_run;
let expect_file_name = test_file_path.display().to_string();
let Expect {
unexpected_fields: expect_unexpected_fields,
warnings: expect_warnings,
schema: expect_schema,
pending: expect_pending,
} = expect;
let (tariff, schema_warnings) = {
let tariff_json = tariff.get();
let doc = json::parse_object(tariff_json).unwrap_or_else(|err| {
panic!("Unable to parse the tariff:\n{err:#?}");
});
let (tariff, warnings) = tariff::from_json(doc, expected_version).into_parts();
let mut unexpected_fields = warnings.unexpected_fields();
test::expect_unexpected_fields(
&expect_file_name,
&mut unexpected_fields,
expect_unexpected_fields,
);
(tariff, warnings)
};
let Report { warnings } = super::lint(&tariff);
assert_not_yet_raised(&expect_file_name, &warnings, expect_pending);
warning::test::assert_warnings(
&format!("`warnings` in the `{expect_file_name}` file"),
&warnings,
expect_warnings,
);
warning::test::assert_warnings(
&format!("`schema` in the `{expect_file_name}` file"),
&schema_warnings,
expect_schema,
);
}
#[track_caller]
fn assert_not_yet_raised<W>(
expect_file_name: &str,
warnings: &warning::Set<W>,
pending: Expectation<BTreeMap<String, Vec<String>>>,
) where
W: crate::Warning,
{
let Expectation::Present(pending) = pending else {
return;
};
let Some(pending) = pending.into_option() else {
return;
};
let raised: BTreeMap<&str, BTreeSet<String>> = warnings
.iter()
.map(|group| {
let (element, warnings) = group.to_parts();
let ids = warnings
.iter()
.map(|w| w.id().as_str().to_owned())
.collect();
(element.path.as_str(), ids)
})
.collect();
let mut now_raised = vec![];
for (path, ids) in &pending {
let Some(raised) = raised.get(path.as_str()) else {
continue;
};
for id in ids {
if raised.contains(id) {
now_raised.push(format!("{path}: {id}"));
}
}
}
assert!(
now_raised.is_empty(),
"The `{expect_file_name}` file lists these warnings as `pending`, but they are now \
being raised. Move them from `pending` into `warnings`:\n{now_raised:#?}"
);
}