Files
readest/apps/readest-app/src/__tests__/services/reviewEditorService.test.ts
T
2026-07-09 14:15:36 +08:00

122 lines
3.9 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'vitest';
import {
createReviewSessionFromPath,
exportReviewedEpub,
generateReviewFeedback,
loadInlineReviewData,
loadReviewGlossary,
saveReviewGptConfig,
retranslateReviewRow,
saveReviewRow,
saveReviewGlossary,
sidecarApi,
switchReviewGlossaryPath,
} from '@/services/reviewEditorService';
afterEach(() => {
vi.unstubAllGlobals();
});
const stubJsonFetch = () => {
const fetchMock = vi.fn(async () =>
Response.json({
status: 'ok',
updated_at: '2026-07-09T00:00:00Z',
current_html: '<span>净化译文</span>',
}),
);
vi.stubGlobal('fetch', fetchMock);
return fetchMock;
};
const firstFetchUrl = (fetchMock: ReturnType<typeof vi.fn>) => {
const input = fetchMock.mock.calls[0]?.[0];
if (input instanceof URL) return input.toString();
if (typeof input === 'string') return input;
return input?.toString() || '';
};
describe('reviewEditorService', () => {
test('adds session_id to sidecar API calls without mutating endpoint paths', async () => {
const fetchMock = stubJsonFetch();
await sidecarApi('http://127.0.0.1:5177', '/api/rows', {}, 'session-a');
const url = new URL(firstFetchUrl(fetchMock));
expect(url.pathname).toBe('/api/rows');
expect(url.searchParams.get('session_id')).toBe('session-a');
});
test('row operations are scoped to the current review session', async () => {
const fetchMock = stubJsonFetch();
await saveReviewRow('http://127.0.0.1:5177', 'session-a', 'R00001', {
current_html: '<span>译文</span>',
marked: true,
issue_type: '误译',
severity: '严重',
tags: '术语',
comment: '备注',
learn_note: '长期规则',
});
await retranslateReviewRow('http://127.0.0.1:5177', 'session-a', 'R00001', '');
await generateReviewFeedback('http://127.0.0.1:5177', 'session-a');
await exportReviewedEpub('http://127.0.0.1:5177', 'session-a');
expect(fetchMock).toHaveBeenCalledTimes(4);
for (const call of fetchMock.mock.calls as unknown as [
string | URL,
RequestInit | undefined,
][]) {
const input = call[0];
const url = new URL(input instanceof URL ? input.toString() : input);
expect(url.searchParams.get('session_id')).toBe('session-a');
}
});
test('inline launch creates sessions without activating sidecar global session', async () => {
const fetchMock = stubJsonFetch();
await createReviewSessionFromPath('http://127.0.0.1:5177', 'C:/books/demo.epub');
const calls = fetchMock.mock.calls as unknown as [string | URL, RequestInit][];
const body = JSON.parse(String(calls[0]?.[1]?.body || '{}'));
expect(body).toMatchObject({
epub_path: 'C:/books/demo.epub',
activate: false,
reset: false,
});
});
test('inline bootstrap and config operations are scoped to the current review session', async () => {
const fetchMock = vi.fn(async () =>
Response.json({
has_session: true,
rows: [],
entries: [],
}),
);
vi.stubGlobal('fetch', fetchMock);
await loadInlineReviewData('http://127.0.0.1:5177', 'session-a');
await saveReviewGptConfig('http://127.0.0.1:5177', 'session-a', {
base_url: 'https://api.example.test/v1',
model: 'model-a',
});
await loadReviewGlossary('http://127.0.0.1:5177', 'session-a');
await switchReviewGlossaryPath('http://127.0.0.1:5177', 'session-a', 'mingcibiao.json');
await saveReviewGlossary('http://127.0.0.1:5177', 'session-a', 'mingcibiao.json', []);
expect(fetchMock).toHaveBeenCalledTimes(7);
for (const call of fetchMock.mock.calls as unknown as [
string | URL,
RequestInit | undefined,
][]) {
const input = call[0];
const url = new URL(input instanceof URL ? input.toString() : input);
expect(url.searchParams.get('session_id')).toBe('session-a');
}
});
});