102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
import { mkdtempSync, rmSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { tmpdir } from 'os';
|
|
import { Cache } from '../src/services/cache.ts';
|
|
import type { TextFragment } from '../src/types.ts';
|
|
|
|
let tmpDir: string;
|
|
let cache: Cache;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = mkdtempSync(join(tmpdir(), 'qa-cache-test-'));
|
|
cache = new Cache(tmpDir);
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('cache — chapters', () => {
|
|
test('returns null on a cold cache', () => {
|
|
expect(cache.tryGetChapters(1, 0, 5)).toBeNull();
|
|
});
|
|
|
|
test('returns null when a chapter in range is missing', () => {
|
|
cache.saveChapters(1, [
|
|
{ index: 0, content: 'Chapter zero content here' },
|
|
// index 1 is intentionally missing
|
|
{ index: 2, content: 'Chapter two content here' },
|
|
]);
|
|
expect(cache.tryGetChapters(1, 0, 2)).toBeNull();
|
|
});
|
|
|
|
test('returns null for invalid range (min > max)', () => {
|
|
expect(cache.tryGetChapters(1, 5, 2)).toBeNull();
|
|
});
|
|
|
|
test('saves and retrieves plain-text chapters', () => {
|
|
cache.saveChapters(1, [
|
|
{ index: 0, content: 'Line one\nLine two' },
|
|
{ index: 1, content: 'Chapter one content here' },
|
|
]);
|
|
const view = cache.tryGetChapters(1, 0, 1);
|
|
expect(view).not.toBeNull();
|
|
expect(view!.id).toBe(1);
|
|
expect(view!.min).toBe(0);
|
|
expect(view!.max).toBe(1);
|
|
expect(view!.chapters).toHaveLength(2);
|
|
expect(view!.chapters[0]!.hasStyle).toBe(false);
|
|
});
|
|
|
|
test('detects styled (JSON fragment) chapters', () => {
|
|
const fragments: TextFragment[] = [{ body: 'Hello world', style: undefined }];
|
|
cache.saveChapters(1, [{ index: 0, content: JSON.stringify(fragments) }]);
|
|
const view = cache.tryGetChapters(1, 0, 0);
|
|
expect(view).not.toBeNull();
|
|
expect(view!.chapters[0]!.hasStyle).toBe(true);
|
|
});
|
|
|
|
test('returns null when a chapter file is too short', () => {
|
|
cache.saveChapters(1, [{ index: 0, content: 'hi' }]); // < 10 bytes
|
|
expect(cache.tryGetChapters(1, 0, 0)).toBeNull();
|
|
});
|
|
|
|
test('handles single-chapter range', () => {
|
|
cache.saveChapters(42, [{ index: 7, content: 'Single chapter content ok' }]);
|
|
const view = cache.tryGetChapters(42, 7, 7);
|
|
expect(view).not.toBeNull();
|
|
expect(view!.chapters).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('cache — TOC', () => {
|
|
test('returns null on a cold cache', () => {
|
|
expect(cache.tryGetToc(1)).toBeNull();
|
|
});
|
|
|
|
test('round-trips a TOC', () => {
|
|
const urls = [
|
|
'https://example.com/chapter-1',
|
|
'https://example.com/chapter-2',
|
|
'https://example.com/chapter-3',
|
|
];
|
|
cache.saveToc(1, urls);
|
|
expect(cache.tryGetToc(1)).toEqual(urls);
|
|
});
|
|
|
|
test('returns null for an empty TOC file', () => {
|
|
cache.saveToc(1, []);
|
|
expect(cache.tryGetToc(1)).toBeNull();
|
|
});
|
|
|
|
test('overwrites an existing TOC on re-save', () => {
|
|
cache.saveToc(1, ['https://example.com/old']);
|
|
cache.saveToc(1, ['https://example.com/new-1', 'https://example.com/new-2']);
|
|
expect(cache.tryGetToc(1)).toEqual([
|
|
'https://example.com/new-1',
|
|
'https://example.com/new-2',
|
|
]);
|
|
});
|
|
});
|