mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
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:
@@ -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</g) ?? []).toHaveLength(3);
|
||||
expect(monitoringSource.match(/return triggerResourceCommand</g) ?? []).toHaveLength(1);
|
||||
expect(monitoringSource.match(/await runResourceAction\(url\);/g) ?? []).toHaveLength(3);
|
||||
expect(monitoringSource.match(/parseOptionalSuccessAPIResponse</g) ?? []).toHaveLength(1);
|
||||
expect(discoverySource).toContain('assertAPIResponseOK(response,');
|
||||
|
||||
+42
-14
@@ -53,6 +53,42 @@ vi.mock('@/api/resourceActions', () => ({
|
||||
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 () => {
|
||||
|
||||
@@ -456,6 +456,31 @@ const defaultAgentCapabilitiesManifest = () => ({
|
||||
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> = {}) => ({
|
||||
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(() => <AIIntelligence />);
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
+14
-7
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user