diff --git a/apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx b/apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx new file mode 100644 index 0000000..0849d05 --- /dev/null +++ b/apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx @@ -0,0 +1,136 @@ +import { cleanup, render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +// Mock @/lib/tender-radar-api +const mockFetchSourceConfig = vi.fn(); +const mockSaveSourceConfig = vi.fn(); + +vi.mock('@/lib/tender-radar-api', () => ({ + fetchSourceConfig: (...args: unknown[]) => mockFetchSourceConfig(...args), + saveSourceConfig: (...args: unknown[]) => mockSaveSourceConfig(...args), +})); + +afterEach(() => { + cleanup(); + // Reset call history AND implementations between tests — restoreAllMocks + // alone does not clear vi.fn() call counts for these module-level mocks, + // which would otherwise leak a prior test's saveSourceConfig call into a + // later "not.toHaveBeenCalled()" assertion. + mockFetchSourceConfig.mockReset(); + mockSaveSourceConfig.mockReset(); + vi.restoreAllMocks(); +}); + +describe('SourceConfigForm', () => { + it('fetches config on mount and renders the returned pollIntervalMin in the interval input', async () => { + mockFetchSourceConfig.mockResolvedValue({ + sourceType: 'doe-opendata', + pollIntervalMin: 90, + isActive: true, + lastIngestedDay: null, + }); + + const { SourceConfigForm } = await import('./SourceConfigForm'); + render(); + + await waitFor(() => { + expect( + screen.getByLabelText(/Abrufintervall/i) as HTMLInputElement, + ).toHaveValue(90); + }); + + expect(mockFetchSourceConfig).toHaveBeenCalledTimes(1); + }); + + it('editing the interval and clicking Speichern calls saveSourceConfig with the new pollIntervalMin and isActive', async () => { + mockFetchSourceConfig.mockResolvedValue({ + sourceType: 'doe-opendata', + pollIntervalMin: 60, + isActive: false, + lastIngestedDay: '2026-07-20T00:00:00.000Z', + }); + mockSaveSourceConfig.mockResolvedValue({ + sourceType: 'doe-opendata', + pollIntervalMin: 120, + isActive: true, + lastIngestedDay: '2026-07-20T00:00:00.000Z', + }); + + const { SourceConfigForm } = await import('./SourceConfigForm'); + render(); + + await waitFor(() => { + expect(screen.getByLabelText(/Abrufintervall/i)).toHaveValue(60); + }); + + fireEvent.change(screen.getByLabelText(/Abrufintervall/i), { + target: { value: '120' }, + }); + fireEvent.click(screen.getByRole('switch')); + + fireEvent.click(screen.getByRole('button', { name: 'Speichern' })); + + await waitFor(() => { + expect(mockSaveSourceConfig).toHaveBeenCalledWith({ + pollIntervalMin: 120, + isActive: true, + }); + }); + }); + + it('rejects an interval below 5 client-side without calling saveSourceConfig', async () => { + mockFetchSourceConfig.mockResolvedValue({ + sourceType: 'doe-opendata', + pollIntervalMin: 60, + isActive: true, + lastIngestedDay: null, + }); + + const { SourceConfigForm } = await import('./SourceConfigForm'); + render(); + + await waitFor(() => { + expect(screen.getByLabelText(/Abrufintervall/i)).toHaveValue(60); + }); + + fireEvent.change(screen.getByLabelText(/Abrufintervall/i), { + target: { value: '2' }, + }); + fireEvent.click(screen.getByRole('button', { name: 'Speichern' })); + + await waitFor(() => { + expect( + screen.getByText(/Intervall muss zwischen 5 und 1440 Minuten liegen/i), + ).toBeInTheDocument(); + }); + expect(mockSaveSourceConfig).not.toHaveBeenCalled(); + }); + + it('rejects an interval above 1440 client-side without calling saveSourceConfig', async () => { + mockFetchSourceConfig.mockResolvedValue({ + sourceType: 'doe-opendata', + pollIntervalMin: 60, + isActive: true, + lastIngestedDay: null, + }); + + const { SourceConfigForm } = await import('./SourceConfigForm'); + render(); + + await waitFor(() => { + expect(screen.getByLabelText(/Abrufintervall/i)).toHaveValue(60); + }); + + fireEvent.change(screen.getByLabelText(/Abrufintervall/i), { + target: { value: '1441' }, + }); + fireEvent.click(screen.getByRole('button', { name: 'Speichern' })); + + await waitFor(() => { + expect( + screen.getByText(/Intervall muss zwischen 5 und 1440 Minuten liegen/i), + ).toBeInTheDocument(); + }); + expect(mockSaveSourceConfig).not.toHaveBeenCalled(); + }); +});