"""分类规则外置单元测试(T-R2,采纳 llmrouter「规则文档即配置」设计)。""" import json from router_system.classifier import (DOMAIN_RULES, RuleClassifier, build_classifier, load_domain_rules) def test_load_missing_file_returns_none(tmp_path): """文件缺失:返回 None(调用方回退内置),不抛异常。""" assert load_domain_rules(tmp_path / "nope.json") is None def test_external_rules_override_domain(tmp_path): """外置文件按 domain 覆盖内置:改写 code 规则即改变路由行为。""" f = tmp_path / "routes.json" f.write_text(json.dumps({"code": [["速排", 2.0]]}, ensure_ascii=False), encoding="utf-8") rules = load_domain_rules(f) assert rules is not None and rules["code"] == [("速排", 2.0)] clf = RuleClassifier() clf.rules = {**DOMAIN_RULES, **rules} r = clf.classify("讲讲速排的思路") assert r.domain == "code" def test_build_classifier_merges_over_builtin(tmp_path): """build_classifier 合并语义:文件只写 legal,其余领域保持内置。""" f = tmp_path / "routes.json" f.write_text(json.dumps({"legal": [["劳动合同", 3.0]]}, ensure_ascii=False), encoding="utf-8") clf = build_classifier({"type": "rule", "rules_file": str(f)}) assert clf.rules["legal"] == [("劳动合同", 3.0)] assert clf.rules["code"] == DOMAIN_RULES["code"] # 未覆盖领域保持内置 r = clf.classify("劳动合同到期不续签需要支付经济补偿吗") assert r.domain == "legal" def test_malformed_file_falls_back_to_builtin(tmp_path): """格式非法:整体回退内置(失败安全,编辑错误不打垮路由)。""" f = tmp_path / "routes.json" f.write_text('{"code": [["坏数据", "不是数字"]]}', encoding="utf-8") assert load_domain_rules(f) is None clf = build_classifier({"type": "rule", "rules_file": str(f)}) assert clf.rules == DOMAIN_RULES r = clf.classify("用 Python 写一个快速排序函数") assert r.domain == "code" def test_explicit_rules_file_missing_uses_builtin(tmp_path): """显式路径不存在:静默回退内置,分类行为不变。""" clf = build_classifier({"type": "rule", "rules_file": str(tmp_path / "no.json")}) assert clf.rules == DOMAIN_RULES