portainer/app/react/docker/containers/ItemView/CreateImageSection/CreateImageSection.test.tsx

285 lines
7.7 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
import { http, HttpResponse } from 'msw';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { server } from '@/setup-tests/server';
import { createMockUser } from '@/react-tools/test-mocks';
import { CreateImageSection } from './CreateImageSection';
vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
...(await importOriginal()),
useCurrentStateAndParams: vi.fn(() => ({
params: { endpointId: 1 },
})),
}));
vi.mock('@/portainer/services/notifications', () => ({
notifySuccess: vi.fn(),
}));
describe('CreateImageSection', () => {
beforeEach(() => {
// Setup MSW handlers for registries API
server.use(
http.get('/api/endpoints/:endpointId/registries', () =>
HttpResponse.json([
{
Id: 1,
Name: 'DockerHub',
Type: 1,
URL: 'docker.io',
Username: 'testuser',
},
{
Id: 2,
Name: 'Private Registry',
Type: 3,
URL: 'registry.example.com',
Username: '',
},
])
),
// Mock images endpoint for autocomplete functionality
http.get('/api/endpoints/:endpointId/docker/images/json', () =>
HttpResponse.json([
{
Id: 'sha256:abc123',
RepoTags: ['docker.io/testuser/my-app:latest', 'nginx:latest'],
},
{
Id: 'sha256:def456',
RepoTags: ['registry.example.com/another-app:v1.0'],
},
])
)
);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('Authorization', () => {
it('should render widget when user has DockerImageCreate authorization', async () => {
renderComponent({
userAuthorizations: {
DockerImageCreate: true,
},
});
await waitFor(() => {
expect(
screen.getByRole('heading', { name: /create image/i })
).toBeVisible();
});
});
it('should not render when user lacks authorization', () => {
renderComponent({
userAuthorizations: {
DockerImageCreate: false,
},
});
expect(
screen.queryByRole('heading', { name: /create image/i })
).not.toBeInTheDocument();
});
});
describe('Basic Rendering', () => {
it('should render widget with title', async () => {
renderComponent();
await waitFor(() => {
expect(
screen.getByRole('heading', { name: /create image/i })
).toBeVisible();
});
});
it('should render description text', async () => {
renderComponent();
await waitFor(() => {
expect(
screen.getByText(/you can create an image from this container/i)
).toBeVisible();
});
});
});
describe('Image Creation', () => {
it('should successfully create image and call onSuccess', async () => {
const onSuccess = vi.fn();
let capturedRequest: {
container?: string;
repo?: string;
tag?: string;
} = {};
// Setup MSW handler for commit
server.use(
http.post(
'/api/endpoints/:endpointId/docker/commit',
async ({ request }) => {
const url = new URL(request.url);
capturedRequest = {
container: url.searchParams.get('container') || undefined,
repo: url.searchParams.get('repo') || undefined,
tag: url.searchParams.get('tag') || undefined,
};
return HttpResponse.json({ Id: 'sha256:abc123' });
}
)
);
renderComponent({ onSuccess });
// Wait for form to load
await waitFor(() => {
expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible();
});
// Fill in image name
const imageInput = screen.getByRole('combobox', { name: 'Image' });
await userEvent.type(imageInput, 'my-app:v1.0');
// Submit form
const createButton = screen.getByRole('button', { name: /^create$/i });
await waitFor(() => {
expect(createButton).not.toBeDisabled();
});
await userEvent.click(createButton);
// Verify API call
await waitFor(() => {
expect(capturedRequest.container).toBe('container123');
expect(capturedRequest.repo).toBe('my-app');
expect(capturedRequest.tag).toBe('v1.0');
});
// Verify onSuccess callback
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled();
});
});
it('should handle API error', async () => {
const onMutationError = vi.fn();
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
server.use(
http.post('/api/endpoints/:endpointId/docker/commit', () =>
HttpResponse.json({ message: 'Container not found' }, { status: 404 })
)
);
renderComponent({ onMutationError });
await waitFor(() => {
expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible();
});
const imageInput = screen.getByRole('combobox', { name: 'Image' });
await userEvent.type(imageInput, 'my-app');
const createButton = screen.getByRole('button', { name: /^create$/i });
await waitFor(() => {
expect(createButton).not.toBeDisabled();
});
await userEvent.click(createButton);
// Wait for the mutation error to be handled
await waitFor(
() => {
expect(onMutationError).toHaveBeenCalled();
},
{ timeout: 3000 }
);
consoleErrorSpy.mockRestore();
});
it('should show loading state during creation', async () => {
server.use(
http.post('/api/endpoints/:endpointId/docker/commit', async () => {
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
return HttpResponse.json({ Id: 'sha256:abc123' });
})
);
renderComponent();
await waitFor(() => {
expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible();
});
const imageInput = screen.getByRole('combobox', { name: 'Image' });
await userEvent.type(imageInput, 'my-app');
const createButton = screen.getByRole('button', { name: /^create$/i });
await waitFor(() => {
expect(createButton).not.toBeDisabled();
});
await userEvent.click(createButton);
// Check loading state
await waitFor(() => {
expect(
screen.getByRole('button', { name: /creating image/i })
).toBeDisabled();
});
});
});
});
function renderComponent({
userAuthorizations,
onMutationError,
...componentProps
}: Partial<React.ComponentProps<typeof CreateImageSection>> & {
userAuthorizations?: Record<string, boolean>;
onMutationError?: (error: unknown) => void;
} = {}) {
const defaultProps: React.ComponentProps<typeof CreateImageSection> = {
environmentId: 1,
containerId: 'container123',
...componentProps,
};
const defaultAuthorizations = {
DockerImageCreate: true,
};
const mockUser = createMockUser({
EndpointAuthorizations: {
1: userAuthorizations || defaultAuthorizations,
},
Id: 1,
Role: 1,
});
const Wrapper = withTestQueryProvider(
withUserProvider(withTestRouter(CreateImageSection), mockUser),
onMutationError ? { onMutationError } : undefined
);
return render(<Wrapper {...defaultProps} />);
}