UNPKG

auto-translatr

Version:
399 lines (398 loc) 16.4 kB
import * as fs from 'fs'; import * as path from 'path'; import { getDefaultSettings, readSettingsFile, createDefaultSettingsFile, loadSettings } from '../settingsReader'; import { isLeft, isRight } from '../utils'; // Mock console methods const originalConsoleLog = console.log; const originalConsoleWarn = console.warn; const originalConsoleError = console.error; beforeEach(() => { console.log = jest.fn(); console.warn = jest.fn(); console.error = jest.fn(); // Clear all mocks before each test jest.clearAllMocks(); jest.restoreAllMocks(); }); afterEach(() => { console.log = originalConsoleLog; console.warn = originalConsoleWarn; console.error = originalConsoleError; // Restore all mocks after each test jest.restoreAllMocks(); }); describe('settingsReader', () => { const testDir = path.join(__dirname, 'test-settings'); const settingsFileName = 'auto-translate.settings.json'; const settingsFilePath = path.join(testDir, settingsFileName); const originalEnv = process.env.OPENAI_API_KEY; beforeEach(() => { // Create test directory if (fs.existsSync(testDir)) { try { fs.rmSync(testDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } } fs.mkdirSync(testDir, { recursive: true }); // Clean environment delete process.env.OPENAI_API_KEY; }); afterEach(() => { // Clean up test directory with retry logic let retries = 3; while (retries > 0) { try { if (fs.existsSync(testDir)) { fs.rmSync(testDir, { recursive: true, force: true }); } break; } catch (error) { retries--; if (retries === 0) { console.warn('Could not clean up test directory:', error); } } } // Clean up any settings files in current directory try { const currentDirSettingsFile = path.join(process.cwd(), settingsFileName); if (fs.existsSync(currentDirSettingsFile)) { fs.rmSync(currentDirSettingsFile); } } catch (error) { // Ignore cleanup errors } // Restore environment if (originalEnv) { process.env.OPENAI_API_KEY = originalEnv; } else { delete process.env.OPENAI_API_KEY; } }); describe('getDefaultSettings', () => { it('should return default settings without environment variable', () => { const settings = getDefaultSettings(); expect(settings).toEqual({ default: 'en', supported: ['en', 'fr', 'de'], openai: { model: 'gpt-4o-mini', apiKey: '' } }); }); it('should include environment variable for API key when available', () => { process.env.OPENAI_API_KEY = 'test-api-key'; const settings = getDefaultSettings(); expect(settings).toEqual({ default: 'en', supported: ['en', 'fr', 'de'], openai: { model: 'gpt-4o-mini', apiKey: 'test-api-key' } }); }); }); describe('readSettingsFile', () => { const originalCwd = process.cwd(); afterEach(() => { process.chdir(originalCwd); }); it('should return Left when settings file does not exist', () => { process.chdir(testDir); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value.message).toBe('Settings file auto-translate.settings.json not found'); } }); it('should return Right with settings when file exists and is valid', () => { const validSettings = { default: 'es', supported: ['es', 'en', 'pt'], openai: { model: 'gpt-4', apiKey: 'file-api-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(validSettings, null, 2)); const result = readSettingsFile(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value).toEqual(validSettings); } }); it('should automatically add default language to supported array when missing', () => { const settingsWithMissingDefault = { default: 'ja', supported: ['en', 'fr'], openai: { model: 'gpt-4', apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithMissingDefault, null, 2)); const result = readSettingsFile(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value.supported).toEqual(['ja', 'en', 'fr']); expect(result.value.default).toBe('ja'); } // Should log a warning expect(console.warn).toHaveBeenCalledWith("⚠️ Default language 'ja' not found in supported languages. Adding it automatically."); }); it('should merge environment API key when file has empty API key', () => { process.env.OPENAI_API_KEY = 'env-api-key'; const settingsWithoutApiKey = { default: 'fr', supported: ['fr', 'en'], openai: { model: 'gpt-3.5-turbo', apiKey: '' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithoutApiKey, null, 2)); const result = readSettingsFile(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value.openai.apiKey).toBe('env-api-key'); } }); it('should prefer file API key over environment when file has non-empty API key', () => { process.env.OPENAI_API_KEY = 'env-api-key'; const settingsWithApiKey = { default: 'de', supported: ['de', 'en'], openai: { model: 'gpt-4', apiKey: 'file-api-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithApiKey, null, 2)); const result = readSettingsFile(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value.openai.apiKey).toBe('file-api-key'); } }); it('should return Left when settings file has invalid JSON', () => { process.chdir(testDir); fs.writeFileSync(settingsFilePath, '{ invalid json }'); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value).toBeInstanceOf(Error); } }); it('should return Left when default field is missing', () => { const settingsWithoutDefault = { supported: ['en', 'fr'], openai: { model: 'gpt-4', apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithoutDefault, null, 2)); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value.message).toBe('Settings file is missing required field: default'); } }); it('should return Left when supported field is missing', () => { const settingsWithoutSupported = { default: 'en', openai: { model: 'gpt-4', apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithoutSupported, null, 2)); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value.message).toBe('Settings file is missing required field: supported (must be a non-empty array)'); } }); it('should return Left when supported field is empty array', () => { const settingsWithEmptySupported = { default: 'en', supported: [], openai: { model: 'gpt-4', apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithEmptySupported, null, 2)); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value.message).toBe('Settings file is missing required field: supported (must be a non-empty array)'); } }); it('should return Left when openai.model field is missing', () => { const settingsWithoutModel = { default: 'en', supported: ['en', 'fr'], openai: { apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithoutModel, null, 2)); const result = readSettingsFile(); expect(isLeft(result)).toBe(true); if (isLeft(result)) { expect(result.value.message).toBe('Settings file is missing required field: openai.model'); } }); }); describe('createDefaultSettingsFile', () => { const originalCwd = process.cwd(); afterEach(() => { process.chdir(originalCwd); }); it('should create settings file with default values', () => { process.chdir(testDir); const result = createDefaultSettingsFile(); expect(isRight(result)).toBe(true); expect(fs.existsSync(settingsFilePath)).toBe(true); const fileContent = JSON.parse(fs.readFileSync(settingsFilePath, 'utf-8')); expect(fileContent).toEqual({ default: 'en', supported: ['en', 'fr', 'de'], openai: { model: 'gpt-4o-mini', apiKey: '' // Should be empty in file for security } }); if (isRight(result)) { // But should return settings with environment API key if available expect(result.value.openai.apiKey).toBe(''); } }); it('should create settings file and return settings with environment API key', () => { process.env.OPENAI_API_KEY = 'test-env-key'; process.chdir(testDir); const result = createDefaultSettingsFile(); expect(isRight(result)).toBe(true); // File should still have empty API key for security const fileContent = JSON.parse(fs.readFileSync(settingsFilePath, 'utf-8')); expect(fileContent.openai.apiKey).toBe(''); // But returned settings should have environment API key if (isRight(result)) { expect(result.value.openai.apiKey).toBe('test-env-key'); } }); it('should log creation messages', () => { process.chdir(testDir); createDefaultSettingsFile(); expect(console.log).toHaveBeenCalledWith('📄 Created auto-translate.settings.json with default settings'); expect(console.log).toHaveBeenCalledWith('💡 Please set your OpenAI API key in the file or as OPENAI_API_KEY environment variable'); }); }); describe('loadSettings', () => { const originalCwd = process.cwd(); afterEach(() => { process.chdir(originalCwd); }); it('should return existing settings when file exists', () => { const existingSettings = { default: 'ja', supported: ['ja', 'en', 'ko'], openai: { model: 'gpt-4-turbo', apiKey: 'existing-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(existingSettings, null, 2)); const result = loadSettings(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value).toEqual(existingSettings); } expect(console.log).toHaveBeenCalledWith('⚙️ Loaded settings from auto-translate.settings.json'); }); it('should create default settings when file does not exist', () => { process.chdir(testDir); const result = loadSettings(); expect(isRight(result)).toBe(true); expect(fs.existsSync(settingsFilePath)).toBe(true); if (isRight(result)) { expect(result.value).toEqual({ default: 'en', supported: ['en', 'fr', 'de'], openai: { model: 'gpt-4o-mini', apiKey: '' } }); } expect(console.log).toHaveBeenCalledWith('⚙️ Settings file auto-translate.settings.json not found, creating default settings...'); }); it('should auto-fix settings when default language is missing from supported array', () => { const settingsWithMissingDefault = { default: 'pt', supported: ['en', 'fr'], openai: { model: 'gpt-4', apiKey: 'test-key' } }; process.chdir(testDir); fs.writeFileSync(settingsFilePath, JSON.stringify(settingsWithMissingDefault, null, 2)); const result = loadSettings(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value.supported).toEqual(['pt', 'en', 'fr']); expect(result.value.default).toBe('pt'); } expect(console.warn).toHaveBeenCalledWith("⚠️ Default language 'pt' not found in supported languages. Adding it automatically."); }); }); describe('Settings type validation', () => { it('should handle settings with all required fields', () => { const validSettings = { default: 'pt', supported: ['pt', 'es', 'en'], openai: { model: 'gpt-4', apiKey: 'test-key' } }; const originalCwd = process.cwd(); process.chdir(testDir); try { fs.writeFileSync(settingsFilePath, JSON.stringify(validSettings, null, 2)); const result = readSettingsFile(); expect(isRight(result)).toBe(true); if (isRight(result)) { expect(result.value.default).toBe('pt'); expect(result.value.supported).toContain('pt'); expect(result.value.supported).toContain('es'); expect(result.value.supported).toContain('en'); expect(result.value.openai.model).toBe('gpt-4'); expect(result.value.openai.apiKey).toBe('test-key'); } } finally { process.chdir(originalCwd); } }); }); });