import { describe, test, expect } from 'bun:test'; import { analyzeTocPage } from '../src/services/analyzer.ts'; const SCOPE = '#list-chapter .chapter-archive-grid'; const PATTERN = 'https://novelbin\\.com/b/the-mech-touch/.*'; function makeHtml(opts: { title?: string; body?: string; links?: string[]; extraHead?: string; }) { const links = (opts.links ?? []) .map(href => `link`) .join(''); return ` ${opts.title ?? 'Test Page'} ${opts.extraHead ?? ''} ${opts.body ?? ''}${links} `; } describe('analyzer — Cloudflare detection', () => { test('flags "Just a moment" title as Cloudflare', () => { const html = makeHtml({ title: 'Just a moment...' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const cf = d.findings.find(f => f.check === 'Cloudflare challenge')!; expect(cf.passed).toBe(false); expect(d.likelyCause).toContain('Cloudflare'); }); test('flags #challenge-form element as Cloudflare', () => { const html = makeHtml({ body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const cf = d.findings.find(f => f.check === 'Cloudflare challenge')!; expect(cf.passed).toBe(false); }); test('does not flag a clean page as Cloudflare', () => { const html = makeHtml({ title: 'The Mech Touch — Chapters', body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const cf = d.findings.find(f => f.check === 'Cloudflare challenge')!; expect(cf.passed).toBe(true); }); }); describe('analyzer — security block detection', () => { test('flags "you have been blocked" body text', () => { const html = makeHtml({ body: '

You have been blocked from accessing this site.

' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'Security / bot block')!; expect(check.passed).toBe(false); expect(d.likelyCause).toContain('Security block'); }); }); describe('analyzer — CAPTCHA detection', () => { test('flags g-recaptcha div', () => { const html = makeHtml({ body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'CAPTCHA')!; expect(check.passed).toBe(false); }); }); describe('analyzer — login wall detection', () => { test('flags password input', () => { const html = makeHtml({ body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'Login wall')!; expect(check.passed).toBe(false); expect(d.likelyCause).toContain('Login'); }); }); describe('analyzer — scope selector breakdown', () => { test('reports root missing when neither segment exists', () => { const html = makeHtml({ body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'TOC scope selector')!; expect(check.passed).toBe(false); expect(check.detail).toContain('#list-chapter'); expect(check.detail).toContain('not found anywhere'); expect(d.likelyCause).toContain('redesigned'); }); test('reports inner selector missing when root exists but child does not', () => { const html = makeHtml({ body: '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'TOC scope selector')!; expect(check.passed).toBe(false); expect(check.detail).toContain('#list-chapter'); expect(check.detail).toContain('.chapter-archive-grid'); expect(d.likelyCause).toContain('Inner selector'); }); test('notes when inner selector exists elsewhere on the page (wrong hierarchy)', () => { const html = makeHtml({ body: '
', }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'TOC scope selector')!; expect(check.passed).toBe(false); expect(check.detail).toContain('exists elsewhere'); }); test('passes when full scope matches', () => { const html = makeHtml({ body: '
', }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'TOC scope selector')!; expect(check.passed).toBe(true); }); }); describe('analyzer — link pattern check', () => { test('finds matching links outside the scope when scope is missing', () => { const links = [ 'https://novelbin.com/b/the-mech-touch/chapter-1', 'https://novelbin.com/b/the-mech-touch/chapter-2', ]; const html = makeHtml({ body: '
' + links.map(l => `ch`).join('') + '
' }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'Link pattern (anywhere)')!; expect(check.passed).toBe(true); expect(check.detail).toContain('2 link'); expect(d.likelyCause).toContain('scope selector'); }); test('reports no matching links and samples what is there', () => { const html = makeHtml({ links: ['https://other.com/a', 'https://other.com/b'] }); const d = analyzeTocPage(html, SCOPE, PATTERN); const check = d.findings.find(f => f.check === 'Link pattern (anywhere)')!; expect(check.passed).toBe(false); expect(check.detail).toContain('other.com'); }); }); describe('analyzer — actualUrl', () => { test('includes the landed URL in the diagnosis', () => { const html = makeHtml({}); const d = analyzeTocPage(html, SCOPE, PATTERN, 'https://novelbin.com/redirected'); expect(d.actualUrl).toBe('https://novelbin.com/redirected'); }); });