refactor: move inline tests to separate files via #[path]

This commit is contained in:
2026-05-12 16:39:58 +02:00
parent 00218366da
commit 763d622601
58 changed files with 3267 additions and 3267 deletions

View File

@@ -76,119 +76,5 @@ fn set_field(row: &mut ImportRow, field: &DomainField, value: String) {
}
#[cfg(test)]
mod tests {
use super::*;
use domain::models::{DomainField, FieldMapping, ParsedFile, RowResult, Transform};
fn sample_file() -> ParsedFile {
ParsedFile {
columns: vec!["Name".into(), "Stars".into(), "Date".into()],
rows: vec![
vec!["Inception".into(), "10".into(), "2024-01-15".into()],
vec!["Dune".into(), "8".into(), "2024-02-20".into()],
vec!["".into(), "3".into(), "2024-03-01".into()], // missing title → invalid
],
}
}
fn full_mappings() -> Vec<FieldMapping> {
vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
FieldMapping { source_column: "Date".into(), domain_field: DomainField::WatchedAt, transform: Transform::Identity },
]
}
#[test]
fn maps_valid_rows() {
let results = apply_mapping(&sample_file(), &full_mappings());
assert_eq!(results.len(), 3);
// First two rows are valid
assert!(matches!(results[0].result, RowResult::Valid(_)));
assert!(matches!(results[1].result, RowResult::Valid(_)));
// is_duplicate defaults to false
assert!(!results[0].is_duplicate);
}
#[test]
fn applies_rating_scale_transform() {
let results = apply_mapping(&sample_file(), &full_mappings());
if let RowResult::Valid(row) = &results[0].result {
// 10 * 0.5 = 5
assert_eq!(row.rating.as_deref(), Some("5"));
} else {
panic!("expected Valid");
}
}
#[test]
fn marks_missing_required_fields_invalid() {
let results = apply_mapping(&sample_file(), &full_mappings());
// Row 2 has empty title
assert!(matches!(results[2].result, RowResult::Invalid { .. }));
}
#[test]
fn ignores_unmapped_columns() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into(), "Extra".into()],
rows: vec![vec!["Inception".into(), "ignored".into()]],
};
let results = apply_mapping(&file, &mappings);
assert_eq!(results.len(), 1);
// Missing rating and watched_at → invalid
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}
#[test]
fn nonexistent_source_column_skipped() {
let mappings = vec![
FieldMapping { source_column: "DoesNotExist".into(), domain_field: DomainField::Title, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into()],
rows: vec![vec!["Inception".into()]],
};
let results = apply_mapping(&file, &mappings);
// Column not found → field not set → invalid (missing title, rating, watched_at)
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}
#[test]
fn collects_all_errors_not_just_first() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
// no watched_at mapping
];
let file = ParsedFile {
columns: vec!["Name".into(), "Stars".into()],
rows: vec![vec!["Inception".into(), "notanumber".into()]],
};
let results = apply_mapping(&file, &mappings);
if let RowResult::Invalid { errors, .. } = &results[0].result {
assert!(errors.iter().any(|e| e.contains("not a number")), "expected rating error, got: {:?}", errors);
assert!(errors.iter().any(|e| e.contains("watched_at")), "expected watched_at error, got: {:?}", errors);
} else {
panic!("expected Invalid");
}
}
#[test]
fn non_numeric_rating_produces_error_in_row() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
FieldMapping { source_column: "Date".into(), domain_field: DomainField::WatchedAt, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into(), "Stars".into(), "Date".into()],
rows: vec![vec!["Inception".into(), "five".into(), "2024-01-15".into()]],
};
let results = apply_mapping(&file, &mappings);
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}
}
#[path = "tests/mapper.rs"]
mod tests;

View File

@@ -9,42 +9,5 @@ pub use json::parse_json;
pub use xlsx::parse_xlsx;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn csv_parses_headers_and_rows() {
let data = b"title,rating,watched_at\nInception,5,2024-01-01\nDune,4,2024-02-15\n";
let file = parse_csv(data).unwrap();
assert_eq!(file.columns, vec!["title", "rating", "watched_at"]);
assert_eq!(file.rows.len(), 2);
assert_eq!(file.rows[0], vec!["Inception", "5", "2024-01-01"]);
}
#[test]
fn csv_rejects_empty() {
assert!(parse_csv(b"").is_err());
}
#[test]
fn tsv_parses_correctly() {
let data = b"title\trating\nInception\t5\n";
let file = parse_csv(data).unwrap();
assert_eq!(file.columns, vec!["title", "rating"]);
assert_eq!(file.rows[0], vec!["Inception", "5"]);
}
#[test]
fn json_array_of_objects() {
let data = br#"[{"title":"Inception","rating":"5"},{"title":"Dune","rating":"4"}]"#;
let file = parse_json(data).unwrap();
assert_eq!(file.columns.len(), 2);
assert!(file.columns.contains(&"title".to_string()));
assert_eq!(file.rows.len(), 2);
}
#[test]
fn json_empty_array_errors() {
assert!(parse_json(b"[]").is_err());
}
}
#[path = "tests.rs"]
mod tests;

View File

@@ -0,0 +1,37 @@
use super::*;
#[test]
fn csv_parses_headers_and_rows() {
let data = b"title,rating,watched_at\nInception,5,2024-01-01\nDune,4,2024-02-15\n";
let file = parse_csv(data).unwrap();
assert_eq!(file.columns, vec!["title", "rating", "watched_at"]);
assert_eq!(file.rows.len(), 2);
assert_eq!(file.rows[0], vec!["Inception", "5", "2024-01-01"]);
}
#[test]
fn csv_rejects_empty() {
assert!(parse_csv(b"").is_err());
}
#[test]
fn tsv_parses_correctly() {
let data = b"title\trating\nInception\t5\n";
let file = parse_csv(data).unwrap();
assert_eq!(file.columns, vec!["title", "rating"]);
assert_eq!(file.rows[0], vec!["Inception", "5"]);
}
#[test]
fn json_array_of_objects() {
let data = br#"[{"title":"Inception","rating":"5"},{"title":"Dune","rating":"4"}]"#;
let file = parse_json(data).unwrap();
assert_eq!(file.columns.len(), 2);
assert!(file.columns.contains(&"title".to_string()));
assert_eq!(file.rows.len(), 2);
}
#[test]
fn json_empty_array_errors() {
assert!(parse_json(b"[]").is_err());
}

View File

@@ -0,0 +1,114 @@
use super::*;
use domain::models::{DomainField, FieldMapping, ParsedFile, RowResult, Transform};
fn sample_file() -> ParsedFile {
ParsedFile {
columns: vec!["Name".into(), "Stars".into(), "Date".into()],
rows: vec![
vec!["Inception".into(), "10".into(), "2024-01-15".into()],
vec!["Dune".into(), "8".into(), "2024-02-20".into()],
vec!["".into(), "3".into(), "2024-03-01".into()], // missing title → invalid
],
}
}
fn full_mappings() -> Vec<FieldMapping> {
vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
FieldMapping { source_column: "Date".into(), domain_field: DomainField::WatchedAt, transform: Transform::Identity },
]
}
#[test]
fn maps_valid_rows() {
let results = apply_mapping(&sample_file(), &full_mappings());
assert_eq!(results.len(), 3);
// First two rows are valid
assert!(matches!(results[0].result, RowResult::Valid(_)));
assert!(matches!(results[1].result, RowResult::Valid(_)));
// is_duplicate defaults to false
assert!(!results[0].is_duplicate);
}
#[test]
fn applies_rating_scale_transform() {
let results = apply_mapping(&sample_file(), &full_mappings());
if let RowResult::Valid(row) = &results[0].result {
// 10 * 0.5 = 5
assert_eq!(row.rating.as_deref(), Some("5"));
} else {
panic!("expected Valid");
}
}
#[test]
fn marks_missing_required_fields_invalid() {
let results = apply_mapping(&sample_file(), &full_mappings());
// Row 2 has empty title
assert!(matches!(results[2].result, RowResult::Invalid { .. }));
}
#[test]
fn ignores_unmapped_columns() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into(), "Extra".into()],
rows: vec![vec!["Inception".into(), "ignored".into()]],
};
let results = apply_mapping(&file, &mappings);
assert_eq!(results.len(), 1);
// Missing rating and watched_at → invalid
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}
#[test]
fn nonexistent_source_column_skipped() {
let mappings = vec![
FieldMapping { source_column: "DoesNotExist".into(), domain_field: DomainField::Title, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into()],
rows: vec![vec!["Inception".into()]],
};
let results = apply_mapping(&file, &mappings);
// Column not found → field not set → invalid (missing title, rating, watched_at)
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}
#[test]
fn collects_all_errors_not_just_first() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
// no watched_at mapping
];
let file = ParsedFile {
columns: vec!["Name".into(), "Stars".into()],
rows: vec![vec!["Inception".into(), "notanumber".into()]],
};
let results = apply_mapping(&file, &mappings);
if let RowResult::Invalid { errors, .. } = &results[0].result {
assert!(errors.iter().any(|e| e.contains("not a number")), "expected rating error, got: {:?}", errors);
assert!(errors.iter().any(|e| e.contains("watched_at")), "expected watched_at error, got: {:?}", errors);
} else {
panic!("expected Invalid");
}
}
#[test]
fn non_numeric_rating_produces_error_in_row() {
let mappings = vec![
FieldMapping { source_column: "Name".into(), domain_field: DomainField::Title, transform: Transform::Identity },
FieldMapping { source_column: "Stars".into(), domain_field: DomainField::Rating, transform: Transform::RatingScale(0.5) },
FieldMapping { source_column: "Date".into(), domain_field: DomainField::WatchedAt, transform: Transform::Identity },
];
let file = ParsedFile {
columns: vec!["Name".into(), "Stars".into(), "Date".into()],
rows: vec![vec!["Inception".into(), "five".into(), "2024-01-15".into()]],
};
let results = apply_mapping(&file, &mappings);
assert!(matches!(results[0].result, RowResult::Invalid { .. }));
}