|
14 | 14 | from deepwork.review.config import ReviewRule, ReviewTask |
15 | 15 | from deepwork.review.mcp import ( |
16 | 16 | ReviewToolError, |
| 17 | + all_reviews_passed_for_files, |
17 | 18 | get_configured_reviews, |
18 | 19 | mark_passed, |
19 | 20 | run_review, |
@@ -757,3 +758,114 @@ def test_get_configured_reviews_ignores_passed_markers( |
757 | 758 | result = get_configured_reviews(tmp_path) |
758 | 759 | assert len(result) == 1 |
759 | 760 | assert result[0]["name"] == "test_rule" |
| 761 | + |
| 762 | + |
| 763 | +@pytest.mark.usefixtures("without_standard_schemas") |
| 764 | +class TestAllReviewsPassedForFiles: |
| 765 | + """Tests for all_reviews_passed_for_files — validates PLUG-REQ-001.7.""" |
| 766 | + |
| 767 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 768 | + @patch("deepwork.review.mcp.load_all_rules", return_value=([], [])) |
| 769 | + def test_empty_file_list(self, mock_load: Any, mock_schema: Any, tmp_path: Path) -> None: |
| 770 | + assert all_reviews_passed_for_files(tmp_path, []) is True |
| 771 | + |
| 772 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 773 | + @patch("deepwork.review.mcp.load_all_rules", return_value=([], [])) |
| 774 | + def test_no_rules(self, mock_load: Any, mock_schema: Any, tmp_path: Path) -> None: |
| 775 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.py"]) is True |
| 776 | + |
| 777 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 778 | + @patch("deepwork.review.mcp.load_all_rules") |
| 779 | + def test_only_catch_all_rule(self, mock_load: Any, mock_schema: Any, tmp_path: Path) -> None: |
| 780 | + """Catch-all rules are excluded, so only having one means True.""" |
| 781 | + mock_load.return_value = ([_make_catch_all_rule(tmp_path)], []) |
| 782 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.py"]) is True |
| 783 | + |
| 784 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 785 | + @patch("deepwork.review.mcp.load_all_rules") |
| 786 | + def test_non_matching_rule(self, mock_load: Any, mock_schema: Any, tmp_path: Path) -> None: |
| 787 | + """A non-catch-all rule that doesn't match the files returns True.""" |
| 788 | + py_rule = _make_rule(tmp_path) # **/*.py |
| 789 | + mock_load.return_value = ([py_rule], []) |
| 790 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.ts"]) is True |
| 791 | + |
| 792 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 793 | + @patch("deepwork.review.mcp.load_all_rules") |
| 794 | + def test_matching_rule_with_passed_marker( |
| 795 | + self, mock_load: Any, mock_schema: Any, tmp_path: Path |
| 796 | + ) -> None: |
| 797 | + from deepwork.review.instructions import INSTRUCTIONS_DIR, compute_review_id |
| 798 | + |
| 799 | + rule = _make_rule(tmp_path) |
| 800 | + mock_load.return_value = ([rule], []) |
| 801 | + |
| 802 | + # Create the file so compute_review_id produces a stable hash |
| 803 | + (tmp_path / "src").mkdir(parents=True, exist_ok=True) |
| 804 | + (tmp_path / "src" / "app.py").write_text("print('hello')") |
| 805 | + |
| 806 | + # First call to get the tasks and compute their IDs |
| 807 | + from deepwork.review.matcher import match_files_to_rules |
| 808 | + |
| 809 | + tasks = match_files_to_rules(["src/app.py"], [rule], tmp_path) |
| 810 | + assert len(tasks) >= 1 |
| 811 | + review_id = compute_review_id(tasks[0], tmp_path) |
| 812 | + |
| 813 | + # Create the .passed marker |
| 814 | + instructions_dir = tmp_path / INSTRUCTIONS_DIR |
| 815 | + instructions_dir.mkdir(parents=True, exist_ok=True) |
| 816 | + (instructions_dir / f"{review_id}.passed").write_bytes(b"") |
| 817 | + |
| 818 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.py"]) is True |
| 819 | + |
| 820 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 821 | + @patch("deepwork.review.mcp.load_all_rules") |
| 822 | + def test_matching_rule_without_passed_marker( |
| 823 | + self, mock_load: Any, mock_schema: Any, tmp_path: Path |
| 824 | + ) -> None: |
| 825 | + rule = _make_rule(tmp_path) |
| 826 | + mock_load.return_value = ([rule], []) |
| 827 | + |
| 828 | + (tmp_path / "src").mkdir(parents=True, exist_ok=True) |
| 829 | + (tmp_path / "src" / "app.py").write_text("print('hello')") |
| 830 | + |
| 831 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.py"]) is False |
| 832 | + |
| 833 | + @patch("deepwork.review.mcp.gen_schema_rules", return_value=([], [])) |
| 834 | + @patch("deepwork.review.mcp.load_all_rules") |
| 835 | + def test_mixed_passed_and_not_passed( |
| 836 | + self, mock_load: Any, mock_schema: Any, tmp_path: Path |
| 837 | + ) -> None: |
| 838 | + from deepwork.review.instructions import INSTRUCTIONS_DIR, compute_review_id |
| 839 | + from deepwork.review.matcher import match_files_to_rules |
| 840 | + |
| 841 | + rule_py = _make_rule(tmp_path) # **/*.py |
| 842 | + rule_ts = ReviewRule( |
| 843 | + name="ts_rule", |
| 844 | + description="TS rule.", |
| 845 | + include_patterns=["**/*.ts"], |
| 846 | + exclude_patterns=[], |
| 847 | + strategy="individual", |
| 848 | + instructions="Review TS.", |
| 849 | + agent=None, |
| 850 | + all_changed_filenames=False, |
| 851 | + unchanged_matching_files=False, |
| 852 | + precomputed_info_bash_command=None, |
| 853 | + source_dir=tmp_path, |
| 854 | + source_file=tmp_path / ".deepreview", |
| 855 | + source_line=5, |
| 856 | + ) |
| 857 | + mock_load.return_value = ([rule_py, rule_ts], []) |
| 858 | + |
| 859 | + (tmp_path / "src").mkdir(parents=True, exist_ok=True) |
| 860 | + (tmp_path / "src" / "app.py").write_text("py") |
| 861 | + (tmp_path / "src" / "app.ts").write_text("ts") |
| 862 | + |
| 863 | + # Mark only the py task as passed |
| 864 | + py_tasks = match_files_to_rules(["src/app.py"], [rule_py], tmp_path) |
| 865 | + review_id = compute_review_id(py_tasks[0], tmp_path) |
| 866 | + instructions_dir = tmp_path / INSTRUCTIONS_DIR |
| 867 | + instructions_dir.mkdir(parents=True, exist_ok=True) |
| 868 | + (instructions_dir / f"{review_id}.passed").write_bytes(b"") |
| 869 | + |
| 870 | + # ts task is not passed → overall False |
| 871 | + assert all_reviews_passed_for_files(tmp_path, ["src/app.py", "src/app.ts"]) is False |
0 commit comments