diff --git a/frontend/src/hooks/__tests__/useImport.test.ts b/frontend/src/hooks/__tests__/useImport.test.ts new file mode 100644 index 00000000..07e3137d --- /dev/null +++ b/frontend/src/hooks/__tests__/useImport.test.ts @@ -0,0 +1,93 @@ +import { renderHook, act, waitFor } from '@testing-library/react' +import { vi, describe, it, expect, beforeEach } from 'vitest' +import React from 'react' +import { createTestQueryClient } from '../../test/createTestQueryClient' +import { QueryClientProvider } from '@tanstack/react-query' + +import * as api from '../../api/import' +import { useImport } from '../useImport' + +vi.mock('../../api/import', () => ({ + uploadCaddyfile: vi.fn(), + getImportPreview: vi.fn(), + commitImport: vi.fn(), + getImportStatus: vi.fn(), + cancelImport: vi.fn(), +})) + +const wrapper = ({ children }: { children: React.ReactNode }) => { + const qc = createTestQueryClient() + return React.createElement(QueryClientProvider, { client: qc }, children) +} + +describe('useImport (unit)', () => { + beforeEach(() => vi.clearAllMocks()) + + it('commit throws when there is no active session (guards)', async () => { + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: false }) + + const { result } = renderHook(() => useImport(), { wrapper }) + + await waitFor(() => expect(result.current.loading).toBe(false)) + + await expect(result.current.commit({}, {})).rejects.toThrow(/No active session/) + }) + + it('does not surface preview error when there is no pending session', async () => { + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: false }) + vi.mocked(api.getImportPreview).mockRejectedValue(new Error('preview fail')) + + const { result } = renderHook(() => useImport(), { wrapper }) + + await waitFor(() => expect(result.current.loading).toBe(false)) + // preview endpoint failed but there is no pending session => error should be null + expect(result.current.error).toBeNull() + }) + + it('surfaces preview error when session is pending and commit not succeeded', async () => { + vi.mocked(api.getImportStatus).mockResolvedValue({ + has_pending: true, + session: { id: 's1', state: 'reviewing', created_at: '2026-01-31T00:00:00.000Z', updated_at: '2026-01-31T00:00:00.000Z' }, + }) + vi.mocked(api.getImportPreview).mockRejectedValue(new Error('preview fail')) + + const { result } = renderHook(() => useImport(), { wrapper }) + + await waitFor(() => expect(result.current.loading).toBe(false)) + expect(result.current.error).toContain('preview fail') + }) + + it('enables preview query when session state is pending', async () => { + const session = { + id: 's-pending', + state: 'pending' as const, + created_at: '2026-01-31T00:00:00.000Z', + updated_at: '2026-01-31T00:00:00.000Z', + } + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session }) + vi.mocked(api.getImportPreview).mockResolvedValue({ session, preview: { hosts: [], conflicts: [], errors: [] } } as any) + + const { result } = renderHook(() => useImport(), { wrapper }) + + await waitFor(() => expect(result.current.loading).toBe(false)) + expect(api.getImportPreview).toHaveBeenCalled() + }) + + it('upload stores immediate uploadPreview and exposes preview', async () => { + const mockPreview = { + session: { id: 's1', state: 'reviewing', created_at: '2026-01-31T00:00:00.000Z', updated_at: '2026-01-31T00:00:00.000Z' }, + preview: { hosts: [], conflicts: [], errors: [] }, + } + vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: false }) + vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockPreview as any) + + const { result } = renderHook(() => useImport(), { wrapper }) + + await act(async () => { + await result.current.upload('some content') + }) + + expect(api.uploadCaddyfile).toHaveBeenCalled() + expect(result.current.preview).toEqual(mockPreview) + }) +})