Files
portainer/app/react/docker/containers/update/groupContainersForUpdate.test.ts
claude code agent ccd5897915 fix(automation): gate stack redeploy on PortainerStackUpdate + bulk/name polish (#10 review)
F1: single-container "Update now" and bulk "Update" now require
PortainerStackUpdate when the resolved path is a stack, disabling the
action with a tooltip / skipping it rather than letting the click 403.

F2: resolveContainerUpdatePath only matches a Docker Compose stack; a
same-named swarm/kubernetes stack is treated as external.

F3: SecondaryActions no longer renders an empty ButtonGroup when all of
recreate/duplicate/update-now are hidden.

F4: bulk update reports an explicit no-op toast and counts containers vs
stacks honestly in the success summary.

F5: bulk toasts use trimmed container names (no leading slash).

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

67 lines
1.8 KiB
TypeScript

import { Stack, StackType } from '@/react/common/stacks/types';
import { COMPOSE_STACK_NAME_LABEL } from '@/react/constants';
import { groupContainersForUpdate } from './groupContainersForUpdate';
import { ContainerUpdateContext } from './types';
function buildContext(
overrides: Partial<ContainerUpdateContext>
): ContainerUpdateContext {
return {
id: 'c1',
name: 'container',
image: 'nginx:latest',
environmentId: 3,
...overrides,
};
}
const stack = {
Id: 7,
Name: 'my-stack',
EndpointId: 3,
Type: StackType.DockerCompose,
} as Stack;
describe('groupContainersForUpdate', () => {
it('redeploys a stack only once when several of its containers are selected', () => {
const contexts = [
buildContext({
id: 'a',
labels: { [COMPOSE_STACK_NAME_LABEL]: 'my-stack' },
}),
buildContext({
id: 'b',
labels: { [COMPOSE_STACK_NAME_LABEL]: 'my-stack' },
}),
];
const result = groupContainersForUpdate(contexts, [stack]);
expect(result.stacks).toHaveLength(1);
expect(result.stacks[0].stackId).toBe(7);
expect(result.standalone).toHaveLength(0);
expect(result.external).toHaveLength(0);
});
it('partitions standalone, stack and external containers', () => {
const contexts = [
buildContext({ id: 'standalone', labels: {} }),
buildContext({
id: 'stack',
labels: { [COMPOSE_STACK_NAME_LABEL]: 'my-stack' },
}),
buildContext({
id: 'external',
labels: { [COMPOSE_STACK_NAME_LABEL]: 'not-in-portainer' },
}),
];
const result = groupContainersForUpdate(contexts, [stack]);
expect(result.standalone.map((c) => c.id)).toEqual(['standalone']);
expect(result.stacks.map((s) => s.stackId)).toEqual([7]);
expect(result.external.map((c) => c.id)).toEqual(['external']);
});
});