/** * web_search 搜索引擎 HTML 解析器单元测试(v0.4.1 测试补齐) * 覆盖:node-html-parser 结构化解析(主层)、自域名链接过滤、 * 相对链接补全、空/异常 HTML 容错 */ import { describe, it, expect } from 'vitest'; import { parseBing, parseBaidu, parseSogou, parse360 } from '../web-search'; describe('parseBing — 结构化解析', () => { const BING_HTML = `
  1. 第一篇 TypeScript 文章

    这是第一条结果的摘要内容,讲述 TypeScript 高级用法。

  2. 第二篇 Node.js 文章

    第二条结果的摘要。

  3. Bing 内部视频链接

    不应出现在结果中。

`; it('解析结果块并提取 title/url/snippet', () => { const results = parseBing(BING_HTML); expect(results).toHaveLength(2); expect(results[0]).toMatchObject({ title: '第一篇 TypeScript 文章', url: 'https://example.com/article-1', snippet: '这是第一条结果的摘要内容,讲述 TypeScript 高级用法。', engine: 'bing', weight: 90, }); expect(results[1].url).toBe('https://example.com/article-2'); }); it('过滤指向 bing.com 自身域名的链接', () => { const results = parseBing(BING_HTML); expect(results.some((r) => r.url.includes('bing.com'))).toBe(false); }); it('空 HTML 返回空数组', () => { expect(parseBing('')).toHaveLength(0); expect(parseBing('')).toHaveLength(0); }); }); describe('parseBaidu — 结构化解析', () => { const BAIDU_HTML = `

百度结果一

第一条摘要内容。

跳转链接结果

无 data-url 的结果(跳转链接被过滤)。
`; it('优先使用 data-url 真实链接', () => { const results = parseBaidu(BAIDU_HTML); expect(results).toHaveLength(1); expect(results[0]).toMatchObject({ title: '百度结果一', url: 'https://example.com/real-url-1', engine: '百度', weight: 80, }); }); it('baidu.com/link 跳转链接被过滤', () => { const results = parseBaidu(BAIDU_HTML); expect(results.some((r) => r.url.includes('baidu.com/link'))).toBe(false); }); it('空 HTML 返回空数组', () => { expect(parseBaidu('')).toHaveLength(0); }); }); describe('parseSogou — 结构化解析', () => { const SOGOU_HTML = `

搜狗结果一

搜狗结果一的摘要文本。

搜狗直链结果

直链结果的摘要。

`; it('相对链接补全 sogou.com 前缀', () => { const results = parseSogou(SOGOU_HTML); expect(results).toHaveLength(2); expect(results[0]).toMatchObject({ title: '搜狗结果一', url: 'https://www.sogou.com/link?url=sogou-internal-1', engine: '搜狗', weight: 75, }); }); it('http 开头的直链不补全前缀', () => { const results = parseSogou(SOGOU_HTML); expect(results[1].url).toBe('https://example.com/direct'); expect(results[1].snippet).toBe('直链结果的摘要。'); }); it('空 HTML 返回空数组', () => { expect(parseSogou('')).toHaveLength(0); }); }); describe('parse360 — 结构化解析', () => { const SO360_HTML = `
`; it('解析结果并过滤 so.com 自身链接', () => { const results = parse360(SO360_HTML); expect(results).toHaveLength(1); expect(results[0]).toMatchObject({ title: '360 结果一', url: 'https://example.com/360-result', snippet: '360 搜索结果的摘要描述。', engine: '360搜索', weight: 75, }); }); it('空 HTML 返回空数组', () => { expect(parse360('')).toHaveLength(0); }); }); describe('解析器降级路径', () => { it('结构化解析无结果且正则也无结果时返回空数组(不抛错)', () => { // 非搜索结果页 HTML(如错误页/验证码页) const notSearchPage = '
请输入验证码
'; expect(parseBing(notSearchPage)).toHaveLength(0); expect(parseBaidu(notSearchPage)).toHaveLength(0); expect(parseSogou(notSearchPage)).toHaveLength(0); expect(parse360(notSearchPage)).toHaveLength(0); }); });