Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 100 additions & 1 deletion src/pipeline/store_reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ASC
",
)
.map_err(StoreReducerError::WriteDatabase)?;
Expand Down Expand Up @@ -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 ASC;

INSERT OR REPLACE INTO stage_metadata (key, value)
VALUES
Expand Down Expand Up @@ -4008,6 +4010,50 @@ mod tests {
);
}

#[test]
fn materialized_package_and_file_lists_are_path_sorted() {
let fixture = Fixture::new("deterministic-lists");
write_fixture_file(&fixture.path, "zeta/z.go");
write_fixture_file(&fixture.path, "alpha/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("zeta/z.go"),
&[],
))
.expect("zeta file should enqueue");
handle
.store_file_analysis(file_result_with_imports(
fixture.path.join("alpha/a.go"),
&[],
))
.expect("alpha file should enqueue");

reducer.finish().expect("reducer should finish");

let connection = Connection::open(fixture.db_path()).expect("db should open");
assert_eq!(
text_rows(
&connection,
"SELECT file_path FROM source_file_packages ORDER BY rowid"
),
vec!["alpha/a.go".to_owned(), "zeta/z.go".to_owned()]
);
assert_eq!(
text_rows(
&connection,
"SELECT relative_path FROM file_facts ORDER BY rowid"
),
vec!["alpha/a.go".to_owned(), "zeta/z.go".to_owned()]
);
}

#[test]
fn materializes_go_file_risk_scores_with_terms_facts_and_flags() {
let fixture = Fixture::new("file-risk");
Expand Down Expand Up @@ -4208,6 +4254,51 @@ mod tests {
);
}

#[test]
fn materialized_risk_rows_break_score_ties_by_path() {
let fixture = Fixture::new("file-risk-tie-sort");
write_fixture_file(&fixture.path, "zeta.go");
write_fixture_file(&fixture.path, "alpha.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(go_result_with_metrics(
fixture.path.join("zeta.go"),
100,
10,
3,
false,
false,
))
.expect("zeta go file should enqueue");
handle
.store_file_analysis(go_result_with_metrics(
fixture.path.join("alpha.go"),
100,
10,
3,
false,
false,
))
.expect("alpha go file should enqueue");

reducer.finish().expect("reducer should finish");

let connection = Connection::open(fixture.db_path()).expect("db should open");
assert_eq!(
text_rows(
&connection,
"SELECT relative_path FROM file_risk_scores ORDER BY rank ASC",
),
vec!["alpha.go".to_owned(), "zeta.go".to_owned()]
);
}

#[test]
fn excludes_generated_and_vendor_go_files_from_risk_scores() {
let fixture = Fixture::new("file-risk-generated-vendor");
Expand Down Expand Up @@ -4452,6 +4543,14 @@ mod tests {
.expect("scalar query should run")
}

fn text_rows(connection: &Connection, sql: &str) -> Vec<String> {
let mut statement = connection.prepare(sql).expect("text rows should prepare");
let rows = statement
.query_map([], |row| row.get(0))
.expect("text rows should query");
rows.map(|row| row.expect("text row should read")).collect()
}

fn scalar_f64(connection: &Connection, sql: &str) -> f64 {
connection
.query_row(sql, [], |row| row.get(0))
Expand Down
27 changes: 26 additions & 1 deletion src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,7 @@ fn load_risk_rows(connection: &Connection) -> rusqlite::Result<Vec<RiskRow>> {
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| {
Expand Down Expand Up @@ -1664,6 +1664,31 @@ mod tests {
assert_eq!(snapshot.rows[0].owners.len(), 1);
}

#[test]
fn loads_risk_rows_by_score_descending_then_path_ascending() {
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(
"UPDATE file_risk_scores SET rank = 1, score = 0.8, risk_10 = 8.0 WHERE relative_path = 'src/safe.go'",
[],
)
.expect("safe row should update");
connection
.execute(
"UPDATE file_risk_scores SET rank = 2, score = 0.8, risk_10 = 8.0 WHERE relative_path = 'src/risky.go'",
[],
)
.expect("risky row should update");

let snapshot = TuiDatabaseSnapshot::load_from_dir(&fixture.path);

assert_eq!(snapshot.rows.len(), 2);
assert_eq!(snapshot.rows[0].relative_path, "src/risky.go");
assert_eq!(snapshot.rows[1].relative_path, "src/safe.go");
}

#[test]
fn empty_risk_tables_return_scored_go_empty_state() {
let fixture = Fixture::new("empty-risk");
Expand Down
Loading