@@ -178,6 +178,66 @@ func TestCompose_GroupedKindFilterMatchesSubject(t *testing.T) {
178178 }
179179}
180180
181+ func TestCompose_GroupedDiagnosisRequiresEveryMemberToAgree (t * testing.T ) {
182+ p := & fakeProvider {
183+ problems : []k8s.Detection {
184+ {
185+ Kind : "Pod" , Namespace : "ns" , Name : "web-a" , Severity : "critical" , Reason : "CrashLoopBackOff" ,
186+ OwnerKind : "Deployment" , OwnerName : "web" ,
187+ Cause : "command not found" , Action : "fix the container command" ,
188+ },
189+ {Kind : "Pod" , Namespace : "ns" , Name : "web-b" , Severity : "critical" , Reason : "CrashLoopBackOff" , OwnerKind : "Deployment" , OwnerName : "web" },
190+ },
191+ }
192+ out := Compose (p , Filters {Grouped : true })
193+ if len (out ) != 1 {
194+ t .Fatalf ("got %d issues, want 1: %+v" , len (out ), out )
195+ }
196+ if out [0 ].Cause != "" || out [0 ].Action != "" {
197+ t .Fatalf ("mixed parsed/unparsed rollup must omit diagnosis, got cause=%q action=%q" , out [0 ].Cause , out [0 ].Action )
198+ }
199+ }
200+
201+ func TestCompose_GroupedDiagnosisKeepsIdenticalMemberDiagnosis (t * testing.T ) {
202+ p := & fakeProvider {
203+ problems : []k8s.Detection {
204+ {
205+ Kind : "Pod" , Namespace : "ns" , Name : "web-a" , Severity : "critical" , Reason : "CrashLoopBackOff" ,
206+ OwnerKind : "Deployment" , OwnerName : "web" ,
207+ Cause : "command not found" , Action : "fix the container command" ,
208+ },
209+ {
210+ Kind : "Pod" , Namespace : "ns" , Name : "web-b" , Severity : "critical" , Reason : "CrashLoopBackOff" ,
211+ OwnerKind : "Deployment" , OwnerName : "web" ,
212+ Cause : "command not found" , Action : "fix the container command" ,
213+ },
214+ },
215+ }
216+ out := Compose (p , Filters {Grouped : true })
217+ if len (out ) != 1 {
218+ t .Fatalf ("got %d issues, want 1: %+v" , len (out ), out )
219+ }
220+ if out [0 ].Cause != "command not found" || out [0 ].Action != "fix the container command" {
221+ t .Fatalf ("identical member diagnosis should be promoted, got cause=%q action=%q" , out [0 ].Cause , out [0 ].Action )
222+ }
223+ }
224+
225+ func TestCompose_GroupedSingleIssueKeepsDiagnosis (t * testing.T ) {
226+ p := & fakeProvider {
227+ problems : []k8s.Detection {{
228+ Kind : "Pod" , Namespace : "ns" , Name : "standalone" , Severity : "critical" , Reason : "CrashLoopBackOff" ,
229+ Cause : "command not found" , Action : "fix the container command" ,
230+ }},
231+ }
232+ out := Compose (p , Filters {Grouped : true })
233+ if len (out ) != 1 {
234+ t .Fatalf ("got %d issues, want 1: %+v" , len (out ), out )
235+ }
236+ if out [0 ].Cause == "" || out [0 ].Action == "" {
237+ t .Fatalf ("single-resource issue should keep diagnosis, got %+v" , out [0 ])
238+ }
239+ }
240+
181241func TestCompose_DropsInfoSeverityFromQueue (t * testing.T ) {
182242 // info-severity problems are inert/posture (deprecated-RBAC residue,
183243 // singleton-StatefulSet headless-DNS trivia) — excluded from the live issue
@@ -342,7 +402,11 @@ func TestCompose_PVCPendingDedupesOverMissingStorageClass(t *testing.T) {
342402 // One incident, one row: the missing-ref row names the cause and wins.
343403 p := & fakeProvider {
344404 problems : []k8s.Detection {
345- {Kind : "PersistentVolumeClaim" , Namespace : "ns" , Name : "stuck-pvc" , Severity : "high" , Reason : "Pending" , IssueTiming : "started_at_resource_creation" , IssueTimingBasis : "phase" },
405+ {
406+ Kind : "PersistentVolumeClaim" , Namespace : "ns" , Name : "stuck-pvc" , Severity : "high" , Reason : "Pending" ,
407+ Cause : "Storage provisioner failed to create a volume." , Action : "Check the CSI controller logs." ,
408+ IssueTiming : "started_at_resource_creation" , IssueTimingBasis : "phase" ,
409+ },
346410 },
347411 missingRefs : []k8s.Detection {
348412 {Kind : "PersistentVolumeClaim" , Namespace : "ns" , Name : "stuck-pvc" , Severity : "critical" , Reason : "Missing StorageClass" , Fingerprint : "Missing StorageClass|abc" , IssueTiming : "started_at_resource_creation" , IssueTimingBasis : "phase" },
@@ -361,6 +425,9 @@ func TestCompose_PVCPendingDedupesOverMissingStorageClass(t *testing.T) {
361425 if pvcRows [0 ].Reason != "Missing StorageClass" {
362426 t .Errorf ("the cause-naming missing-ref row must win, got reason %q" , pvcRows [0 ].Reason )
363427 }
428+ if pvcRows [0 ].Source != SourceMissingRef {
429+ t .Errorf ("missing-ref row must win over enriched phase row, got source %q" , pvcRows [0 ].Source )
430+ }
364431 if pvcRows [0 ].IssueTiming != "started_at_resource_creation" || pvcRows [0 ].IssueTimingBasis != "phase" {
365432 t .Errorf ("surviving row must keep at-creation/phase timing, got (%q, %q)" , pvcRows [0 ].IssueTiming , pvcRows [0 ].IssueTimingBasis )
366433 }
0 commit comments