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