mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-06 14:20:10 +00:00
Implement file hash-based library matching and remove fuzzy ASIN matching
Adds file hash-based matching for Audiobookshelf library items to ensure 100% accurate ASIN assignment for RMAB-organized content. Removes fuzzy matching from library availability checks, making all matching ASIN-only to eliminate false positives and race conditions. Updates database schema, processors, and matcher utilities; adds new tests and documentation for the new matching strategy. Removes obsolete scripts, Dockerfile, and related tests; updates docker-compose for test environments.
This commit is contained in:
@@ -32,6 +32,12 @@ describe('LoginPage', () => {
|
||||
resetMockRouter();
|
||||
resetMockAuthState();
|
||||
localStorage.clear();
|
||||
document.cookie.split(';').forEach((cookie) => {
|
||||
const name = cookie.split('=')[0]?.trim();
|
||||
if (name) {
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
||||
}
|
||||
});
|
||||
setMockSearchParams('');
|
||||
window.innerWidth = 1024;
|
||||
vi.resetModules();
|
||||
@@ -520,4 +526,120 @@ describe('LoginPage', () => {
|
||||
|
||||
expect(await screen.findByText('Access Denied')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('falls back to cookies when mobile auth has no hash', async () => {
|
||||
const setAuthDataMock = vi.fn();
|
||||
setMockAuthState({ setAuthData: setAuthDataMock, isLoading: false });
|
||||
setMockSearchParams('auth=success&redirect=/requests');
|
||||
|
||||
const userData = { id: 'user-10', username: 'cookie-user', role: 'user' };
|
||||
document.cookie = 'accessToken=cookie-access';
|
||||
document.cookie = 'refreshToken=cookie-refresh';
|
||||
document.cookie = `userData=${encodeURIComponent(JSON.stringify(userData))}`;
|
||||
|
||||
const fetchMock = vi.fn(async (input: RequestInfo) => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url === '/api/auth/providers') return makeJsonResponse(baseProviders);
|
||||
if (url === '/api/audiobooks/covers') return makeJsonResponse({ success: true, covers: [] });
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const { default: LoginPage } = await import('@/app/login/page');
|
||||
render(<LoginPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setAuthDataMock).toHaveBeenCalledWith(userData, 'cookie-access');
|
||||
expect(routerMock.push).toHaveBeenCalledWith('/requests');
|
||||
});
|
||||
|
||||
expect(localStorage.getItem('accessToken')).toBe('cookie-access');
|
||||
expect(localStorage.getItem('refreshToken')).toBe('cookie-refresh');
|
||||
});
|
||||
|
||||
it('shows an error when cookie auth payload is invalid', async () => {
|
||||
const setAuthDataMock = vi.fn();
|
||||
setMockAuthState({ setAuthData: setAuthDataMock, isLoading: false });
|
||||
setMockSearchParams('auth=success');
|
||||
document.cookie = 'accessToken=cookie-access';
|
||||
document.cookie = 'userData=not-json';
|
||||
|
||||
const fetchMock = vi.fn(async (input: RequestInfo) => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url === '/api/auth/providers') return makeJsonResponse(baseProviders);
|
||||
if (url === '/api/audiobooks/covers') return makeJsonResponse({ success: true, covers: [] });
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const { default: LoginPage } = await import('@/app/login/page');
|
||||
render(<LoginPage />);
|
||||
|
||||
expect(await screen.findByText('Login failed. Please try again.')).toBeInTheDocument();
|
||||
expect(setAuthDataMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows an error when cookie auth data is missing', async () => {
|
||||
setMockSearchParams('auth=success');
|
||||
|
||||
const fetchMock = vi.fn(async (input: RequestInfo) => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url === '/api/auth/providers') return makeJsonResponse(baseProviders);
|
||||
if (url === '/api/audiobooks/covers') return makeJsonResponse({ success: true, covers: [] });
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const { default: LoginPage } = await import('@/app/login/page');
|
||||
render(<LoginPage />);
|
||||
|
||||
expect(await screen.findByText('Authentication failed. Please try again.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('redirects to Plex OAuth on mobile without opening a popup', async () => {
|
||||
window.innerWidth = 500;
|
||||
const loginMock = vi.fn().mockResolvedValue(undefined);
|
||||
setMockAuthState({ login: loginMock, isLoading: false });
|
||||
|
||||
const fetchMock = vi.fn(async (input: RequestInfo) => {
|
||||
const url = typeof input === 'string' ? input : input.url;
|
||||
if (url === '/api/auth/providers') return makeJsonResponse(baseProviders);
|
||||
if (url === '/api/audiobooks/covers') return makeJsonResponse({ success: true, covers: [] });
|
||||
if (url === '/api/auth/plex/login') {
|
||||
return makeJsonResponse({ pinId: 321, authUrl: 'http://plex/mobile' });
|
||||
}
|
||||
throw new Error(`Unexpected fetch: ${url}`);
|
||||
});
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const openMock = vi.fn();
|
||||
vi.stubGlobal('open', openMock);
|
||||
|
||||
const originalLocation = window.location;
|
||||
delete (window as any).location;
|
||||
(window as any).location = {
|
||||
...originalLocation,
|
||||
href: 'http://localhost/login',
|
||||
hash: '',
|
||||
pathname: '/login',
|
||||
search: '',
|
||||
};
|
||||
|
||||
const { default: LoginPage } = await import('@/app/login/page');
|
||||
render(<LoginPage />);
|
||||
|
||||
const loginButton = await screen.findByRole('button', { name: 'Login with Plex' });
|
||||
fireEvent.click(loginButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openMock).not.toHaveBeenCalled();
|
||||
expect(loginMock).not.toHaveBeenCalled();
|
||||
expect(window.location.href).toBe('http://plex/mobile');
|
||||
});
|
||||
|
||||
(window as any).location = originalLocation;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user