diff --git a/web/src/routes/admin/integrations/integrations.test.ts b/web/src/routes/admin/integrations/integrations.test.ts index c7e79622..30b33732 100644 --- a/web/src/routes/admin/integrations/integrations.test.ts +++ b/web/src/routes/admin/integrations/integrations.test.ts @@ -95,6 +95,17 @@ const defaultCoverProviders = { sources_version: 1 }; +const testResponseOk = { + ok: true as const, + version: '2.0.5', + quality_profiles: [ + { id: 1, name: 'FLAC' }, + { id: 2, name: 'MP3-320' } + ], + metadata_profiles: [{ id: 1, name: 'Standard' }], + root_folders: [{ path: '/music', accessible: true, free_space: 12345 }] +}; + const smtpUnset: SMTPConfig = { enabled: false, host: '', @@ -509,3 +520,118 @@ describe('/admin/integrations — SMTP card', () => { ); }); }); + +describe('Lidarr first-time setup (Test-then-Save)', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + test('Save calls test first, fills defaults from response, then put-config', async () => { + vi.mocked(createLidarrConfigQuery).mockReturnValue(mockQuery(cfgUnset)); + vi.mocked(createQualityProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createMetadataProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createRootFoldersQuery).mockReturnValue(mockQuery([])); + vi.mocked(createCoverProvidersQuery).mockReturnValue(mockQuery(defaultCoverProviders)); + vi.mocked(createSMTPConfigQuery).mockReturnValue(mockQuery(smtpUnset)); + vi.mocked(testLidarrConnection).mockResolvedValue(testResponseOk); + vi.mocked(putLidarrConfig).mockResolvedValue(cfgConnected); + + render(IntegrationsPage); + + await fireEvent.input(screen.getByLabelText(/Base URL/i), { + target: { value: 'http://lidarr.local' } + }); + await fireEvent.input(screen.getByLabelText(/API key/i), { + target: { value: 'secret-key' } + }); + await fireEvent.click(screen.getByRole('button', { name: /Save changes/i })); + + await waitFor(() => { + expect(testLidarrConnection).toHaveBeenCalledWith({ + base_url: 'http://lidarr.local', + api_key: 'secret-key' + }); + }); + await waitFor(() => { + expect(putLidarrConfig).toHaveBeenCalledWith({ + enabled: true, + base_url: 'http://lidarr.local', + api_key: 'secret-key', + default_quality_profile_id: 1, + default_metadata_profile_id: 1, + default_root_folder_path: '/music' + }); + }); + }); + + test('Save aborts when test fails and surfaces the error', async () => { + vi.mocked(createLidarrConfigQuery).mockReturnValue(mockQuery(cfgUnset)); + vi.mocked(createQualityProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createMetadataProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createRootFoldersQuery).mockReturnValue(mockQuery([])); + vi.mocked(createCoverProvidersQuery).mockReturnValue(mockQuery(defaultCoverProviders)); + vi.mocked(createSMTPConfigQuery).mockReturnValue(mockQuery(smtpUnset)); + vi.mocked(testLidarrConnection).mockResolvedValue({ + ok: false, + error: 'lidarr_unreachable' + }); + + render(IntegrationsPage); + + await fireEvent.input(screen.getByLabelText(/Base URL/i), { + target: { value: 'http://wrong.local' } + }); + await fireEvent.input(screen.getByLabelText(/API key/i), { + target: { value: 'k' } + }); + await fireEvent.click(screen.getByRole('button', { name: /Save changes/i })); + + await waitFor(() => { + expect(testLidarrConnection).toHaveBeenCalled(); + }); + expect(putLidarrConfig).not.toHaveBeenCalled(); + await waitFor(() => { + expect(screen.getByText(/Save failed — lidarr_unreachable/i)).toBeInTheDocument(); + }); + }); + + test('Save preserves operator-picked defaults over auto-defaults', async () => { + vi.mocked(createLidarrConfigQuery).mockReturnValue(mockQuery(cfgUnset)); + vi.mocked(createQualityProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createMetadataProfilesQuery).mockReturnValue(mockQuery([])); + vi.mocked(createRootFoldersQuery).mockReturnValue(mockQuery([])); + vi.mocked(createCoverProvidersQuery).mockReturnValue(mockQuery(defaultCoverProviders)); + vi.mocked(createSMTPConfigQuery).mockReturnValue(mockQuery(smtpUnset)); + vi.mocked(testLidarrConnection).mockResolvedValue(testResponseOk); + vi.mocked(putLidarrConfig).mockResolvedValue(cfgConnected); + + render(IntegrationsPage); + + await fireEvent.input(screen.getByLabelText(/Base URL/i), { + target: { value: 'http://lidarr.local' } + }); + await fireEvent.input(screen.getByLabelText(/API key/i), { + target: { value: 'secret-key' } + }); + // Click Test first to populate the dropdowns, then change the + // quality profile selection before clicking Save. + await fireEvent.click(screen.getByRole('button', { name: /Test connection/i })); + await waitFor(() => { + expect(testLidarrConnection).toHaveBeenCalledTimes(1); + }); + const qualitySelect = screen.getByLabelText(/Default quality profile/i); + await fireEvent.change(qualitySelect, { target: { value: '2' } }); + + await fireEvent.click(screen.getByRole('button', { name: /Save changes/i })); + + await waitFor(() => { + expect(putLidarrConfig).toHaveBeenCalledWith( + expect.objectContaining({ + default_quality_profile_id: 2, + default_metadata_profile_id: 1, + default_root_folder_path: '/music' + }) + ); + }); + }); +});