c9ca1b538d
- Remove sys.path manipulation from kb_workflow.py - Update CLI imports to use kb_workflow.pipeline prefix - Update all test file imports and patch paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
223 lines
8.2 KiB
Python
223 lines
8.2 KiB
Python
"""Tests for ArchiveProcessor
|
|
|
|
Tests that ArchiveProcessor:
|
|
- Copies md file to correct platform directory
|
|
- Uses correct filename format {YYYYMMDD}_{content_id}_analysis.md
|
|
- Skips when step already completed
|
|
- Detects existing archive by content_id
|
|
- Handles missing analysis file with error
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from kb_workflow.pipeline.processors.archive import ArchiveProcessor
|
|
from kb_workflow.pipeline.schemas import ArchiveInput, ArchiveOutput
|
|
from kb_workflow.pipeline.state import WorkflowStateManager
|
|
|
|
|
|
# =============================================================================
|
|
# Fixtures
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
def archive_test_env(tmp_path):
|
|
"""Create test environment for archive tests"""
|
|
# Create intermediate content directory
|
|
content_dir = tmp_path / "intermediate" / "bilibili_BV1test"
|
|
content_dir.mkdir(parents=True)
|
|
|
|
# Create analysis file
|
|
analysis_file = content_dir / "analysis.md"
|
|
analysis_file.write_text("# Test Analysis\n\nContent here.")
|
|
|
|
# Create archive directory
|
|
archive_dir = tmp_path / "archive"
|
|
archive_dir.mkdir(parents=True)
|
|
|
|
# Create state manager
|
|
state_manager = WorkflowStateManager(content_dir)
|
|
|
|
return {
|
|
"content_dir": content_dir,
|
|
"analysis_file": analysis_file,
|
|
"archive_dir": archive_dir,
|
|
"state_manager": state_manager,
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# ArchiveProcessor Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestArchiveProcessorCopyToPlatformDir:
|
|
"""Test that archive file is copied to correct platform directory"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_copies_to_platform_dir(self, archive_test_env):
|
|
"""Test that archive file is copied to correct platform directory"""
|
|
mock_settings = MagicMock()
|
|
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
|
|
|
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
|
mock_get_settings.return_value = mock_settings
|
|
|
|
processor = ArchiveProcessor()
|
|
# Mock _ask_overwrite to return True (user says yes) in case of collision
|
|
processor._ask_overwrite = lambda f: True
|
|
|
|
input_data = ArchiveInput(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
content_dir=str(archive_test_env["content_dir"]),
|
|
analysis_file=str(archive_test_env["analysis_file"]),
|
|
content_type="video"
|
|
)
|
|
|
|
result = await processor.process(input_data)
|
|
|
|
assert result.success is True
|
|
assert result.skipped is False
|
|
assert "bilibili" in result.archive_file
|
|
assert result.archive_file.endswith("_analysis.md")
|
|
|
|
|
|
class TestArchiveProcessorFilenameFormat:
|
|
"""Test that archive filename follows correct format"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_filename_format(self, archive_test_env):
|
|
"""Test that archive filename follows {YYYYMMDD}_{content_id}_analysis.md format"""
|
|
mock_settings = MagicMock()
|
|
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
|
|
|
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
|
mock_get_settings.return_value = mock_settings
|
|
|
|
processor = ArchiveProcessor()
|
|
# Mock _ask_overwrite to return True (user says yes) in case of collision
|
|
processor._ask_overwrite = lambda f: True
|
|
|
|
input_data = ArchiveInput(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
content_dir=str(archive_test_env["content_dir"]),
|
|
analysis_file=str(archive_test_env["analysis_file"]),
|
|
content_type="video"
|
|
)
|
|
|
|
result = await processor.process(input_data)
|
|
|
|
# Check filename format
|
|
archive_path = Path(result.archive_file)
|
|
filename = archive_path.name
|
|
today = datetime.now().strftime("%Y%m%d")
|
|
expected = f"{today}_BV1test_analysis.md"
|
|
assert filename == expected
|
|
|
|
|
|
class TestArchiveProcessorSkipsCompleted:
|
|
"""Test that archive step is skipped when already completed"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_skips_completed(self, archive_test_env):
|
|
"""Test that archive step is skipped when already completed"""
|
|
mock_settings = MagicMock()
|
|
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
|
|
|
# Create state with archive already completed
|
|
state = archive_test_env["state_manager"].get_or_create(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
source_url="https://test.com"
|
|
)
|
|
state.mark_step_completed("archive")
|
|
archive_test_env["state_manager"].save(state)
|
|
|
|
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
|
mock_get_settings.return_value = mock_settings
|
|
|
|
processor = ArchiveProcessor(state_manager=archive_test_env["state_manager"])
|
|
input_data = ArchiveInput(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
content_dir=str(archive_test_env["content_dir"]),
|
|
analysis_file=str(archive_test_env["analysis_file"]),
|
|
content_type="video"
|
|
)
|
|
|
|
result = await processor.process(input_data)
|
|
|
|
assert result.success is True
|
|
assert result.skipped is True
|
|
|
|
|
|
class TestArchiveProcessorDetectsExisting:
|
|
"""Test that archive processor detects existing archive by content_id"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_detects_existing_archive(self, archive_test_env):
|
|
"""Test that archive processor detects existing archive by content_id"""
|
|
mock_settings = MagicMock()
|
|
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
|
|
|
# Create existing archive with different timestamp
|
|
platform_dir = archive_test_env["archive_dir"] / "bilibili"
|
|
platform_dir.mkdir(parents=True, exist_ok=True)
|
|
existing = platform_dir / "20260101_BV1test_analysis.md"
|
|
existing.write_text("Old content")
|
|
|
|
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
|
mock_get_settings.return_value = mock_settings
|
|
|
|
processor = ArchiveProcessor()
|
|
# Mock _ask_overwrite to return False (user says no)
|
|
processor._ask_overwrite = lambda f: False
|
|
|
|
input_data = ArchiveInput(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
content_dir=str(archive_test_env["content_dir"]),
|
|
analysis_file=str(archive_test_env["analysis_file"]),
|
|
content_type="video"
|
|
)
|
|
|
|
result = await processor.process(input_data)
|
|
|
|
assert result.success is True
|
|
assert result.skipped is True
|
|
|
|
|
|
class TestArchiveProcessorHandlesMissingFile:
|
|
"""Test that archive processor handles missing analysis file"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handles_missing_file(self, archive_test_env):
|
|
"""Test that archive processor handles missing analysis file"""
|
|
mock_settings = MagicMock()
|
|
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
|
|
|
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
|
mock_get_settings.return_value = mock_settings
|
|
|
|
processor = ArchiveProcessor()
|
|
# Mock _ask_overwrite to avoid stdin issues
|
|
processor._ask_overwrite = lambda f: True
|
|
|
|
input_data = ArchiveInput(
|
|
content_id="BV1test",
|
|
platform="bilibili",
|
|
content_dir=str(archive_test_env["content_dir"]),
|
|
analysis_file="/nonexistent/analysis.md", # Missing file
|
|
content_type="video"
|
|
)
|
|
|
|
result = await processor.process(input_data)
|
|
|
|
assert result.success is False
|
|
assert "not found" in result.error_message.lower()
|