UNPKG

@translated/lara-mcp

Version:

Lara API official MCP server

342 lines (341 loc) 13.7 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { translateHandler, translateSchema } from '../../mcp/tools/translate.js'; import { getMockTranslator, setupTranslatorMock } from '../utils/mocks.js'; // Setup mocks setupTranslatorMock(); describe('translateSchema', () => { it('should validate valid input', () => { const validInput = { text: [{ text: 'Hello world', translatable: true }], target: 'it-IT' }; expect(() => translateSchema.parse(validInput)).not.toThrow(); }); it('should reject invalid input', () => { const invalidInput = { text: 'not an array', target: 'it-IT' }; expect(() => translateSchema.parse(invalidInput)).toThrow(); }); it('should require target language', () => { const missingTarget = { text: [{ text: 'Hello world', translatable: true }] }; expect(() => translateSchema.parse(missingTarget)).toThrow(); }); it('should validate glossaries parameter', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', glossaries: ['gls_xyz123', 'gls_abc456'] })).not.toThrow(); }); it('should validate no_trace parameter', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', no_trace: true })).not.toThrow(); }); it('should validate priority parameter', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', priority: 'background' })).not.toThrow(); }); it('should validate timeout_in_millis parameter', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', timeout_in_millis: 30000 })).not.toThrow(); }); it('should reject invalid priority value', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', priority: 'invalid' })).toThrow(); }); it('should reject negative timeout', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', timeout_in_millis: -1000 })).toThrow(); }); it('should reject Infinity timeout', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', timeout_in_millis: Infinity })).toThrow(); }); it('should reject timeout over max limit', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', timeout_in_millis: 999999999 })).toThrow(); }); it('should reject float timeout values', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', timeout_in_millis: 1000.5 })).toThrow(); }); it('should reject too many glossaries', () => { const tooMany = Array(100).fill('gls_abc123'); expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', glossaries: tooMany })).toThrow(); }); it('should reject invalid glossary ID format', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', glossaries: ['invalid-id'] })).toThrow(); }); it('should accept instructions with fewer than 20 words each', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: ['Translate formally', 'Use a creative and concise tone'] })).not.toThrow(); }); it('should accept an instruction with exactly 20 words', () => { const twentyWords = Array(20).fill('word').join(' '); expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: [twentyWords] })).not.toThrow(); }); it('should reject an instruction with more than 20 words', () => { const twentyOneWords = Array(21).fill('word').join(' '); expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: [twentyOneWords] })).toThrow(/no more than 20 words/); }); it('should reject when any instruction exceeds the word limit', () => { const longInstruction = Array(25).fill('word').join(' '); expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: ['Translate formally', longInstruction] })).toThrow(/no more than 20 words/); }); it('should accept an empty instructions array', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: [] })).not.toThrow(); }); it('should handle instructions with extra whitespace correctly', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', instructions: [' Translate formally '] })).not.toThrow(); }); it('should accept valid style values', () => { for (const style of ['faithful', 'fluid', 'creative']) { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', style })).not.toThrow(); } }); it('should reject invalid style value', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', style: 'invalid' })).toThrow(); }); it('should accept reasoning boolean', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', reasoning: true })).not.toThrow(); }); it('should reject non-boolean reasoning', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', reasoning: 'yes' })).toThrow(); }); it('should accept valid content_type values', () => { for (const content_type of ['text/plain', 'text/html', 'application/xliff+xml']) { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', content_type })).not.toThrow(); } }); it('should reject invalid content_type value', () => { expect(() => translateSchema.parse({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', content_type: 'application/json' })).toThrow(); }); }); describe('translateHandler', () => { let mockTranslator; beforeEach(() => { mockTranslator = getMockTranslator(); }); it('should call lara.translate with correct parameters', async () => { const mockTranslationResult = { translation: [{ text: 'Ciao mondo', translatable: true }] }; mockTranslator.translate.mockResolvedValue(mockTranslationResult); const args = { text: [{ text: 'Hello world', translatable: true }], target: 'it-IT' }; const result = await translateHandler(args, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(args.text, null, args.target, {}); expect(result).toEqual(mockTranslationResult.translation); }); it('should handle context and instructions', async () => { const mockTranslationResult = { translation: [{ text: 'Ciao mondo', translatable: true }] }; mockTranslator.translate.mockResolvedValue(mockTranslationResult); const args = { text: [{ text: 'Hello world', translatable: true }], target: 'it-IT', context: 'Casual conversation', instructions: ['Use informal tone'] }; await translateHandler(args, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(args.text, null, args.target, { instructions: [ 'Use informal tone', 'Always consider the following contextual information: Casual conversation' ], adaptTo: undefined }); }); it('should pass glossaries option to SDK', async () => { const mockTranslation = { translation: [{ text: 'ciao', translatable: true }], sourceLanguage: 'en-EN', contentType: 'text/plain' }; mockTranslator.translate.mockResolvedValue(mockTranslation); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', glossaries: ['gls_xyz123'] }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ glossaries: ['gls_xyz123'] })); }); it('should pass noTrace option to SDK', async () => { const mockTranslation = { translation: [{ text: 'ciao', translatable: true }], sourceLanguage: 'en-EN', contentType: 'text/plain' }; mockTranslator.translate.mockResolvedValue(mockTranslation); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', no_trace: true }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ noTrace: true })); }); it('should pass priority and timeout options to SDK', async () => { const mockTranslation = { translation: [{ text: 'ciao', translatable: true }], sourceLanguage: 'en-EN', contentType: 'text/plain' }; mockTranslator.translate.mockResolvedValue(mockTranslation); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', priority: 'background', timeout_in_millis: 60000 }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ priority: 'background', timeoutInMillis: 60000 })); }); it('should pass style option to SDK', async () => { mockTranslator.translate.mockResolvedValue({ translation: [{ text: 'ciao', translatable: true }] }); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', style: 'creative' }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ style: 'creative' })); }); it('should pass reasoning option to SDK', async () => { mockTranslator.translate.mockResolvedValue({ translation: [{ text: 'ciao', translatable: true }] }); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', reasoning: true }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ reasoning: true })); }); it('should pass content_type as contentType to SDK', async () => { mockTranslator.translate.mockResolvedValue({ translation: [{ text: 'ciao', translatable: true }] }); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', content_type: 'text/html' }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ contentType: 'text/html' })); }); it('should pass style, reasoning, and content_type together', async () => { mockTranslator.translate.mockResolvedValue({ translation: [{ text: 'ciao', translatable: true }] }); await translateHandler({ text: [{ text: 'hello', translatable: true }], target: 'it-IT', style: 'fluid', reasoning: true, content_type: 'application/xliff+xml' }, mockTranslator); expect(mockTranslator.translate).toHaveBeenCalledWith(expect.anything(), null, 'it-IT', expect.objectContaining({ style: 'fluid', reasoning: true, contentType: 'application/xliff+xml' })); }); });