feat: add normalized config tracking and improve command

- Add normalize_to_final_build() to map tune configs to final builds (323/233/332)
- Add normalized_config field to BestCarRecord
- Change CarCounts.by_rid key from String to (String, String) for per-config tracking
- Change CarCounts.guid_to_rid to HashMap<String, HashSet<(String, String)>>
- Extract owned tune configs from my_cars.json (engineMajor/weightMajor/chassisMajor)
- Add improve command: owned cars not yet at final build, sorted by track count
- Strip version prefixes (v2c_/v1c_) from config keys at parse time
- Update dedup check in update_best_car to consider normalized_config
- Add atomic_write helper for safe CSV output
- Delete completed plans/code-review-v2.md
- Apply rustfmt formatting across codebase
This commit is contained in:
Jan-Peter von Hunnius 2026-05-11 16:23:04 +02:00
parent 67a79f190a
commit bf21d3d1f6
25 changed files with 735 additions and 759 deletions

View file

@ -2,22 +2,17 @@
use std::fs;
use mockito::{Server, ServerGuard, Mock};
use mockito::{Mock, Server, ServerGuard};
use top_drives::get_file;
/// Helper: create a mock that returns a successful response with the given body.
fn mock_success(server: &mut ServerGuard, body: &str) -> Mock {
server.mock("GET", "/data.json")
.with_status(200)
.with_body(body)
.create()
server.mock("GET", "/data.json").with_status(200).with_body(body).create()
}
/// Helper: create a mock that returns the given HTTP status.
fn mock_status(server: &mut ServerGuard, status: usize) -> Mock {
server.mock("GET", "/data.json")
.with_status(status)
.create()
server.mock("GET", "/data.json").with_status(status).create()
}
#[tokio::test]
@ -39,7 +34,8 @@ async fn test_get_file_success() {
async fn test_get_file_empty_response_retries_and_fails() {
let mut server = Server::new_async().await;
// Return 200 with empty body — triggers retry, then fails
let m = server.mock("GET", "/data.json")
let m = server
.mock("GET", "/data.json")
.with_status(200)
.with_body("")
.expect(4) // 1 initial + 3 retries
@ -58,10 +54,7 @@ async fn test_get_file_empty_response_retries_and_fails() {
async fn test_get_file_server_error_retries() {
let mut server = Server::new_async().await;
// Return 500 three times, then 200
let m_fail = server.mock("GET", "/data.json")
.with_status(500)
.expect(3)
.create();
let m_fail = server.mock("GET", "/data.json").with_status(500).expect(3).create();
let m_ok = mock_success(&mut server, "recovered");
let dir = tempfile::tempdir().unwrap();
@ -96,7 +89,8 @@ async fn test_get_file_client_error_fails_immediately() {
async fn test_get_file_persistent_server_error_fails() {
let mut server = Server::new_async().await;
// 503 every time — should retry 3 times then fail
let m = server.mock("GET", "/data.json")
let m = server
.mock("GET", "/data.json")
.with_status(503)
.expect(4) // 1 initial + 3 retries
.create();