import logging def test_verify_internal_token_requires_env_by_default(monkeypatch): import services.code_review_pipeline_service as module monkeypatch.setattr(module, "INTERNAL_TOKEN", "") monkeypatch.setattr(module, "ALLOW_INSECURE_WEBHOOK", False) assert module.verify_internal_token("") is False assert module.verify_internal_token("anything") is False def test_verify_internal_token_allows_explicit_dev_override(monkeypatch): import services.code_review_pipeline_service as module monkeypatch.setattr(module, "INTERNAL_TOKEN", "") monkeypatch.setattr(module, "ALLOW_INSECURE_WEBHOOK", True) assert module.verify_internal_token("") is True def test_code_review_guard_auto_fixes_high_risk_findings(monkeypatch): """ADR-020:CRITICAL/HIGH 不再走 HITL,一律 auto_fix=true。""" import services.code_review_pipeline_service as module monkeypatch.setattr(module, "AUTO_FIX_ENABLED", True) pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline.state["severity_summary"] = {"critical": 1, "high": 1, "medium": 0, "low": 0} guarded = pipeline._guard_ea_decision( {"priority": "critical", "auto_fix": True, "reasoning": "建議修復", "fix_files": ["services/example.py"]}, [ {"severity": "CRITICAL", "file": "services/example.py"}, {"severity": "HIGH", "file": "services/example.py"}, ], ) assert guarded["auto_fix"] is True assert guarded["human_review_needed"] is False def test_code_review_error_notification_failure_is_logged(monkeypatch, caplog): import services.telegram_templates as telegram_templates import services.code_review_pipeline_service as module def broken_send(_message): raise RuntimeError("telegram down") monkeypatch.setattr(telegram_templates, "_send_telegram_raw", broken_send) caplog.set_level(logging.WARNING, logger="services.code_review_pipeline_service") pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline._notify_error("boom") assert "失敗通知發送失敗" in caplog.text def test_code_review_guard_main_switch_can_disable_auto_fix(monkeypatch): """ADR-020:主開關 CODE_REVIEW_AUTO_FIX_ENABLED=false 可即時切斷自動修復鏈,但不再回退到 HITL。""" import services.code_review_pipeline_service as module monkeypatch.setattr(module, "AUTO_FIX_ENABLED", False) pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline.state["severity_summary"] = {"critical": 0, "high": 0, "medium": 1, "low": 0} guarded = pipeline._guard_ea_decision( {"priority": "medium", "auto_fix": True, "reasoning": "建議修復", "fix_files": ["services/example.py"]}, [{"severity": "MEDIUM", "file": "services/example.py"}], ) assert guarded["auto_fix"] is False # ADR-020:即使主開關關掉,也不再標記 human_review_needed(不存在 HITL 通道) assert guarded["human_review_needed"] is False def test_code_review_no_findings_no_auto_fix(monkeypatch): """無 finding 時,auto_fix=false 但不應標記為 human_review_needed。""" import services.code_review_pipeline_service as module monkeypatch.setattr(module, "AUTO_FIX_ENABLED", True) pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline.state["severity_summary"] = {"critical": 0, "high": 0, "medium": 0, "low": 0} guarded = pipeline._guard_ea_decision( {"priority": "low", "auto_fix": False, "reasoning": "無 finding", "fix_files": []}, [], ) assert guarded["auto_fix"] is False assert guarded["human_review_needed"] is False def test_guard_upgrades_llm_false_to_true_when_findings_exist(monkeypatch): """ADR-020 核心保證:即使 LLM EA 回傳 auto_fix=False,只要有 finding 且主開關開,guard 必升級為 true。""" import services.code_review_pipeline_service as module monkeypatch.setattr(module, "AUTO_FIX_ENABLED", True) pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline.state["severity_summary"] = {"critical": 0, "high": 0, "medium": 1, "low": 0} guarded = pipeline._guard_ea_decision( {"priority": "medium", "auto_fix": False, "reasoning": "LLM 保守判斷", "fix_files": ["services/example.py"]}, [{"severity": "MEDIUM", "file": "services/example.py"}], ) # ADR-020 不允許 LLM 把決策降級為 HITL assert guarded["auto_fix"] is True assert guarded["human_review_needed"] is False def test_guard_upgrades_llm_human_review_true_to_false(monkeypatch): """ADR-020:即使 LLM 回 human_review_needed=true,guard 也應強制改為 false。""" import services.code_review_pipeline_service as module monkeypatch.setattr(module, "AUTO_FIX_ENABLED", True) pipeline = module.CodeReviewPipeline("abcdef123456", ["services/example.py"]) pipeline.state["severity_summary"] = {"critical": 1, "high": 0, "medium": 0, "low": 0} guarded = pipeline._guard_ea_decision( { "priority": "critical", "auto_fix": False, "human_review_needed": True, "reasoning": "LLM 想走人工審查", "fix_files": ["services/example.py"], }, [{"severity": "CRITICAL", "file": "services/example.py"}], ) assert guarded["auto_fix"] is True assert guarded["human_review_needed"] is False def test_complete_notification_marks_non_whitelisted_aider_files(monkeypatch): """tests/docs/config 等 finding 不應在完成通知中宣稱 AiderHeal 會自動修復。""" import services.telegram_templates as telegram_templates import services.code_review_pipeline_service as module messages = [] monkeypatch.setattr(telegram_templates, "_send_telegram_raw", messages.append) pipeline = module.CodeReviewPipeline( "abcdef123456", ["tests/test_market_intel_skeleton.py"], ) pipeline.state["severity_summary"] = { "critical": 0, "high": 1, "medium": 0, "low": 0, } pipeline._notify_complete( [ { "severity": "HIGH", "description": "疑似硬編碼敏感字串", "file": "tests/test_market_intel_skeleton.py", } ], "", { "priority": "high", "auto_fix": True, "fix_files": ["tests/test_market_intel_skeleton.py"], }, ) assert messages assert "不在自動修復白名單" in messages[0] assert "已觸發自動修復" not in messages[0]