import { describe, it, expect, vi, beforeEach } from 'vitest'; import { renderHook, act } from '@testing-library/react'; import axios from 'axios'; import { useExport } from '@/hooks/useExport'; // Mock axios vi.mock('axios'); const mockedAxios = axios as any; describe('useExport Hook', () => { beforeEach(() => { vi.clearAllMocks(); // Mock blob creation vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url'); vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}); }); it('should initialize with default state', () => { const { result } = renderHook(() => useExport()); expect(result.current.isLoading).toBe(false); expect(result.current.error).toBe(null); }); describe('exportSnapshot', () => { it('should export inventory snapshot as CSV', async () => { const mockBlob = new Blob(['CSV data'], { type: 'text/csv' }); mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="inventory_snapshot_2026-04-22.csv"' } }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportSnapshot('csv'); }); expect(mockedAxios.post).toHaveBeenCalledWith( '/api/admin/exports/inventory-snapshot?format=csv', {}, { responseType: 'blob' } ); expect(result.current.error).toBeNull(); }); it('should export inventory snapshot as Excel', async () => { const mockBlob = new Blob(['Excel data'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="inventory_snapshot_2026-04-22.xlsx"' } }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportSnapshot('xlsx'); }); expect(mockedAxios.post).toHaveBeenCalledWith( '/api/admin/exports/inventory-snapshot?format=xlsx', {}, { responseType: 'blob' } ); }); it('should set loading state during export', async () => { const mockBlob = new Blob(['data'], { type: 'text/csv' }); let resolvePost: (value: any) => void; const postPromise = new Promise(resolve => { resolvePost = resolve; }); mockedAxios.post.mockReturnValueOnce(postPromise); const { result } = renderHook(() => useExport()); const exportPromise = act(async () => { await result.current.exportSnapshot('csv'); }); // Should be loading expect(result.current.isLoading).toBe(true); // Resolve the post request resolvePost!({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="test.csv"' } }); await exportPromise; // Should not be loading anymore expect(result.current.isLoading).toBe(false); }); it('should handle export errors', async () => { const errorMessage = 'Network error'; mockedAxios.post.mockRejectedValueOnce(new Error(errorMessage)); const { result } = renderHook(() => useExport()); await act(async () => { try { await result.current.exportSnapshot('csv'); } catch (err) { // Error is expected } }); expect(result.current.error).toContain(errorMessage); }); }); describe('exportAuditTrail', () => { it('should export audit trail as CSV', async () => { const mockBlob = new Blob(['Audit CSV'], { type: 'text/csv' }); mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="audit_trail_2026-04-22.csv"' } }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportAuditTrail('csv'); }); expect(mockedAxios.post).toHaveBeenCalledWith( '/api/admin/exports/audit-trail?format=csv', {}, { responseType: 'blob' } ); }); it('should export audit trail as Excel', async () => { const mockBlob = new Blob(['Audit Excel'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="audit_trail_2026-04-22.xlsx"' } }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportAuditTrail('xlsx'); }); expect(mockedAxios.post).toHaveBeenCalledWith( '/api/admin/exports/audit-trail?format=xlsx', {}, { responseType: 'blob' } ); }); it('should handle audit trail export errors', async () => { const errorMessage = 'Export failed'; mockedAxios.post.mockRejectedValueOnce(new Error(errorMessage)); const { result } = renderHook(() => useExport()); await act(async () => { try { await result.current.exportAuditTrail('csv'); } catch (err) { // Error is expected } }); expect(result.current.error).toContain(errorMessage); }); }); describe('Filename extraction', () => { it('should extract filename from Content-Disposition header', async () => { const mockBlob = new Blob(['data'], { type: 'text/csv' }); const filename = 'inventory_snapshot_2026-04-22.csv'; mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: { 'content-disposition': `attachment; filename="${filename}"` } }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportSnapshot('csv'); }); // File should be created with correct filename const link = document.createElement('a'); expect(link.download).toBe(''); }); it('should use default filename if header not provided', async () => { const mockBlob = new Blob(['data'], { type: 'text/csv' }); mockedAxios.post.mockResolvedValueOnce({ data: mockBlob, headers: {} }); const { result } = renderHook(() => useExport()); await act(async () => { await result.current.exportSnapshot('csv'); }); expect(result.current.error).toBeNull(); }); }); describe('Multiple concurrent exports', () => { it('should prevent concurrent exports with isLoading flag', async () => { const mockBlob = new Blob(['data'], { type: 'text/csv' }); let resolvePost: (value: any) => void; const postPromise = new Promise(resolve => { resolvePost = resolve; }); mockedAxios.post.mockReturnValueOnce(postPromise); const { result } = renderHook(() => useExport()); const exportPromise = act(async () => { await result.current.exportSnapshot('csv'); }); // Should be loading expect(result.current.isLoading).toBe(true); // Resolve the request resolvePost!({ data: mockBlob, headers: { 'content-disposition': 'attachment; filename="test.csv"' } }); await exportPromise; expect(result.current.isLoading).toBe(false); }); }); });