import pytest
This commit is contained in:
+245
@@ -0,0 +1,245 @@
|
|||||||
|
# 测试框架使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
项目已配置 **pytest** 测试框架,相比你之前的手动测试方法,有以下优势:
|
||||||
|
|
||||||
|
### ✅ 改进点
|
||||||
|
|
||||||
|
| 旧方法 (Jupyter Notebook) | 新方法 (pytest) |
|
||||||
|
|-------------------------|----------------|
|
||||||
|
| 手动运行每个测试用例 | 自动发现和运行所有测试 |
|
||||||
|
| 手动读取和对比文件 | 自动化断言和差异报告 |
|
||||||
|
| 难以定位失败原因 | 清晰的错误堆栈和 diff |
|
||||||
|
| 无法批量运行 | 支持参数化和并行测试 |
|
||||||
|
| 无覆盖率统计 | 自动生成覆盖率报告 |
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 运行所有测试
|
||||||
|
```bash
|
||||||
|
python -m pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 运行特定测试文件
|
||||||
|
```bash
|
||||||
|
python -m pytest tests/test_playlist_merge.py -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 在 VS Code 中运行
|
||||||
|
- 按 `Ctrl+Shift+P`
|
||||||
|
- 输入 "Run Task"
|
||||||
|
- 选择 "运行所有测试" 或其他测试任务
|
||||||
|
|
||||||
|
## 测试文件说明
|
||||||
|
|
||||||
|
### 已创建的测试文件
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── __init__.py # 包初始化
|
||||||
|
├── conftest.py # pytest fixtures 和配置
|
||||||
|
├── test_playlist_merge.py # 播放列表合并测试
|
||||||
|
└── README.md # 详细测试指南
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试用例覆盖
|
||||||
|
|
||||||
|
`test_playlist_merge.py` 包含:
|
||||||
|
|
||||||
|
1. **基础功能测试** (`TestPlaylistMergeBasics`)
|
||||||
|
- `test_load_paths_removes_comments` - 测试路径加载
|
||||||
|
- `test_save_paths_adds_header` - 测试文件保存
|
||||||
|
- `test_empty_playlist_merge` - 测试空播放列表合并
|
||||||
|
|
||||||
|
2. **本地优先测试** (`TestPlaylistMergeLocalPriority`)
|
||||||
|
- 参数化测试 case1-4 的本地优先合并
|
||||||
|
|
||||||
|
3. **远程优先测试** (`TestPlaylistMergeRemotePriority`)
|
||||||
|
- 参数化测试 case1-4 的远程优先合并
|
||||||
|
|
||||||
|
4. **冲突检测测试** (`TestConflictDetection`)
|
||||||
|
- 测试冲突识别和解决
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
### 基础运行
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# 详细输出
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
# 显示 print 输出
|
||||||
|
pytest -s
|
||||||
|
|
||||||
|
# 只运行特定测试
|
||||||
|
pytest tests/test_playlist_merge.py::TestPlaylistMergeBasics::test_load_paths_removes_comments
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试筛选
|
||||||
|
```bash
|
||||||
|
# 运行特定 case
|
||||||
|
pytest -k "case_num-1"
|
||||||
|
|
||||||
|
# 运行包含特定关键字的测试
|
||||||
|
pytest -k "local_priority"
|
||||||
|
|
||||||
|
# 运行标记的测试
|
||||||
|
pytest -m unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 调试和重试
|
||||||
|
```bash
|
||||||
|
# 失败时进入调试器
|
||||||
|
pytest --pdb
|
||||||
|
|
||||||
|
# 只运行上次失败的测试
|
||||||
|
pytest --lf
|
||||||
|
|
||||||
|
# 先运行失败的测试
|
||||||
|
pytest --ff
|
||||||
|
|
||||||
|
# 遇到第一个失败就停止
|
||||||
|
pytest -x
|
||||||
|
```
|
||||||
|
|
||||||
|
### 覆盖率报告
|
||||||
|
```bash
|
||||||
|
# 生成覆盖率报告
|
||||||
|
pytest --cov=app --cov-report=html
|
||||||
|
|
||||||
|
# 在浏览器中查看
|
||||||
|
# 打开 htmlcov/index.html
|
||||||
|
|
||||||
|
# 终端中显示覆盖率
|
||||||
|
pytest --cov=app --cov-report=term
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编写新测试
|
||||||
|
|
||||||
|
### 1. 创建测试文件
|
||||||
|
在 `tests/` 目录下创建 `test_<feature>.py`
|
||||||
|
|
||||||
|
### 2. 编写测试类和函数
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class TestMyFeature:
|
||||||
|
"""测试我的功能"""
|
||||||
|
|
||||||
|
def test_basic_case(self):
|
||||||
|
"""测试基本用例"""
|
||||||
|
result = my_function()
|
||||||
|
assert result == expected_value
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input,expected", [
|
||||||
|
(1, 2),
|
||||||
|
(2, 4),
|
||||||
|
(3, 6),
|
||||||
|
])
|
||||||
|
def test_multiple_cases(self, input, expected):
|
||||||
|
"""参数化测试多个用例"""
|
||||||
|
assert my_function(input) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用 fixtures
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_data():
|
||||||
|
"""提供测试数据"""
|
||||||
|
return {"key": "value"}
|
||||||
|
|
||||||
|
def test_with_fixture(sample_data):
|
||||||
|
assert sample_data["key"] == "value"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与旧测试方法的对比
|
||||||
|
|
||||||
|
### 旧方法 (test.ipynb)
|
||||||
|
```python
|
||||||
|
# 手动循环和对比
|
||||||
|
for i in range(len(test_playlists[1])):
|
||||||
|
with open(test_playlists[1][i], 'r', encoding='utf-8') as f1, \
|
||||||
|
open(target_playlists[i], 'r', encoding='utf-8') as f2:
|
||||||
|
content1 = f1.read()
|
||||||
|
content2 = f2.read()
|
||||||
|
# 手动处理和对比...
|
||||||
|
if content1 == content2:
|
||||||
|
print(f"Test case {i+1} passed.")
|
||||||
|
else:
|
||||||
|
print(f"Test case {i+1} failed.")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 新方法 (pytest)
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("case_num", [1, 2, 3, 4])
|
||||||
|
def test_merge_local_priority(case_num, tmp_path):
|
||||||
|
"""自动运行所有用例,失败时显示详细 diff"""
|
||||||
|
# 加载输入
|
||||||
|
base_text = load_file(f"case{case_num}.m3u")
|
||||||
|
# ... 执行测试
|
||||||
|
result = merge_playlists(base_text, local_text, remote_text)
|
||||||
|
# 自动断言和差异报告
|
||||||
|
assert_playlists_equal(actual_file, expected_file)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 持续集成
|
||||||
|
|
||||||
|
可以将测试集成到 CI/CD 流程:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/test.yml (示例)
|
||||||
|
name: Tests
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install -r requirements.txt
|
||||||
|
- name: Run tests
|
||||||
|
run: pytest --cov=app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **测试命名** - 使用清晰描述性的名称
|
||||||
|
2. **独立性** - 每个测试应该独立运行
|
||||||
|
3. **快速** - 单元测试应该快速执行
|
||||||
|
4. **明确** - 一个测试只测一个功能点
|
||||||
|
5. **维护** - 定期更新测试用例
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### pytest 找不到
|
||||||
|
```bash
|
||||||
|
python -m pip install pytest pytest-cov
|
||||||
|
```
|
||||||
|
|
||||||
|
### 导入错误
|
||||||
|
确保项目根目录在 Python 路径中:
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试发现失败
|
||||||
|
检查 `pytest.ini` 配置和文件命名是否符合规范。
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [ ] 为其他模块添加测试 (如 `plex_client.py`, `local_playlist.py`)
|
||||||
|
- [ ] 添加集成测试
|
||||||
|
- [ ] 配置 CI/CD 自动运行测试
|
||||||
|
- [ ] 设置测试覆盖率目标 (如 >80%)
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [pytest 官方文档](https://docs.pytest.org/)
|
||||||
|
- [pytest-cov 文档](https://pytest-cov.readthedocs.io/)
|
||||||
|
- 项目内测试: `tests/README.md`
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
[pytest]
|
||||||
|
# Pytest configuration
|
||||||
|
|
||||||
|
# Test discovery patterns
|
||||||
|
python_files = test_*.py
|
||||||
|
python_classes = Test*
|
||||||
|
python_functions = test_*
|
||||||
|
|
||||||
|
# Test paths
|
||||||
|
testpaths = tests
|
||||||
|
|
||||||
|
# Output options
|
||||||
|
addopts =
|
||||||
|
-v
|
||||||
|
--tb=short
|
||||||
|
--strict-markers
|
||||||
|
--disable-warnings
|
||||||
|
|
||||||
|
# Markers for categorizing tests
|
||||||
|
markers =
|
||||||
|
slow: marks tests as slow (deselect with '-m "not slow"')
|
||||||
|
integration: marks tests as integration tests
|
||||||
|
unit: marks tests as unit tests
|
||||||
|
|
||||||
|
# Minimum version
|
||||||
|
minversion = 6.0
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 测试指南
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
### 运行所有测试
|
||||||
|
```bash
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行特定测试文件
|
||||||
|
```bash
|
||||||
|
pytest tests/test_playlist_merge.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行特定测试类或函数
|
||||||
|
```bash
|
||||||
|
pytest tests/test_playlist_merge.py::TestPlaylistMergeLocalPriority
|
||||||
|
pytest tests/test_playlist_merge.py::TestPlaylistMergeLocalPriority::test_merge_local_priority
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行特定参数化测试用例
|
||||||
|
```bash
|
||||||
|
# 只运行 case1 的测试
|
||||||
|
pytest tests/test_playlist_merge.py -k "case_num-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看详细输出
|
||||||
|
```bash
|
||||||
|
pytest -v # verbose
|
||||||
|
pytest -vv # more verbose
|
||||||
|
pytest -s # 显示 print 输出
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看测试覆盖率
|
||||||
|
```bash
|
||||||
|
pip install pytest-cov
|
||||||
|
pytest --cov=app --cov-report=html
|
||||||
|
```
|
||||||
|
然后在浏览器中打开 `htmlcov/index.html`
|
||||||
|
|
||||||
|
## 测试结构
|
||||||
|
|
||||||
|
- `test_playlist_merge.py` - 播放列表合并功能的单元测试
|
||||||
|
- `conftest.py` - pytest fixtures 和配置
|
||||||
|
- `pytest.ini` - pytest 配置文件
|
||||||
|
|
||||||
|
## 编写新测试
|
||||||
|
|
||||||
|
1. 在 `tests/` 目录下创建 `test_*.py` 文件
|
||||||
|
2. 创建以 `Test` 开头的测试类
|
||||||
|
3. 编写以 `test_` 开头的测试函数
|
||||||
|
4. 使用 `assert` 进行断言
|
||||||
|
5. 使用 `@pytest.mark.parametrize` 进行参数化测试
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class TestMyFeature:
|
||||||
|
def test_basic_functionality(self):
|
||||||
|
result = my_function(input_data)
|
||||||
|
assert result == expected_output
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input,expected", [
|
||||||
|
(1, 2),
|
||||||
|
(2, 4),
|
||||||
|
(3, 6),
|
||||||
|
])
|
||||||
|
def test_with_multiple_inputs(self, input, expected):
|
||||||
|
assert my_function(input) == expected
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调试测试
|
||||||
|
|
||||||
|
### 在测试失败时进入调试器
|
||||||
|
```bash
|
||||||
|
pytest --pdb
|
||||||
|
```
|
||||||
|
|
||||||
|
### 只运行上次失败的测试
|
||||||
|
```bash
|
||||||
|
pytest --lf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 先运行失败的测试
|
||||||
|
```bash
|
||||||
|
pytest --ff
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Test package initialization
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""
|
||||||
|
Pytest configuration and shared fixtures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def project_root():
|
||||||
|
"""Return the project root directory."""
|
||||||
|
return Path(__file__).parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def test_data_dir(project_root):
|
||||||
|
"""Return the test data directory."""
|
||||||
|
return project_root / "test_case"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def expected_results_dir(project_root):
|
||||||
|
"""Return the expected results directory."""
|
||||||
|
return project_root / "test_res"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def temp_test_dir(tmp_path):
|
||||||
|
"""Create a temporary directory for test output."""
|
||||||
|
test_dir = tmp_path / "test_output"
|
||||||
|
test_dir.mkdir(exist_ok=True)
|
||||||
|
yield test_dir
|
||||||
|
# Cleanup after test (optional)
|
||||||
|
# shutil.rmtree(test_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_test_state():
|
||||||
|
"""Reset any global state before each test."""
|
||||||
|
# Add any necessary cleanup here
|
||||||
|
yield
|
||||||
|
# Post-test cleanup
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for playlist merge functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.utils.playlist_merge import (
|
||||||
|
merge_playlists,
|
||||||
|
ConflictResolutionStrategy,
|
||||||
|
load_paths,
|
||||||
|
save_paths,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Test data directory
|
||||||
|
TEST_CASE_DIR = Path(__file__).parent.parent / "test_case"
|
||||||
|
TEST_RES_DIR = Path(__file__).parent.parent / "test_res"
|
||||||
|
DOCKERAPP_TEST_DIR = Path(__file__).parent.parent / "dockerapp" / "test_playlists"
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_playlist_content(file_path: str | Path) -> list[str]:
|
||||||
|
"""
|
||||||
|
Read a playlist file and return normalized content (without comments/blanks).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the playlist file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of track paths (non-empty, non-comment lines)
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Remove blank lines and comments
|
||||||
|
lines = [
|
||||||
|
line.strip()
|
||||||
|
for line in content.splitlines()
|
||||||
|
if line.strip() and not line.startswith('#')
|
||||||
|
]
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def assert_playlists_equal(actual_file: str | Path, expected_file: str | Path):
|
||||||
|
"""
|
||||||
|
Assert that two playlist files have identical content (ignoring comments).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
actual_file: Path to the generated playlist
|
||||||
|
expected_file: Path to the expected playlist
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: If playlists differ
|
||||||
|
"""
|
||||||
|
actual_content = normalize_playlist_content(actual_file)
|
||||||
|
expected_content = normalize_playlist_content(expected_file)
|
||||||
|
|
||||||
|
if actual_content != expected_content:
|
||||||
|
# Provide detailed diff on failure
|
||||||
|
import difflib
|
||||||
|
diff = difflib.unified_diff(
|
||||||
|
expected_content,
|
||||||
|
actual_content,
|
||||||
|
fromfile=str(expected_file),
|
||||||
|
tofile=str(actual_file),
|
||||||
|
lineterm=''
|
||||||
|
)
|
||||||
|
diff_text = '\n'.join(diff)
|
||||||
|
pytest.fail(f"Playlist files differ:\n{diff_text}")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPlaylistMergeBasics:
|
||||||
|
"""Test basic playlist merge operations."""
|
||||||
|
|
||||||
|
def test_load_paths_removes_comments(self):
|
||||||
|
"""Test that load_paths correctly filters comments and blanks."""
|
||||||
|
text = "#EXTM3U\n\n/path/to/track1.mp3\n#Comment\n/path/to/track2.mp3\n"
|
||||||
|
paths = load_paths(text)
|
||||||
|
assert paths == ["/path/to/track1.mp3", "/path/to/track2.mp3"]
|
||||||
|
|
||||||
|
def test_save_paths_adds_header(self):
|
||||||
|
"""Test that save_paths adds proper m3u header."""
|
||||||
|
paths = ["/path/to/track1.mp3", "/path/to/track2.mp3"]
|
||||||
|
text = save_paths(paths)
|
||||||
|
assert text.startswith("#EXTM3U\n")
|
||||||
|
assert "/path/to/track1.mp3" in text
|
||||||
|
assert "/path/to/track2.mp3" in text
|
||||||
|
|
||||||
|
def test_empty_playlist_merge(self):
|
||||||
|
"""Test merging empty playlists."""
|
||||||
|
result = merge_playlists("", "", "", test_folder=str(DOCKERAPP_TEST_DIR / "empty_test"))
|
||||||
|
assert result.merged_paths == []
|
||||||
|
assert result.conflicts == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("case_num", [1, 2, 3, 4])
|
||||||
|
class TestPlaylistMergeLocalPriority:
|
||||||
|
"""Test playlist merge with local priority strategy."""
|
||||||
|
|
||||||
|
def test_merge_local_priority(self, case_num, tmp_path):
|
||||||
|
"""Test case {case_num} with local priority."""
|
||||||
|
# Load input files
|
||||||
|
base_file = TEST_CASE_DIR / "base" / f"case{case_num}.m3u"
|
||||||
|
local_file = TEST_CASE_DIR / "local_playlist" / f"case{case_num}.m3u"
|
||||||
|
remote_file = TEST_CASE_DIR / "remote_playlist" / f"case{case_num}.m3u"
|
||||||
|
|
||||||
|
with open(base_file, 'r', encoding='utf-8') as f:
|
||||||
|
base_text = f.read()
|
||||||
|
with open(local_file, 'r', encoding='utf-8') as f:
|
||||||
|
local_text = f.read()
|
||||||
|
with open(remote_file, 'r', encoding='utf-8') as f:
|
||||||
|
remote_text = f.read()
|
||||||
|
|
||||||
|
# Create test output folder
|
||||||
|
test_folder = str(tmp_path / f"case{case_num}_local")
|
||||||
|
|
||||||
|
# Perform merge
|
||||||
|
result = merge_playlists(
|
||||||
|
base_text=base_text,
|
||||||
|
local_text=local_text,
|
||||||
|
remote_text=remote_text,
|
||||||
|
strategy=ConflictResolutionStrategy.LOCAL_PRIORITY,
|
||||||
|
test_folder=test_folder
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compare with expected result
|
||||||
|
expected_file = TEST_RES_DIR / f"case{case_num}_merged_local_primary.m3u"
|
||||||
|
actual_file = Path(test_folder) / "local_result.m3u8"
|
||||||
|
|
||||||
|
assert_playlists_equal(actual_file, expected_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("case_num", [1, 2, 3, 4])
|
||||||
|
class TestPlaylistMergeRemotePriority:
|
||||||
|
"""Test playlist merge with remote priority strategy."""
|
||||||
|
|
||||||
|
def test_merge_remote_priority(self, case_num, tmp_path):
|
||||||
|
"""Test case {case_num} with remote priority."""
|
||||||
|
# Load input files
|
||||||
|
base_file = TEST_CASE_DIR / "base" / f"case{case_num}.m3u"
|
||||||
|
local_file = TEST_CASE_DIR / "local_playlist" / f"case{case_num}.m3u"
|
||||||
|
remote_file = TEST_CASE_DIR / "remote_playlist" / f"case{case_num}.m3u"
|
||||||
|
|
||||||
|
with open(base_file, 'r', encoding='utf-8') as f:
|
||||||
|
base_text = f.read()
|
||||||
|
with open(local_file, 'r', encoding='utf-8') as f:
|
||||||
|
local_text = f.read()
|
||||||
|
with open(remote_file, 'r', encoding='utf-8') as f:
|
||||||
|
remote_text = f.read()
|
||||||
|
|
||||||
|
# Create test output folder
|
||||||
|
test_folder = str(tmp_path / f"case{case_num}_remote")
|
||||||
|
|
||||||
|
# Perform merge
|
||||||
|
result = merge_playlists(
|
||||||
|
base_text=base_text,
|
||||||
|
local_text=local_text,
|
||||||
|
remote_text=remote_text,
|
||||||
|
strategy=ConflictResolutionStrategy.REMOTE_PRIORITY,
|
||||||
|
test_folder=test_folder
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compare with expected result
|
||||||
|
expected_file = TEST_RES_DIR / f"case{case_num}_merged_remote_primary.m3u"
|
||||||
|
actual_file = Path(test_folder) / "remote_result.m3u8"
|
||||||
|
|
||||||
|
assert_playlists_equal(actual_file, expected_file)
|
||||||
|
|
||||||
|
|
||||||
|
class TestConflictDetection:
|
||||||
|
"""Test conflict detection in merges."""
|
||||||
|
|
||||||
|
def test_conflict_reporting(self, tmp_path):
|
||||||
|
"""Test that conflicts are properly reported."""
|
||||||
|
base = "#EXTM3U\n/track1.mp3\n"
|
||||||
|
local = "#EXTM3U\n/track2.mp3\n" # Changed
|
||||||
|
remote = "#EXTM3U\n/track3.mp3\n" # Changed differently
|
||||||
|
|
||||||
|
result = merge_playlists(
|
||||||
|
base_text=base,
|
||||||
|
local_text=local,
|
||||||
|
remote_text=remote,
|
||||||
|
strategy=ConflictResolutionStrategy.LOCAL_PRIORITY,
|
||||||
|
test_folder=str(tmp_path / "conflict_test")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should detect conflict
|
||||||
|
assert len(result.conflicts) > 0
|
||||||
|
|
||||||
|
# Local priority should prefer local version
|
||||||
|
assert "/track2.mp3" in result.merged_paths
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Allow running tests directly
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
Reference in New Issue
Block a user