use std::collections::HashMap;
use crate::lint::{finding_from_step, Finding, Severity};
use crate::model::TestFile;
pub fn lint(file: &TestFile, path: &str) -> Vec<Finding> {
let mut findings = Vec::new();
let mut seen: HashMap<&str, usize> = HashMap::new();
for (idx, (name, _group)) in file.tests.iter().enumerate() {
if let Some(&first_idx) = seen.get(name.as_str()) {
let group = &file.tests[name];
if let Some(step) = group.steps.first() {
findings.push(finding_from_step(
"TL007",
Severity::Error,
path,
Some(format!("{}::{}", path, name)),
step,
format!(
"Duplicate test name `{}` (first occurrence at index {}, duplicate at index {}).",
name, first_idx, idx
),
Some(
"Give each test a unique name so `--select FILE::TEST` is unambiguous.".to_string(),
),
));
} else {
findings.push(Finding {
rule_id: "TL007",
severity: Severity::Error,
file: path.to_string(),
line: None,
column: None,
step_path: Some(format!("{}::{}", path, name)),
message: format!("Duplicate test name `{}` with no steps.", name),
hint: Some(
"Give each test a unique name so `--select FILE::TEST` is unambiguous."
.to_string(),
),
});
}
} else {
seen.insert(name.as_str(), idx);
}
}
findings
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Request, Step, TestGroup};
use indexmap::IndexMap;
fn file_with_tests(tests: Vec<(&str, Vec<Step>)>) -> TestFile {
let mut map = IndexMap::new();
for (name, steps) in tests {
map.insert(
name.to_string(),
TestGroup {
description: None,
tags: Vec::new(),
steps,
serial_only: false,
},
);
}
TestFile {
version: None,
name: "f.tarn.yaml".into(),
description: None,
tags: Vec::new(),
openapi_operation_ids: None,
env: std::collections::HashMap::new(),
redaction: None,
defaults: None,
setup: Vec::new(),
teardown: Vec::new(),
tests: map,
steps: Vec::new(),
cookies: None,
serial_only: false,
group: None,
}
}
fn step(name: &str) -> Step {
Step {
name: name.into(),
description: None,
request: Request {
method: "GET".into(),
url: "http://example.com".into(),
headers: std::collections::HashMap::new(),
auth: None,
body: None,
form: None,
graphql: None,
multipart: None,
},
capture: std::collections::HashMap::new(),
assertions: None,
run_if: None,
unless: None,
retries: None,
timeout: None,
connect_timeout: None,
follow_redirects: None,
max_redirs: None,
delay: None,
poll: None,
script: None,
cookies: None,
debug: false,
location: None,
assertion_locations: std::collections::HashMap::new(),
}
}
#[test]
fn fires_on_distinct_named_test_collision() {
let file = file_with_tests(vec![
("unique_a", vec![step("s1")]),
("unique_b", vec![step("s2")]),
]);
let findings = lint(&file, "f.tarn.yaml");
assert!(findings.is_empty(), "got {:?}", findings);
}
#[test]
fn quiet_on_unique_names() {
let file = file_with_tests(vec![
("list_users", vec![step("a")]),
("create_user", vec![step("b")]),
]);
let findings = lint(&file, "f.tarn.yaml");
assert!(findings.is_empty());
}
#[test]
fn quiet_when_there_are_no_tests() {
let file = file_with_tests(vec![]);
let findings = lint(&file, "f.tarn.yaml");
assert!(findings.is_empty());
}
}