/**
* 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 = `
-
这是第一条结果的摘要内容,讲述 TypeScript 高级用法。
-
-
不应出现在结果中。
`;
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 = `
`;
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);
});
});