Skip to content

Commit d833e19

Browse files
committed
fix: bugs
1 parent 344e4df commit d833e19

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

tests/test_refactored.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# tests/test_refactored.py
2+
3+
import sys
4+
import pytest
5+
from pathlib import Path
6+
from unittest.mock import patch, MagicMock
7+
8+
# 确保 src 在路径中,以便导入模块
9+
# 如果你已经 pip install -e . 安装了项目,这一步可能不是必须的,但加上更稳健
10+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
11+
12+
from flatcode.core.scanner import ProjectScanner
13+
from flatcode.core.ignore import load_ignore_rules, is_path_ignored
14+
from flatcode.core.tree import generate_project_tree
15+
from flatcode.cli import main
16+
17+
# --- Fixtures: 搭建测试用的临时文件系统 ---
18+
19+
@pytest.fixture
20+
def complex_project(tmp_path):
21+
"""
22+
创建一个包含多种情况的复杂文件结构:
23+
1. 普通代码文件 (.py)
24+
2. 被忽略的文件 (logs/)
25+
3. 二进制文件 (.png)
26+
4. 强制包含的文件 (!important.log)
27+
5. .mergeignore 文件
28+
"""
29+
# 1. 创建目录
30+
src = tmp_path / "src"
31+
src.mkdir()
32+
logs = tmp_path / "logs"
33+
logs.mkdir()
34+
assets = tmp_path / "assets"
35+
assets.mkdir()
36+
37+
# 2. 创建文本文件
38+
(src / "main.py").write_text("print('main')", encoding="utf-8")
39+
(src / "utils.py").write_text("def util(): pass", encoding="utf-8")
40+
(tmp_path / "README.md").write_text("# Project", encoding="utf-8")
41+
42+
# 3. 创建被忽略的文件
43+
(logs / "app.log").write_text("error...", encoding="utf-8")
44+
(logs / "important.log").write_text("SAVE ME", encoding="utf-8") # 我们将强制包含这个
45+
46+
# 4. 创建二进制文件 (模拟图片)
47+
# 注意:使用 'wb' 写入随机字节,确保它无法被 utf-8 解码
48+
(assets / "image.png").write_bytes(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR")
49+
50+
# 5. 创建 .mergeignore
51+
ignore_content = """
52+
logs/
53+
*.png
54+
!logs/important.log
55+
"""
56+
(tmp_path / ".mergeignore").write_text(ignore_content, encoding="utf-8")
57+
58+
return tmp_path
59+
60+
# --- Test 1: Core - Ignore Logic ---
61+
62+
def test_ignore_logic_parsing(tmp_path):
63+
"""验证 .mergeignore 规则解析的正确性"""
64+
ignore_file = tmp_path / ".mergeignore"
65+
ignore_file.write_text("node_modules/\n!src/keep.js", encoding="utf-8")
66+
67+
rules = load_ignore_rules(ignore_file)
68+
69+
# 验证是否解析出了排除和包含规则
70+
assert ("node_modules/", False) in rules
71+
assert ("src/keep.js", True) in rules
72+
73+
def test_is_path_ignored_logic():
74+
"""验证具体的路径匹配逻辑"""
75+
rules = [
76+
("logs/", False), # Exclude logs folder
77+
("*.tmp", False), # Exclude tmp files
78+
# [FIX] 去掉这里的 '!'。is_path_ignored 期望的是清洗后的 pattern 和 is_inclusion=True
79+
("logs/important.txt", True)
80+
]
81+
82+
# 应该被忽略
83+
# Rule 1 ("logs/") 匹配 -> ignored = True
84+
assert is_path_ignored(Path("logs/debug.log"), rules) is True
85+
86+
# Rule 2 ("*.tmp") 匹配 -> ignored = True
87+
assert is_path_ignored(Path("temp.tmp"), rules) is True
88+
89+
# 不应该被忽略
90+
# 没有规则匹配 -> default ignored = False
91+
assert is_path_ignored(Path("src/main.py"), rules) is False
92+
93+
# 强制包含 (Whitelist)
94+
# Step 1: 匹配 "logs/" -> ignored = True
95+
# Step 2: 匹配 "logs/important.txt" (is_inclusion=True) -> ignored = False (覆盖生效)
96+
assert is_path_ignored(Path("logs/important.txt"), rules) is False
97+
# --- Test 2: Core - Scanner Logic (核心测试) ---
98+
99+
def test_scanner_wildcard_behavior(complex_project):
100+
"""
101+
测试 Scanner 在 '*' 模式下的表现:
102+
1. 应包含 src/main.py (普通文本)
103+
2. 应跳过 assets/image.png (二进制文件,虽然未在ignore中显式排除,但读取失败应静默跳过)
104+
*注:这里我们在 fixture 的 .mergeignore 里显式排除了 *.png,为了测试二进制跳过,我们需要临时修改规则
105+
"""
106+
107+
# 准备规则:我们故意不忽略 .png,看看 Scanner 是否会因为 UnicodeDecodeError 炸掉
108+
# 仅忽略 logs/ 目录
109+
ignore_rules = [("logs/", False)]
110+
extensions = {"*"} # 通配符模式
111+
112+
scanner = ProjectScanner(complex_project, ignore_rules, extensions)
113+
results = list(scanner.scan())
114+
115+
paths = [f.rel_path for f in results]
116+
117+
# 验证:
118+
assert "src/main.py" in paths
119+
assert "README.md" in paths
120+
121+
# 验证二进制文件即使没被忽略,也被静默跳过(不在结果中)
122+
# 如果代码没有处理 UnicodeDecodeError,这里会抛出异常失败
123+
assert "assets/image.png" not in paths
124+
125+
# 验证忽略规则生效
126+
assert "logs/app.log" not in paths
127+
128+
def test_scanner_specific_extensions(complex_project):
129+
"""测试指定后缀模式"""
130+
# 仅扫描 .py
131+
ignore_rules = []
132+
extensions = {".py"}
133+
134+
scanner = ProjectScanner(complex_project, ignore_rules, extensions)
135+
results = list(scanner.scan())
136+
paths = [f.rel_path for f in results]
137+
138+
assert "src/main.py" in paths
139+
assert "src/utils.py" in paths
140+
assert "README.md" not in paths # 应该被过滤掉
141+
142+
# --- Test 3: Core - Tree Generation ---
143+
144+
def test_tree_generation():
145+
paths = [
146+
"src/main.py",
147+
"src/utils/helper.py",
148+
"README.md"
149+
]
150+
tree_str = generate_project_tree(paths, root_name="my_project")
151+
152+
# 简单的字符串包含检查
153+
assert "my_project/" in tree_str
154+
assert "src" in tree_str
155+
assert "├── main.py" in tree_str or "└── main.py" in tree_str
156+
157+
# --- Test 4: Integration - CLI Entry Point ---
158+
159+
def test_cli_integration(complex_project, monkeypatch, capsys):
160+
"""
161+
模拟完整的命令行调用
162+
"""
163+
output_file = complex_project / "output.txt"
164+
165+
# 模拟参数: flatcode [root] -o output.txt -y
166+
# 使用 -y 跳过确认
167+
test_args = [
168+
"flatcode",
169+
str(complex_project),
170+
"-o", output_file.name,
171+
"-y"
172+
]
173+
174+
# Mock sys.argv
175+
with patch.object(sys, "argv", test_args):
176+
# Mock input 避免 bootstrap 过程中的交互 (虽然 -y 跳过最后的确认,但 bootstrap 可能仍需确认复制 gitignore)
177+
# 这里我们假设 bootstrap 逻辑中如果存在 gitignore 可能会问。
178+
# 让 input 永远返回 'n' 以跳过复制
179+
monkeypatch.setattr("builtins.input", lambda _: "n")
180+
181+
main()
182+
183+
# 验证输出文件是否生成
184+
assert output_file.exists()
185+
content = output_file.read_text(encoding="utf-8")
186+
187+
# 验证内容
188+
assert "--- File: src/main.py ---" in content
189+
assert "# Project" in content
190+
# 验证被忽略的没进来
191+
assert "logs/app.log" not in content
192+
# 验证二进制没进来
193+
assert "PNG" not in content

0 commit comments

Comments
 (0)