From edda19aadfebd5b7bdc84d958aab65ad83f6746c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 11 May 2026 23:37:21 +0100 Subject: [PATCH] Catch up frontend tests with collapsed Patrol details and refactored FindingsPanel --- .../src/components/AI/FindingsPanel.tsx | 43 +++++++------------ .../AI/__tests__/FindingsPanel.test.ts | 9 ++-- .../pages/__tests__/AIIntelligence.test.tsx | 24 ++++++++++- .../src/utils/aiFindingPresentation.ts | 19 ++++++++ 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/frontend-modern/src/components/AI/FindingsPanel.tsx b/frontend-modern/src/components/AI/FindingsPanel.tsx index e485f2dade..a3c75dc19e 100644 --- a/frontend-modern/src/components/AI/FindingsPanel.tsx +++ b/frontend-modern/src/components/AI/FindingsPanel.tsx @@ -65,6 +65,7 @@ import { getFindingSubjectPresentation, getFindingTitlePresentation, getFindingRecencyPresentation, + findingHasAppliedFix, hasFindingInvestigationDetails, hasFindingInvestigationHandoffPointer, getInvestigationConfidenceBadgeClasses, @@ -632,23 +633,9 @@ export const FindingsPanel: Component = (props) => { await openFindingInAssistant(finding, 'verify_fix'); }; - // True when the finding has an investigation outcome indicating that - // some remediation step has run against it — anything past "fix - // queued." For these states, Verify fix is a meaningful action; for - // fix_queued (still awaiting approval) and earlier states there is - // nothing applied yet to verify, and for fix_failed the fix didn't - // complete so verification doesn't apply. - const hasAppliedFix = (finding: UnifiedFinding): boolean => { - switch (finding.investigationOutcome) { - case 'fix_executed': - case 'fix_verified': - case 'fix_verification_failed': - case 'fix_verification_unknown': - return true; - default: - return false; - } - }; + // hasAppliedFix logic lives in @/utils/aiFindingPresentation as + // findingHasAppliedFix so the investigation-outcome switch stays in a + // shared adapter (per the resource-type-boundaries test). // Create rule from this is the promotion path: take the operator's // implicit pattern (silencing the same {resource, category} pair @@ -1333,7 +1320,7 @@ export const FindingsPanel: Component = (props) => { Why - + - + + + diff --git a/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts b/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts index d2e985b6d6..27e76aad04 100644 --- a/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts +++ b/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts @@ -51,9 +51,12 @@ describe('FindingsPanel assistant handoff', () => { expect(findingsPanelSource).toContain('buildPatrolAssistantFindingHandoff'); expect(findingsPanelSource).toContain('buildPatrolAssistantApprovalBriefingInput'); expect(findingsPanelSource).toContain('buildPatrolAssistantProposedFixBriefingInput'); - expect(findingsPanelSource).toContain( - 'aiChatStore.openWithPrompt(handoff.prompt, handoff.context)', - ); + // openWithPrompt receives the handoff context spread inline (so the call + // site can layer autoSendInitialPrompt for verb-style entrypoints like + // Investigate / Why / Verify fix). The base handoff.context must still + // flow through. + expect(findingsPanelSource).toContain('aiChatStore.openWithPrompt(handoff.prompt, {'); + expect(findingsPanelSource).toContain('...handoff.context'); expect(findingsPanelSource).toContain('investigationOutcome: finding.investigationOutcome'); expect(findingsPanelSource).toContain('investigationStatus: finding.investigationStatus'); expect(findingsPanelSource).toContain('remediationId: finding.remediationPlanId'); diff --git a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx index 4ee9092ddf..898f4c9405 100644 --- a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx +++ b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx @@ -1154,9 +1154,17 @@ describe('AIIntelligence entitlement gating', () => { expect(screen.getByTestId('patrol-recommended-next-step-action')).toHaveTextContent( 'Run Patrol', ); + expect(findingsPanelState.latestProps).not.toBeNull(); + }); + + // Patrol summary verification + activity-mix copy live behind the + // "Show details" toggle since commit 174d2b04d. Expand the section so + // the deeper assertions can find the text. + fireEvent.click(screen.getByTestId('patrol-summary-details-toggle')); + + await waitFor(() => { expect(screen.getByText('No recent full patrol')).toBeInTheDocument(); expect(screen.getAllByText(/Last activity/i)).toHaveLength(1); - expect(findingsPanelState.latestProps).not.toBeNull(); }); expect(screen.queryByText('No issues found')).not.toBeInTheDocument(); @@ -1328,6 +1336,13 @@ describe('AIIntelligence entitlement gating', () => { 'Patrol has an active runtime issue: Provider billing or quota issue. Recent coverage is also incomplete, so the rest of your infrastructure is not fully verified.', ), ).toBeInTheDocument(); + }); + + // "Runtime issues" and "Latest activity" both live in the collapsible + // Patrol summary detail section (commit 174d2b04d). + fireEvent.click(screen.getByTestId('patrol-summary-details-toggle')); + + await waitFor(() => { expect(screen.getByText('Runtime issues')).toBeInTheDocument(); expect(screen.getByText('Latest activity')).toBeInTheDocument(); }); @@ -1486,6 +1501,13 @@ describe('AIIntelligence entitlement gating', () => { render(() => ); + // "Recent activity mix" copy lives in the collapsible Patrol summary + // detail section (commit 174d2b04d). Expand before asserting. + await waitFor(() => { + expect(screen.getByTestId('patrol-summary-details-toggle')).toBeInTheDocument(); + }); + fireEvent.click(screen.getByTestId('patrol-summary-details-toggle')); + await waitFor(() => { expect( screen.getByText('Recent activity mix: 1 full, 1 alert-triggered, 1 anomaly-triggered'), diff --git a/frontend-modern/src/utils/aiFindingPresentation.ts b/frontend-modern/src/utils/aiFindingPresentation.ts index 224953c191..401ec5169f 100644 --- a/frontend-modern/src/utils/aiFindingPresentation.ts +++ b/frontend-modern/src/utils/aiFindingPresentation.ts @@ -617,6 +617,25 @@ export const doesFindingNeedAttention = ( ); }; +// True when the finding has an investigation outcome indicating that some +// remediation step has run against it — anything past "fix queued." For these +// states, Verify fix is a meaningful action; for fix_queued (still awaiting +// approval) and earlier states there is nothing applied yet to verify, and +// for fix_failed the fix didn't complete so verification doesn't apply. +export const findingHasAppliedFix = ( + finding: Pick, +): boolean => { + switch (finding.investigationOutcome) { + case 'fix_executed': + case 'fix_verified': + case 'fix_verification_failed': + case 'fix_verification_unknown': + return true; + default: + return false; + } +}; + export const getFindingLoopStateBadgeClasses = (loopState: string): string => FINDING_LOOP_STATE_CLASSES[loopState] || DEFAULT_LOOP_STATE_CLASSES;