/** * ContentValidator テスト * * TSK-TEST-003 * REQ-CONT-001: コンテンツ有効性検証 */ import { describe, it, expect, beforeEach } from 'vitest'; import { ContentValidator, type ContentValidationConfig, type ContentValidationResult, validateContent, calculateMeaningfulRatio, detectContentType, DEFAULT_CONTENT_VALIDATION_CONFIG, } from '../content-validator.js'; describe('ContentValidator', () => { let validator: ContentValidator; beforeEach(() => { validator = new ContentValidator(); }); describe('validate', () => { it('有効なコンテンツをvalidと判定する', () => { const content = ` これは十分な長さのテキストコンテンツです。 意味のある情報が含まれています。 複数の段落があり、適切な構造を持っています。 ユーザーにとって有用な情報を提供しています。 `; const result = validator.validate(content); expect(result.status).toBe('valid'); expect(result.meaningfulRatio).toBeGreaterThan(0.5); }); it('空のコンテンツをemptyと判定する', () => { const result = validator.validate(''); expect(result.status).toBe('empty'); expect(result.warnings).toContain('コンテンツが空です'); }); it('空白のみのコンテンツをemptyと判定する', () => { const result = validator.validate(' \n\t '); expect(result.status).toBe('empty'); }); it('短すぎるコンテンツをtoo_shortと判定する', () => { const result = validator.validate('短い'); expect(result.status).toBe('too_short'); expect(result.warnings).toBeDefined(); }); it('ブロックされたコンテンツを検出する', () => { const blockedContents = [ 'Access Denied', '403 Forbidden', 'Please enable JavaScript to view the page', 'このサイトにアクセスする権限がありません', 'CAPTCHA', ]; for (const content of blockedContents) { const result = validator.validate(content + '\n'.repeat(100)); expect(result.status).toBe('blocked'); } }); it('低品質コンテンツにwarningを出す', () => { // 意味のない文字の繰り返し const content = 'a'.repeat(200) + '\n' + 'b'.repeat(200); const result = validator.validate(content); expect(result.status).toBe('warning'); expect(result.warnings?.some((w) => w.includes('意味のある'))).toBe(true); }); }); describe('validateHTML', () => { it('HTMLコンテンツからテキストを抽出して検証する', () => { const html = `
これは十分な長さの本文テキストです。意味のある情報が含まれています。
複数の段落があり、適切な構造を持っています。
`; const result = validator.validateHTML(html); expect(result.status).toBe('valid'); expect(result.contentType).toBe('html'); }); it('script/style タグを除外する', () => { const html = `これは本文です。十分な長さがあり、意味のある情報を含んでいます。
`; const result = validator.validateHTML(html); expect(result.extractedText).not.toContain('color: red'); expect(result.extractedText).not.toContain('console.log'); }); }); describe('getOptions', () => { it('デフォルトオプションを返す', () => { const options = validator.getOptions(); expect(options.minLength).toBe(DEFAULT_VALIDATION_OPTIONS.minLength); expect(options.minMeaningfulRatio).toBe( DEFAULT_VALIDATION_OPTIONS.minMeaningfulRatio ); }); it('カスタムオプションを反映する', () => { const customValidator = new ContentValidator({ minLength: 200, minMeaningfulRatio: 0.8, }); const options = customValidator.getOptions(); expect(options.minLength).toBe(200); expect(options.minMeaningfulRatio).toBe(0.8); }); }); }); describe('validateContent', () => { it('関数として直接呼び出せる', () => { const content = 'これは十分な長さのテキストです。'.repeat(10); const result = validateContent(content); expect(result.status).toBeDefined(); expect(result.meaningfulRatio).toBeDefined(); }); it('オプションを受け入れる', () => { const result = validateContent('短い', { minLength: 5 }); // minLength: 5 で「短い」は3文字なのでtoo_short expect(result.status).toBe('too_short'); }); }); describe('calculateMeaningfulRatio', () => { it('すべて意味のある文字の場合、1.0を返す', () => { const ratio = calculateMeaningfulRatio('これは意味のある文章です'); expect(ratio).toBeCloseTo(1.0, 1); }); it('空白が多い場合、低い値を返す', () => { const ratio = calculateMeaningfulRatio('a b c d'); expect(ratio).toBeLessThan(0.5); }); it('空文字列の場合、0を返す', () => { const ratio = calculateMeaningfulRatio(''); expect(ratio).toBe(0); }); it('日本語テキストを正しく処理する', () => { const ratio = calculateMeaningfulRatio('日本語のテキストです。'); expect(ratio).toBeGreaterThan(0.8); }); it('記号のみの場合、低い値を返す', () => { const ratio = calculateMeaningfulRatio('...---...---...---'); expect(ratio).toBeLessThan(0.3); }); }); describe('detectContentType', () => { it('HTMLを検出する', () => { const type = detectContentType('Test'); expect(type).toBe('html'); }); it('XMLを検出する', () => { const type = detectContentType('