- Implement DockerHandler to handle API requests for listing Docker containers. - Create DockerService to interact with Docker API and retrieve container information. - Add routes for Docker container management in the API. - Introduce frontend API integration for Docker container listing. - Enhance ProxyHostForm to allow quick selection of Docker containers. - Update Docker-related tests to ensure functionality and error handling. - Modify Docker Compose files to enable Docker socket access for local and remote environments. - Add TypeScript configurations for improved build processes.
255 lines
7.5 KiB
TypeScript
255 lines
7.5 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import ProxyHostForm from '../ProxyHostForm'
|
|
import { mockRemoteServers } from '../../test/mockData'
|
|
|
|
// Mock the hooks
|
|
vi.mock('../../hooks/useRemoteServers', () => ({
|
|
useRemoteServers: vi.fn(() => ({
|
|
servers: mockRemoteServers,
|
|
isLoading: false,
|
|
error: null,
|
|
createRemoteServer: vi.fn(),
|
|
updateRemoteServer: vi.fn(),
|
|
deleteRemoteServer: vi.fn(),
|
|
})),
|
|
}))
|
|
|
|
vi.mock('../../hooks/useDocker', () => ({
|
|
useDocker: vi.fn(() => ({
|
|
containers: [
|
|
{
|
|
id: 'container-123',
|
|
names: ['my-app'],
|
|
image: 'nginx:latest',
|
|
state: 'running',
|
|
status: 'Up 2 hours',
|
|
network: 'bridge',
|
|
ip: '172.17.0.2',
|
|
ports: [{ private_port: 80, public_port: 8080, type: 'tcp' }]
|
|
}
|
|
],
|
|
isLoading: false,
|
|
error: null,
|
|
refetch: vi.fn(),
|
|
})),
|
|
}))
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
const renderWithClient = (ui: React.ReactElement) => {
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
{ui}
|
|
</QueryClientProvider>
|
|
)
|
|
}
|
|
|
|
describe('ProxyHostForm', () => {
|
|
const mockOnSubmit = vi.fn(() => Promise.resolve())
|
|
const mockOnCancel = vi.fn()
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('renders create form with empty fields', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Add Proxy Host')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getByPlaceholderText('example.com, www.example.com')).toHaveValue('')
|
|
})
|
|
|
|
it('renders edit form with pre-filled data', async () => {
|
|
const mockHost = {
|
|
uuid: '123',
|
|
domain_names: 'test.com',
|
|
forward_scheme: 'https',
|
|
forward_host: '192.168.1.100',
|
|
forward_port: 8443,
|
|
ssl_forced: true,
|
|
http2_support: true,
|
|
hsts_enabled: true,
|
|
hsts_subdomains: true,
|
|
block_exploits: true,
|
|
websocket_support: false,
|
|
enabled: true,
|
|
locations: [],
|
|
created_at: '2025-11-18T10:00:00Z',
|
|
updated_at: '2025-11-18T10:00:00Z',
|
|
}
|
|
|
|
renderWithClient(
|
|
<ProxyHostForm host={mockHost} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Edit Proxy Host')).toBeInTheDocument()
|
|
})
|
|
expect(screen.getByDisplayValue('test.com')).toBeInTheDocument()
|
|
expect(screen.getByDisplayValue('192.168.1.100')).toBeInTheDocument()
|
|
})
|
|
|
|
it('loads remote servers for quick select', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Local Docker Registry/)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('calls onCancel when cancel button is clicked', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Cancel')).toBeInTheDocument()
|
|
})
|
|
fireEvent.click(screen.getByText('Cancel'))
|
|
expect(mockOnCancel).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('submits form with correct data', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
const domainInput = screen.getByPlaceholderText('example.com, www.example.com')
|
|
const hostInput = screen.getByPlaceholderText('192.168.1.100')
|
|
const portInput = screen.getByDisplayValue('80')
|
|
|
|
fireEvent.change(domainInput, { target: { value: 'newsite.com' } })
|
|
fireEvent.change(hostInput, { target: { value: '10.0.0.1' } })
|
|
fireEvent.change(portInput, { target: { value: '9000' } })
|
|
|
|
fireEvent.click(screen.getByText('Create'))
|
|
|
|
await waitFor(() => {
|
|
expect(mockOnSubmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
domain_names: 'newsite.com',
|
|
forward_host: '10.0.0.1',
|
|
forward_port: 9000,
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
it('handles SSL and WebSocket checkboxes', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('Force SSL')).toBeInTheDocument()
|
|
})
|
|
|
|
const sslCheckbox = screen.getByLabelText('Force SSL')
|
|
const wsCheckbox = screen.getByLabelText('WebSocket Support')
|
|
|
|
expect(sslCheckbox).not.toBeChecked()
|
|
expect(wsCheckbox).not.toBeChecked()
|
|
|
|
fireEvent.click(sslCheckbox)
|
|
fireEvent.click(wsCheckbox)
|
|
|
|
expect(sslCheckbox).toBeChecked()
|
|
expect(wsCheckbox).toBeChecked()
|
|
})
|
|
|
|
it('populates fields when remote server is selected', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Local Docker Registry/)).toBeInTheDocument()
|
|
})
|
|
|
|
const select = screen.getByLabelText('Quick Select: Remote Server')
|
|
fireEvent.change(select, { target: { value: mockRemoteServers[0].uuid } })
|
|
|
|
expect(screen.getByDisplayValue(mockRemoteServers[0].host)).toBeInTheDocument()
|
|
expect(screen.getByDisplayValue(mockRemoteServers[0].port)).toBeInTheDocument()
|
|
})
|
|
|
|
it('populates fields when a docker container is selected', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('Quick Select: Container')).toBeInTheDocument()
|
|
})
|
|
|
|
const select = screen.getByLabelText('Quick Select: Container')
|
|
fireEvent.change(select, { target: { value: 'container-123' } })
|
|
|
|
expect(screen.getByDisplayValue('172.17.0.2')).toBeInTheDocument() // IP
|
|
expect(screen.getByDisplayValue('80')).toBeInTheDocument() // Port
|
|
})
|
|
|
|
it('displays error message on submission failure', async () => {
|
|
const mockErrorSubmit = vi.fn(() => Promise.reject(new Error('Submission failed')))
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockErrorSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
fireEvent.change(screen.getByPlaceholderText('example.com, www.example.com'), {
|
|
target: { value: 'test.com' },
|
|
})
|
|
fireEvent.change(screen.getByPlaceholderText('192.168.1.100'), {
|
|
target: { value: 'localhost' },
|
|
})
|
|
fireEvent.change(screen.getByDisplayValue('80'), {
|
|
target: { value: '8080' },
|
|
})
|
|
|
|
fireEvent.click(screen.getByText('Create'))
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Submission failed')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('handles advanced config input', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
const advancedInput = screen.getByLabelText(/Advanced Caddy Config/i)
|
|
fireEvent.change(advancedInput, { target: { value: 'header_up X-Test "True"' } })
|
|
|
|
expect(advancedInput).toHaveValue('header_up X-Test "True"')
|
|
})
|
|
|
|
it('allows entering a remote docker host', async () => {
|
|
renderWithClient(
|
|
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
|
)
|
|
|
|
const toggle = screen.getByText('Remote Docker?')
|
|
fireEvent.click(toggle)
|
|
|
|
const input = screen.getByPlaceholderText('tcp://100.x.y.z:2375')
|
|
expect(input).toBeInTheDocument()
|
|
|
|
fireEvent.change(input, { target: { value: 'tcp://remote:2375' } })
|
|
expect(input).toHaveValue('tcp://remote:2375')
|
|
})
|
|
})
|