Files
portainer/app/react/docker/containers/queries/useContainerImageStatus.test.tsx
T
claude code agent f69eb3f9eb feat(automation): CE container image update detection endpoint + badge (#9, epic #3 M2)
Add native CE detection of "a newer image is available" for running
containers, surfaced as a read-only HTTP endpoint and a containers-list
badge/column. No applying of updates (M3/M4), no auto-heal (M1).

Backend:
- New CE handler GET /docker/{id}/containers/{containerId}/image_status
  backed by the existing zlib/CE digest engine
  (images.NewClientWithRegistry + ContainerImageStatus). Honors nodeName,
  authz, and routes registry calls through the credential store / SSRF
  AllowList. Engine failures degrade to a 200 {Status:"error"} so the UI
  stays graceful. Response shape: {Status, Message?}.

Frontend (CE-only, no isBE gating; the EE ImageStatus component is left
untouched):
- useContainerImageStatus TanStack Query hook (5min staleTime, no
  refetch-on-focus; backend caches 24h) calling the non-proxied endpoint.
- UpdateStatusBadge component (own assets, neutral on skipped/error).
- "Update available" column in the containers datatable; one cached,
  non-blocking query per visible row.

Tests: Go response-shape unit test; vitest for the badge (all statuses)
and the hook (url + nodeName query param via msw).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:59:54 +03:00

58 lines
1.7 KiB
TypeScript

import { renderHook } from '@testing-library/react-hooks';
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import {
ContainerImageStatus,
useContainerImageStatus,
} from './useContainerImageStatus';
const environmentId = 3;
const containerId = 'abc123';
function renderImageStatusHook(nodeName?: string) {
return renderHook(
() => useContainerImageStatus(environmentId, containerId, nodeName),
{ wrapper: withTestQueryProvider(({ children }) => <>{children}</>) }
);
}
describe('useContainerImageStatus', () => {
it('calls the non-proxied image_status endpoint and returns the status', async () => {
const payload: ContainerImageStatus = { Status: 'outdated' };
server.use(
http.get(
`/api/docker/${environmentId}/containers/${containerId}/image_status`,
() => HttpResponse.json(payload)
)
);
const { result, waitFor } = renderImageStatusHook();
await waitFor(() => result.current.isSuccess);
expect(result.current.data).toEqual(payload);
});
it('forwards nodeName as a query parameter', async () => {
let receivedNodeName: string | null = null;
server.use(
http.get(
`/api/docker/${environmentId}/containers/${containerId}/image_status`,
({ request }) => {
receivedNodeName = new URL(request.url).searchParams.get('nodeName');
return HttpResponse.json<ContainerImageStatus>({ Status: 'updated' });
}
)
);
const { result, waitFor } = renderImageStatusHook('node-2');
await waitFor(() => result.current.isSuccess);
expect(receivedNodeName).toBe('node-2');
});
});