Public Access
The token is no longer stored; a tbp_session flag remembers the login. apiFetch renews the session once on a 401 and sends the request again. Parallel requests and other tabs share one refresh, because Auth revokes the session when a refresh token is used twice. Only a 401 from the refresh ends the session; a 502 or no network shows the normal error.
53 lines
2.2 KiB
React
53 lines
2.2 KiB
React
// @vitest-environment jsdom
|
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
import {act, cleanup, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
|
import {MemoryRouter, useLocation} from 'react-router-dom';
|
|
import App from './App.jsx';
|
|
import {PreferencesProvider} from './context/PreferencesContext.jsx';
|
|
|
|
vi.mock('react-i18next', () => import('./test/i18n-mock.js'));
|
|
vi.mock('./hooks/useLibrarySync.js', () => ({default: () => ({status: 'OK', lastSyncedAt: null, isRunning: false})}));
|
|
vi.mock('./hooks/useForceSync.js', () => ({default: () => ({trigger: vi.fn(), busy: false})}));
|
|
|
|
const DEBOUNCE_WAIT_MS = 700;
|
|
const page = {content: [{appid: 220, name: 'Half-Life 2', playtimeMinutes: 0, status: 'BACKLOG'}], page: 0, size: 20, totalElements: 1, totalPages: 1};
|
|
|
|
const wait = (ms) => act(() => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
|
|
const current = {location: null};
|
|
|
|
function Spy() {
|
|
current.location = useLocation();
|
|
return null;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
localStorage.setItem('tbp_session', '1');
|
|
globalThis.fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => page}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
localStorage.clear();
|
|
});
|
|
|
|
describe('App', () => {
|
|
it('ends the session on a 401 that the refresh cannot fix', async () => {
|
|
render(
|
|
<MemoryRouter initialEntries={['/library']}>
|
|
<PreferencesProvider><App/><Spy/></PreferencesProvider>
|
|
</MemoryRouter>,
|
|
);
|
|
await waitFor(() => expect(screen.getByText('Half-Life 2')).toBeTruthy());
|
|
|
|
fetch.mockImplementation(async () => ({ok: false, status: 401, json: async () => ({})}));
|
|
fireEvent.change(screen.getByRole('searchbox'), {target: {value: 'half'}});
|
|
await wait(DEBOUNCE_WAIT_MS);
|
|
|
|
await waitFor(() => expect(current.location.pathname).toBe('/login'));
|
|
expect(current.location.state).toEqual({sessionExpired: true, from: '/library?q=half&page=0'});
|
|
expect(localStorage.getItem('tbp_session')).toBeNull();
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/auth/refresh'), expect.objectContaining({method: 'POST'}));
|
|
});
|
|
});
|