Files
kplam 51dc8dc4f6 Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈
- L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合)
- 文档三级总结与 2.5 级回退
- query 解析路由与分类
- /admin 管理页面
2026-07-29 21:24:40 +08:00

105 lines
3.8 KiB
Python

"""标题树解析器的单元测试"""
from app.core.headings import parse_headings, render_outline
class TestMarkdownHeadings:
"""Markdown ATX 标题解析"""
def test_multi_level(self):
"""多级 Markdown 标题,层级为 # 数量"""
text = "# 一级标题\n正文内容\n## 二级标题\n更多内容\n### 三级标题\n"
headings = parse_headings(text)
assert [(h.title, h.level, h.line_index) for h in headings] == [
("一级标题", 1, 0),
("二级标题", 2, 2),
("三级标题", 3, 4),
]
def test_hash_without_space_not_heading(self):
"""# 后无空白不是标题"""
assert parse_headings("#不是标题") == []
def test_up_to_six_levels(self):
"""最多支持 6 级标题"""
text = "###### 六级标题\n####### 七级不算\n"
headings = parse_headings(text)
assert len(headings) == 1
assert headings[0].level == 6
class TestChineseChapterHeadings:
"""中文章/节/篇编号标题解析"""
def test_chapter_and_section(self):
"""章=1 级,节=2 级"""
text = "第一章 总则\n正文\n第一节 一般规定\n"
headings = parse_headings(text)
assert [(h.title, h.level) for h in headings] == [("第一章 总则", 1), ("第一节 一般规定", 2)]
def test_pian_is_level_one(self):
"""篇=1 级,支持阿拉伯数字编号"""
text = "第一篇 概述\n第2章 背景\n"
headings = parse_headings(text)
assert [(h.title, h.level) for h in headings] == [("第一篇 概述", 1), ("第2章 背景", 1)]
def test_chinese_enum_is_level_one(self):
"""一、二、…… 固定 1 级"""
text = "一、项目背景\n二、建设目标\n"
headings = parse_headings(text)
assert [(h.title, h.level) for h in headings] == [("一、项目背景", 1), ("二、建设目标", 1)]
def test_overlong_line_not_heading(self):
"""编号行超过 60 字符视为正文"""
text = "第一章 " + "很长的标题" * 12 + "\n"
assert parse_headings(text) == []
class TestNumericHeadings:
"""数字编号标题解析"""
def test_dot_segments_determine_level(self):
"""按点分段数定层级:1.=1、1.1=2、1.1.1=3"""
text = "1. 概述\n1.1 背景\n1.1.1 细节\n"
headings = parse_headings(text)
assert [(h.title, h.level) for h in headings] == [
("1. 概述", 1),
("1.1 背景", 2),
("1.1.1 细节", 3),
]
def test_dunhao_separator(self):
"""顿号分隔的数字编号为 1 级"""
headings = parse_headings("1、基本要求\n2、总体架构\n")
assert [(h.title, h.level) for h in headings] == [("1、基本要求", 1), ("2、总体架构", 1)]
class TestMixedAndPlain:
"""混合模式与无结构文本"""
def test_no_headings_returns_empty(self):
"""无标题文本返回空列表"""
text = "第一段正文内容。\n\n第二段正文内容。\n"
assert parse_headings(text) == []
def test_empty_text(self):
assert parse_headings("") == []
def test_mixed_patterns(self):
"""Markdown 与中文编号混合时均能识别"""
text = "# Markdown 标题\n正文\n第一章 中文标题\n1.1 数字标题\n"
headings = parse_headings(text)
assert [(h.title, h.level) for h in headings] == [
("Markdown 标题", 1),
("第一章 中文标题", 1),
("1.1 数字标题", 2),
]
class TestRenderOutline:
"""标题树渲染为大纲文本"""
def test_indent_by_level(self):
headings = parse_headings("# 安装指南\n## 环境准备\n## 安装步骤\n### 验证\n")
assert render_outline(headings) == "- 安装指南\n - 环境准备\n - 安装步骤\n - 验证"