@@ -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+ }
0 commit comments