From 09e1a0db3a2e45dc66cb6af381db3f4d4c79379f Mon Sep 17 00:00:00 2001 From: kikootwo Date: Thu, 5 Mar 2026 12:20:00 -0500 Subject: [PATCH] Use .gl for Anna's Archive; add manual-import test Replace default Anna's Archive base URL from https://annas-archive.li to https://annas-archive.gl across docs, UI components, API routes, processors, services, and tests. Add comprehensive tests for the admin manual-import API route and enhance the manual-import route to fetch missing ASIN details from Audnexus and create audiobook records with proper error handling and logging. Update related test expectations and FlareSolverr test usages to reflect the new default URL. --- documentation/integrations/ebook-sidecar.md | 10 +- documentation/settings-pages.md | 4 +- .../admin/components/RecentRequestsTable.tsx | 2 +- .../components/RequestActionsDropdown.tsx | 2 +- .../admin/settings/tabs/EbookTab/EbookTab.tsx | 4 +- .../tabs/EbookTab/useEbookSettings.ts | 4 +- src/app/api/admin/manual-import/route.ts | 40 ++- src/app/api/admin/settings/ebook/route.ts | 2 +- src/app/api/admin/settings/route.ts | 2 +- .../[asin]/interactive-search-ebook/route.ts | 2 +- .../[id]/interactive-search-ebook/route.ts | 2 +- .../processors/direct-download.processor.ts | 2 +- src/lib/processors/search-ebook.processor.ts | 2 +- src/lib/services/ebook-scraper.ts | 4 +- tests/api/admin-manual-import.routes.test.ts | 258 ++++++++++++++++++ tests/api/admin-settings-core.routes.test.ts | 2 +- tests/api/admin-settings-tests.routes.test.ts | 6 +- .../RequestActionsDropdown.test.tsx | 2 +- .../tabs/EbookTab/useEbookSettings.test.tsx | 4 +- .../admin-settings-indexers.test.tsx | 2 +- .../direct-download.processor.test.ts | 6 +- .../processors/search-ebook.processor.test.ts | 10 +- tests/services/ebook-scraper.test.ts | 14 +- 23 files changed, 338 insertions(+), 48 deletions(-) create mode 100644 tests/api/admin-manual-import.routes.test.ts diff --git a/documentation/integrations/ebook-sidecar.md b/documentation/integrations/ebook-sidecar.md index 6ef526e..c50dfd4 100644 --- a/documentation/integrations/ebook-sidecar.md +++ b/documentation/integrations/ebook-sidecar.md @@ -51,7 +51,7 @@ Ebooks are first-class citizens in RMAB, with their own request type, tracking, | Key | Default | Description | |-----|---------|-------------| | `ebook_annas_archive_enabled` | `false` | Enable Anna's Archive downloads | -| `ebook_sidecar_base_url` | `https://annas-archive.li` | Base URL for mirror | +| `ebook_sidecar_base_url` | `https://annas-archive.gl` | Base URL for mirror | | `ebook_sidecar_flaresolverr_url` | `` (empty) | FlareSolverr proxy URL (optional) | #### Section 2: Indexer Search @@ -180,18 +180,18 @@ Configure URL in Admin Settings → E-book Sidecar: `http://localhost:8191` ### Method 1: ASIN Search (exact match) ``` -Search: https://annas-archive.li/search?ext=epub&lang=en&q="asin:B09TWSRMCB" +Search: https://annas-archive.gl/search?ext=epub&lang=en&q="asin:B09TWSRMCB" ↓ -MD5 Page: https://annas-archive.li/md5/[md5] +MD5 Page: https://annas-archive.gl/md5/[md5] ↓ -Slow Download: https://annas-archive.li/slow_download/[md5]/0/5 +Slow Download: https://annas-archive.gl/slow_download/[md5]/0/5 ↓ File Server: http://[server]/path/to/file.epub ``` ### Method 2: Title + Author (fallback) ``` -Search: https://annas-archive.li/search?q=Title+Author&ext=epub&lang=en +Search: https://annas-archive.gl/search?q=Title+Author&ext=epub&lang=en ↓ (Same flow from MD5 page) ``` diff --git a/documentation/settings-pages.md b/documentation/settings-pages.md index 393492b..db299b2 100644 --- a/documentation/settings-pages.md +++ b/documentation/settings-pages.md @@ -81,7 +81,7 @@ src/app/admin/settings/ 1. **Anna's Archive Section** - Enable toggle for Anna's Archive downloads - - Base URL (default: `https://annas-archive.li`) + - Base URL (default: `https://annas-archive.gl`) - FlareSolverr URL (optional, for Cloudflare bypass) 2. **Indexer Search Section** @@ -101,7 +101,7 @@ src/app/admin/settings/ | `ebook_sidecar_preferred_format` | `epub` | Preferred format | | `ebook_auto_grab_enabled` | `true` | Auto-create ebook requests after audiobook downloads | | `ebook_kindle_fix_enabled` | `false` | Apply Kindle compatibility fixes to EPUB files | -| `ebook_sidecar_base_url` | `https://annas-archive.li` | Anna's Archive mirror | +| `ebook_sidecar_base_url` | `https://annas-archive.gl` | Anna's Archive mirror | | `ebook_sidecar_flaresolverr_url` | `` | FlareSolverr URL | **Behavior:** diff --git a/src/app/admin/components/RecentRequestsTable.tsx b/src/app/admin/components/RecentRequestsTable.tsx index a4812ae..65e7c95 100644 --- a/src/app/admin/components/RecentRequestsTable.tsx +++ b/src/app/admin/components/RecentRequestsTable.tsx @@ -163,7 +163,7 @@ function getInitialParams(): { }; } -export function RecentRequestsTable({ ebookSidecarEnabled = false, annasArchiveBaseUrl = 'https://annas-archive.li' }: RecentRequestsTableProps) { +export function RecentRequestsTable({ ebookSidecarEnabled = false, annasArchiveBaseUrl = 'https://annas-archive.gl' }: RecentRequestsTableProps) { const toast = useToast(); // Get initial filter state from URL (only evaluated once due to lazy init) diff --git a/src/app/admin/components/RequestActionsDropdown.tsx b/src/app/admin/components/RequestActionsDropdown.tsx index da60da1..7416a4d 100644 --- a/src/app/admin/components/RequestActionsDropdown.tsx +++ b/src/app/admin/components/RequestActionsDropdown.tsx @@ -47,7 +47,7 @@ export function RequestActionsDropdown({ onFetchEbook, onSearchTermsUpdated, ebookSidecarEnabled = false, - annasArchiveBaseUrl = 'https://annas-archive.li', + annasArchiveBaseUrl = 'https://annas-archive.gl', isLoading = false, }: RequestActionsDropdownProps) { const [isOpen, setIsOpen] = useState(false); diff --git a/src/app/admin/settings/tabs/EbookTab/EbookTab.tsx b/src/app/admin/settings/tabs/EbookTab/EbookTab.tsx index 6719e93..58150d1 100644 --- a/src/app/admin/settings/tabs/EbookTab/EbookTab.tsx +++ b/src/app/admin/settings/tabs/EbookTab/EbookTab.tsx @@ -90,9 +90,9 @@ export function EbookTab({ ebook, onChange, onSuccess, onError, markAsSaved }: E updateEbook('baseUrl', e.target.value)} - placeholder="https://annas-archive.li" + placeholder="https://annas-archive.gl" className="font-mono" />

diff --git a/src/app/admin/settings/tabs/EbookTab/useEbookSettings.ts b/src/app/admin/settings/tabs/EbookTab/useEbookSettings.ts index c27c259..21bf669 100644 --- a/src/app/admin/settings/tabs/EbookTab/useEbookSettings.ts +++ b/src/app/admin/settings/tabs/EbookTab/useEbookSettings.ts @@ -53,7 +53,7 @@ export function useEbookSettings({ ebook, onChange, onSuccess, onError, markAsSa headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: ebook.flaresolverrUrl, - baseUrl: ebook.baseUrl || 'https://annas-archive.li', + baseUrl: ebook.baseUrl || 'https://annas-archive.gl', }), }); @@ -83,7 +83,7 @@ export function useEbookSettings({ ebook, onChange, onSuccess, onError, markAsSa annasArchiveEnabled: ebook.annasArchiveEnabled || false, indexerSearchEnabled: ebook.indexerSearchEnabled || false, format: ebook.preferredFormat || 'epub', - baseUrl: ebook.baseUrl || 'https://annas-archive.li', + baseUrl: ebook.baseUrl || 'https://annas-archive.gl', flaresolverrUrl: ebook.flaresolverrUrl || '', autoGrabEnabled: ebook.autoGrabEnabled ?? true, kindleFixEnabled: ebook.kindleFixEnabled ?? false, diff --git a/src/app/api/admin/manual-import/route.ts b/src/app/api/admin/manual-import/route.ts index dfbb6d0..d7bd546 100644 --- a/src/app/api/admin/manual-import/route.ts +++ b/src/app/api/admin/manual-import/route.ts @@ -155,10 +155,42 @@ export async function POST(request: NextRequest) { audiobookId = newBook.id; logger.info(`Created audiobook record from cache for ASIN ${asin}: ${newBook.id}`); } else { - return NextResponse.json( - { error: 'Audiobook not found for the given ASIN' }, - { status: 404 } - ); + // Not in DB — fetch live from Audnexus and create a record + try { + const audibleService = getAudibleService(); + const liveData = await audibleService.getAudiobookDetails(asin); + if (liveData) { + const newBook = await prisma.audiobook.create({ + data: { + audibleAsin: asin, + title: liveData.title, + author: liveData.author, + coverArtUrl: liveData.coverArtUrl, + narrator: liveData.narrator, + series: liveData.series, + seriesPart: liveData.seriesPart, + seriesAsin: liveData.seriesAsin, + year: liveData.releaseDate + ? new Date(liveData.releaseDate).getFullYear() || undefined + : undefined, + status: 'pending', + }, + }); + audiobookId = newBook.id; + logger.info(`Created audiobook record from Audnexus for ASIN ${asin}: ${newBook.id}`); + } else { + return NextResponse.json( + { error: 'Audiobook not found for the given ASIN' }, + { status: 404 } + ); + } + } catch (audnexusError) { + logger.error(`Failed to fetch ASIN ${asin} from Audnexus: ${audnexusError instanceof Error ? audnexusError.message : String(audnexusError)}`); + return NextResponse.json( + { error: 'Audiobook not found for the given ASIN' }, + { status: 404 } + ); + } } } } diff --git a/src/app/api/admin/settings/ebook/route.ts b/src/app/api/admin/settings/ebook/route.ts index 766429c..1b8ec7e 100644 --- a/src/app/api/admin/settings/ebook/route.ts +++ b/src/app/api/admin/settings/ebook/route.ts @@ -78,7 +78,7 @@ export async function PUT(request: NextRequest) { // Anna's Archive specific settings { key: 'ebook_sidecar_base_url', - value: baseUrl || 'https://annas-archive.li', + value: baseUrl || 'https://annas-archive.gl', category: 'ebook', description: 'Base URL for Anna\'s Archive', }, diff --git a/src/app/api/admin/settings/route.ts b/src/app/api/admin/settings/route.ts index f6e4707..550caed 100644 --- a/src/app/api/admin/settings/route.ts +++ b/src/app/api/admin/settings/route.ts @@ -138,7 +138,7 @@ export async function GET(request: NextRequest) { (configMap.get('ebook_annas_archive_enabled') === undefined && configMap.get('ebook_sidecar_enabled') === 'true'), indexerSearchEnabled: configMap.get('ebook_indexer_search_enabled') === 'true', // Anna's Archive specific settings - baseUrl: configMap.get('ebook_sidecar_base_url') || 'https://annas-archive.li', + baseUrl: configMap.get('ebook_sidecar_base_url') || 'https://annas-archive.gl', flaresolverrUrl: configMap.get('ebook_sidecar_flaresolverr_url') || '', // General settings preferredFormat: configMap.get('ebook_sidecar_preferred_format') || 'epub', diff --git a/src/app/api/audiobooks/[asin]/interactive-search-ebook/route.ts b/src/app/api/audiobooks/[asin]/interactive-search-ebook/route.ts index 97e5f7d..de57632 100644 --- a/src/app/api/audiobooks/[asin]/interactive-search-ebook/route.ts +++ b/src/app/api/audiobooks/[asin]/interactive-search-ebook/route.ts @@ -227,7 +227,7 @@ export async function POST( const isAnnasArchiveEnabled = annasArchiveEnabled === 'true'; const isIndexerSearchEnabled = indexerSearchEnabled === 'true'; const format = preferredFormat || 'epub'; - const annasBaseUrl = baseUrl || 'https://annas-archive.li'; + const annasBaseUrl = baseUrl || 'https://annas-archive.gl'; // Get language code from Audible region config const region = await configService.getAudibleRegion() as AudibleRegion; diff --git a/src/app/api/requests/[id]/interactive-search-ebook/route.ts b/src/app/api/requests/[id]/interactive-search-ebook/route.ts index 6ff4285..5848f77 100644 --- a/src/app/api/requests/[id]/interactive-search-ebook/route.ts +++ b/src/app/api/requests/[id]/interactive-search-ebook/route.ts @@ -136,7 +136,7 @@ export async function POST( const isAnnasArchiveEnabled = annasArchiveEnabled === 'true'; const isIndexerSearchEnabled = indexerSearchEnabled === 'true'; const format = preferredFormat || 'epub'; - const annasBaseUrl = baseUrl || 'https://annas-archive.li'; + const annasBaseUrl = baseUrl || 'https://annas-archive.gl'; // Get language code from Audible region config const region = await configService.getAudibleRegion() as AudibleRegion; diff --git a/src/lib/processors/direct-download.processor.ts b/src/lib/processors/direct-download.processor.ts index 2f4a2f7..d8296d2 100644 --- a/src/lib/processors/direct-download.processor.ts +++ b/src/lib/processors/direct-download.processor.ts @@ -79,7 +79,7 @@ export async function processStartDirectDownload(payload: StartDirectDownloadPay // Get download configuration const configService = getConfigService(); const downloadsDir = await configService.get('download_dir') || '/downloads'; - const baseUrl = await configService.get('ebook_sidecar_base_url') || 'https://annas-archive.li'; + const baseUrl = await configService.get('ebook_sidecar_base_url') || 'https://annas-archive.gl'; const preferredFormat = await configService.get('ebook_sidecar_preferred_format') || 'epub'; const flaresolverrUrl = await configService.get('ebook_sidecar_flaresolverr_url') || undefined; diff --git a/src/lib/processors/search-ebook.processor.ts b/src/lib/processors/search-ebook.processor.ts index 6fbd667..cd9e3ab 100644 --- a/src/lib/processors/search-ebook.processor.ts +++ b/src/lib/processors/search-ebook.processor.ts @@ -150,7 +150,7 @@ async function searchAnnasArchive( logger: RMABLogger ): Promise { const configService = getConfigService(); - const baseUrl = await configService.get('ebook_sidecar_base_url') || 'https://annas-archive.li'; + const baseUrl = await configService.get('ebook_sidecar_base_url') || 'https://annas-archive.gl'; const flaresolverrUrl = await configService.get('ebook_sidecar_flaresolverr_url') || undefined; // Get language code from Audible region config diff --git a/src/lib/services/ebook-scraper.ts b/src/lib/services/ebook-scraper.ts index a091fd3..e480dda 100644 --- a/src/lib/services/ebook-scraper.ts +++ b/src/lib/services/ebook-scraper.ts @@ -128,7 +128,7 @@ async function fetchHtml( */ export async function testFlareSolverrConnection( flaresolverrUrl: string, - baseUrl: string = 'https://annas-archive.li' + baseUrl: string = 'https://annas-archive.gl' ): Promise<{ success: boolean; message: string; responseTime?: number }> { const startTime = Date.now(); @@ -168,7 +168,7 @@ export async function downloadEbook( author: string, targetDir: string, preferredFormat: string = 'epub', - baseUrl: string = 'https://annas-archive.li', + baseUrl: string = 'https://annas-archive.gl', logger?: RMABLogger, flaresolverrUrl?: string, languageCode: string = 'en' diff --git a/tests/api/admin-manual-import.routes.test.ts b/tests/api/admin-manual-import.routes.test.ts new file mode 100644 index 0000000..5d53346 --- /dev/null +++ b/tests/api/admin-manual-import.routes.test.ts @@ -0,0 +1,258 @@ +/** + * Component: Admin Manual Import API Route Tests + * Documentation: documentation/features/manual-import.md + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createPrismaMock } from '../helpers/prisma'; + +let authRequest: any; +let requestBody: any; + +const prismaMock = createPrismaMock(); +const requireAuthMock = vi.hoisted(() => vi.fn()); +const requireAdminMock = vi.hoisted(() => vi.fn()); +const jobQueueMock = vi.hoisted(() => ({ + addOrganizeJob: vi.fn(() => Promise.resolve()), +})); +const audibleServiceMock = vi.hoisted(() => ({ + getAudiobookDetails: vi.fn(), +})); + +// fs mock +const fsMock = vi.hoisted(() => ({ + stat: vi.fn(), + readdir: vi.fn(), +})); + +vi.mock('@/lib/db', () => ({ + prisma: prismaMock, +})); + +vi.mock('@/lib/middleware/auth', () => ({ + requireAuth: requireAuthMock, + requireAdmin: requireAdminMock, + AuthenticatedRequest: {}, +})); + +vi.mock('@/lib/services/job-queue.service', () => ({ + getJobQueueService: () => jobQueueMock, +})); + +vi.mock('@/lib/integrations/audible.service', () => ({ + getAudibleService: () => audibleServiceMock, +})); + +vi.mock('fs/promises', () => fsMock); + +vi.mock('path', async () => { + const actual = await vi.importActual('path'); + return { + ...actual, + default: actual, + resolve: (...args: string[]) => actual.posix.resolve(...args), + extname: actual.posix.extname, + }; +}); + +describe('Admin manual-import route', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.resetModules(); + + authRequest = { user: { id: 'admin-1', role: 'admin' } }; + requestBody = { asin: 'B00TEST0001', folderPath: '/bookdrop/author/title' }; + + requireAuthMock.mockImplementation((_req: any, handler: any) => handler(authRequest)); + requireAdminMock.mockImplementation((_req: any, handler: any) => handler()); + + // Default: download_dir and media_dir not configured, bookdrop exists + prismaMock.configuration.findUnique.mockResolvedValue(null); + fsMock.stat.mockImplementation(async (p: string) => { + if (p === '/bookdrop') return { isDirectory: () => true }; + if (p === '/bookdrop/author/title') return { isDirectory: () => true }; + throw new Error('ENOENT'); + }); + fsMock.readdir.mockResolvedValue([ + { name: 'chapter1.m4b', isFile: () => true }, + ]); + }); + + it('creates audiobook from Audnexus when ASIN is not in DB or cache', async () => { + // Neither audiobook nor audibleCache has this ASIN + prismaMock.audiobook.findFirst.mockResolvedValueOnce(null); + prismaMock.audibleCache.findUnique.mockResolvedValueOnce(null); + + // Audnexus returns live data + audibleServiceMock.getAudiobookDetails.mockResolvedValueOnce({ + asin: 'B00TEST0001', + title: 'Live Title', + author: 'Live Author', + coverArtUrl: 'https://example.com/cover.jpg', + narrator: 'Live Narrator', + series: 'Test Series', + seriesPart: '1', + seriesAsin: 'SERIES0001', + releaseDate: '2024-01-15', + }); + + // audiobook.create returns the new record + prismaMock.audiobook.create.mockResolvedValueOnce({ + id: 'ab-new', + audibleAsin: 'B00TEST0001', + title: 'Live Title', + author: 'Live Author', + status: 'pending', + }); + + // audiobook.findUnique for the verification step + prismaMock.audiobook.findUnique.mockResolvedValueOnce({ + id: 'ab-new', + audibleAsin: 'B00TEST0001', + title: 'Live Title', + author: 'Live Author', + status: 'pending', + }); + + // No existing request + prismaMock.request.findFirst.mockResolvedValueOnce(null); + prismaMock.request.create.mockResolvedValueOnce({ id: 'req-new' }); + + const { POST } = await import('@/app/api/admin/manual-import/route'); + const request = { + json: vi.fn().mockResolvedValue(requestBody), + nextUrl: new URL('http://localhost/api/admin/manual-import'), + }; + const response = await POST(request as any); + const payload = await response.json(); + + expect(response.status).toBe(200); + expect(payload.success).toBe(true); + expect(audibleServiceMock.getAudiobookDetails).toHaveBeenCalledWith('B00TEST0001'); + expect(prismaMock.audiobook.create).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + audibleAsin: 'B00TEST0001', + title: 'Live Title', + author: 'Live Author', + }), + }) + ); + }); + + it('returns 404 when ASIN is not in DB, cache, or Audnexus', async () => { + prismaMock.audiobook.findFirst.mockResolvedValueOnce(null); + prismaMock.audibleCache.findUnique.mockResolvedValueOnce(null); + audibleServiceMock.getAudiobookDetails.mockResolvedValueOnce(null); + + const { POST } = await import('@/app/api/admin/manual-import/route'); + const request = { + json: vi.fn().mockResolvedValue(requestBody), + nextUrl: new URL('http://localhost/api/admin/manual-import'), + }; + const response = await POST(request as any); + const payload = await response.json(); + + expect(response.status).toBe(404); + expect(payload.error).toBe('Audiobook not found for the given ASIN'); + }); + + it('returns 404 when Audnexus lookup throws an error', async () => { + prismaMock.audiobook.findFirst.mockResolvedValueOnce(null); + prismaMock.audibleCache.findUnique.mockResolvedValueOnce(null); + audibleServiceMock.getAudiobookDetails.mockRejectedValueOnce(new Error('Network timeout')); + + const { POST } = await import('@/app/api/admin/manual-import/route'); + const request = { + json: vi.fn().mockResolvedValue(requestBody), + nextUrl: new URL('http://localhost/api/admin/manual-import'), + }; + const response = await POST(request as any); + const payload = await response.json(); + + expect(response.status).toBe(404); + expect(payload.error).toBe('Audiobook not found for the given ASIN'); + }); + + it('uses existing audiobook record when ASIN is in DB', async () => { + prismaMock.audiobook.findFirst.mockResolvedValueOnce({ + id: 'ab-existing', + audibleAsin: 'B00TEST0001', + }); + + prismaMock.audiobook.findUnique.mockResolvedValueOnce({ + id: 'ab-existing', + audibleAsin: 'B00TEST0001', + title: 'Existing Title', + author: 'Existing Author', + status: 'pending', + }); + + prismaMock.request.findFirst.mockResolvedValueOnce(null); + prismaMock.request.create.mockResolvedValueOnce({ id: 'req-1' }); + + const { POST } = await import('@/app/api/admin/manual-import/route'); + const request = { + json: vi.fn().mockResolvedValue(requestBody), + nextUrl: new URL('http://localhost/api/admin/manual-import'), + }; + const response = await POST(request as any); + const payload = await response.json(); + + expect(response.status).toBe(200); + expect(payload.success).toBe(true); + // Should NOT have queried audibleCache for ASIN resolution + expect(prismaMock.audibleCache.findUnique).not.toHaveBeenCalled(); + }); + + it('uses audibleCache when ASIN is not in audiobook table but is cached', async () => { + prismaMock.audiobook.findFirst.mockResolvedValueOnce(null); + prismaMock.audibleCache.findUnique.mockResolvedValueOnce({ + asin: 'B00TEST0001', + title: 'Cached Title', + author: 'Cached Author', + coverArtUrl: 'https://example.com/cached.jpg', + narrator: 'Cached Narrator', + }); + + // audiobook.create from cache + prismaMock.audiobook.create.mockResolvedValueOnce({ + id: 'ab-from-cache', + audibleAsin: 'B00TEST0001', + title: 'Cached Title', + author: 'Cached Author', + status: 'pending', + }); + + prismaMock.audiobook.findUnique.mockResolvedValueOnce({ + id: 'ab-from-cache', + audibleAsin: 'B00TEST0001', + title: 'Cached Title', + author: 'Cached Author', + status: 'pending', + }); + + prismaMock.request.findFirst.mockResolvedValueOnce(null); + prismaMock.request.create.mockResolvedValueOnce({ id: 'req-2' }); + + const { POST } = await import('@/app/api/admin/manual-import/route'); + const request = { + json: vi.fn().mockResolvedValue(requestBody), + nextUrl: new URL('http://localhost/api/admin/manual-import'), + }; + const response = await POST(request as any); + const payload = await response.json(); + + expect(response.status).toBe(200); + expect(payload.success).toBe(true); + // audiobook.create should have used cache data, not Audnexus + expect(prismaMock.audiobook.create).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + title: 'Cached Title', + author: 'Cached Author', + }), + }) + ); + }); +}); diff --git a/tests/api/admin-settings-core.routes.test.ts b/tests/api/admin-settings-core.routes.test.ts index 18a648a..7c8d391 100644 --- a/tests/api/admin-settings-core.routes.test.ts +++ b/tests/api/admin-settings-core.routes.test.ts @@ -355,7 +355,7 @@ describe('Admin settings core routes', () => { it('updates ebook settings', async () => { const request = { - json: vi.fn().mockResolvedValue({ enabled: true, format: 'epub', baseUrl: 'https://annas-archive.li' }), + json: vi.fn().mockResolvedValue({ enabled: true, format: 'epub', baseUrl: 'https://annas-archive.gl' }), }; const { PUT } = await import('@/app/api/admin/settings/ebook/route'); diff --git a/tests/api/admin-settings-tests.routes.test.ts b/tests/api/admin-settings-tests.routes.test.ts index e12ecc2..10a8905 100644 --- a/tests/api/admin-settings-tests.routes.test.ts +++ b/tests/api/admin-settings-tests.routes.test.ts @@ -348,14 +348,14 @@ describe('Admin settings test routes', () => { it('tests FlareSolverr connection', async () => { testFlareSolverrMock.mockResolvedValueOnce({ success: true }); - const request = { json: vi.fn().mockResolvedValue({ url: 'http://flare', baseUrl: 'https://annas-archive.li' }) }; + const request = { json: vi.fn().mockResolvedValue({ url: 'http://flare', baseUrl: 'https://annas-archive.gl' }) }; const { POST } = await import('@/app/api/admin/settings/ebook/test-flaresolverr/route'); const response = await POST(request as any); const payload = await response.json(); expect(payload.success).toBe(true); - expect(testFlareSolverrMock).toHaveBeenCalledWith('http://flare', 'https://annas-archive.li'); + expect(testFlareSolverrMock).toHaveBeenCalledWith('http://flare', 'https://annas-archive.gl'); }); it('rejects FlareSolverr test when URL is missing', async () => { @@ -382,7 +382,7 @@ describe('Admin settings test routes', () => { it('returns error when FlareSolverr test throws', async () => { testFlareSolverrMock.mockRejectedValueOnce(new Error('flare down')); - const request = { json: vi.fn().mockResolvedValue({ url: 'http://flare', baseUrl: 'https://annas-archive.li' }) }; + const request = { json: vi.fn().mockResolvedValue({ url: 'http://flare', baseUrl: 'https://annas-archive.gl' }) }; const { POST } = await import('@/app/api/admin/settings/ebook/test-flaresolverr/route'); const response = await POST(request as any); diff --git a/tests/app/admin/components/RequestActionsDropdown.test.tsx b/tests/app/admin/components/RequestActionsDropdown.test.tsx index 8104a90..53bd133 100644 --- a/tests/app/admin/components/RequestActionsDropdown.test.tsx +++ b/tests/app/admin/components/RequestActionsDropdown.test.tsx @@ -87,7 +87,7 @@ describe('RequestActionsDropdown', () => { author: 'Author', status: 'downloaded', type: 'ebook', - torrentUrl: JSON.stringify(['https://annas-archive.li/slow_download/abc123def456abc123def456abc123de/0/5']), + torrentUrl: JSON.stringify(['https://annas-archive.gl/slow_download/abc123def456abc123def456abc123de/0/5']), }} onManualSearch={vi.fn().mockResolvedValue(undefined)} onCancel={vi.fn().mockResolvedValue(undefined)} diff --git a/tests/app/admin/settings/tabs/EbookTab/useEbookSettings.test.tsx b/tests/app/admin/settings/tabs/EbookTab/useEbookSettings.test.tsx index 3133dab..e9a95f6 100644 --- a/tests/app/admin/settings/tabs/EbookTab/useEbookSettings.test.tsx +++ b/tests/app/admin/settings/tabs/EbookTab/useEbookSettings.test.tsx @@ -28,7 +28,7 @@ const renderHook = (hook: () => T) => { const baseEbook = { enabled: true, preferredFormat: 'epub', - baseUrl: 'https://annas-archive.li', + baseUrl: 'https://annas-archive.gl', flaresolverrUrl: 'http://flare', }; @@ -93,7 +93,7 @@ describe('useEbookSettings', () => { expect(result.current.flaresolverrTestResult?.success).toBe(true); // Verify baseUrl is included in the request body const callBody = JSON.parse(fetchWithAuthMock.mock.calls[0][1].body); - expect(callBody.baseUrl).toBe('https://annas-archive.li'); + expect(callBody.baseUrl).toBe('https://annas-archive.gl'); expect(callBody.url).toBe('http://flare'); }); diff --git a/tests/components/admin-settings-indexers.test.tsx b/tests/components/admin-settings-indexers.test.tsx index 3cba095..98b89e1 100644 --- a/tests/components/admin-settings-indexers.test.tsx +++ b/tests/components/admin-settings-indexers.test.tsx @@ -115,7 +115,7 @@ describe('IndexersTab - Auto-load Indexers on Tab Activation', () => { ebook: { enabled: false, preferredFormat: 'epub', - baseUrl: 'https://annas-archive.li', + baseUrl: 'https://annas-archive.gl', flaresolverrUrl: '', }, }; diff --git a/tests/processors/direct-download.processor.test.ts b/tests/processors/direct-download.processor.test.ts index ed3466a..60359c7 100644 --- a/tests/processors/direct-download.processor.test.ts +++ b/tests/processors/direct-download.processor.test.ts @@ -63,7 +63,7 @@ describe('processStartDirectDownload', () => { vi.clearAllMocks(); configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'downloads_dir') return '/downloads'; - if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.li'; + if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl'; if (key === 'ebook_sidecar_preferred_format') return 'epub'; return null; }); @@ -238,7 +238,7 @@ describe('processStartDirectDownload', () => { configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'downloads_dir') return '/downloads'; - if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.li'; + if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl'; if (key === 'ebook_sidecar_preferred_format') return 'epub'; if (key === 'ebook_sidecar_flaresolverr_url') return 'http://flaresolverr:8191'; return null; @@ -286,7 +286,7 @@ describe('processStartDirectDownload', () => { expect(ebookScraperMock.extractDownloadUrl).toHaveBeenCalledWith( 'https://slow.example.com/book', - 'https://annas-archive.li', + 'https://annas-archive.gl', 'epub', expect.anything(), 'http://flaresolverr:8191' diff --git a/tests/processors/search-ebook.processor.test.ts b/tests/processors/search-ebook.processor.test.ts index f8c8dc2..ab2799b 100644 --- a/tests/processors/search-ebook.processor.test.ts +++ b/tests/processors/search-ebook.processor.test.ts @@ -43,7 +43,7 @@ describe('processSearchEbook', () => { configServiceMock.getAudibleRegion.mockResolvedValue('us'); configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'ebook_sidecar_preferred_format') return 'epub'; - if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.li'; + if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl'; if (key === 'ebook_annas_archive_enabled') return 'true'; if (key === 'ebook_indexer_search_enabled') return 'false'; return null; @@ -79,7 +79,7 @@ describe('processSearchEbook', () => { expect(ebookScraperMock.searchByAsin).toHaveBeenCalledWith( 'B001ASIN', 'epub', - 'https://annas-archive.li', + 'https://annas-archive.gl', expect.anything(), undefined, 'en' @@ -124,7 +124,7 @@ describe('processSearchEbook', () => { 'Another Book', 'Another Author', 'epub', - 'https://annas-archive.li', + 'https://annas-archive.gl', expect.anything(), undefined, 'en' @@ -229,7 +229,7 @@ describe('processSearchEbook', () => { configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'ebook_sidecar_preferred_format') return 'epub'; - if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.li'; + if (key === 'ebook_sidecar_base_url') return 'https://annas-archive.gl'; if (key === 'ebook_sidecar_flaresolverr_url') return 'http://flaresolverr:8191'; if (key === 'ebook_annas_archive_enabled') return 'true'; if (key === 'ebook_indexer_search_enabled') return 'false'; @@ -255,7 +255,7 @@ describe('processSearchEbook', () => { expect(ebookScraperMock.searchByAsin).toHaveBeenCalledWith( 'B006ASIN', 'epub', - 'https://annas-archive.li', + 'https://annas-archive.gl', expect.anything(), 'http://flaresolverr:8191', 'en' diff --git a/tests/services/ebook-scraper.test.ts b/tests/services/ebook-scraper.test.ts index d31ed40..abb1e8b 100644 --- a/tests/services/ebook-scraper.test.ts +++ b/tests/services/ebook-scraper.test.ts @@ -63,7 +63,7 @@ describe('E-book sidecar', () => { }, }); - const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.li'); + const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.gl'); expect(result.success).toBe(true); expect(result.responseTime).toBeTypeOf('number'); @@ -95,7 +95,7 @@ describe('E-book sidecar', () => { }, }); - const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.li'); + const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.gl'); expect(result.success).toBe(false); }); @@ -103,7 +103,7 @@ describe('E-book sidecar', () => { it('returns error details when FlareSolverr request fails', async () => { axiosMock.post.mockRejectedValue(new Error('flare down')); - const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.li'); + const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.gl'); expect(result.success).toBe(false); expect(result.message).toContain('flare down'); @@ -117,7 +117,7 @@ describe('E-book sidecar', () => { }, }); - const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.li'); + const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.gl'); expect(result.success).toBe(false); expect(result.message).toContain('FlareSolverr error'); @@ -132,7 +132,7 @@ describe('E-book sidecar', () => { }, }); - const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.li'); + const result = await testFlareSolverrConnection('http://flare', 'https://annas-archive.gl'); expect(result.success).toBe(false); expect(result.message).toContain('FlareSolverr returned HTTP 403'); @@ -221,7 +221,7 @@ describe('E-book sidecar', () => { throw new Error(`Unexpected URL: ${url}`); }); - const promise = downloadEbook('ASIN-NO', 'Title', 'Author', '/downloads', 'pdf', 'https://annas-archive.li', undefined, 'http://flare'); + const promise = downloadEbook('ASIN-NO', 'Title', 'Author', '/downloads', 'pdf', 'https://annas-archive.gl', undefined, 'http://flare'); await vi.runAllTimersAsync(); const result = await promise; @@ -417,7 +417,7 @@ describe('E-book sidecar', () => { 'Author', '/downloads', 'epub', - 'https://annas-archive.li', + 'https://annas-archive.gl', undefined, 'http://flare' );