import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import ConfigSync from '@/pages/ConfigSync'
const mockSyncLogs = {
items: [
{
id: 1,
account_id: 'acc-001',
client_fingerprint: 'fp-abc123def456',
action: 'push',
config_keys: 'model_config,prompt_config',
client_values: '{"model":"gpt-4"}',
saas_values: '{"model":"gpt-3.5"}',
resolution: 'client_wins',
created_at: '2026-04-07T10:30:00Z',
},
{
id: 2,
account_id: 'acc-002',
client_fingerprint: 'fp-xyz789',
action: 'pull',
config_keys: 'privacy_settings',
client_values: null,
saas_values: '{"analytics":true}',
resolution: null,
created_at: '2026-04-06T08:00:00Z',
},
],
total: 2,
page: 1,
page_size: 20,
}
const server = setupServer()
beforeEach(() => {
server.listen({ onUnhandledRequest: 'bypass' })
})
afterEach(() => {
server.close()
})
function renderWithProviders(ui: React.ReactElement) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
return render(
{ui}
,
)
}
describe('ConfigSync', () => {
it('renders page title', () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json({ items: [], total: 0, page: 1, page_size: 20 })
}),
)
renderWithProviders()
expect(screen.getByText('配置同步日志')).toBeInTheDocument()
})
it('shows loading state', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', async () => {
await new Promise(resolve => setTimeout(resolve, 500))
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
expect(document.querySelector('.ant-spin')).toBeTruthy()
})
it('displays sync logs with Chinese action labels', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
// Action labels are mapped to Chinese: push → 推送, pull → 拉取
await waitFor(() => {
expect(screen.getByText('推送')).toBeInTheDocument()
})
expect(screen.getByText('拉取')).toBeInTheDocument()
})
it('displays config keys for each log', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('model_config,prompt_config')).toBeInTheDocument()
})
})
it('displays resolution column', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('client_wins')).toBeInTheDocument()
})
})
it('color-codes action tags', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
await waitFor(() => {
const pushTag = screen.getByText('推送').closest('.ant-tag')
expect(pushTag?.className).toMatch(/blue/)
})
const pullTag = screen.getByText('拉取').closest('.ant-tag')
expect(pullTag?.className).toMatch(/cyan/)
})
it('renders table column headers', async () => {
server.use(
http.get('*/api/v1/config/sync-logs', () => {
return HttpResponse.json(mockSyncLogs)
}),
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('操作')).toBeInTheDocument()
})
expect(screen.getByText('客户端指纹')).toBeInTheDocument()
expect(screen.getByText('配置键')).toBeInTheDocument()
})
})