diff --git a/frontend-modern/src/api/__tests__/apiErrorStatus.guardrails.test.ts b/frontend-modern/src/api/__tests__/apiErrorStatus.guardrails.test.ts index 740a8380a..98ac9d2be 100644 --- a/frontend-modern/src/api/__tests__/apiErrorStatus.guardrails.test.ts +++ b/frontend-modern/src/api/__tests__/apiErrorStatus.guardrails.test.ts @@ -149,7 +149,7 @@ describe('API error-status guardrails', () => { /const response = await apiFetch\(url,\s*\{\s*method: 'POST',\s*\}\);/g, ) ?? [], ).toHaveLength(1); - expect(monitoringSource.match(/return triggerResourceCommand ({ state: 'completed', result: { success: true }, }), + getAction: vi.fn().mockResolvedValue({ + audit: { + id: 'detail-action-1', + createdAt: '2026-06-12T20:00:00Z', + updatedAt: '2026-06-12T20:00:00Z', + state: 'pending_approval', + decisionRevision: 0, + request: { + requestId: 'detail-request-1', + resourceId: 'app-container-web', + capabilityName: 'restart', + reason: 'restart Docker container edge-web from the resource details.', + requestedBy: 'ui:resource-detail', + }, + plan: { + actionId: 'detail-action-1', + requestId: 'detail-request-1', + allowed: true, + requiresApproval: true, + approvalPolicy: 'admin', + rollbackAvailable: false, + expiresAt: '2026-06-12T20:05:00Z', + policyDecision: { + version: 0, + status: 'legacy_unknown', + scope: { orgId: '', resourceId: '', capabilityName: '' }, + authorities: [], + approvalRequirement: { version: 0, floor: 'admin', quorum: 1, disallowRequester: false }, + planningAllowed: false, + requiresApproval: true, + }, + }, + verificationOutcome: { status: 'unknown' }, + }, + events: [], + }), }, })); @@ -115,7 +151,7 @@ describe('ResourceDetailDrawer for Docker containers', () => { )); const restartButton = screen.getByRole('button', { - name: 'Restart edge-web through governed action', + name: 'Review restart for edge-web', }); expect(restartButton.closest('[data-docker-container-actions-surface]')).toHaveAttribute( 'data-docker-container-actions-surface', @@ -123,29 +159,21 @@ describe('ResourceDetailDrawer for Docker containers', () => { ); fireEvent.click(restartButton); - fireEvent.click(screen.getByRole('button', { name: 'Click again to restart edge-web' })); await waitFor(() => expect(ResourceActionsAPI.planAction).toHaveBeenCalledWith( expect.objectContaining({ resourceId: 'app-container-web', capabilityName: 'restart', + reason: expect.stringContaining('from the resource details'), requestedBy: 'ui:resource-detail', }), ), ); - await waitFor(() => - expect(ResourceActionsAPI.executeAction).toHaveBeenCalledWith( - 'detail-action-1', - expect.stringContaining('from the resource details'), - ), - ); - expect(ResourceActionsAPI.decideAction).toHaveBeenCalledWith( - 'detail-action-1', - 'approved', - expect.stringContaining('restart Docker container edge-web'), - ); - await waitFor(() => expect(onResourceActionSettled).toHaveBeenCalledTimes(1)); + expect(await screen.findByRole('dialog', { name: 'Restart' })).toBeInTheDocument(); + expect(ResourceActionsAPI.decideAction).not.toHaveBeenCalled(); + expect(ResourceActionsAPI.executeAction).not.toHaveBeenCalled(); + expect(onResourceActionSettled).not.toHaveBeenCalled(); }); it('adds a metrics history tab for app-containers with a metrics target', async () => { diff --git a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx index 57b0302eb..497f54f87 100644 --- a/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx +++ b/frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx @@ -456,6 +456,31 @@ const defaultAgentCapabilitiesManifest = () => ({ capabilities: [], }); +const defaultPatrolAutonomySettings = (overrides: Record = {}) => ({ + autonomy_level: 'monitor', + requested_autonomy_level: 'monitor', + effective_autonomy_level: 'monitor', + full_mode_unlocked: false, + autopilot_acknowledgement: { + code: 'not_requested', + active: false, + currentVersion: 1, + acceptedScope: [], + acceptedLimits: { + policyAllowlistRequired: true, + emergencyStopHonored: true, + approvalFloorsHonored: true, + verificationReconciledWhenSupported: true, + evidenceClassDisclosed: true, + inconclusiveOutcomeAllowed: true, + executionSuccessIsNotOutcomeTruth: true, + }, + }, + investigation_budget: 15, + investigation_timeout_sec: 300, + ...overrides, +}); + const defaultOperationsLoopStatus = (overrides: Record = {}) => ({ nextAction: 'run_patrol', progressLabel: 'Run Patrol to produce actionable issue evidence.', @@ -547,17 +572,9 @@ describe('AIIntelligence entitlement gating', () => { getCorrelationsMock.mockReset(); getPatrolStatusMock.mockResolvedValue(defaultPatrolStatus()); - getPatrolAutonomySettingsMock.mockResolvedValue({ - autonomy_level: 'monitor', - full_mode_unlocked: false, - investigation_budget: 15, - investigation_timeout_sec: 300, - }); + getPatrolAutonomySettingsMock.mockResolvedValue(defaultPatrolAutonomySettings()); updatePatrolAutonomySettingsMock.mockResolvedValue({ - settings: { - autonomy_level: 'monitor', - full_mode_unlocked: false, - }, + settings: defaultPatrolAutonomySettings(), }); triggerPatrolRunMock.mockResolvedValue(undefined); getPatrolRunHistoryMock.mockResolvedValue([]); @@ -1205,13 +1222,11 @@ describe('AIIntelligence entitlement gating', () => { fireEvent.click(screen.getByRole('button', { name: 'Autopilot' })); - await waitFor(() => { - expect( - screen.getByText( - 'Patrol can act automatically within policy and still asks when approval is required.', - ), - ).toBeInTheDocument(); - }); + // Autopilot no longer switches directly; it opens the acknowledgement + // dialog and the mode stays where it was until the user records one. + expect(await screen.findByRole('dialog', { name: 'Activate Autopilot' })).toBeInTheDocument(); + expect(updatePatrolAutonomySettingsMock).not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole('button', { name: 'Close Autopilot acknowledgement' })); expect(screen.getByRole('link', { name: 'Open Patrol settings' })).toHaveAttribute( 'href', @@ -1248,10 +1263,11 @@ describe('AIIntelligence entitlement gating', () => { return {}; }); updatePatrolAutonomySettingsMock.mockResolvedValue({ - settings: { + settings: defaultPatrolAutonomySettings({ autonomy_level: 'approval', - full_mode_unlocked: false, - }, + requested_autonomy_level: 'approval', + effective_autonomy_level: 'approval', + }), }); render(() => ); @@ -1268,7 +1284,8 @@ describe('AIIntelligence entitlement gating', () => { expect(updatePatrolAutonomySettingsMock).toHaveBeenCalledWith( expect.objectContaining({ autonomy_level: 'approval', - full_mode_unlocked: false, + investigation_budget: 15, + investigation_timeout_sec: 300, }), ); }); diff --git a/frontend-modern/src/utils/__tests__/infrastructureSummaryCache.branchcov0717.test.ts b/frontend-modern/src/utils/__tests__/infrastructureSummaryCache.branchcov0717.test.ts index 8995d6fbd..6ff73574b 100644 --- a/frontend-modern/src/utils/__tests__/infrastructureSummaryCache.branchcov0717.test.ts +++ b/frontend-modern/src/utils/__tests__/infrastructureSummaryCache.branchcov0717.test.ts @@ -324,13 +324,20 @@ describe('readInfrastructureSummaryCache — hit / miss / expiry / empty boundar it('returns a cache hit when age equals maxAgeMs exactly (strict > boundary)', () => { // `Date.now() - parsed.cachedAt > maxAgeMs` uses strict >, so an entry that // is exactly maxAgeMs old must still be a hit (boundary is inclusive). - const maxAge = 60_000; - const cachedAt = now() - maxAge; - storePayload( - '1h', - buildPayload({ cachedAt, charts: undefined }), - ); - expect(readInfrastructureSummaryCache('1h', maxAge)).not.toBeNull(); + // Frozen clock: with real timers a millisecond tick between the write and + // the read pushes the age past maxAgeMs and flips the verdict. + vi.useFakeTimers(); + try { + const maxAge = 60_000; + const cachedAt = now() - maxAge; + storePayload( + '1h', + buildPayload({ cachedAt, charts: undefined }), + ); + expect(readInfrastructureSummaryCache('1h', maxAge)).not.toBeNull(); + } finally { + vi.useRealTimers(); + } }); it('returns an empty map (cache hit, but empty) when charts is absent', () => {