/**
* Component: Setup Initializing Page Tests
* Documentation: documentation/setup-wizard.md
*/
// @vitest-environment jsdom
import React from 'react';
import { act, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { resetMockRouter, routerMock } from '../../helpers/mock-next-navigation';
describe('InitializingPage', () => {
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
localStorage.clear();
window.location.hash = '';
resetMockRouter();
});
it('redirects to login when auth data is missing', async () => {
window.location.hash = '';
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await waitFor(() => {
expect(routerMock.push).toHaveBeenCalledWith(
'/login?error=Authentication%20data%20missing'
);
});
});
it('processes auth data and completes job monitoring', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
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', lastRunJobId: 'run-1' },
{ id: 'job-2', type: 'plex_library_scan', lastRunJobId: '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' } }) };
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
expect(localStorage.getItem('accessToken')).toBe('token-123');
expect(window.location.hash).toBe('');
const completedMessages = screen.getAllByText('Completed successfully');
expect(completedMessages.length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Go to Homepage' })).toBeEnabled();
});
it('marks jobs as error when no recent job is found', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
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' },
],
}),
};
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
expect(screen.getAllByText(/Job did not start/).length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Go to Homepage' })).toBeEnabled();
});
it('redirects when auth data fails to parse', async () => {
window.location.hash = '#authData=';
const errorMock = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await waitFor(() => {
expect(routerMock.push).toHaveBeenCalledWith(
'/login?error=Failed%20to%20process%20authentication'
);
});
expect(errorMock).toHaveBeenCalledWith(
'[Initializing] Failed to process auth data:',
expect.any(Error)
);
});
it('marks jobs as error when scheduled jobs fetch fails', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
const fetchMock = vi.fn(async (input: RequestInfo) => {
const url = typeof input === 'string' ? input : input.toString();
if (url === '/api/admin/jobs') {
return { ok: false, json: async () => ({}) };
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
expect(screen.getAllByText(/Failed to fetch job configuration/).length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Go to Homepage' })).toBeEnabled();
});
it('marks jobs as failed when job status returns failed', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
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', lastRunJobId: 'run-1' },
{ id: 'job-2', type: 'plex_library_scan', lastRunJobId: 'run-2' },
],
}),
};
}
if (url === '/api/admin/job-status/run-1' || url === '/api/admin/job-status/run-2') {
return { ok: true, json: async () => ({ job: { status: 'failed' } }) };
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
expect(screen.getAllByText(/Job failed to complete/).length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Go to Homepage' })).toBeEnabled();
});
it('marks jobs as error when scheduled job configuration is missing', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
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', lastRunJobId: 'run-1' }],
}),
};
}
if (url === '/api/admin/job-status/run-1') {
return { ok: true, json: async () => ({ job: { status: 'completed' } }) };
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
expect(screen.getAllByText(/Job configuration not found/).length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: 'Go to Homepage' })).toBeEnabled();
});
it('navigates to homepage when setup is complete', async () => {
vi.useFakeTimers();
const authData = {
accessToken: 'token-123',
refreshToken: 'refresh-123',
user: { id: 'user-1', username: 'admin' },
};
window.location.hash = `#authData=${encodeURIComponent(JSON.stringify(authData))}`;
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', lastRunJobId: 'run-1' },
{ id: 'job-2', type: 'plex_library_scan', lastRunJobId: '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' } }) };
}
return { ok: true, json: async () => ({}) };
});
vi.stubGlobal('fetch', fetchMock);
const { default: InitializingPage } = await import('@/app/setup/initializing/page');
render();
await act(async () => {
await vi.runAllTimersAsync();
});
await act(async () => {
await screen.getByRole('button', { name: 'Go to Homepage' }).click();
});
expect(routerMock.push).toHaveBeenCalledWith('/');
});
});