Skip to content

Commit 0759724

Browse files
committed
test: add coverage for detect_primary_change, metadata_breaking, bug_evidence, connections
- 3 tests for detect_primary_change (new API, removed API, largest file) - 2 tests for detect_metadata_breaking (MSRV, pub use removal) - 5 tests for detect_bug_evidence (hash fix, C-style, bug keyword, fixme, hotfix) - 1 test for connection content assertion - 2 tests for signature edge cases (single-line fn, const no-body fallback) - Fix exact pattern count assertion (24 builtins)
1 parent f79883f commit 0759724

3 files changed

Lines changed: 248 additions & 3 deletions

File tree

src/services/analyzer.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,4 +636,48 @@ mod tests {
636636
let sig = AnalyzerService::extract_signature(node, source);
637637
assert_eq!(sig.as_deref(), Some("pub trait Handler"));
638638
}
639+
640+
#[cfg(feature = "lang-rust")]
641+
#[test]
642+
fn extract_signature_single_line_function() {
643+
let source = "fn foo() {}\n";
644+
let mut parser = Parser::new();
645+
parser
646+
.set_language(&tree_sitter_rust::LANGUAGE.into())
647+
.unwrap();
648+
let tree = parser.parse(source, None).unwrap();
649+
let node = tree.root_node().child(0).unwrap();
650+
let sig = AnalyzerService::extract_signature(node, source);
651+
assert!(
652+
sig.is_some(),
653+
"single-line function should have a signature"
654+
);
655+
assert!(
656+
sig.as_ref().unwrap().contains("fn foo()"),
657+
"signature should contain fn foo(), got: {:?}",
658+
sig
659+
);
660+
}
661+
662+
#[cfg(feature = "lang-rust")]
663+
#[test]
664+
fn extract_signature_const_item_no_body() {
665+
let source = "pub const MAX: usize = 100;\n";
666+
let mut parser = Parser::new();
667+
parser
668+
.set_language(&tree_sitter_rust::LANGUAGE.into())
669+
.unwrap();
670+
let tree = parser.parse(source, None).unwrap();
671+
let node = tree.root_node().child(0).unwrap();
672+
let sig = AnalyzerService::extract_signature(node, source);
673+
assert!(
674+
sig.is_some(),
675+
"const item should have a signature (first-line fallback)"
676+
);
677+
assert!(
678+
sig.as_ref().unwrap().contains("MAX"),
679+
"signature should contain const name, got: {:?}",
680+
sig
681+
);
682+
}
639683
}

tests/context.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,203 @@ fn prompt_shows_signatures_when_available() {
11651165
ctx.symbols_added
11661166
);
11671167
}
1168+
1169+
// ─── Test Coverage: detect_primary_change (#35) ───────────────────────────────
1170+
1171+
#[test]
1172+
fn primary_change_prefers_new_public_api() {
1173+
let changes = make_staged_changes(vec![make_file_change(
1174+
"src/lib.rs",
1175+
ChangeStatus::Modified,
1176+
"+pub fn new_api() {}",
1177+
1,
1178+
0,
1179+
)]);
1180+
let sym = make_symbol("new_api", SymbolKind::Function, "src/lib.rs", true, true);
1181+
let ctx = ContextBuilder::build(&changes, &[sym], &default_config());
1182+
assert!(
1183+
ctx.primary_change.as_ref().unwrap().contains("new_api"),
1184+
"should mention new public API: {:?}",
1185+
ctx.primary_change
1186+
);
1187+
}
1188+
1189+
#[test]
1190+
fn primary_change_falls_back_to_removed_public() {
1191+
let changes = make_staged_changes(vec![make_file_change(
1192+
"src/lib.rs",
1193+
ChangeStatus::Modified,
1194+
"-pub fn old_api() {}",
1195+
0,
1196+
1,
1197+
)]);
1198+
let sym = make_symbol("old_api", SymbolKind::Function, "src/lib.rs", true, false);
1199+
let ctx = ContextBuilder::build(&changes, &[sym], &default_config());
1200+
assert!(
1201+
ctx.primary_change.as_ref().unwrap().contains("old_api"),
1202+
"should mention removed public API: {:?}",
1203+
ctx.primary_change
1204+
);
1205+
}
1206+
1207+
#[test]
1208+
fn primary_change_falls_back_to_largest_file() {
1209+
let changes = make_staged_changes(vec![
1210+
make_file_change("src/a.rs", ChangeStatus::Modified, "+x", 1, 0),
1211+
make_file_change("src/b.rs", ChangeStatus::Modified, "+large change", 50, 10),
1212+
]);
1213+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1214+
assert!(
1215+
ctx.primary_change.as_ref().unwrap().contains("b"),
1216+
"should mention largest file: {:?}",
1217+
ctx.primary_change
1218+
);
1219+
}
1220+
1221+
// ─── Test Coverage: detect_metadata_breaking (#36) ────────────────────────────
1222+
1223+
#[test]
1224+
fn metadata_breaking_detects_msrv_change() {
1225+
let changes = make_staged_changes(vec![make_file_change(
1226+
"Cargo.toml",
1227+
ChangeStatus::Modified,
1228+
"+rust-version = \"1.75\"",
1229+
1,
1230+
0,
1231+
)]);
1232+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1233+
assert!(
1234+
!ctx.metadata_breaking_signals.is_empty(),
1235+
"should detect MSRV change"
1236+
);
1237+
}
1238+
1239+
#[test]
1240+
fn metadata_breaking_detects_pub_use_removal() {
1241+
let changes = make_staged_changes(vec![make_file_change(
1242+
"src/lib.rs",
1243+
ChangeStatus::Modified,
1244+
"-pub use crate::old_api::*;",
1245+
0,
1246+
1,
1247+
)]);
1248+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1249+
assert!(
1250+
ctx.metadata_breaking_signals
1251+
.iter()
1252+
.any(|s| s.contains("pub use")),
1253+
"should detect removed pub use: {:?}",
1254+
ctx.metadata_breaking_signals
1255+
);
1256+
}
1257+
1258+
// ─── Test Coverage: detect_bug_evidence all patterns (#38) ────────────────────
1259+
1260+
#[test]
1261+
fn bug_evidence_detects_hash_fix() {
1262+
let changes = make_staged_changes(vec![make_file_change(
1263+
"src/lib.py",
1264+
ChangeStatus::Modified,
1265+
"+# fix: off by one",
1266+
1,
1267+
0,
1268+
)]);
1269+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1270+
assert!(ctx.has_bug_evidence, "should detect '# fix' pattern");
1271+
}
1272+
1273+
#[test]
1274+
fn bug_evidence_detects_c_style_fix() {
1275+
let changes = make_staged_changes(vec![make_file_change(
1276+
"src/lib.c",
1277+
ChangeStatus::Modified,
1278+
"+/* fix: memory leak */",
1279+
1,
1280+
0,
1281+
)]);
1282+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1283+
assert!(ctx.has_bug_evidence, "should detect '/* fix' pattern");
1284+
}
1285+
1286+
#[test]
1287+
fn bug_evidence_detects_bug_keyword() {
1288+
let changes = make_staged_changes(vec![make_file_change(
1289+
"src/lib.rs",
1290+
ChangeStatus::Modified,
1291+
"+// bug: incorrect index",
1292+
1,
1293+
0,
1294+
)]);
1295+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1296+
assert!(ctx.has_bug_evidence, "should detect '// bug' pattern");
1297+
}
1298+
1299+
#[test]
1300+
fn bug_evidence_detects_fixme() {
1301+
let changes = make_staged_changes(vec![make_file_change(
1302+
"src/lib.rs",
1303+
ChangeStatus::Modified,
1304+
"+// FIXME this is broken",
1305+
1,
1306+
0,
1307+
)]);
1308+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1309+
assert!(ctx.has_bug_evidence, "should detect 'fixme' pattern");
1310+
}
1311+
1312+
#[test]
1313+
fn bug_evidence_detects_hotfix() {
1314+
let changes = make_staged_changes(vec![make_file_change(
1315+
"src/lib.rs",
1316+
ChangeStatus::Modified,
1317+
"+// hotfix for prod issue",
1318+
1,
1319+
0,
1320+
)]);
1321+
let ctx = ContextBuilder::build(&changes, &[], &default_config());
1322+
assert!(ctx.has_bug_evidence, "should detect 'hotfix' pattern");
1323+
}
1324+
1325+
// ─── Test Coverage: connection content assertion (#41) ────────────────────────
1326+
1327+
#[test]
1328+
fn connection_content_mentions_symbol_name() {
1329+
let changes = make_staged_changes(vec![
1330+
make_file_change(
1331+
"src/services/validator.rs",
1332+
ChangeStatus::Modified,
1333+
"+ let result = parse(input);",
1334+
1,
1335+
0,
1336+
),
1337+
make_file_change(
1338+
"src/services/parser.rs",
1339+
ChangeStatus::Modified,
1340+
"-pub fn parse(s: &str) -> Ast {\n+pub fn parse(s: &str, strict: bool) -> Ast {",
1341+
1,
1342+
1,
1343+
),
1344+
]);
1345+
let symbols = vec![
1346+
make_symbol(
1347+
"parse",
1348+
SymbolKind::Function,
1349+
"src/services/parser.rs",
1350+
true,
1351+
true,
1352+
),
1353+
make_symbol(
1354+
"parse",
1355+
SymbolKind::Function,
1356+
"src/services/parser.rs",
1357+
true,
1358+
false,
1359+
),
1360+
];
1361+
let ctx = ContextBuilder::build(&changes, &symbols, &default_config());
1362+
assert!(
1363+
ctx.connections.iter().any(|c| c.contains("parse")),
1364+
"connection should mention symbol name 'parse': {:?}",
1365+
ctx.connections
1366+
);
1367+
}

tests/safety.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,10 @@ fn detects_sendgrid_key() {
536536
#[test]
537537
fn build_patterns_default_has_all_builtins() {
538538
let patterns = build_patterns(&[], &[]);
539-
assert!(
540-
patterns.len() >= 20,
541-
"expected at least 20 built-in patterns, got {}",
539+
assert_eq!(
540+
patterns.len(),
541+
24,
542+
"expected exactly 24 built-in patterns, got {}",
542543
patterns.len()
543544
);
544545
}

0 commit comments

Comments
 (0)