164 lines
4.6 KiB
TypeScript
164 lines
4.6 KiB
TypeScript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { MemoryRouter } from 'react-router-dom'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import type { Task, TaskListResponse } from './api/client'
|
|
import App from './App'
|
|
|
|
const task: Task = {
|
|
id: 'echo_task',
|
|
name: 'Echo task',
|
|
cron: '0 0 2 * * *',
|
|
timezone: 'Asia/Shanghai',
|
|
enabled: true,
|
|
timeout_secs: 300,
|
|
concurrency_policy: 'skip',
|
|
retry_policy: {
|
|
max_attempts: 0,
|
|
delay_secs: 0,
|
|
},
|
|
action: {
|
|
type: 'script',
|
|
working_dir: 'D:\\Projects\\echo',
|
|
interpreter: 'python.exe',
|
|
script: 'main.py',
|
|
args: [],
|
|
env: {},
|
|
},
|
|
created_at: '2026-10-18T10:00:00Z',
|
|
updated_at: '2026-10-18T10:00:00Z',
|
|
}
|
|
|
|
const taskList: TaskListResponse = {
|
|
items: [task],
|
|
page: 1,
|
|
page_size: 20,
|
|
total: 1,
|
|
}
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
|
|
function createFetchMock() {
|
|
return vi.fn(
|
|
async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
const url =
|
|
typeof input === 'string'
|
|
? input
|
|
: input instanceof URL
|
|
? input.toString()
|
|
: input.url
|
|
|
|
if (url.endsWith('/api/v1/health')) {
|
|
return jsonResponse({
|
|
status: 'ok',
|
|
service: 'task-pilot-backend',
|
|
version: '0.1.0',
|
|
})
|
|
}
|
|
if (url.endsWith('/api/v1/session')) {
|
|
return jsonResponse({ token: 'test-token' })
|
|
}
|
|
if (url.endsWith('/api/v1/cron/preview')) {
|
|
return jsonResponse({
|
|
timezone: 'Asia/Shanghai',
|
|
runs: [
|
|
'2026-10-19 02:00:00',
|
|
'2026-10-20 02:00:00',
|
|
],
|
|
})
|
|
}
|
|
if (url.includes('/api/v1/tasks') && init?.method === 'POST') {
|
|
return jsonResponse(
|
|
{
|
|
...task,
|
|
...JSON.parse(String(init.body)),
|
|
},
|
|
201,
|
|
)
|
|
}
|
|
if (url.includes('/api/v1/tasks')) {
|
|
return jsonResponse(taskList)
|
|
}
|
|
return jsonResponse({ code: 'NOT_FOUND', message: 'not found', details: {} }, 404)
|
|
},
|
|
)
|
|
}
|
|
|
|
function renderApp() {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<MemoryRouter>
|
|
<App />
|
|
</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
)
|
|
}
|
|
|
|
describe('TaskDashboard', () => {
|
|
it('renders tasks returned by the Rust API', async () => {
|
|
vi.stubGlobal('fetch', createFetchMock())
|
|
renderApp()
|
|
|
|
expect(await screen.findByText('Echo task')).toBeInTheDocument()
|
|
expect(screen.getByText('echo_task')).toBeInTheDocument()
|
|
expect(screen.getByText('0 0 2 * * *')).toBeInTheDocument()
|
|
expect(await screen.findByText('在线')).toBeInTheDocument()
|
|
})
|
|
|
|
it('creates a script task with the visible form values', async () => {
|
|
const fetchMock = createFetchMock()
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const user = userEvent.setup()
|
|
renderApp()
|
|
|
|
await user.click(screen.getByRole('button', { name: '+ 新建任务' }))
|
|
await user.click(screen.getByRole('button', { name: '+ 添加参数' }))
|
|
expect(
|
|
screen.getByPlaceholderText('参数名 (如 --mode 或 -v)'),
|
|
).toBeInTheDocument()
|
|
expect(
|
|
screen.getByPlaceholderText('参数值 (可选,无值可留空)'),
|
|
).toBeInTheDocument()
|
|
await user.clear(screen.getByLabelText(/任务标识/))
|
|
await user.type(screen.getByLabelText(/任务标识/), 'new_task')
|
|
await user.type(screen.getByLabelText('名称'), 'New task')
|
|
await user.type(screen.getByLabelText('工作目录'), 'D:\\Projects\\new')
|
|
|
|
await user.click(
|
|
screen.getByRole('button', { name: '保存并热刷新' }),
|
|
)
|
|
|
|
await waitFor(() => {
|
|
const createCall = fetchMock.mock.calls.find(
|
|
([input, init]) =>
|
|
String(input).endsWith('/api/v1/tasks') && init?.method === 'POST',
|
|
)
|
|
expect(createCall).toBeDefined()
|
|
})
|
|
|
|
const createCall = fetchMock.mock.calls.find(
|
|
([input, init]) =>
|
|
String(input).endsWith('/api/v1/tasks') && init?.method === 'POST',
|
|
)
|
|
const payload = JSON.parse(String(createCall?.[1]?.body))
|
|
expect(payload.id).toBe('new_task')
|
|
expect(payload.name).toBe('New task')
|
|
expect(payload.action.type).toBe('script')
|
|
expect(payload.action.working_dir).toBe('D:\\Projects\\new')
|
|
})
|
|
})
|