// Package recruitment 的链接评分规则单元测试。 // // 用贵阳市人社局「人事招考」列表页的真实锚点样本做回归,防止评分规则 // 调整后再次出现「导航页被误抓」或「真实公告被漏抓」两类问题。 package recruitment import "testing" // TestScoreAnchor 覆盖真实列表页中的典型锚点形态。 func TestScoreAnchor(t *testing.T) { incWords := defaultIncludeWords excWords := defaultExcludeWords minScore := defaultMinScore cases := []struct { name string a anchor want bool // true=应保留(公告正文),false=应过滤(导航/栏目页) }{ // ---------- 应保留:真实公告正文 ---------- { name: "公告正文-带文件后缀", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/202609/t20260908_90834079.html", Text: "2026年贵阳贵安面向退役军人选拔培养中小学教师公告", }, want: true, }, { name: "公告正文-标题不含标准词但URL是正文", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/202603/t20260303_89596144.html", Text: "贵阳贵安2026年公开招聘事业单位工作人员岗位取消情况公告", }, want: true, }, { name: "公告正文-仅URL可判定", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/202512/t20251225_89089652.html", Text: "关于做好2026年度考试录用公务员工作的通知", }, want: true, }, // ---------- 应过滤:栏目目录页(以 / 结尾) ---------- { name: "栏目页-人事招考", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/", Text: "人事招考", }, want: false, }, { name: "栏目页-重点领域公开", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/fdzdgklmzdlygk/", Text: "重点领域公开", }, want: false, }, { name: "栏目页-政府非公开信息目录", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zffgkxxml/", Text: "政府非公开信息目录", }, want: false, }, { name: "栏目页-建议提案公开", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/jytagk/", Text: "建议提案公开", }, want: false, }, { name: "栏目页-依申请公开", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/ysqgk/index.html", Text: "依申请公开", }, want: false, }, { name: "导航页-政府信息公开制度", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/zfxxgkzd/", Text: "政府信息 公开制度", }, want: false, }, { name: "导航页-政府信息公开年报", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/zfxxgknb/", Text: "政府信息 公开年报", }, want: false, }, { name: "导航页-返回首页", a: anchor{ URL: "http://rsj.guiyang.gov.cn/", Text: "返回首页", }, want: false, }, { name: "导航页-机构概况", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/fdzdgklm/zfxxgkjggk/", Text: "机构概况", }, want: false, }, // ---------- 应过滤:友链/外部站点(URL 以 .html 结尾易误判) ---------- { name: "友链-贵阳人才网", a: anchor{ URL: "https://www.gyrc.cn/jobPosition/result.html", Text: "贵阳人才网 - 贵阳市人才服务中心官方网站", }, want: false, }, { name: "友链-信用中国", a: anchor{ URL: "http://cx.guiyang.gov.cn/index.html", Text: "信用中国(贵州贵阳)", }, want: false, }, { name: "工具页-依申请公开", a: anchor{ URL: "http://rsj.guiyang.gov.cn/zfxxgk/ysqgk/index.html", Text: "贵阳市人力资源和社会保障局- 依申请公开", }, want: false, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := scoreAnchor(c.a, incWords, excWords, minScore) if got != c.want { t.Errorf("scoreAnchor() = %v, want %v\n url=%s\n text=%s", got, c.want, c.a.URL, c.a.Text) } }) } } // TestCleanText 验证标题清洗:去除标签与折叠空白。 func TestCleanText(t *testing.T) { cases := []struct{ in, want string }{ {"\n\t\t\t人事招考", "人事招考"}, {"贵阳贵安 2026 年招聘公告", "贵阳贵安 2026 年招聘公告"}, {" 前后空白 ", "前后空白"}, } for _, c := range cases { if got := cleanText(c.in); got != c.want { t.Errorf("cleanText(%q) = %q, want %q", c.in, got, c.want) } } } // TestIsImageFileName 验证图片文件名识别(用于剔除图片型锚点噪声)。 func TestIsImageFileName(t *testing.T) { yes := []string{"ysqgk3.png", "banner.jpg", "logo.gif", "icon.SVG", "pic.webp"} no := []string{"", "2026年招聘公告", "index.html", "关于招聘的通知"} for _, s := range yes { if !isImageFileName(s) { t.Errorf("isImageFileName(%q) = false, want true", s) } } for _, s := range no { if isImageFileName(s) { t.Errorf("isImageFileName(%q) = true, want false", s) } } } // TestExtractAnchorsImageFallback 验证图片型锚点不会以「文件名」形式进入候选。 // 复现真实页面: func TestExtractAnchorsImageFallback(t *testing.T) { html := ` ysqgk3.png ` got := extractAnchors(html, "http://rsj.guiyang.gov.cn/") for _, a := range got { if isImageFileName(a.Text) { t.Errorf("图片型锚点应以文件名泄漏进候选: text=%q url=%s", a.Text, a.URL) } } // 该锚点应被完全丢弃(无可读文本)。 if len(got) != 0 { t.Errorf("期望丢弃图片型锚点,实际保留 %d 个: %+v", len(got), got) } }