Files
deepseek-harness/scripts/translation-prompt.spec.ts
2026-07-23 23:12:56 +08:00

201 lines
8.4 KiB
TypeScript

/** Unit tests for the prompt-v4 renderer and three-section response parser. */
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
consumeTranslationResponse,
parseTranslationResponse,
renderTranslationPrompt,
renderTranslationRequest,
renderTranslationResponse,
} from './translation-prompt.ts'
const root = resolve(import.meta.dirname, '..')
const document = readFileSync(join(root, 'docs/i18n/translation-prompt.md'), 'utf8')
const terminology = '| English | 中文 |\n|---|---|\n| agent | agent |'
describe('translation prompt rendering', () => {
it('renders both directions with every placeholder resolved', () => {
const en = renderTranslationPrompt(document, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })
expect(en).toContain('from English to Chinese')
expect(en).toContain(terminology)
expect(en).not.toContain('{{')
expect(en).toContain('plain source stays plain (必须)')
expect(en).toContain('When the target language is English, use the "English" column without a Chinese gloss')
expect(en).toContain('for a Chinese target, use an established Chinese rendering')
expect(en).toContain('for an English target, use the established English technical term')
expect(en).toContain('does an English target use established English terminology')
expect(en).toContain('The parser removes exactly one framing escape')
const zh = renderTranslationPrompt(document, { sourceLanguage: 'Chinese', sourceFilename: 'guide.zh.md', terminology })
expect(zh).toContain('from Chinese to English')
})
it('rejects a template with unknown or missing placeholders', () => {
const alien = document.replaceAll('{{terminology}}', '{{terms_prompt}}')
expect(() => renderTranslationPrompt(alien, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/unsupported placeholder/)
const missing = document.replaceAll('{{terminology}}', '')
expect(() => renderTranslationPrompt(missing, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/required placeholder/)
})
it('rejects unmatched placeholder delimiters', () => {
for (const delimiter of ['{{', '}}']) {
const malformed = document.replace('Your task is to translate', `Your task ${delimiter} is to translate`)
expect(() => renderTranslationPrompt(malformed, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/malformed placeholder syntax/)
}
})
it('assembles bare few-shot turns before the real source document', () => {
const request = renderTranslationRequest(document, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
sourceDocument: '# Guide\n\nNew source.',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(request.targetFilename).toBe('guide.zh.md')
expect(request.messages.map(message => message.role)).toEqual(['system', 'user', 'assistant', 'user'])
expect(request.messages.slice(1).map(message => message.content)).toEqual([
'# Example\n\nEnglish.',
'# 示例\n\n中文。',
'# Guide\n\nNew source.',
])
const reverse = renderTranslationRequest(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.zh.md',
sourceDocument: '# 指南\n\n新源文。',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(reverse.targetFilename).toBe('guide.md')
expect(reverse.messages.slice(1).map(message => message.content)).toEqual([
'# 示例\n\n中文。',
'# Example\n\nEnglish.',
'# 指南\n\n新源文。',
])
})
})
describe('translation response sections', () => {
it('round-trips Markdown bodies', () => {
const response = { translation: '# 标题\n\n正文 **加粗**。', review: '- [Tone] 修正一处。\n- 无修正', final: '# 标题\n\n定稿。' }
expect(parseTranslationResponse(renderTranslationResponse(response))).toEqual(response)
})
it('tolerates a fenced xml wrapper around the whole response', () => {
const fenced = '```xml\n<translation>\nA\n</translation>\n\n<review>\n- 无修正\n</review>\n\n<final>\nA\n</final>\n```'
expect(parseTranslationResponse(fenced).final).toBe('A')
})
it('keeps an inline close tag inside prose from terminating the section', () => {
const doc = { translation: 'the wire format uses </translation> as its close tag', review: '- 无修正', final: 'F' }
expect(parseTranslationResponse(renderTranslationResponse(doc))).toEqual(doc)
})
it('round-trips wrapper-tag lines inside Markdown bodies', () => {
const doc = {
translation: '```xml\n</translation>\n```',
review: '- [Structure] Preserved `<final>` on its own line.',
final: 'literal delimiters\n</final>\n\\</final>',
}
const rendered = renderTranslationResponse(doc)
expect(parseTranslationResponse(rendered)).toEqual(doc)
expect(() => parseTranslationResponse(rendered.replace('\\</translation>', '</translation>'))).toThrow(/duplicate <translation>/)
})
it('rejects a duplicate section appearing before final', () => {
const early = '<translation>\nA\n</translation>\n<translation>\nB\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>'
expect(() => parseTranslationResponse(early)).toThrow(/duplicate <translation>/)
})
it('rejects missing, unterminated, or duplicated sections', () => {
expect(() => parseTranslationResponse('<translation>\nA\n</translation>')).toThrow(/missing or unterminated <review>/)
expect(() => parseTranslationResponse('<translation>\nA')).toThrow(/missing or unterminated <translation>/)
const dup = '<translation>\nA\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>\n<final>\nG\n</final>'
expect(() => parseTranslationResponse(dup)).toThrow(/duplicate <final>/)
expect(() => parseTranslationResponse(`${renderTranslationResponse({ translation: 'A', review: 'R', final: 'F' })}\nstray`))
.toThrow(/content is not allowed outside/)
})
it('inserts or corrects the target switcher after parsing a new-pair response', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '# 指南\n\nEnglish | [中文](guide.zh.md)\n\n定稿。',
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
it('preserves YAML frontmatter before inserting the target switcher', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: [
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'定稿。',
].join('\n'),
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
it('rejects unterminated YAML frontmatter before the target H1', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '---\nlayout: home\n\n# 指南\n\n定稿。',
})
expect(() => consumeTranslationResponse(response, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
})).toThrow(/unterminated YAML frontmatter/)
})
it('rejects a source filename that contradicts the translation direction', () => {
expect(() => renderTranslationPrompt(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/does not match source language Chinese/)
})
it('inserts the English target switcher for a Chinese source', () => {
const response = renderTranslationResponse({
translation: '# Guide\n\nDraft.',
review: '- [None] No corrections.',
final: '# Guide\n\nFinal.',
})
expect(consumeTranslationResponse(response, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.zh.md',
}).final).toContain('\n\nEnglish | [中文](guide.zh.md)\n\n')
})
})