diff --git a/src/pipeline/store_reducer.rs b/src/pipeline/store_reducer.rs index 4a451dc..a329bf3 100644 --- a/src/pipeline/store_reducer.rs +++ b/src/pipeline/store_reducer.rs @@ -1962,6 +1962,7 @@ fn finalize_source_dependencies( WHERE is_active = 1 AND language_id = 'go' AND relative_path IS NOT NULL + ORDER BY relative_path ", ) .map_err(StoreReducerError::WriteDatabase)?; @@ -2277,7 +2278,8 @@ fn materialize_file_facts( GROUP BY source_path ) source_out ON file_analysis.relative_path = source_out.source_path - WHERE file_analysis.is_active = 1; + WHERE file_analysis.is_active = 1 + ORDER BY file_analysis.relative_path; INSERT OR REPLACE INTO stage_metadata (key, value) VALUES @@ -3648,6 +3650,44 @@ mod tests { ); } + #[test] + fn materializes_source_packages_and_file_facts_in_relative_path_order() { + let fixture = Fixture::new("deterministic-file-lists"); + write_fixture_file(&fixture.path, "b.go"); + write_fixture_file(&fixture.path, "a.go"); + + let (event_sender, _event_receiver) = mpsc::channel(); + let reducer = + StoreReducer::start(&fixture.path, StoreReducerOptions::default(), event_sender) + .expect("reducer should start"); + let handle = reducer.handle(); + + handle + .store_file_analysis(file_result_with_imports(fixture.path.join("b.go"), &[])) + .expect("b file should enqueue"); + handle + .store_file_analysis(file_result_with_imports(fixture.path.join("a.go"), &[])) + .expect("a file should enqueue"); + + reducer.finish().expect("reducer should finish"); + + let connection = Connection::open(fixture.db_path()).expect("db should open"); + assert_eq!( + scalar_text( + &connection, + "SELECT GROUP_CONCAT(file_path, ',') FROM source_file_packages", + ), + "a.go,b.go" + ); + assert_eq!( + scalar_text( + &connection, + "SELECT GROUP_CONCAT(relative_path, ',') FROM file_facts", + ), + "a.go,b.go" + ); + } + #[test] fn materializes_file_facts_without_git_as_zero_metrics() { let fixture = Fixture::new("file-facts-no-git"); diff --git a/src/tui.rs b/src/tui.rs index d82a4b4..c9cb52e 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -869,7 +869,7 @@ fn load_risk_rows(connection: &Connection) -> rusqlite::Result> { FROM file_risk_scores score LEFT JOIN file_facts facts ON facts.relative_path = score.relative_path - ORDER BY score.rank ASC + ORDER BY score.score DESC, score.relative_path ASC ", )?; let rows = statement.query_map([], |row| { @@ -1664,6 +1664,34 @@ mod tests { assert_eq!(snapshot.rows[0].owners.len(), 1); } + #[test] + fn loads_risk_rows_by_score_desc_then_path_asc() { + let fixture = Fixture::new("risk-sort"); + create_tui_db(&fixture.db_path()); + let connection = Connection::open(fixture.db_path()).expect("db should open"); + connection + .execute_batch( + " + UPDATE file_risk_scores + SET rank = 2, score = 0.9 + WHERE relative_path = 'src/risky.go'; + UPDATE file_risk_scores + SET rank = 1, score = 0.9 + WHERE relative_path = 'src/safe.go'; + ", + ) + .expect("fixture rows should update"); + + let snapshot = TuiDatabaseSnapshot::load_from_dir(&fixture.path); + + let paths = snapshot + .rows + .iter() + .map(|row| row.relative_path.as_str()) + .collect::>(); + assert_eq!(paths, vec!["src/risky.go", "src/safe.go"]); + } + #[test] fn empty_risk_tables_return_scored_go_empty_state() { let fixture = Fixture::new("empty-risk");