"""M7 调度与日报模块单元测试。""" from unittest.mock import patch from scheduler.pipeline import PipelineResult, StepResult # --------------------------------------------------------------------------- # # StepResult # --------------------------------------------------------------------------- # class TestStepResult: """StepResult 测试。""" def test_success_step(self): sr = StepResult(name="extract", success=True, elapsed_sec=5.0, message="17 篇") assert sr.name == "extract" assert sr.success assert sr.message == "17 篇" def test_failed_step(self): sr = StepResult(name="translate", success=False, elapsed_sec=30.0, message="API timeout") assert not sr.success assert "timeout" in sr.message # --------------------------------------------------------------------------- # # PipelineResult # --------------------------------------------------------------------------- # class TestPipelineResult: """PipelineResult 测试。""" def test_all_success(self): result = PipelineResult(steps=[ StepResult(name="extract", success=True, elapsed_sec=1.0), StepResult(name="dedup", success=True, elapsed_sec=0.5), ]) assert result.all_success assert result.success_count == 2 def test_partial_failure(self): result = PipelineResult(steps=[ StepResult(name="extract", success=True, elapsed_sec=1.0), StepResult(name="translate", success=False, elapsed_sec=30.0), StepResult(name="embed", success=True, elapsed_sec=2.0), ]) assert not result.all_success assert result.success_count == 2 def test_empty(self): result = PipelineResult() assert result.all_success # 空集合 vacuously true assert result.success_count == 0 # --------------------------------------------------------------------------- # # run_pipeline(mock 各模块) # --------------------------------------------------------------------------- # class TestRunPipeline: """run_pipeline 测试(mock 各步骤)。""" @patch("scheduler.pipeline.run_step_extract") @patch("scheduler.pipeline.run_step_dedup") @patch("scheduler.pipeline.run_step_embed") @patch("scheduler.pipeline.run_step_index") def test_pipeline_runs_all_steps(self, mock_idx, mock_emb, mock_dedup, mock_ext): from scheduler.pipeline import run_pipeline mock_ext.return_value = StepResult(name="extract", success=True, elapsed_sec=1) mock_dedup.return_value = StepResult(name="dedup", success=True, elapsed_sec=1) mock_emb.return_value = StepResult(name="embed", success=True, elapsed_sec=1) mock_idx.return_value = StepResult(name="index", success=True, elapsed_sec=1) result = run_pipeline("20260621", skip_report=True) assert result.success_count >= 4 @patch("scheduler.pipeline.run_step_extract") @patch("scheduler.pipeline.run_step_dedup") def test_pipeline_continues_on_failure(self, mock_dedup, mock_ext): from scheduler.pipeline import run_pipeline mock_ext.return_value = StepResult(name="extract", success=False, elapsed_sec=1, message="error") mock_dedup.return_value = StepResult(name="dedup", success=True, elapsed_sec=1) result = run_pipeline("20260621", steps=["extract", "dedup"], skip_report=True) # extract 失败但 dedup 仍然执行 assert result.success_count == 1 def test_unknown_step_skipped(self): from scheduler.pipeline import run_pipeline result = run_pipeline("20260621", steps=["nonexistent_step"], skip_report=True) assert len(result.steps) == 1 assert not result.steps[0].success # --------------------------------------------------------------------------- # # generate_report(无数据场景) # --------------------------------------------------------------------------- # class TestGenerateReport: """generate_report 测试。""" @patch("scheduler.reporter._load_events_window") @patch("scheduler.reporter._collect_stats_window") def test_no_data_returns_none(self, mock_stats, mock_events): from scheduler.reporter import generate_report mock_events.return_value = [] mock_stats.return_value = { "proc": 0, "deduped": 0, "emb_count": 0, "qdrant_count": 0, "raw_total": 0, "raw_by_source": {}, } result = generate_report() assert result is None