122 lines
3.9 KiB
TypeScript
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');
|
|
}
|
|
});
|
|
});
|