diff --git a/frontend/src/__tests__/pages/generate.test.tsx b/frontend/src/__tests__/pages/generate.test.tsx index 4da4885..3fe8962 100644 --- a/frontend/src/__tests__/pages/generate.test.tsx +++ b/frontend/src/__tests__/pages/generate.test.tsx @@ -28,6 +28,15 @@ vi.mock('~/api/config', () => ({ }, })); +vi.mock('~/api/themes', () => ({ + themesApi: { + list: vi.fn(), + create: vi.fn(), + update: vi.fn(), + remove: vi.fn(), + }, +})); + // Mock the SSE connection utility vi.mock('~/utils/sse', () => ({ createSSEConnection: vi.fn(), @@ -36,6 +45,7 @@ vi.mock('~/utils/sse', () => ({ import { synthesesApi } from '~/api/syntheses'; import { settingsApi } from '~/api/settings'; import { configApi } from '~/api/config'; +import { themesApi } from '~/api/themes'; import { createSSEConnection } from '~/utils/sse'; import GenerateSynthesis from '~/pages/GenerateSynthesis'; @@ -44,6 +54,11 @@ const mockedListProviders = vi.mocked(configApi.listProviders); const mockedGenerate = vi.mocked(synthesesApi.generate); const mockedProgressUrl = vi.mocked(synthesesApi.progressUrl); const mockedCreateSSE = vi.mocked(createSSEConnection); +const mockedListThemes = vi.mocked(themesApi.list); + +const MOCK_THEMES = [ + { id: 'theme-1', name: 'IA', theme: 'Intelligence Artificielle', categories: ['Cat1'], max_items_per_category: 5, max_age_days: 7, summary_length: 2, created_at: '2026-03-01T00:00:00Z', updated_at: '2026-03-01T00:00:00Z' }, +]; afterEach(() => { vi.clearAllMocks(); @@ -53,6 +68,7 @@ describe('GenerateSynthesis Page', () => { it('should render launch button with settings info', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); renderWithProviders(() => ); @@ -69,6 +85,7 @@ describe('GenerateSynthesis Page', () => { it('should call POST /syntheses/generate on launch', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); @@ -101,15 +118,16 @@ describe('GenerateSynthesis Page', () => { it('should show progress bar from SSE events', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); const mockSSEConnection = { events: vi.fn(() => [ - { type: 'progress' as const, data: { step: 'search', message: 'Recherche...', percent: 25 } }, + { type: 'progress' as const, data: { step: 'sources', message: 'Analyse des sources...', percent: 25 } }, ]), status: vi.fn(() => 'connected' as const), - latestProgress: vi.fn(() => ({ step: 'search', message: 'Recherche...', percent: 25 })), + latestProgress: vi.fn(() => ({ step: 'sources', message: 'Analyse des sources...', percent: 25 })), completedSynthesisId: vi.fn(() => null), errorMessage: vi.fn(() => null), close: vi.fn(), @@ -128,22 +146,23 @@ describe('GenerateSynthesis Page', () => { await waitFor(() => { expect(screen.getByText('25%')).toBeInTheDocument(); }); - expect(screen.getByText('Recherche...')).toBeInTheDocument(); + expect(screen.getByText('Analyse des sources...')).toBeInTheDocument(); }); it('should render step checklist with correct states', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); const mockSSEConnection = { events: vi.fn(() => [ - { type: 'progress' as const, data: { step: 'search', message: 'Recherche...', percent: 25 } }, - { type: 'progress' as const, data: { step: 'scraping', message: 'Verification...', percent: 50 } }, + { type: 'progress' as const, data: { step: 'sources', message: 'Analyse...', percent: 25 } }, + { type: 'progress' as const, data: { step: 'websearch', message: 'Recherche web...', percent: 50 } }, ]), status: vi.fn(() => 'connected' as const), - latestProgress: vi.fn(() => ({ step: 'scraping', message: 'Verification...', percent: 50 })), + latestProgress: vi.fn(() => ({ step: 'websearch', message: 'Recherche web...', percent: 50 })), completedSynthesisId: vi.fn(() => null), errorMessage: vi.fn(() => null), close: vi.fn(), @@ -161,14 +180,11 @@ describe('GenerateSynthesis Page', () => { await waitFor(() => { expect( - screen.getByText("Recherche d'actualites"), + screen.getByText('Sources personnalisees'), ).toBeInTheDocument(); }); expect( - screen.getByText('Verification des sources'), - ).toBeInTheDocument(); - expect( - screen.getByText('Redaction des resumes'), + screen.getByText('Recherche web'), ).toBeInTheDocument(); expect(screen.getByText('Sauvegarde')).toBeInTheDocument(); }); @@ -176,6 +192,7 @@ describe('GenerateSynthesis Page', () => { it('should show retry button on error', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); @@ -209,6 +226,7 @@ describe('GenerateSynthesis Page', () => { it('should show completion message on success', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); diff --git a/frontend/src/__tests__/pages/settings.test.tsx b/frontend/src/__tests__/pages/settings.test.tsx index d8fee71..cebaccc 100644 --- a/frontend/src/__tests__/pages/settings.test.tsx +++ b/frontend/src/__tests__/pages/settings.test.tsx @@ -69,10 +69,10 @@ describe('Settings Page', () => { renderWithProviders(() => ); await waitFor(() => { - const themeInput = screen.getByLabelText( - 'Theme de la recherche', + const maxArticlesInput = screen.getByLabelText( + 'Articles max par source', ) as HTMLInputElement; - expect(themeInput.value).toBe('Intelligence Artificielle'); + expect(maxArticlesInput.value).toBe('3'); }); }); @@ -86,7 +86,7 @@ describe('Settings Page', () => { await waitFor(() => { expect( - screen.getByLabelText('Theme de la recherche'), + screen.getByLabelText('Articles max par source'), ).toBeInTheDocument(); }); @@ -113,7 +113,8 @@ describe('Settings Page', () => { expect(screen.getAllByText('OpenAI').length).toBeGreaterThanOrEqual(1); // Verify the provider select has the options - const providerSelect = screen.getByLabelText("Fournisseur d'IA") as HTMLSelectElement; + const providerSelect = document.getElementById('aiProvider') as HTMLSelectElement; + expect(providerSelect).toBeTruthy(); const options = providerSelect.querySelectorAll('option'); const optionTexts = Array.from(options).map((o) => o.textContent); expect(optionTexts).toContain('Google Gemini'); @@ -138,9 +139,7 @@ describe('Settings Page', () => { expect(geminiTexts).toContain('Gemini 2.5 Pro'); // Change provider to OpenAI - const providerSelect = screen.getByLabelText( - "Fournisseur d'IA", - ) as HTMLSelectElement; + const providerSelect = document.getElementById('aiProvider') as HTMLSelectElement; fireEvent.change(providerSelect, { target: { value: 'openai' } }); await waitFor(() => { @@ -291,36 +290,30 @@ describe('Settings Page', () => { }); }); - it('should show validation error for empty theme', async () => { + it('should show validation error from backend on save', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(mockProvidersWithOpenAI); mockedListKeys.mockResolvedValue([]); // Simulate backend returning a validation error mockedUpdateSettings.mockRejectedValue({ status: 422, - message: 'Theme is required', + message: 'Validation error', }); renderWithProviders(() => ); await waitFor(() => { expect( - screen.getByLabelText('Theme de la recherche'), + screen.getByLabelText('Articles max par source'), ).toBeInTheDocument(); }); - // Clear the theme field - const themeInput = screen.getByLabelText( - 'Theme de la recherche', - ) as HTMLInputElement; - fireEvent.input(themeInput, { target: { value: '' } }); - // Click save const saveButton = screen.getByText('Enregistrer les parametres'); fireEvent.click(saveButton); await waitFor(() => { - expect(screen.getByText('Theme is required')).toBeInTheDocument(); + expect(screen.getByText('Validation error')).toBeInTheDocument(); }); }); });