from __future__ import annotations import hashlib import json import tempfile import unittest from pathlib import Path from claridoc.models import PipelineConfig from claridoc.pipeline import _mock_provider_warning, run_pipeline from claridoc.templates import mock_pipeline_config from tests.helpers import make_brief, make_sources class PipelineTests(unittest.TestCase): def test_mixed_mock_pipeline_is_explicitly_flagged(self) -> None: config_data = mock_pipeline_config() config_data["writer"] = {"provider": "claude"} warning = _mock_provider_warning(PipelineConfig.from_dict(config_data)) self.assertIn("mixes external providers", warning) self.assertIn("synthetic", warning) def test_end_to_end_mock_run_creates_auditable_artifacts(self) -> None: with tempfile.TemporaryDirectory() as temp: output = Path(temp) / "run" result = run_pipeline( make_brief(), make_sources(), PipelineConfig.from_dict(mock_pipeline_config()), output, ) self.assertTrue(result.passed) self.assertTrue(result.final_path.is_file()) self.assertTrue(result.report_path.is_file()) self.assertTrue(result.manifest_path.is_file()) self.assertTrue((output / "final" / "provenance.md").is_file()) self.assertTrue((output / "final" / "evidence-map.json").is_file()) run_data = json.loads((output / "run.json").read_text(encoding="utf-8")) self.assertTrue(run_data["passed"]) self.assertTrue(any("deterministic mocks" in warning for warning in result.warnings)) report_text = result.report_path.read_text(encoding="utf-8") self.assertIn("Provider topology", report_text) self.assertIn("synthetic", report_text) manifest = json.loads(result.manifest_path.read_text(encoding="utf-8")) paths = {item["path"] for item in manifest["files"]} self.assertIn("final/document.md", paths) self.assertIn("provider-events.jsonl", paths) self.assertIn("final/provenance.md", paths) self.assertIn("final/evidence-map.json", paths) self.assertNotIn("manifest.json", paths) for item in manifest["files"]: artifact = output / item["path"] self.assertEqual(artifact.stat().st_size, item["bytes"]) self.assertEqual(hashlib.sha256(artifact.read_bytes()).hexdigest(), item["sha256"]) def test_revision_limit_is_enforced_when_gate_cannot_pass(self) -> None: with tempfile.TemporaryDirectory() as temp: output = Path(temp) / "run" config_data = mock_pipeline_config() config_data["quality_gate"]["minimum_score"] = 99 config_data["quality_gate"]["max_revisions"] = 1 result = run_pipeline( make_brief(), make_sources(), PipelineConfig.from_dict(config_data), output, ) self.assertFalse(result.passed) self.assertEqual(len(result.rounds), 2) self.assertTrue((output / "rounds" / "round-01" / "revision.raw.txt").is_file()) def test_reviewer_role_cannot_escape_artifact_directory(self) -> None: with tempfile.TemporaryDirectory() as temp: root = Path(temp) output = root / "run" config_data = mock_pipeline_config() config_data["reviewers"] = [{"role": "../../logic reviewer", "provider": "mock"}] run_pipeline( make_brief(), make_sources(), PipelineConfig.from_dict(config_data), output, ) review_files = list((output / "rounds" / "round-01").glob("review-*")) self.assertTrue(any(path.name.endswith("logic-reviewer.json") for path in review_files)) self.assertEqual(list(root.glob("logic*")), []) if __name__ == "__main__": unittest.main()