test(frontend): repin four suites to the governed action review and autopilot acknowledgement flows

Build and Test has been red since 2026-07-11 because these tests still
pinned the pre-review-flow behavior:

- apiErrorStatus guardrail expected 3 triggerResourceCommand returns in
  monitoring.ts; the governed container update flow moved two callers out.
- ResourceDetailDrawer docker test drove the retired click-twice confirm;
  the drawer now plans the action and opens ActionReviewDialog, so the
  test pins surface attribution and the dialog handoff instead (the
  dialog's own suite covers approve/execute).
- AIIntelligence patrol tests mocked the old autonomy settings shape and
  expected Autopilot to switch modes directly; settings now carry
  requested/effective levels plus autopilot_acknowledgement, and the
  Autopilot button opens the acknowledgement dialog without saving.
- infrastructureSummaryCache boundary test measured age with real timers,
  so any millisecond tick between write and read flipped the strict >
  verdict; the clock is now frozen for the boundary assertion.
This commit is contained in:
rcourtman
2026-07-17 14:30:06 +01:00
parent f8e8135a32
commit 63c2118a16
4 changed files with 95 additions and 43 deletions
@@ -149,7 +149,7 @@ describe('API error-status guardrails', () => {
/const response = await apiFetch\(url,\s*\{\s*method: 'POST',\s*\}\);/g, /const response = await apiFetch\(url,\s*\{\s*method: 'POST',\s*\}\);/g,
) ?? [], ) ?? [],
).toHaveLength(1); ).toHaveLength(1);
expect(monitoringSource.match(/return triggerResourceCommand</g) ?? []).toHaveLength(3); expect(monitoringSource.match(/return triggerResourceCommand</g) ?? []).toHaveLength(1);
expect(monitoringSource.match(/await runResourceAction\(url\);/g) ?? []).toHaveLength(3); expect(monitoringSource.match(/await runResourceAction\(url\);/g) ?? []).toHaveLength(3);
expect(monitoringSource.match(/parseOptionalSuccessAPIResponse</g) ?? []).toHaveLength(1); expect(monitoringSource.match(/parseOptionalSuccessAPIResponse</g) ?? []).toHaveLength(1);
expect(discoverySource).toContain('assertAPIResponseOK(response,'); expect(discoverySource).toContain('assertAPIResponseOK(response,');
@@ -53,6 +53,42 @@ vi.mock('@/api/resourceActions', () => ({
state: 'completed', state: 'completed',
result: { success: true }, 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', { 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( expect(restartButton.closest('[data-docker-container-actions-surface]')).toHaveAttribute(
'data-docker-container-actions-surface', 'data-docker-container-actions-surface',
@@ -123,29 +159,21 @@ describe('ResourceDetailDrawer for Docker containers', () => {
); );
fireEvent.click(restartButton); fireEvent.click(restartButton);
fireEvent.click(screen.getByRole('button', { name: 'Click again to restart edge-web' }));
await waitFor(() => await waitFor(() =>
expect(ResourceActionsAPI.planAction).toHaveBeenCalledWith( expect(ResourceActionsAPI.planAction).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
resourceId: 'app-container-web', resourceId: 'app-container-web',
capabilityName: 'restart', capabilityName: 'restart',
reason: expect.stringContaining('from the resource details'),
requestedBy: 'ui:resource-detail', requestedBy: 'ui:resource-detail',
}), }),
), ),
); );
await waitFor(() => expect(await screen.findByRole('dialog', { name: 'Restart' })).toBeInTheDocument();
expect(ResourceActionsAPI.executeAction).toHaveBeenCalledWith( expect(ResourceActionsAPI.decideAction).not.toHaveBeenCalled();
'detail-action-1', expect(ResourceActionsAPI.executeAction).not.toHaveBeenCalled();
expect.stringContaining('from the resource details'), expect(onResourceActionSettled).not.toHaveBeenCalled();
),
);
expect(ResourceActionsAPI.decideAction).toHaveBeenCalledWith(
'detail-action-1',
'approved',
expect.stringContaining('restart Docker container edge-web'),
);
await waitFor(() => expect(onResourceActionSettled).toHaveBeenCalledTimes(1));
}); });
it('adds a metrics history tab for app-containers with a metrics target', async () => { it('adds a metrics history tab for app-containers with a metrics target', async () => {
@@ -456,6 +456,31 @@ const defaultAgentCapabilitiesManifest = () => ({
capabilities: [], capabilities: [],
}); });
const defaultPatrolAutonomySettings = (overrides: Record<string, unknown> = {}) => ({
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<string, unknown> = {}) => ({ const defaultOperationsLoopStatus = (overrides: Record<string, unknown> = {}) => ({
nextAction: 'run_patrol', nextAction: 'run_patrol',
progressLabel: 'Run Patrol to produce actionable issue evidence.', progressLabel: 'Run Patrol to produce actionable issue evidence.',
@@ -547,17 +572,9 @@ describe('AIIntelligence entitlement gating', () => {
getCorrelationsMock.mockReset(); getCorrelationsMock.mockReset();
getPatrolStatusMock.mockResolvedValue(defaultPatrolStatus()); getPatrolStatusMock.mockResolvedValue(defaultPatrolStatus());
getPatrolAutonomySettingsMock.mockResolvedValue({ getPatrolAutonomySettingsMock.mockResolvedValue(defaultPatrolAutonomySettings());
autonomy_level: 'monitor',
full_mode_unlocked: false,
investigation_budget: 15,
investigation_timeout_sec: 300,
});
updatePatrolAutonomySettingsMock.mockResolvedValue({ updatePatrolAutonomySettingsMock.mockResolvedValue({
settings: { settings: defaultPatrolAutonomySettings(),
autonomy_level: 'monitor',
full_mode_unlocked: false,
},
}); });
triggerPatrolRunMock.mockResolvedValue(undefined); triggerPatrolRunMock.mockResolvedValue(undefined);
getPatrolRunHistoryMock.mockResolvedValue([]); getPatrolRunHistoryMock.mockResolvedValue([]);
@@ -1205,13 +1222,11 @@ describe('AIIntelligence entitlement gating', () => {
fireEvent.click(screen.getByRole('button', { name: 'Autopilot' })); fireEvent.click(screen.getByRole('button', { name: 'Autopilot' }));
await waitFor(() => { // Autopilot no longer switches directly; it opens the acknowledgement
expect( // dialog and the mode stays where it was until the user records one.
screen.getByText( expect(await screen.findByRole('dialog', { name: 'Activate Autopilot' })).toBeInTheDocument();
'Patrol can act automatically within policy and still asks when approval is required.', expect(updatePatrolAutonomySettingsMock).not.toHaveBeenCalled();
), fireEvent.click(screen.getByRole('button', { name: 'Close Autopilot acknowledgement' }));
).toBeInTheDocument();
});
expect(screen.getByRole('link', { name: 'Open Patrol settings' })).toHaveAttribute( expect(screen.getByRole('link', { name: 'Open Patrol settings' })).toHaveAttribute(
'href', 'href',
@@ -1248,10 +1263,11 @@ describe('AIIntelligence entitlement gating', () => {
return {}; return {};
}); });
updatePatrolAutonomySettingsMock.mockResolvedValue({ updatePatrolAutonomySettingsMock.mockResolvedValue({
settings: { settings: defaultPatrolAutonomySettings({
autonomy_level: 'approval', autonomy_level: 'approval',
full_mode_unlocked: false, requested_autonomy_level: 'approval',
}, effective_autonomy_level: 'approval',
}),
}); });
render(() => <AIIntelligence />); render(() => <AIIntelligence />);
@@ -1268,7 +1284,8 @@ describe('AIIntelligence entitlement gating', () => {
expect(updatePatrolAutonomySettingsMock).toHaveBeenCalledWith( expect(updatePatrolAutonomySettingsMock).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
autonomy_level: 'approval', autonomy_level: 'approval',
full_mode_unlocked: false, investigation_budget: 15,
investigation_timeout_sec: 300,
}), }),
); );
}); });
@@ -324,13 +324,20 @@ describe('readInfrastructureSummaryCache — hit / miss / expiry / empty boundar
it('returns a cache hit when age equals maxAgeMs exactly (strict > boundary)', () => { it('returns a cache hit when age equals maxAgeMs exactly (strict > boundary)', () => {
// `Date.now() - parsed.cachedAt > maxAgeMs` uses strict >, so an entry that // `Date.now() - parsed.cachedAt > maxAgeMs` uses strict >, so an entry that
// is exactly maxAgeMs old must still be a hit (boundary is inclusive). // is exactly maxAgeMs old must still be a hit (boundary is inclusive).
const maxAge = 60_000; // Frozen clock: with real timers a millisecond tick between the write and
const cachedAt = now() - maxAge; // the read pushes the age past maxAgeMs and flips the verdict.
storePayload( vi.useFakeTimers();
'1h', try {
buildPayload({ cachedAt, charts: undefined }), const maxAge = 60_000;
); const cachedAt = now() - maxAge;
expect(readInfrastructureSummaryCache('1h', maxAge)).not.toBeNull(); 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', () => { it('returns an empty map (cache hit, but empty) when charts is absent', () => {