"""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()