/** * Component: Finalize Step Tests * Documentation: documentation/setup-wizard.md */ // @vitest-environment jsdom import React from 'react'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; describe('FinalizeStep', () => { afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); localStorage.clear(); }); it('shows OIDC-only instructions and completes setup', async () => { const onComplete = vi.fn(); const onBack = vi.fn(); const { FinalizeStep } = await import('@/app/setup/steps/FinalizeStep'); render( ); expect(screen.getByText('Setup Complete!')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Back' })); fireEvent.click(screen.getByRole('button', { name: 'Finish Setup' })); expect(onBack).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); }); it('marks jobs as error when no access token is available', async () => { const onComplete = vi.fn(); const onBack = vi.fn(); const { FinalizeStep } = await import('@/app/setup/steps/FinalizeStep'); render( ); await waitFor(() => { expect(screen.getAllByText(/Authentication required/).length).toBeGreaterThan(0); }); }); it('runs initial jobs and enables completion on success', async () => { vi.useFakeTimers(); localStorage.setItem('accessToken', 'token'); const fetchMock = vi.fn(async (input: RequestInfo) => { const url = typeof input === 'string' ? input : input.toString(); if (url === '/api/admin/jobs') { return { ok: true, json: async () => ({ jobs: [ { id: 'job-1', type: 'audible_refresh' }, { id: 'job-2', type: 'plex_library_scan' }, ], }), }; } if (url === '/api/admin/jobs/job-1/trigger') { return { ok: true, json: async () => ({ jobId: 'run-1' }) }; } if (url === '/api/admin/jobs/job-2/trigger') { return { ok: true, json: async () => ({ jobId: 'run-2' }) }; } if (url === '/api/admin/job-status/run-1' || url === '/api/admin/job-status/run-2') { return { ok: true, json: async () => ({ job: { status: 'completed' } }) }; } throw new Error(`Unexpected fetch: ${url}`); }); vi.stubGlobal('fetch', fetchMock); const onComplete = vi.fn(); const onBack = vi.fn(); const { FinalizeStep } = await import('@/app/setup/steps/FinalizeStep'); render( ); await act(async () => { await vi.runAllTimersAsync(); }); expect(screen.getAllByText('Completed successfully').length).toBeGreaterThan(0); expect(screen.getByRole('button', { name: 'Finish Setup' })).toBeEnabled(); }); it('marks missing job configuration as an error', async () => { vi.useFakeTimers(); localStorage.setItem('accessToken', 'token'); const fetchMock = vi.fn(async (input: RequestInfo) => { const url = typeof input === 'string' ? input : input.toString(); if (url === '/api/admin/jobs') { return { ok: true, json: async () => ({ jobs: [ { id: 'job-1', type: 'audible_refresh' }, ], }), }; } if (url === '/api/admin/jobs/job-1/trigger') { return { ok: true, json: async () => ({ jobId: 'run-1' }) }; } if (url === '/api/admin/job-status/run-1') { return { ok: true, json: async () => ({ job: { status: 'completed' } }) }; } throw new Error(`Unexpected fetch: ${url}`); }); vi.stubGlobal('fetch', fetchMock); const { FinalizeStep } = await import('@/app/setup/steps/FinalizeStep'); render( ); await act(async () => { await vi.runAllTimersAsync(); }); expect(screen.getAllByText(/Job configuration not found/).length).toBeGreaterThan(0); expect(screen.getByRole('button', { name: 'Finish Setup' })).toBeEnabled(); }); });