import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock the API client const mockApiGet = vi.fn(); const mockApiPost = vi.fn(); const mockApiPatch = vi.fn(); const mockApiDelete = vi.fn(); vi.mock('@/api/client', () => ({ apiGet: (...args: any[]) => mockApiGet(...args), apiPost: (...args: any[]) => mockApiPost(...args), apiPatch: (...args: any[]) => mockApiPatch(...args), apiDelete: (...args: any[]) => mockApiDelete(...args), })); // Mock react-query vi.mock('@tanstack/react-query', () => ({ useQueryClient: () => ({ invalidateQueries: vi.fn(), }), useQuery: vi.fn(), useMutation: vi.fn(({ mutationFn, onSuccess }) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })), })); describe('Workflow API Hooks', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('useWorkflows', () => { it('calls apiGet with correct endpoint', async () => { const { useWorkflows } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryKey, queryFn }: any) => { expect(queryKey[0]).toBe('workflows'); queryFn(); return { data: undefined, isLoading: false }; }); useWorkflows(); expect(mockApiGet).toHaveBeenCalledWith( expect.stringContaining('/workflows?') ); }); it('passes is_active filter when provided', async () => { const { useWorkflows } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryFn }: any) => { queryFn(); return { data: undefined, isLoading: false }; }); useWorkflows(1, 20, true); expect(mockApiGet).toHaveBeenCalledWith( expect.stringContaining('is_active=true') ); }); }); describe('useWorkflow', () => { it('calls apiGet with correct endpoint', async () => { const { useWorkflow } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryKey, queryFn, enabled }: any) => { expect(queryKey).toEqual(['workflows', 'wf-123']); expect(enabled).toBe(true); queryFn(); return { data: undefined, isLoading: false }; }); useWorkflow('wf-123'); expect(mockApiGet).toHaveBeenCalledWith('/workflows/wf-123'); }); it('is disabled when id is undefined', async () => { const { useWorkflow } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ enabled }: any) => { expect(enabled).toBe(false); return { data: undefined, isLoading: false }; }); useWorkflow(undefined); expect(mockApiGet).not.toHaveBeenCalled(); }); }); describe('useCreateWorkflow', () => { it('calls apiPost with correct endpoint and data', async () => { const { useCreateWorkflow } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useCreateWorkflow(); const payload = { name: 'Test WF', steps: [{ name: 'S1', type: 'action' as const, config: {} }], }; mockApiPost.mockResolvedValue({ id: 'wf-1', ...payload }); await mutateAsync(payload); expect(mockApiPost).toHaveBeenCalledWith('/workflows', payload); }); }); describe('useUpdateWorkflow', () => { it('calls apiPatch with correct endpoint and data', async () => { const { useUpdateWorkflow } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useUpdateWorkflow(); const payload = { id: 'wf-1', data: { name: 'Updated' } }; mockApiPatch.mockResolvedValue({ id: 'wf-1', name: 'Updated' }); await mutateAsync(payload); expect(mockApiPatch).toHaveBeenCalledWith('/workflows/wf-1', { name: 'Updated', }); }); }); describe('useDeleteWorkflow', () => { it('calls apiDelete with correct endpoint', async () => { const { useDeleteWorkflow } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useDeleteWorkflow(); mockApiDelete.mockResolvedValue(undefined); await mutateAsync('wf-1'); expect(mockApiDelete).toHaveBeenCalledWith('/workflows/wf-1'); }); }); describe('useWorkflowInstances', () => { it('calls apiGet with instances endpoint', async () => { const { useWorkflowInstances } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryFn }: any) => { queryFn(); return { data: undefined, isLoading: false }; }); useWorkflowInstances(); expect(mockApiGet).toHaveBeenCalledWith( expect.stringContaining('/workflows/instances?') ); }); it('passes status filter when provided', async () => { const { useWorkflowInstances } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryFn }: any) => { queryFn(); return { data: undefined, isLoading: false }; }); useWorkflowInstances(1, 20, 'completed'); expect(mockApiGet).toHaveBeenCalledWith( expect.stringContaining('status=completed') ); }); }); describe('useWorkflowInstance', () => { it('calls apiGet with instance detail endpoint', async () => { const { useWorkflowInstance } = await import('../workflows'); const { useQuery } = await import('@tanstack/react-query'); (useQuery as any).mockImplementation(({ queryFn, enabled }: any) => { expect(enabled).toBe(true); queryFn(); return { data: undefined, isLoading: false }; }); useWorkflowInstance('inst-1'); expect(mockApiGet).toHaveBeenCalledWith( '/workflows/instances/inst-1' ); }); }); describe('useCreateWorkflowInstance', () => { it('calls apiPost with correct endpoint and data', async () => { const { useCreateWorkflowInstance } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useCreateWorkflowInstance(); const payload = { workflowId: 'wf-1', data: { context: { foo: 'bar' } }, }; mockApiPost.mockResolvedValue({ id: 'inst-1' }); await mutateAsync(payload); expect(mockApiPost).toHaveBeenCalledWith( '/workflows/wf-1/instances', { context: { foo: 'bar' } } ); }); }); describe('useAdvanceWorkflowInstance', () => { it('calls apiPost with advance endpoint and decision', async () => { const { useAdvanceWorkflowInstance } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useAdvanceWorkflowInstance(); const payload = { instanceId: 'inst-1', data: { decision: 'approve' as const, comment: 'LGTM' }, }; mockApiPost.mockResolvedValue({ id: 'inst-1', status: 'in_progress' }); await mutateAsync(payload); expect(mockApiPost).toHaveBeenCalledWith( '/workflows/instances/inst-1/advance', { decision: 'approve', comment: 'LGTM' } ); }); }); describe('useCancelWorkflowInstance', () => { it('calls apiPost with cancel endpoint', async () => { const { useCancelWorkflowInstance } = await import('../workflows'); const { useMutation } = await import('@tanstack/react-query'); (useMutation as any).mockImplementation(({ mutationFn, onSuccess }: any) => ({ mutateAsync: async (args: any) => { const result = await mutationFn(args); if (onSuccess) onSuccess(result, args); return result; }, isPending: false, })); const { mutateAsync } = useCancelWorkflowInstance(); mockApiPost.mockResolvedValue({ id: 'inst-1', status: 'cancelled' }); await mutateAsync('inst-1'); expect(mockApiPost).toHaveBeenCalledWith( '/workflows/instances/inst-1/cancel' ); }); }); });