Skip to content

Commit 8608641

Browse files
committed
ci(reviewer-bot): add marker-based test jobs
1 parent 315ceeb commit 8608641

20 files changed

+93
-94
lines changed

.github/workflows/reviewer-bot-tests.yml

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,52 @@ jobs:
3232
- name: Install dependencies
3333
run: uv sync --dev
3434

35-
- name: Run reviewer bot unit and integration tests with targeted coverage
35+
- name: Run reviewer bot unit tests
36+
run: uv run pytest tests/unit/reviewer_bot
37+
38+
reviewer-bot-integration:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v4
43+
44+
- name: Install uv
45+
uses: astral-sh/setup-uv@v6
46+
47+
- name: Install dependencies
48+
run: uv sync --dev
49+
50+
- name: Run reviewer bot integration tests
51+
run: uv run pytest tests/integration/reviewer_bot -m integration
52+
53+
reviewer-bot-contract:
54+
runs-on: ubuntu-latest
55+
steps:
56+
- name: Checkout repository
57+
uses: actions/checkout@v4
58+
59+
- name: Install uv
60+
uses: astral-sh/setup-uv@v6
61+
62+
- name: Install dependencies
63+
run: uv sync --dev
64+
65+
- name: Run reviewer bot contract tests
66+
run: uv run pytest tests/contract/reviewer_bot -m contract
67+
68+
reviewer-bot-coverage:
69+
runs-on: ubuntu-latest
70+
steps:
71+
- name: Checkout repository
72+
uses: actions/checkout@v4
73+
74+
- name: Install uv
75+
uses: astral-sh/setup-uv@v6
76+
77+
- name: Install dependencies
78+
run: uv sync --dev
79+
80+
- name: Run reviewer bot coverage suite
3681
run: >-
3782
mkdir -p artifacts/coverage &&
3883
COVERAGE_FILE=artifacts/coverage/.coverage
@@ -66,18 +111,3 @@ jobs:
66111
f"reviewer-bot branch coverage below floor: {branches:.2f}% < 48%"
67112
)
68113
PY
69-
70-
reviewer-bot-contract:
71-
runs-on: ubuntu-latest
72-
steps:
73-
- name: Checkout repository
74-
uses: actions/checkout@v4
75-
76-
- name: Install uv
77-
uses: astral-sh/setup-uv@v6
78-
79-
- name: Install dependencies
80-
run: uv sync --dev
81-
82-
- name: Run reviewer bot contract tests
83-
run: uv run pytest tests/contract/reviewer_bot

tests/contract/reviewer_bot/test_adapter_contract.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from typing import get_type_hints
2+
import pytest
3+
4+
pytestmark = pytest.mark.contract
25

36
from scripts import reviewer_bot
47
from scripts.reviewer_bot_lib.context import (
@@ -9,48 +12,40 @@
912
)
1013
from scripts.reviewer_bot_lib.runtime import ReviewerBotRuntime
1114

12-
1315
def test_reviewer_bot_exports_runtime_modules():
1416
assert reviewer_bot.requests is not None
1517
assert reviewer_bot.sys is not None
1618
assert reviewer_bot.datetime is not None
1719
assert reviewer_bot.timezone is not None
1820

19-
2021
def test_reviewer_bot_satisfies_runtime_context_protocol():
2122
assert isinstance(reviewer_bot, ReviewerBotContext)
2223

23-
2424
def test_reviewer_bot_satisfies_narrower_lock_and_state_protocols():
2525
assert isinstance(reviewer_bot, GitHubTransportContext)
2626
assert isinstance(reviewer_bot, StateStoreContext)
2727
assert isinstance(reviewer_bot, LeaseLockContext)
2828

29-
3029
def test_render_lock_commit_message_uses_direct_json_import():
3130
rendered = reviewer_bot.render_lock_commit_message({"lock_state": "unlocked"})
3231
assert reviewer_bot.LOCK_COMMIT_MARKER in rendered
3332

34-
3533
def test_maybe_record_head_observation_repair_wrapper_exports_structured_result_type():
3634
hints = get_type_hints(reviewer_bot.maybe_record_head_observation_repair)
3735

3836
assert hints["return"] is reviewer_bot.lifecycle_module.HeadObservationRepairResult
3937

40-
4138
def test_runtime_context_protocol_exposes_structured_head_repair_contract():
4239
hints = get_type_hints(ReviewerBotContext.maybe_record_head_observation_repair)
4340

4441
assert hints["return"] is reviewer_bot.lifecycle_module.HeadObservationRepairResult
4542

46-
4743
def test_runtime_bot_returns_concrete_runtime_object():
4844
runtime = reviewer_bot._runtime_bot()
4945

5046
assert isinstance(runtime, ReviewerBotRuntime)
5147
assert runtime.EVENT_INTENT_MUTATING == reviewer_bot.EVENT_INTENT_MUTATING
5248

53-
5449
def test_build_event_context_returns_structured_context(monkeypatch):
5550
monkeypatch.setenv("EVENT_NAME", "workflow_run")
5651
monkeypatch.setenv("EVENT_ACTION", "completed")
@@ -64,7 +59,6 @@ def test_build_event_context_returns_structured_context(monkeypatch):
6459
assert context.workflow_run_event == "pull_request_review"
6560
assert context.issue_labels == ("coding guideline",)
6661

67-
6862
def test_execute_run_returns_execution_result(monkeypatch):
6963
monkeypatch.setenv("EVENT_NAME", "pull_request_review")
7064
monkeypatch.setenv("EVENT_ACTION", "submitted")

tests/contract/reviewer_bot/test_privileged_commands_workflow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from pathlib import Path
2+
import pytest
23

4+
pytestmark = pytest.mark.contract
35

46
def test_privileged_commands_workflow_executes_source_entrypoint():
57
workflow_text = Path(".github/workflows/reviewer-bot-privileged-commands.yml").read_text(

tests/contract/reviewer_bot/test_reviewer_board_workflow_contracts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from pathlib import Path
2+
import pytest
23

3-
import yaml
4+
pytestmark = pytest.mark.contract
45

6+
import yaml
57

68
def test_sweeper_repair_workflow_exposes_reviewer_board_preview_dispatch():
79
data = yaml.safe_load(Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8"))
@@ -13,7 +15,6 @@ def test_sweeper_repair_workflow_exposes_reviewer_board_preview_dispatch():
1315
assert issue_number_input["required"] is False
1416
assert issue_number_input["type"] == "string"
1517

16-
1718
def test_sweeper_repair_workflow_scopes_reviewer_board_env_to_preview_only():
1819
workflow_text = Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8")
1920
assert "ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}" in workflow_text

tests/contract/reviewer_bot/test_workflow_artifact_contracts.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from pathlib import Path
2-
32
import pytest
43

5-
from scripts import reviewer_bot
4+
pytestmark = pytest.mark.contract
65

6+
from scripts import reviewer_bot
77

88
@pytest.mark.parametrize(
99
("workflow_path", "artifact_name", "payload_name"),
@@ -38,7 +38,6 @@ def test_observer_workflow_files_match_expected_artifact_contract(
3838
assert artifact_name in workflow_text
3939
assert payload_name in workflow_text
4040

41-
4241
@pytest.mark.parametrize(
4342
("payload", "workflow_name", "workflow_file", "artifact_name", "payload_name"),
4443
[
@@ -106,7 +105,6 @@ def test_deferred_workflow_identity_helpers_match_expected_contract(
106105
assert reviewer_bot.reconcile_module._artifact_expected_name(payload) == artifact_name
107106
assert reviewer_bot.reconcile_module._artifact_expected_payload_name(payload) == payload_name
108107

109-
110108
def test_validate_workflow_run_artifact_identity_rejects_triggering_name_mismatch(monkeypatch):
111109
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Wrong Workflow")
112110
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "success")
@@ -122,7 +120,6 @@ def test_validate_workflow_run_artifact_identity_rejects_triggering_name_mismatc
122120
with pytest.raises(RuntimeError, match="Triggering workflow name mismatch"):
123121
reviewer_bot.reconcile_module._validate_workflow_run_artifact_identity(payload)
124122

125-
126123
def test_validate_workflow_run_artifact_identity_rejects_run_attempt_mismatch(monkeypatch):
127124
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer")
128125
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_ATTEMPT", "2")
@@ -139,7 +136,6 @@ def test_validate_workflow_run_artifact_identity_rejects_run_attempt_mismatch(mo
139136
with pytest.raises(RuntimeError, match="run_attempt mismatch"):
140137
reviewer_bot.reconcile_module._validate_workflow_run_artifact_identity(payload)
141138

142-
143139
def test_validate_workflow_run_artifact_identity_requires_successful_conclusion(monkeypatch):
144140
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_NAME", "Reviewer Bot PR Comment Observer")
145141
monkeypatch.setenv("WORKFLOW_RUN_TRIGGERING_CONCLUSION", "failure")

tests/contract/reviewer_bot/test_workflow_files.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
from pathlib import Path
2+
import pytest
23

3-
import yaml
4+
pytestmark = pytest.mark.contract
45

6+
import yaml
57

68
def test_issue_comment_direct_workflow_exports_issue_state():
79
workflow_text = Path(".github/workflows/reviewer-bot-issue-comment-direct.yml").read_text(
810
encoding="utf-8"
911
)
1012
assert "ISSUE_STATE: ${{ github.event.issue.state }}" in workflow_text
1113

12-
1314
def test_pr_comment_observer_workflow_builds_payload_inline_without_bot_src_root():
1415
workflow = Path(".github/workflows/reviewer-bot-pr-comment-observer.yml").read_text(encoding="utf-8")
1516
assert "BOT_SRC_ROOT" not in workflow
1617
assert "build_pr_comment_observer_payload" not in workflow
1718
assert "Fetch trusted bot source tarball" not in workflow
1819

19-
2020
def test_trusted_pr_comment_workflow_preflights_same_repo_before_mutation():
2121
data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-trusted.yml").read_text(encoding="utf-8"))
2222
job = data["jobs"]["reviewer-bot-pr-comment-trusted"]
@@ -31,7 +31,6 @@ def test_trusted_pr_comment_workflow_preflights_same_repo_before_mutation():
3131
assert "https://api.github.com/repos/{repo}/pulls/{pr_number}" in workflow_text
3232
assert "RUN_TRUSTED_PR_COMMENT" in workflow_text
3333

34-
3534
def test_pr_comment_observer_workflow_uses_inline_payload_builder():
3635
data = yaml.safe_load(Path(".github/workflows/reviewer-bot-pr-comment-observer.yml").read_text(encoding="utf-8"))
3736
job = data["jobs"]["observer"]
@@ -42,7 +41,6 @@ def test_pr_comment_observer_workflow_uses_inline_payload_builder():
4241
assert "build_pr_comment_observer_payload" not in workflow_text
4342
assert 'uv run --project "$BOT_SRC_ROOT"' not in workflow_text
4443

45-
4644
def test_review_comment_observer_workflow_exists_and_is_read_only():
4745
data = yaml.safe_load(
4846
Path(".github/workflows/reviewer-bot-pr-review-comment-observer.yml").read_text(encoding="utf-8")
@@ -60,7 +58,6 @@ def test_review_comment_observer_workflow_exists_and_is_read_only():
6058
assert "checkout" not in workflow_text
6159
assert "pull_request_review_comment" in workflow_text
6260

63-
6461
def test_mutating_reviewer_bot_workflows_do_not_share_global_github_concurrency():
6562
workflow_paths = [
6663
".github/workflows/reviewer-bot-issues.yml",
@@ -76,7 +73,6 @@ def test_mutating_reviewer_bot_workflows_do_not_share_global_github_concurrency(
7673
for job in data.get("jobs", {}).values():
7774
assert "concurrency" not in job
7875

79-
8076
def test_workflow_policy_split_and_lock_only_boundaries():
8177
workflows_dir = Path(".github/workflows")
8278
required = {
@@ -111,14 +107,12 @@ def test_workflow_policy_split_and_lock_only_boundaries():
111107
if value:
112108
assert "@" in value and len(value.split("@", 1)[1]) == 40
113109

114-
115110
def test_workflow_summaries_and_runbook_references_exist():
116111
runbook = Path("docs/reviewer-bot-review-freshness-operator-runbook.md")
117112
assert runbook.exists()
118113
reconcile = Path(".github/workflows/reviewer-bot-reconcile.yml").read_text(encoding="utf-8")
119114
assert "docs/reviewer-bot-review-freshness-operator-runbook.md" in reconcile
120115

121-
122116
def test_build_pr_comment_observer_payload_marks_trusted_direct_same_repo_as_observer_noop(monkeypatch):
123117
from scripts import reviewer_bot
124118

tests/integration/reviewer_bot/test_accept_no_fls_changes.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import json
22
import subprocess
3-
43
import pytest
54

5+
pytestmark = pytest.mark.integration
6+
67
from builder import build_cli
78
from scripts import reviewer_bot
89

9-
1010
def test_list_changed_files_ignores_untracked_bootstrap_noise(monkeypatch, tmp_path):
1111
commands_seen = []
1212

@@ -23,7 +23,6 @@ def fake_run_command(command, cwd, check=True):
2323
assert reviewer_bot.automation_module.list_changed_files(tmp_path) == []
2424
assert commands_seen == [["git", "diff", "--name-only"], ["git", "diff", "--cached", "--name-only"]]
2525

26-
2726
def test_list_changed_files_reports_tracked_changes_only(monkeypatch, tmp_path):
2827
def fake_run_command(command, cwd, check=True):
2928
if command == ["git", "diff", "--name-only"]:
@@ -36,7 +35,6 @@ def fake_run_command(command, cwd, check=True):
3635

3736
assert reviewer_bot.automation_module.list_changed_files(tmp_path) == ["README.md", "src/spec.lock"]
3837

39-
4038
def test_accept_no_fls_changes_honors_explicit_target_repo_root(monkeypatch, tmp_path):
4139
monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path))
4240
monkeypatch.setenv("IS_PULL_REQUEST", "false")
@@ -55,7 +53,6 @@ def fake_list_changed_files(repo_root):
5553
assert (message, success) == ("❌ Working tree is not clean; refusing to update spec.lock.", False)
5654
assert observed["cwd"] == tmp_path
5755

58-
5956
def test_accept_no_fls_changes_uses_locked_nested_uv_commands(monkeypatch, tmp_path):
6057
monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path))
6158
monkeypatch.setenv("IS_PULL_REQUEST", "false")
@@ -86,7 +83,6 @@ def fake_run_command(command, cwd, check=False):
8683
(["uv", "run", "--locked", "python", "./make.py", "--update-spec-lock-file"], tmp_path, False),
8784
]
8885

89-
9086
def test_accept_no_fls_changes_surfaces_locked_uv_failure_details(monkeypatch, tmp_path):
9187
monkeypatch.setenv("REVIEWER_BOT_TARGET_REPO_ROOT", str(tmp_path))
9288
monkeypatch.setenv("IS_PULL_REQUEST", "false")
@@ -110,7 +106,6 @@ def fake_run_command(command, cwd, check=False):
110106
assert "Audit command failed." in message
111107
assert "--locked was provided" in message
112108

113-
114109
def test_update_spec_lock_file_mode_exits_before_build_docs(monkeypatch, tmp_path):
115110
monkeypatch.setattr(
116111
build_cli.argparse.ArgumentParser,

tests/integration/reviewer_bot/test_app_closed_issue_cleanup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from scripts import reviewer_bot
22
from tests.fixtures.reviewer_bot import make_state
3+
import pytest
34

5+
pytestmark = pytest.mark.integration
46

57
def test_execute_run_closed_issue_comment_cleanup_persists_removed_review_entry(monkeypatch):
68
monkeypatch.setenv("EVENT_NAME", "issue_comment")
@@ -54,7 +56,6 @@ def fake_load_state(*, fail_on_unavailable=False):
5456
assert sync_calls[0][0] is reloaded_state
5557
assert sync_calls[0][1] == [42]
5658

57-
5859
def test_execute_run_closed_issue_comment_without_entry_skips_save(monkeypatch):
5960
monkeypatch.setenv("EVENT_NAME", "issue_comment")
6061
monkeypatch.setenv("EVENT_ACTION", "created")

tests/integration/reviewer_bot/test_app_execution.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from scripts import reviewer_bot
22
from tests.fixtures.reviewer_bot import make_state
3+
import pytest
34

5+
pytestmark = pytest.mark.integration
46

57
def test_execute_run_reloads_state_before_syncing_status_labels(monkeypatch):
68
monkeypatch.setenv("EVENT_NAME", "issue_comment")
@@ -58,7 +60,6 @@ def fake_sync_status_labels_for_items(state, issue_numbers):
5860
"sync",
5961
]
6062

61-
6263
def test_execute_run_returns_failure_when_save_state_fails(monkeypatch):
6364
monkeypatch.setenv("EVENT_NAME", "issue_comment")
6465
monkeypatch.setenv("EVENT_ACTION", "created")
@@ -75,7 +76,6 @@ def test_execute_run_returns_failure_when_save_state_fails(monkeypatch):
7576
assert result.exit_code == 1
7677
assert result.state_changed is True
7778

78-
7979
def test_execute_run_returns_failure_for_invalid_workflow_run_context(monkeypatch):
8080
monkeypatch.setenv("EVENT_NAME", "workflow_run")
8181
monkeypatch.setenv("EVENT_ACTION", "completed")

0 commit comments

Comments
 (0)