Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions scripts/reviewer_bot_lib/bootstrap_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def github_api_request(self, *args, **kwargs):
def github_api(self, *args, **kwargs):
return github_api.github_api(self._runtime_getter(), *args, **kwargs)

def github_graphql_request(self, *args, **kwargs):
return github_api.github_graphql_request(self._runtime_getter(), *args, **kwargs)

def get_github_token(self):
return github_api.get_github_token(self._runtime_getter())

Expand Down
7 changes: 7 additions & 0 deletions scripts/reviewer_bot_lib/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
STATE_READ_RETRY_LIMIT_ENV,
STATUS_PROJECTION_EPOCH,
TRANSITION_PERIOD_DAYS,
AssignmentAttempt,
GitHubApiResult,
)


Expand Down Expand Up @@ -230,7 +232,9 @@ class ReviewerBotRuntime:
BOT_MENTION = BOT_MENTION
COMMANDS = COMMANDS
FLS_AUDIT_LABEL = FLS_AUDIT_LABEL
AssignmentAttempt = AssignmentAttempt
AUTHOR_ASSOCIATION_TRUST_ALLOWLIST = AUTHOR_ASSOCIATION_TRUST_ALLOWLIST
GitHubApiResult = GitHubApiResult
REVIEWER_REQUEST_422_TEMPLATE = REVIEWER_REQUEST_422_TEMPLATE
REVIEW_FRESHNESS_RUNBOOK_PATH = REVIEW_FRESHNESS_RUNBOOK_PATH
REVIEW_DEADLINE_DAYS = REVIEW_DEADLINE_DAYS
Expand Down Expand Up @@ -365,6 +369,9 @@ def github_api_request(self, *args, **kwargs):
def github_api(self, *args, **kwargs):
return self.github.github_api(*args, **kwargs)

def github_graphql_request(self, *args, **kwargs):
return self.adapters.github.github_graphql_request(*args, **kwargs)

def collect_touched_item(self, issue_number: int | None) -> None:
self.touch_tracker.collect(issue_number)

Expand Down
5 changes: 0 additions & 5 deletions scripts/reviewer_bot_lib/runtime_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,19 +487,14 @@ class CommentApplicationRuntimeContext(Protocol):
BOT_MENTION: str
github: CommentGitHubWriteContext

def get_config_value(self, name: str, default: str = "") -> str: ...


@runtime_checkable
class CommentRoutingRuntimeContext(Protocol):
BOT_NAME: str
BOT_MENTION: str
AUTHOR_ASSOCIATION_TRUST_ALLOWLIST: tuple[str, ...]
logger: Logger
adapters: CommentRoutingAdaptersContext

def assert_lock_held(self, context: str) -> None: ...

def collect_touched_item(self, issue_number: int | None) -> None: ...

def github_api(self, *args, **kwargs) -> Any | None: ...
35 changes: 35 additions & 0 deletions tests/contract/reviewer_bot/test_adapter_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from scripts import reviewer_bot
from scripts.reviewer_bot_lib import event_inputs, lease_lock, review_state
from scripts.reviewer_bot_lib.config import AssignmentAttempt, GitHubApiResult
from scripts.reviewer_bot_lib.runtime import ReviewerBotRuntime, StdErrLogger
from scripts.reviewer_bot_lib.runtime_protocols import (
CommentApplicationRuntimeContext,
Expand All @@ -20,6 +21,7 @@
ReconcileWorkflowRuntimeContext,
)
from tests.fixtures.fake_runtime import FakeReviewerBotRuntime
from tests.fixtures.http_responses import FakeGitHubResponse
from tests.fixtures.reviewer_bot import make_state


Expand Down Expand Up @@ -97,6 +99,29 @@ def test_runtime_head_repair_contract_is_runtime_scoped():
assert hints["return"].__name__ == "HeadObservationRepairResult"


def test_bootstrapped_runtime_re_exposes_retained_github_type_homes():
runtime = reviewer_bot._runtime_bot()

assert runtime.AssignmentAttempt is AssignmentAttempt
assert runtime.GitHubApiResult is GitHubApiResult


def test_bootstrapped_runtime_supports_typed_rest_and_assignment_paths(monkeypatch):
runtime = reviewer_bot._runtime_bot()
monkeypatch.setenv("GITHUB_TOKEN", "token")
runtime.rest_transport = SimpleNamespace(
request=lambda *args, **kwargs: FakeGitHubResponse(201, {"ok": True}, "ok")
)

response = runtime.github_api_request("GET", "issues/42")
attempt = runtime.github.request_pr_reviewer_assignment(42, "alice")

assert isinstance(response, GitHubApiResult)
assert response.ok is True
assert isinstance(attempt, AssignmentAttempt)
assert attempt.success is True


def _protocol_member_names(protocol_type) -> set[str]:
return set(protocol_type.__annotations__) | {
name
Expand Down Expand Up @@ -546,6 +571,9 @@ def test_f2a_runtime_surface_inventory_fixture_records_retained_triples():
capabilities = {entry["capability"]: entry for entry in inventory["capability_triples"]}

assert capabilities["comment-event dispatch"]["classification"] == "retained final surface"
assert capabilities["typed REST request result"]["classification"] == "retained final surface"
assert capabilities["typed assignment helper result"]["classification"] == "retained final surface"
assert capabilities["reviewer-board GraphQL metadata read"]["classification"] == "retained final surface"
assert "workflow-run dispatch" not in capabilities
assert "refresh reviewer review from live preferred review" not in capabilities
assert "repair missing reviewer review state" not in capabilities
Expand All @@ -571,6 +599,13 @@ def test_f2a_runtime_surface_inventory_matches_bootstrap_adapter_examples():
assert capabilities["mandatory approver satisfaction"]["bootstrap_adapter"].endswith(
"satisfy_mandatory_approver_requirement"
)
assert capabilities["typed REST request result"]["bootstrap_adapter"].endswith("github_api_request")
assert capabilities["typed assignment helper result"]["bootstrap_adapter"].endswith(
"request_pr_reviewer_assignment"
)
assert capabilities["reviewer-board GraphQL metadata read"]["bootstrap_adapter"].endswith(
"github_graphql_request"
)


def test_f2c_no_runtime_surface_triples_are_deletion_ready_yet():
Expand Down
10 changes: 5 additions & 5 deletions tests/contract/reviewer_bot/test_comment_application_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def test_comment_application_stores_pending_privileged_command_from_typed_reques
comment_author="dana",
comment_body="@guidelines-bot /accept-no-fls-changes",
)
harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted"
harness.runtime.post_comment = lambda issue_number, body: True
harness.runtime.github.get_user_permission_status = lambda username, required_permission="triage": "granted"
harness.runtime.github.post_comment = lambda issue_number, body: True

changed = comment_application.process_comment_event(
harness.runtime,
Expand Down Expand Up @@ -77,8 +77,8 @@ def test_comment_application_freezes_pending_privileged_command_metadata_shape_a
comment_body="@guidelines-bot /accept-no-fls-changes",
)
harness.runtime.set_config_value("STATE_ISSUE_NUMBER", "314")
harness.runtime.get_user_permission_status = lambda username, required_permission="triage": "granted"
harness.runtime.post_comment = lambda issue_number, body: posted.append((issue_number, body)) or True
harness.runtime.github.get_user_permission_status = lambda username, required_permission="triage": "granted"
harness.runtime.github.post_comment = lambda issue_number, body: posted.append((issue_number, body)) or True

changed = comment_application.process_comment_event(
harness.runtime,
Expand Down Expand Up @@ -170,7 +170,7 @@ def test_n1_comment_application_obeys_policy_selected_handler_without_inline_com
"handle_queue_command",
lambda bot, current_state: calls.append((bot, current_state)) or ("", True),
)
harness.runtime.add_reaction = lambda *args, **kwargs: True
harness.runtime.github.add_reaction = lambda *args, **kwargs: True

changed = comment_application.process_comment_event(
harness.runtime,
Expand Down
20 changes: 20 additions & 0 deletions tests/contract/reviewer_bot/test_fake_runtime_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,21 @@ def test_fake_runtime_optional_lock_hooks_are_replaceable(monkeypatch):
assert calls == ["acquire", "release"]


def test_fake_runtime_exposes_retained_runtime_labels_and_lock_helpers(monkeypatch):
runtime = FakeReviewerBotRuntime(monkeypatch)

assert runtime.COMMANDS
assert runtime.REVIEW_LABELS
assert hasattr(runtime, "github_graphql_request")
assert hasattr(runtime, "normalize_lock_metadata")
assert hasattr(runtime, "get_state_issue")
assert hasattr(runtime, "get_state_issue_snapshot")
assert hasattr(runtime, "conditional_patch_state_issue")
assert hasattr(runtime, "render_state_issue_body")
assert hasattr(runtime, "get_lock_ref_snapshot")
assert hasattr(runtime, "renew_state_issue_lease_lock")


def test_fake_runtime_uses_explicit_public_service_fields(monkeypatch):
runtime = FakeReviewerBotRuntime(monkeypatch)

Expand Down Expand Up @@ -322,6 +337,11 @@ def test_f2a_runtime_surface_inventory_matches_fake_runtime_branch_examples():
assert capabilities["privileged accept-no-fls-changes execution"]["fake_runtime_branch"].endswith(
"handle_accept_no_fls_changes_command"
)
assert capabilities["typed REST request result"]["fake_runtime_branch"].endswith("GitHubApiResult")
assert capabilities["typed assignment helper result"]["fake_runtime_branch"].endswith("AssignmentAttempt")
assert capabilities["reviewer-board GraphQL metadata read"]["fake_runtime_branch"].endswith(
"github_graphql_request"
)
assert capabilities["mandatory approver satisfaction"]["fake_runtime_branch"].endswith(
"satisfy_mandatory_approver_requirement"
)
Expand Down
11 changes: 11 additions & 0 deletions tests/contract/reviewer_bot/test_runtime_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

def _assert_core_runtime_surface(runtime) -> None:
assert hasattr(runtime, "get_config_value")
assert hasattr(runtime, "AssignmentAttempt")
assert hasattr(runtime, "GitHubApiResult")
assert hasattr(runtime, "COMMANDS")
assert hasattr(runtime, "REVIEW_LABELS")
assert hasattr(runtime, "github_graphql_request")
assert hasattr(runtime, "infra")
assert hasattr(runtime, "domain")
assert runtime.infra.config is runtime.config
Expand All @@ -34,6 +39,12 @@ def _assert_core_runtime_surface(runtime) -> None:
assert runtime.domain.handlers is runtime.handlers


def test_fake_runtime_default_lock_state_matches_production_contract(monkeypatch):
runtime = FakeReviewerBotRuntime(monkeypatch)

assert runtime.ACTIVE_LEASE_CONTEXT is None


def test_runtime_bot_returns_concrete_runtime_object():
runtime = reviewer_bot._runtime_bot()

Expand Down
13 changes: 12 additions & 1 deletion tests/fixtures/app_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,18 @@ def stub_save_state(self, func) -> None:
self.state_store.stub_save(func)

def stub_lock(self, *, acquire=None, release=None, refresh=None) -> None:
self.locks.stub(acquire=acquire, release=release, refresh=refresh)
def wrapped_acquire():
context = acquire() if acquire is not None else object()
self.runtime.ACTIVE_LEASE_CONTEXT = context if context is not None else object()
return context

def wrapped_release():
result = release() if release is not None else True
if result:
self.runtime.ACTIVE_LEASE_CONTEXT = None
return result

self.locks.stub(acquire=wrapped_acquire, release=wrapped_release, refresh=refresh)

def stub_handler(self, name: str, func) -> None:
self.handlers.stub(name, func)
Expand Down
10 changes: 5 additions & 5 deletions tests/fixtures/commands_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,25 +118,25 @@ def capture_comment_side_effects(self):
return record_comment_side_effects(self.runtime)

def stub_assignees(self, assignees):
self.runtime.get_issue_assignees = lambda issue_number: assignees
self.runtime.github.get_issue_assignees = lambda issue_number: assignees

def stub_assignment(self, *, success: bool = True, status_code: int = 201):
def attempt(issue_number, username):
return AssignmentAttempt(success=success, status_code=status_code)

self.runtime.request_pr_reviewer_assignment = attempt
self.runtime.assign_issue_assignee = attempt
self.runtime.github.request_pr_reviewer_assignment = attempt
self.runtime.github.assign_issue_assignee = attempt

def stub_permission(self, status: str) -> None:
self.runtime.get_user_permission_status = lambda username, required_permission="triage": status
self.runtime.github.get_user_permission_status = lambda username, required_permission="triage": status

def stub_handler(self, name: str, func) -> None:
self.handlers.stub(name, func)

def automation_runner(self) -> AutomationRunner:
runner = AutomationRunner()
self._monkeypatch.setattr(automation_module, "run_command", runner.run)
self.runtime.run_command = runner.run
self.runtime.adapters.automation.run_command = runner.run
return runner

def assignment_request(self, *, issue_number: int):
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/comment_routing_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self, monkeypatch):
self._monkeypatch = monkeypatch
self.github = RouteGitHubApi()
self.runtime = FakeReviewerBotRuntime(monkeypatch, github=self.github)
self.runtime.ACTIVE_LEASE_CONTEXT = object()
self.config = self.runtime.config
self.handlers = self.runtime.handlers

Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/equivalence/runtime_surface/triple_inventory.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@
"bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapCommandAdapterServices.handle_accept_no_fls_changes_command",
"fake_runtime_branch": "tests/fixtures/fake_runtime.py:handle_accept_no_fls_changes_command",
"classification": "retained final surface"
},
{
"capability": "typed REST request result",
"runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:GitHubApiResult",
"bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapGitHubServices.github_api_request",
"fake_runtime_branch": "tests/fixtures/fake_runtime.py:GitHubApiResult",
"classification": "retained final surface"
},
{
"capability": "typed assignment helper result",
"runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:AssignmentAttempt",
"bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapGitHubServices.request_pr_reviewer_assignment",
"fake_runtime_branch": "tests/fixtures/fake_runtime.py:AssignmentAttempt",
"classification": "retained final surface"
},
{
"capability": "reviewer-board GraphQL metadata read",
"runtime_forwarder": "scripts/reviewer_bot_lib/runtime.py:github_graphql_request",
"bootstrap_adapter": "scripts/reviewer_bot_lib/bootstrap_runtime.py:_BootstrapGitHubServices.github_graphql_request",
"fake_runtime_branch": "tests/fixtures/fake_runtime.py:github_graphql_request",
"classification": "retained final surface"
}
]
}
Loading
Loading