CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
languageUtils.test.ts573 linesDownload Raw Back to utils
1/**2 * @license3 * Copyright 2025 Qwen team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, beforeEach, vi } from 'vitest';8import * as fs from 'node:fs';9import * as path from 'node:path';10 11// Mock fs module12vi.mock('node:fs', () => ({13  existsSync: vi.fn(),14  mkdirSync: vi.fn(),15  writeFileSync: vi.fn(),16  readFileSync: vi.fn(),17}));18 19// Mock i18n module20vi.mock('../i18n/index.js', () => ({21  detectSystemLanguage: vi.fn(),22  getLanguageNameFromLocale: vi.fn((locale: string) => {23    const map: Record<string, string> = {24      en: 'English',25      'zh-tw': 'Traditional Chinese',26      zh: 'Chinese',27      ru: 'Russian',28      de: 'German',29      ja: 'Japanese',30      ko: 'Korean',31      fr: 'French',32      es: 'Spanish',33    };34    return map[locale.toLowerCase()] || 'English';35  }),36}));37 38// Mock @qwen-code/qwen-code-core39vi.mock('@qwen-code/qwen-code-core', () => ({40  Storage: {41    getGlobalQwenDir: vi.fn(() => '/mock/home/.qwen'),42  },43}));44 45import * as i18n from '../i18n/index.js';46import {47  OUTPUT_LANGUAGE_AUTO,48  isAutoLanguage,49  normalizeOutputLanguage,50  resolveOutputLanguage,51  writeOutputLanguageFile,52  updateOutputLanguageFile,53  writeOutputLanguageAndRegisterPath,54  initializeLlmOutputLanguage,55} from './languageUtils.js';56 57describe('languageUtils', () => {58  beforeEach(() => {59    vi.clearAllMocks();60  });61 62  describe('OUTPUT_LANGUAGE_AUTO', () => {63    it('should be "auto"', () => {64      expect(OUTPUT_LANGUAGE_AUTO).toBe('auto');65    });66  });67 68  describe('isAutoLanguage', () => {69    it('should return true for "auto"', () => {70      expect(isAutoLanguage('auto')).toBe(true);71    });72 73    it('should return true for "AUTO" (case insensitive)', () => {74      expect(isAutoLanguage('AUTO')).toBe(true);75    });76 77    it('should return true for "Auto" (case insensitive)', () => {78      expect(isAutoLanguage('Auto')).toBe(true);79    });80 81    it('should return true for undefined', () => {82      expect(isAutoLanguage(undefined)).toBe(true);83    });84 85    it('should return true for null', () => {86      expect(isAutoLanguage(null)).toBe(true);87    });88 89    it('should return true for empty string', () => {90      expect(isAutoLanguage('')).toBe(true);91    });92 93    it('should return false for explicit language', () => {94      expect(isAutoLanguage('Chinese')).toBe(false);95    });96 97    it('should return false for locale code', () => {98      expect(isAutoLanguage('zh')).toBe(false);99    });100  });101 102  describe('normalizeOutputLanguage', () => {103    it('should convert "en" to "English"', () => {104      expect(normalizeOutputLanguage('en')).toBe('English');105    });106 107    it('should normalize "english" to "English"', () => {108      expect(normalizeOutputLanguage('english')).toBe('English');109      expect(normalizeOutputLanguage('English')).toBe('English');110      expect(normalizeOutputLanguage('en-US')).toBe('English');111    });112 113    it('should convert "zh" to "Chinese"', () => {114      expect(normalizeOutputLanguage('zh')).toBe('Chinese');115    });116 117    it('should convert "ru" to "Russian"', () => {118      expect(normalizeOutputLanguage('ru')).toBe('Russian');119    });120 121    it('should convert "de" to "German"', () => {122      expect(normalizeOutputLanguage('de')).toBe('German');123    });124 125    it('should convert "ja" to "Japanese"', () => {126      expect(normalizeOutputLanguage('ja')).toBe('Japanese');127    });128 129    it('should be case insensitive for locale codes', () => {130      expect(normalizeOutputLanguage('ZH')).toBe('Chinese');131      expect(normalizeOutputLanguage('Ru')).toBe('Russian');132    });133 134    it('should convert "zh-TW" (mixed case) to "Traditional Chinese"', () => {135      expect(normalizeOutputLanguage('zh-TW')).toBe('Traditional Chinese');136      expect(normalizeOutputLanguage('zh-tw')).toBe('Traditional Chinese');137      expect(normalizeOutputLanguage('ZH-TW')).toBe('Traditional Chinese');138    });139 140    it('should preserve explicit language names as-is', () => {141      expect(normalizeOutputLanguage('Japanese')).toBe('Japanese');142      expect(normalizeOutputLanguage('French')).toBe('French');143      expect(normalizeOutputLanguage('french')).toBe('French');144    });145 146    it('should preserve unknown language names as-is', () => {147      expect(normalizeOutputLanguage('CustomLanguage')).toBe('CustomLanguage');148      expect(normalizeOutputLanguage('日本語')).toBe('日本語');149    });150  });151 152  describe('resolveOutputLanguage', () => {153    it('should resolve "auto" to detected system language', () => {154      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');155 156      expect(resolveOutputLanguage('auto')).toBe('Chinese');157      expect(i18n.detectSystemLanguage).toHaveBeenCalled();158    });159 160    it('should resolve undefined to detected system language', () => {161      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('ru');162 163      expect(resolveOutputLanguage(undefined)).toBe('Russian');164    });165 166    it('should resolve null to detected system language', () => {167      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('de');168 169      expect(resolveOutputLanguage(null)).toBe('German');170    });171 172    it('should normalize explicit locale codes', () => {173      expect(resolveOutputLanguage('zh')).toBe('Chinese');174      expect(i18n.detectSystemLanguage).not.toHaveBeenCalled();175    });176 177    it('should normalize "english"', () => {178      expect(resolveOutputLanguage('english')).toBe('English');179      expect(resolveOutputLanguage('en-US')).toBe('English');180      expect(i18n.detectSystemLanguage).not.toHaveBeenCalled();181    });182 183    it('should preserve explicit language names', () => {184      expect(resolveOutputLanguage('Japanese')).toBe('Japanese');185    });186  });187 188  describe('writeOutputLanguageFile', () => {189    beforeEach(() => {190      vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);191      vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);192    });193 194    it('should create directory and write file', () => {195      writeOutputLanguageFile('Chinese');196 197      const globalDir = '/mock/home/.qwen';198      const expectedDir = path.join(globalDir);199      const expectedFilePath = path.join(globalDir, 'output-language.md');200 201      expect(fs.mkdirSync).toHaveBeenCalledWith(expectedDir, {202        recursive: true,203      });204      expect(fs.writeFileSync).toHaveBeenCalledWith(205        expectedFilePath,206        expect.any(String),207        'utf-8',208      );209    });210 211    it('should include language in file content', () => {212      writeOutputLanguageFile('Japanese');213 214      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];215      expect(writtenContent).toContain('Japanese');216      expect(writtenContent).toContain(217        '# Output language preference: Japanese',218      );219    });220 221    it('should include machine-readable marker', () => {222      writeOutputLanguageFile('Chinese');223 224      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];225      expect(writtenContent).toContain(226        '<!-- qwen-code:llm-output-language: Chinese -->',227      );228    });229 230    it('should sanitize language for marker (remove dangerous characters)', () => {231      writeOutputLanguageFile('Test--Language');232 233      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];234      // The marker should have -- removed, but the heading preserves original235      expect(writtenContent).toContain(236        '# Output language preference: Test--Language',237      );238      expect(writtenContent).toContain(239        '<!-- qwen-code:llm-output-language: TestLanguage -->',240      );241    });242 243    it('should use mandatory language rule instead of preference', () => {244      writeOutputLanguageFile('Chinese');245 246      const writtenContent = vi.mocked(fs.writeFileSync).mock247        .calls[0][1] as string;248      expect(writtenContent).toContain(249        'You MUST always respond in **Chinese**',250      );251      expect(writtenContent).toContain(252        'This is a mandatory requirement, not a preference.',253      );254      expect(writtenContent).not.toContain('Prefer responding');255    });256 257    it('should write to custom targetPath when provided', () => {258      writeOutputLanguageFile('Korean', '/proj/.qwen/output-language.md');259 260      expect(fs.mkdirSync).toHaveBeenCalledWith('/proj/.qwen', {261        recursive: true,262      });263      expect(fs.writeFileSync).toHaveBeenCalledWith(264        '/proj/.qwen/output-language.md',265        expect.stringContaining('Korean'),266        'utf-8',267      );268    });269 270    it('should include exception clause for explicit user language requests', () => {271      writeOutputLanguageFile('English');272 273      const writtenContent = vi.mocked(fs.writeFileSync).mock274        .calls[0][1] as string;275      expect(writtenContent).toContain('## Exception');276      expect(writtenContent).toContain(277        "switch to the user's requested language for the remainder of the conversation",278      );279    });280 281    it('should use the correct language name throughout the template', () => {282      writeOutputLanguageFile('Japanese');283 284      const writtenContent = vi.mocked(fs.writeFileSync).mock285        .calls[0][1] as string;286      expect(writtenContent).toContain(287        'You MUST always respond in **Japanese**',288      );289      expect(writtenContent).toContain('## Rule');290      expect(writtenContent).toContain('## Exception');291    });292  });293 294  describe('updateOutputLanguageFile', () => {295    beforeEach(() => {296      vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);297      vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);298    });299 300    it('should resolve "auto" and write resolved language', () => {301      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');302 303      updateOutputLanguageFile('auto');304 305      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];306      expect(writtenContent).toContain('Chinese');307    });308 309    it('should normalize locale codes and write full name', () => {310      updateOutputLanguageFile('ja');311 312      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];313      expect(writtenContent).toContain('Japanese');314    });315 316    it('should write explicit language names directly', () => {317      updateOutputLanguageFile('French');318 319      const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1];320      expect(writtenContent).toContain('French');321    });322  });323 324  describe('initializeLlmOutputLanguage', () => {325    beforeEach(() => {326      vi.mocked(fs.existsSync).mockReturnValue(false);327      vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);328      vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);329      vi.mocked(fs.readFileSync).mockReturnValue('');330    });331 332    it('should create file when it does not exist', () => {333      vi.mocked(fs.existsSync).mockReturnValue(false);334      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('en');335 336      initializeLlmOutputLanguage();337 338      expect(fs.mkdirSync).toHaveBeenCalled();339      expect(fs.writeFileSync).toHaveBeenCalledWith(340        expect.stringContaining('output-language.md'),341        expect.stringContaining('English'),342        'utf-8',343      );344    });345 346    it('should NOT overwrite file when it already exists with valid content', () => {347      vi.mocked(fs.existsSync).mockReturnValue(true);348      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('en');349      vi.mocked(fs.readFileSync).mockReturnValue(350        `# Output language preference: French351<!-- qwen-code:llm-output-language: French -->352`,353      );354 355      initializeLlmOutputLanguage();356 357      expect(fs.writeFileSync).not.toHaveBeenCalled();358    });359 360    it('should NOT overwrite file even when setting differs from existing content', () => {361      vi.mocked(fs.existsSync).mockReturnValue(true);362      vi.mocked(fs.readFileSync).mockReturnValue(363        `# Output language preference: French364<!-- qwen-code:llm-output-language: French -->365`,366      );367 368      initializeLlmOutputLanguage('Japanese');369 370      // Should NOT overwrite - user's existing file takes precedence371      expect(fs.writeFileSync).not.toHaveBeenCalled();372    });373 374    it('should resolve "auto" to detected system language', () => {375      vi.mocked(fs.existsSync).mockReturnValue(false);376      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');377 378      initializeLlmOutputLanguage('auto');379 380      expect(fs.writeFileSync).toHaveBeenCalledWith(381        expect.stringContaining('output-language.md'),382        expect.stringContaining('Chinese'),383        'utf-8',384      );385    });386 387    it('should detect Chinese locale and create Chinese rule file', () => {388      vi.mocked(fs.existsSync).mockReturnValue(false);389      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');390 391      initializeLlmOutputLanguage();392 393      expect(fs.writeFileSync).toHaveBeenCalledWith(394        expect.stringContaining('output-language.md'),395        expect.stringContaining('Chinese'),396        'utf-8',397      );398    });399 400    it('should detect Russian locale and create Russian rule file', () => {401      vi.mocked(fs.existsSync).mockReturnValue(false);402      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('ru');403 404      initializeLlmOutputLanguage();405 406      expect(fs.writeFileSync).toHaveBeenCalledWith(407        expect.stringContaining('output-language.md'),408        expect.stringContaining('Russian'),409        'utf-8',410      );411    });412 413    it('should detect German locale and create German rule file', () => {414      vi.mocked(fs.existsSync).mockReturnValue(false);415      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('de');416 417      initializeLlmOutputLanguage();418 419      expect(fs.writeFileSync).toHaveBeenCalledWith(420        expect.stringContaining('output-language.md'),421        expect.stringContaining('German'),422        'utf-8',423      );424    });425 426    it('should handle file read errors gracefully', () => {427      vi.mocked(fs.existsSync).mockReturnValue(true);428      vi.mocked(fs.readFileSync).mockImplementation(() => {429        throw new Error('Read error');430      });431      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('en');432 433      // Should not throw, and should create new file434      expect(() => initializeLlmOutputLanguage()).not.toThrow();435      expect(fs.writeFileSync).toHaveBeenCalled();436    });437 438    it('should parse legacy heading format', () => {439      vi.mocked(fs.existsSync).mockReturnValue(true);440      vi.mocked(fs.readFileSync).mockReturnValue(441        '# CRITICAL: Chinese Output Language Rule - HIGHEST PRIORITY',442      );443      vi.mocked(i18n.detectSystemLanguage).mockReturnValue('zh');444 445      initializeLlmOutputLanguage();446 447      // Should not overwrite since file already has Chinese448      expect(fs.writeFileSync).not.toHaveBeenCalled();449    });450  });451 452  describe('output-language.md path resolution priority', () => {453    it('should prefer project-level path over global path', () => {454      const projectPath = '/project/.qwen/output-language.md';455      const globalPath = '/mock/home/.qwen/output-language.md';456 457      vi.mocked(fs.existsSync).mockImplementation((p) => {458        if (p.toString() === projectPath) return true;459        if (p.toString() === globalPath) return true;460        return false;461      });462 463      let resolvedPath: string | undefined;464      if (fs.existsSync(projectPath)) {465        resolvedPath = projectPath;466      } else if (fs.existsSync(globalPath)) {467        resolvedPath = globalPath;468      }469 470      expect(resolvedPath).toBe(projectPath);471    });472 473    it('should fall back to global path when project-level does not exist', () => {474      const projectPath = '/project/.qwen/output-language.md';475      const globalPath = '/mock/home/.qwen/output-language.md';476 477      vi.mocked(fs.existsSync).mockImplementation((p) => {478        if (p.toString() === projectPath) return false;479        if (p.toString() === globalPath) return true;480        return false;481      });482 483      let resolvedPath: string | undefined;484      if (fs.existsSync(projectPath)) {485        resolvedPath = projectPath;486      } else if (fs.existsSync(globalPath)) {487        resolvedPath = globalPath;488      }489 490      expect(resolvedPath).toBe(globalPath);491    });492 493    it('should return undefined when neither path exists', () => {494      const projectPath = '/project/.qwen/output-language.md';495      const globalPath = '/mock/home/.qwen/output-language.md';496 497      vi.mocked(fs.existsSync).mockReturnValue(false);498 499      let resolvedPath: string | undefined;500      if (fs.existsSync(projectPath)) {501        resolvedPath = projectPath;502      } else if (fs.existsSync(globalPath)) {503        resolvedPath = globalPath;504      }505 506      expect(resolvedPath).toBeUndefined();507    });508  });509 510  describe('writeOutputLanguageAndRegisterPath', () => {511    beforeEach(() => {512      vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);513      vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);514    });515 516    it('writes to config-bound path when available', () => {517      const config = {518        getOutputLanguageFilePath: vi519          .fn()520          .mockReturnValue('/proj/.qwen/output-language.md'),521        setOutputLanguageFilePath: vi.fn(),522      };523 524      writeOutputLanguageAndRegisterPath('Chinese', config);525 526      expect(fs.writeFileSync).toHaveBeenCalledWith(527        '/proj/.qwen/output-language.md',528        expect.stringContaining('Chinese'),529        'utf-8',530      );531      expect(config.setOutputLanguageFilePath).not.toHaveBeenCalled();532    });533 534    it('writes to global default and registers path when config path is undefined', () => {535      const config = {536        getOutputLanguageFilePath: vi.fn().mockReturnValue(undefined),537        setOutputLanguageFilePath: vi.fn(),538      };539 540      writeOutputLanguageAndRegisterPath('Japanese', config);541 542      expect(fs.writeFileSync).toHaveBeenCalledWith(543        expect.stringContaining('output-language.md'),544        expect.stringContaining('Japanese'),545        'utf-8',546      );547      expect(config.setOutputLanguageFilePath).toHaveBeenCalledWith(548        expect.stringContaining('output-language.md'),549      );550    });551 552    it('handles null config gracefully', () => {553      writeOutputLanguageAndRegisterPath('Korean', null);554 555      expect(fs.writeFileSync).toHaveBeenCalledWith(556        expect.stringContaining('output-language.md'),557        expect.stringContaining('Korean'),558        'utf-8',559      );560    });561 562    it('handles undefined config gracefully', () => {563      writeOutputLanguageAndRegisterPath('Russian', undefined);564 565      expect(fs.writeFileSync).toHaveBeenCalledWith(566        expect.stringContaining('output-language.md'),567        expect.stringContaining('Russian'),568        'utf-8',569      );570    });571  });572});573 
basant307/AI_Governance_Project · CoolFace