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' );