@@ -155,3 +155,96 @@ func TestBuildAssessReport_ReviewCountSeparateFromKeep(t *testing.T) {
155155 t .Errorf ("keepDismissedCount = %d, want 1" , assessReport .Summary .KeepDismissed )
156156 }
157157}
158+
159+ func TestAssessAlerts_DiscardForOverlappingOpenAlerts (t * testing.T ) {
160+ // Two open alerts from different rules at the same location.
161+ // The higher-numbered alert should be marked "discard" (lower number is canonical).
162+ alerts := []alertEntry {
163+ {Number : 1 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection" , Severity : "8.8" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
164+ {Number : 5 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection-v2" , Severity : "8.8" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
165+ }
166+
167+ assessed := assessAlerts (alerts )
168+
169+ var discardCount int
170+ for _ , a := range assessed {
171+ if a .Recommendation == "discard" {
172+ discardCount ++
173+ if a .Number != 5 {
174+ t .Errorf ("expected alert #5 to be discard, got alert #%d" , a .Number )
175+ }
176+ }
177+ }
178+ if discardCount != 1 {
179+ t .Errorf ("expected 1 discard recommendation, got %d" , discardCount )
180+ }
181+
182+ // The canonical alert (lower number) keeps "keep" recommendation
183+ for _ , a := range assessed {
184+ if a .Number == 1 && a .Recommendation != "keep" {
185+ t .Errorf ("canonical alert #1 recommendation = %q, want keep" , a .Recommendation )
186+ }
187+ }
188+ }
189+
190+ func TestAssessAlerts_DiscardPreservesOverlapMetadata (t * testing.T ) {
191+ alerts := []alertEntry {
192+ {Number : 1 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
193+ {Number : 5 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection-v2" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
194+ }
195+
196+ assessed := assessAlerts (alerts )
197+
198+ for _ , a := range assessed {
199+ if a .Number == 5 {
200+ if len (a .OverlappingAlerts ) == 0 {
201+ t .Error ("discarded alert #5 should list overlapping alerts" )
202+ }
203+ if a .RecommendReason == "" {
204+ t .Error ("discarded alert #5 should have a recommend reason" )
205+ }
206+ }
207+ }
208+ }
209+
210+ func TestAssessAlerts_DiscardNotAppliedToMixedStateOverlaps (t * testing.T ) {
211+ // When an open alert overlaps with a dismissed alert, it should be "review" not "discard".
212+ // Discard only applies when all overlapping alerts are open.
213+ alerts := []alertEntry {
214+ {Number : 1 , State : "dismissed" , Rule : ruleEntry {ID : "js/sql-injection" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 },
215+ DismissedReason : strPtr ("false positive" )},
216+ {Number : 5 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection-v2" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
217+ }
218+
219+ assessed := assessAlerts (alerts )
220+
221+ for _ , a := range assessed {
222+ if a .Number == 5 {
223+ if a .Recommendation == "discard" {
224+ t .Error ("alert #5 overlapping with dismissed alert should be 'review', not 'discard'" )
225+ }
226+ if a .Recommendation != "review" {
227+ t .Errorf ("alert #5 recommendation = %q, want review" , a .Recommendation )
228+ }
229+ }
230+ }
231+ }
232+
233+ func TestBuildAssessReport_DiscardCount (t * testing.T ) {
234+ // Two open alerts at same location → one should be discarded
235+ alerts := []alertEntry {
236+ {Number : 1 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
237+ {Number : 5 , State : "open" , Rule : ruleEntry {ID : "js/sql-injection-v2" }, Location : locationEntry {Path : "src/db.js" , StartLine : 42 }},
238+ }
239+
240+ report := buildReport ("test/repo" , nil , alerts )
241+ assessed := assessAlerts (alerts )
242+ assessReport := buildAssessReport (report , assessed )
243+
244+ if assessReport .Summary .DiscardCount != 1 {
245+ t .Errorf ("discardCount = %d, want 1" , assessReport .Summary .DiscardCount )
246+ }
247+ if assessReport .Summary .KeepCount != 1 {
248+ t .Errorf ("keepCount = %d, want 1 (canonical alert kept)" , assessReport .Summary .KeepCount )
249+ }
250+ }
0 commit comments