import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { useProviders, useCreateProvider, useUpdateProvider, useDeleteProvider } from '@/hooks/useProviders'; import type { Provider, CreateProviderInput, UpdateProviderInput } from '@/types'; import { MessagePlugin } from 'tdesign-react'; // Mock MessagePlugin vi.mock('tdesign-react', () => ({ MessagePlugin: { success: vi.fn(), error: vi.fn(), }, })); // Test data const mockProviders: Provider[] = [ { id: 'provider-1', name: 'OpenAI', apiKey: 'sk-xxx', baseUrl: 'https://api.openai.com', enabled: true, createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', }, { id: 'provider-2', name: 'Anthropic', apiKey: 'sk-yyy', baseUrl: 'https://api.anthropic.com', enabled: false, createdAt: '2026-02-01T00:00:00Z', updatedAt: '2026-02-01T00:00:00Z', }, ]; const mockCreatedProvider: Provider = { id: 'provider-3', name: 'NewProvider', apiKey: 'sk-zzz', baseUrl: 'https://api.newprovider.com', enabled: true, createdAt: '2026-03-01T00:00:00Z', updatedAt: '2026-03-01T00:00:00Z', }; // MSW handlers const handlers = [ http.get('/api/providers', () => { return HttpResponse.json(mockProviders); }), http.post('/api/providers', async ({ request }) => { const body = await request.json() as Record; return HttpResponse.json({ ...mockCreatedProvider, ...body, }); }), http.put('/api/providers/:id', async ({ request, params }) => { const body = await request.json() as Record; const existing = mockProviders.find((p) => p.id === params['id']); return HttpResponse.json({ ...existing, ...body }); }), http.delete('/api/providers/:id', () => { return new HttpResponse(null, { status: 204 }); }), ]; const server = setupServer(...handlers); function createTestQueryClient() { return new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); } function createWrapper() { const testQueryClient = createTestQueryClient(); return function Wrapper({ children }: { children: React.ReactNode }) { return ( {children} ); }; } beforeAll(() => server.listen()); afterEach(() => { server.resetHandlers(); vi.clearAllMocks(); }); afterAll(() => server.close()); describe('useProviders', () => { it('fetches and returns provider list', async () => { const { result } = renderHook(() => useProviders(), { wrapper: createWrapper(), }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toEqual(mockProviders); expect(result.current.data).toHaveLength(2); expect(result.current.data![0]!.name).toBe('OpenAI'); expect(result.current.data![1]!.name).toBe('Anthropic'); }); }); describe('useCreateProvider', () => { it('calls API and invalidates provider queries', async () => { const queryClient = createTestQueryClient(); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); function Wrapper({ children }: { children: React.ReactNode }) { return ( {children} ); } const { result } = renderHook(() => useCreateProvider(), { wrapper: Wrapper, }); const input: CreateProviderInput = { id: 'provider-3', name: 'NewProvider', apiKey: 'sk-zzz', baseUrl: 'https://api.newprovider.com', enabled: true, }; result.current.mutate(input); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toMatchObject({ id: 'provider-3', name: 'NewProvider', }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] }); expect(MessagePlugin.success).toHaveBeenCalledWith('供应商创建成功'); }); it('calls message.error on failure', async () => { server.use( http.post('/api/providers', () => { return HttpResponse.json({ message: '创建失败' }, { status: 500 }); }), ); const { result } = renderHook(() => useCreateProvider(), { wrapper: createWrapper(), }); const input: CreateProviderInput = { id: 'provider-3', name: 'NewProvider', apiKey: 'sk-zzz', baseUrl: 'https://api.newprovider.com', enabled: true, }; result.current.mutate(input); await waitFor(() => expect(result.current.isError).toBe(true)); expect(MessagePlugin.error).toHaveBeenCalled(); }); }); describe('useUpdateProvider', () => { it('calls API and invalidates provider queries', async () => { const queryClient = createTestQueryClient(); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); function Wrapper({ children }: { children: React.ReactNode }) { return ( {children} ); } const { result } = renderHook(() => useUpdateProvider(), { wrapper: Wrapper, }); const input: UpdateProviderInput = { name: 'UpdatedProvider' }; result.current.mutate({ id: 'provider-1', input }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toMatchObject({ name: 'UpdatedProvider', }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] }); expect(MessagePlugin.success).toHaveBeenCalledWith('供应商更新成功'); }); it('calls message.error on failure', async () => { server.use( http.put('/api/providers/:id', () => { return HttpResponse.json({ message: '更新失败' }, { status: 500 }); }), ); const { result } = renderHook(() => useUpdateProvider(), { wrapper: createWrapper(), }); result.current.mutate({ id: 'provider-1', input: { name: 'Updated' } }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(MessagePlugin.error).toHaveBeenCalled(); }); }); describe('useDeleteProvider', () => { it('calls API and invalidates provider queries', async () => { const queryClient = createTestQueryClient(); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); function Wrapper({ children }: { children: React.ReactNode }) { return ( {children} ); } const { result } = renderHook(() => useDeleteProvider(), { wrapper: Wrapper, }); result.current.mutate('provider-1'); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] }); expect(MessagePlugin.success).toHaveBeenCalledWith('供应商删除成功'); }); it('calls message.error on failure', async () => { server.use( http.delete('/api/providers/:id', () => { return HttpResponse.json({ message: '删除失败' }, { status: 500 }); }), ); const { result } = renderHook(() => useDeleteProvider(), { wrapper: createWrapper(), }); result.current.mutate('provider-1'); await waitFor(() => expect(result.current.isError).toBe(true)); expect(MessagePlugin.error).toHaveBeenCalled(); }); });