Refactor: extract lib.rs into focused sub-modules, add --output-dir CLI option

- Split monolithic src/lib.rs (~640 lines) into 7 sub-modules:
  types.rs, context.rs, user_data.rs, translate.rs, http.rs,
  csv_util.rs, test_utils.rs
- Add configurable --output-dir CLI option for CSV output files
- Implement atomic file writes (write-to-temp + rename) for all CSVs
- Change compute_car_counts() to return CarCounts directly (not Result)
- Add test-utils feature gate for shared test helpers
- Add HTTP mock tests with mockito (6 tests)
- Add proptest tests for compute_car_counts (3 property tests)
- Update integration tests to use shared test utilities
- Update documentation (API.md, ARCHITECTURE.md, DATA_STRUCTURES.md)
This commit is contained in:
Jan-Peter von Hunnius 2026-05-08 20:21:46 +02:00
parent d2c1418e5e
commit 67a79f190a
27 changed files with 1648 additions and 789 deletions

View file

@ -11,35 +11,7 @@ use top_drives::{
compare_cars, compute_car_counts, escape_csv_field, load_my_cars_data,
translate_track_name, translate_track_type,
};
// -- Helpers --
fn make_record(car_rid: &str, rq: u8, time: f64, guid: &str) -> BestCarRecord {
BestCarRecord {
car_rid: car_rid.to_string(),
brand: "TestBrand".to_string(),
only_name: "TestModel".to_string(),
year: 2020,
rq,
config: "111".to_string(),
time,
owned: false,
prize: false,
guid: guid.to_string(),
}
}
fn make_car(rid: &str, rq: u8, guid: &str) -> Car {
Car {
rid: rid.to_string(),
brand: "TestBrand".to_string(),
only_name: "TestModel".to_string(),
year: 2020,
rq,
prize: false,
guid: guid.to_string(),
}
}
use top_drives::test_utils::{make_record, make_car};
// =======================================================================
// Integration: AnalysisContext → CarCounts pipeline
@ -49,7 +21,7 @@ fn make_car(rid: &str, rq: u8, guid: &str) -> Car {
fn test_full_pipeline_empty_input() {
let ctx = AnalysisContext::new();
let car_list: Vec<Car> = vec![];
let counts = compute_car_counts(&ctx, car_list).unwrap();
let counts = compute_car_counts(&ctx, car_list);
assert!(counts.by_rid.is_empty());
assert!(counts.guid_to_rid.is_empty());
@ -70,7 +42,7 @@ fn test_full_pipeline_single_car_multiple_tracks() {
}
let car = make_car("Ferrari_F40", 95, "guid_f40");
let counts = compute_car_counts(&ctx, vec![car]).unwrap();
let counts = compute_car_counts(&ctx, vec![car]);
assert_eq!(counts.by_rid.len(), 1);
let (rec, count) = counts.by_rid.get("Ferrari_F40").unwrap();
@ -124,7 +96,7 @@ fn test_full_pipeline_multiple_cars_different_rq() {
make_car("Car_C", 100, "guid_c"),
];
let counts = compute_car_counts(&ctx, car_list).unwrap();
let counts = compute_car_counts(&ctx, car_list);
assert_eq!(counts.by_rid.len(), 3);
@ -265,7 +237,7 @@ fn test_ownership_filtering_in_counts() {
make_car("Unowned_Car", 90, "guid_unowned"),
];
let counts = compute_car_counts(&ctx, car_list).unwrap();
let counts = compute_car_counts(&ctx, car_list);
// Both should be in by_rid
assert_eq!(counts.by_rid.len(), 2);