|
1 | 1 | import json |
2 | 2 | from pathlib import Path |
3 | | -from urllib.parse import unquote |
| 3 | +from urllib.parse import unquote, urlparse |
4 | 4 |
|
5 | 5 | import pytest |
6 | 6 |
|
|
10 | 10 | maintenance, |
11 | 11 | maintenance_schedule, |
12 | 12 | reconcile, |
| 13 | + state_store, |
13 | 14 | ) |
14 | 15 | from scripts.reviewer_bot_lib.config import STATUS_AWAITING_REVIEWER_RESPONSE_LABEL |
15 | 16 | from tests.fixtures.app_harness import AppHarness |
| 17 | +from tests.fixtures.http_responses import FakeGitHubResponse |
16 | 18 | from tests.fixtures.reviewer_bot import make_state, make_tracked_review_state |
| 19 | +from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi, github_result |
17 | 20 |
|
18 | 21 | pytestmark = pytest.mark.integration |
19 | 22 |
|
@@ -61,6 +64,27 @@ def github_api_request(method, endpoint, data=None, extra_headers=None, **kwargs |
61 | 64 | return runtime, label_ops |
62 | 65 |
|
63 | 66 |
|
| 67 | +def _route_bootstrapped_rest_request(routes: RouteGitHubApi): |
| 68 | + def request(method, url, *, headers=None, json_data=None, timeout_seconds=None): |
| 69 | + parts = urlparse(url).path.strip("/").split("/") |
| 70 | + endpoint = "/".join(parts[3:]) |
| 71 | + result = routes.github_api_request( |
| 72 | + method, |
| 73 | + endpoint, |
| 74 | + data=json_data, |
| 75 | + extra_headers=headers, |
| 76 | + timeout_seconds=timeout_seconds, |
| 77 | + ) |
| 78 | + return FakeGitHubResponse( |
| 79 | + result.status_code or 0, |
| 80 | + payload=result.payload, |
| 81 | + text=result.text, |
| 82 | + headers=result.headers, |
| 83 | + ) |
| 84 | + |
| 85 | + return request |
| 86 | + |
| 87 | + |
64 | 88 | def test_app_harness_exposes_focused_runtime_services(monkeypatch): |
65 | 89 | harness = AppHarness(monkeypatch) |
66 | 90 |
|
@@ -400,6 +424,83 @@ def test_bootstrapped_runtime_workflow_dispatch_check_overdue_preserves_touched_ |
400 | 424 | assert runtime.ACTIVE_LEASE_CONTEXT is None |
401 | 425 |
|
402 | 426 |
|
| 427 | +def test_bootstrapped_runtime_workflow_dispatch_check_overdue_uses_real_save_state_without_conditional_issue_headers( |
| 428 | + monkeypatch, |
| 429 | +): |
| 430 | + runtime = reviewer_bot._runtime_bot() |
| 431 | + state = make_state() |
| 432 | + state["status_projection_epoch"] = runtime.STATUS_PROJECTION_EPOCH |
| 433 | + issue_number = "314" |
| 434 | + issue_body = state_store.render_state_issue_body(state) |
| 435 | + routes = RouteGitHubApi().add_request_sequence( |
| 436 | + "GET", |
| 437 | + f"issues/{issue_number}", |
| 438 | + [ |
| 439 | + github_result( |
| 440 | + 200, |
| 441 | + {"body": issue_body, "html_url": f"https://example.com/issues/{issue_number}"}, |
| 442 | + headers={"ETag": '"etag-1"'}, |
| 443 | + ), |
| 444 | + github_result( |
| 445 | + 200, |
| 446 | + {"body": issue_body, "html_url": f"https://example.com/issues/{issue_number}"}, |
| 447 | + headers={"ETag": '"etag-2"'}, |
| 448 | + ), |
| 449 | + ], |
| 450 | + ).add_request( |
| 451 | + "PATCH", |
| 452 | + f"issues/{issue_number}", |
| 453 | + status_code=200, |
| 454 | + payload={"body": "updated"}, |
| 455 | + ) |
| 456 | + |
| 457 | + def acquire_lock(): |
| 458 | + runtime.ACTIVE_LEASE_CONTEXT = object() |
| 459 | + return runtime.ACTIVE_LEASE_CONTEXT |
| 460 | + |
| 461 | + def release_lock(): |
| 462 | + runtime.ACTIVE_LEASE_CONTEXT = None |
| 463 | + return True |
| 464 | + |
| 465 | + def handle_scheduled_check_result(bot, current_state): |
| 466 | + del bot |
| 467 | + current_state["current_index"] = 1 |
| 468 | + return maintenance.ScheduleHandlerResult(True, []) |
| 469 | + |
| 470 | + monkeypatch.setattr(runtime.locks, "acquire", acquire_lock) |
| 471 | + monkeypatch.setattr(runtime.locks, "release", release_lock) |
| 472 | + monkeypatch.setattr(runtime.locks, "refresh", lambda: True) |
| 473 | + monkeypatch.setattr(runtime.rest_transport, "request", _route_bootstrapped_rest_request(routes)) |
| 474 | + monkeypatch.setattr(runtime.adapters.workflow, "process_pass_until_expirations", lambda current_state: (current_state, [])) |
| 475 | + monkeypatch.setattr(runtime.adapters.workflow, "sync_members_with_queue", lambda current_state: (current_state, [])) |
| 476 | + monkeypatch.setattr(maintenance_schedule, "handle_scheduled_check_result", handle_scheduled_check_result) |
| 477 | + monkeypatch.setenv("EVENT_NAME", "workflow_dispatch") |
| 478 | + monkeypatch.setenv("EVENT_ACTION", "") |
| 479 | + monkeypatch.setenv("MANUAL_ACTION", "check-overdue") |
| 480 | + monkeypatch.setenv("GITHUB_TOKEN", "token") |
| 481 | + monkeypatch.setenv("REPO_OWNER", "rustfoundation") |
| 482 | + monkeypatch.setenv("REPO_NAME", "safety-critical-rust-coding-guidelines") |
| 483 | + monkeypatch.setenv("STATE_ISSUE_NUMBER", issue_number) |
| 484 | + |
| 485 | + result = reviewer_bot.execute_run(reviewer_bot.build_event_context(runtime), runtime) |
| 486 | + |
| 487 | + assert result.exit_code == 0 |
| 488 | + assert result.state_changed is True |
| 489 | + assert runtime.ACTIVE_LEASE_CONTEXT is None |
| 490 | + assert sum( |
| 491 | + 1 |
| 492 | + for call in routes.request_calls |
| 493 | + if call.method == "GET" and call.endpoint == f"issues/{issue_number}" |
| 494 | + ) >= 2 |
| 495 | + patch_call = routes.request_calls[-1] |
| 496 | + assert patch_call.method == "PATCH" |
| 497 | + assert patch_call.endpoint == f"issues/{issue_number}" |
| 498 | + assert patch_call.data is not None |
| 499 | + assert "current_index: 1" in patch_call.data["body"] |
| 500 | + assert patch_call.extra_headers is not None |
| 501 | + assert all(header_name.lower() != "if-match" for header_name in patch_call.extra_headers) |
| 502 | + |
| 503 | + |
403 | 504 | def test_d4a_app_branch_to_phase_map_is_frozen_pre_edit(): |
404 | 505 | app_text = Path("scripts/reviewer_bot_lib/app.py").read_text(encoding="utf-8") |
405 | 506 |
|
|
0 commit comments