Files
bookorbit/server/src/modules/metadata/lib/cover-cbr.test.ts
T
neon 9b853935bb fix(reader): sort comic names before their extensions
Compare extensionless archive entry paths first so a cover named IMG.jpg precedes page names that extend IMG with a suffix. Keep cover extraction and CBZ/CBR page delivery aligned, and invalidate old page ETags.

Closes #2035
2026-11-02 01:54:30 +00:00

66 lines
2.5 KiB
TypeScript

vi.mock('fs/promises', () => ({ readFile: vi.fn() }));
vi.mock('node-unrar-js', () => ({ createExtractorFromData: vi.fn() }));
import { readFile } from 'fs/promises';
import { createExtractorFromData } from 'node-unrar-js';
import { extractCbrCover } from './cover-cbr';
const mockReadFile = readFile as MockedFunction<typeof readFile>;
const mockCreateExtractor = createExtractorFromData as MockedFunction<typeof createExtractorFromData>;
describe('extractCbrCover', () => {
beforeEach(() => {
vi.resetAllMocks();
mockReadFile.mockResolvedValue(Buffer.from('rar-bytes') as unknown as Awaited<ReturnType<typeof readFile>>);
});
it('picks naturally-sorted first visible image and extracts only that file', async () => {
mockCreateExtractor
.mockResolvedValueOnce({
getFileList: () => ({
fileHeaders: [
{ name: '10.jpg', flags: { directory: false } },
{ name: '2.jpg', flags: { directory: false } },
{ name: '.hidden.jpg', flags: { directory: false } },
],
}),
} as any)
.mockResolvedValueOnce({
extract: () => ({ files: [{ fileHeader: { flags: { directory: false } }, extraction: Uint8Array.from([7, 7, 7]) }] }),
} as any);
await expect(extractCbrCover('/book.cbr')).resolves.toEqual(Buffer.from([7, 7, 7]));
});
it('chooses a complete base filename before a filename that extends its stem', async () => {
const extract = vi.fn(() => ({ files: [{ fileHeader: { flags: { directory: false } }, extraction: Uint8Array.from([1]) }] }));
mockCreateExtractor
.mockResolvedValueOnce({
getFileList: () => ({
fileHeaders: [
{ name: 'IMG_0004_Seite_001_1L.jpg', flags: { directory: false } },
{ name: 'IMG.jpg', flags: { directory: false } },
],
}),
} as any)
.mockResolvedValueOnce({ extract } as any);
await expect(extractCbrCover('/book.cbr')).resolves.toEqual(Buffer.from([1]));
expect(extract).toHaveBeenCalledWith({ files: ['IMG.jpg'] });
});
it('returns null when archive has no visible image files', async () => {
mockCreateExtractor.mockResolvedValue({
getFileList: () => ({ fileHeaders: [{ name: 'notes.txt', flags: { directory: false } }] }),
} as any);
await expect(extractCbrCover('/book.cbr')).resolves.toBeNull();
});
it('returns null when extraction errors', async () => {
mockCreateExtractor.mockRejectedValue(new Error('bad rar'));
await expect(extractCbrCover('/book.cbr')).resolves.toBeNull();
});
});