"""数据模型与 taxonomy 加载的单元测试""" import json import pytest from pydantic import ValidationError from app.models.document import ChunkModel from app.models.knowledge import ( UNCATEGORIZED, CategoryResult, TaxonomyCategory, load_taxonomy, ) from app.models.search import SearchHit, SearchRequest, SearchResponse class TestLoadTaxonomy: """taxonomy 加载与校验""" def test_default_taxonomy(self): """空路径时使用内置默认类目集""" taxonomy = load_taxonomy("") assert len(taxonomy) >= 6 names = [c.name for c in taxonomy] assert UNCATEGORIZED in names assert len(names) == len(set(names)) assert all(isinstance(c, TaxonomyCategory) for c in taxonomy) def test_load_from_json_file(self, tmp_path): """从 JSON 文件加载自定义类目集""" data = [ {"name": "技术", "description": "技术类文档"}, {"name": UNCATEGORIZED, "description": "未分类"}, ] file = tmp_path / "taxonomy.json" file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") taxonomy = load_taxonomy(str(file)) assert [c.name for c in taxonomy] == ["技术", UNCATEGORIZED] assert taxonomy[0].description == "技术类文档" def test_append_uncategorized_when_missing(self, tmp_path): """JSON 文件缺少 uncategorized 时自动追加""" data = [{"name": "技术"}, {"name": "产品"}] file = tmp_path / "taxonomy.json" file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") taxonomy = load_taxonomy(str(file)) names = [c.name for c in taxonomy] assert names == ["技术", "产品", UNCATEGORIZED] def test_duplicate_name_raises(self, tmp_path): """类目 name 重复时报错""" data = [{"name": "技术"}, {"name": "技术"}] file = tmp_path / "taxonomy.json" file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") with pytest.raises(ValueError, match="重复"): load_taxonomy(str(file)) class TestCategoryResult: """CategoryResult 字段校验""" def test_defaults(self): result = CategoryResult(main_category="技术文档", confidence=0.9) assert result.tags == [] assert result.confidence == 0.9 def test_confidence_out_of_range(self): """confidence 越界(>1 或 <0)时校验失败""" with pytest.raises(ValidationError): CategoryResult(main_category="技术文档", confidence=1.5) with pytest.raises(ValidationError): CategoryResult(main_category="技术文档", confidence=-0.1) def test_confidence_boundary_values(self): """confidence 边界值 0 和 1 合法""" assert CategoryResult(main_category="a", confidence=0).confidence == 0 assert CategoryResult(main_category="a", confidence=1).confidence == 1 class TestSearchModels: """检索模型字段校验""" def test_search_hit(self): hit = SearchHit(text="内容", doc_id="doc-1", score=0.85) assert hit.title == "" assert hit.section_path == "" assert hit.doc_summary == "" def test_search_hit_full_fields(self): hit = SearchHit( text="内容", doc_id="doc-1", title="标题", section_path="第一章/第一节", score=0.85, doc_summary="一句话总结", ) assert hit.section_path == "第一章/第一节" def test_search_hit_missing_required(self): """缺少必填字段时校验失败""" with pytest.raises(ValidationError): SearchHit(text="内容", doc_id="doc-1") # type: ignore[call-arg] def test_search_request_default_top_k(self): req = SearchRequest(query="如何报销") assert req.top_k is None def test_search_response_defaults(self): resp = SearchResponse(query="q", hits=[]) assert resp.routed_categories == [] assert resp.fallback is False class TestChunkModel: """ChunkModel 字段校验""" def test_defaults(self): chunk = ChunkModel(doc_id="doc-1", chunk_index=0, text="第一段") assert chunk.section_path == "" def test_full_fields(self): chunk = ChunkModel(doc_id="doc-1", chunk_index=2, text="第三段", section_path="第二章") assert chunk.chunk_index == 2 assert chunk.section_path == "第二章" def test_missing_required(self): """缺少必填字段时校验失败""" with pytest.raises(ValidationError): ChunkModel(doc_id="doc-1", chunk_index=0) # type: ignore[call-arg]