mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 15:03:16 +00:00
* fix: scope public reviewer tokens Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * refactor: simplify reviewer token wiring; fix push re-scope + red test - Remove the redundant _check_or_recreate_sandbox_for_proxy / _refresh_github_proxy_or_recreate_for_proxy wrappers and call the underlying functions directly (they already default the token to None). - process_github_push_event: re-scope the GitHub App token when the push payload lacked repo privacy/id but PR metadata reveals a public repo, so reviewer.py never proxies a full-installation token for a public PR. - Clarify the two-token sequence in trigger_pr_review_from_ref. - Fix pre-existing failing test test_proxy_refresh_failure_recreates_sandbox and add coverage for _reviewer_token_for_repo + push-event scoping. --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
99 lines
3.9 KiB
Python
99 lines
3.9 KiB
Python
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from agent.server import (
|
|
SANDBOX_BACKENDS,
|
|
SANDBOX_CREATING,
|
|
SANDBOX_CREATION_TIMEOUT,
|
|
ensure_sandbox_for_thread,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stale_sandbox_creating_without_cached_backend_resets_and_creates() -> None:
|
|
thread_id = "thread-stale-creating"
|
|
SANDBOX_BACKENDS.clear()
|
|
sandbox_backend = MagicMock()
|
|
sandbox_backend.id = "sandbox-new"
|
|
|
|
stale_at = 0.0 # epoch 0 → far older than the timeout
|
|
thread = {"metadata": {"sandbox_id": SANDBOX_CREATING, "sandbox_creating_at": stale_at}}
|
|
|
|
with (
|
|
patch(
|
|
"agent.server.get_sandbox_id_from_metadata",
|
|
new_callable=AsyncMock,
|
|
return_value=SANDBOX_CREATING,
|
|
),
|
|
patch("agent.server.client.threads.get", new_callable=AsyncMock, return_value=thread),
|
|
patch("agent.server._create_sandbox_with_proxy", new_callable=AsyncMock) as create_sandbox,
|
|
patch("agent.server._configure_git_identity", new_callable=AsyncMock),
|
|
patch("agent.server.client.threads.update", new_callable=AsyncMock) as update_thread,
|
|
):
|
|
create_sandbox.return_value = sandbox_backend
|
|
|
|
result = await ensure_sandbox_for_thread(thread_id)
|
|
|
|
assert result.id == "sandbox-new"
|
|
create_sandbox.assert_awaited_once()
|
|
# Stale sentinel reset (id + timestamp cleared), then fresh creation claims it.
|
|
assert update_thread.await_args_list[0].kwargs == {
|
|
"thread_id": thread_id,
|
|
"metadata": {"sandbox_id": None, "sandbox_creating_at": None},
|
|
}
|
|
assert update_thread.await_args_list[1].kwargs["metadata"]["sandbox_id"] == SANDBOX_CREATING
|
|
assert update_thread.await_args_list[2].kwargs == {
|
|
"thread_id": thread_id,
|
|
"metadata": {"sandbox_id": "sandbox-new"},
|
|
}
|
|
|
|
SANDBOX_BACKENDS.clear()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fresh_sandbox_creating_waits_for_other_worker() -> None:
|
|
"""A recent sentinel from another worker must be waited on, not overwritten."""
|
|
thread_id = "thread-concurrent-creating"
|
|
SANDBOX_BACKENDS.clear()
|
|
existing_backend = MagicMock()
|
|
existing_backend.id = "sandbox-existing"
|
|
|
|
import time
|
|
|
|
fresh_at = time.time() # well within the timeout
|
|
threads = [
|
|
{"metadata": {"sandbox_id": SANDBOX_CREATING, "sandbox_creating_at": fresh_at}},
|
|
{"metadata": {"sandbox_id": "sandbox-existing", "sandbox_creating_at": fresh_at}},
|
|
]
|
|
|
|
async def passthrough(sb, _thread_id, _github_proxy_token=None):
|
|
return sb
|
|
|
|
with (
|
|
patch(
|
|
"agent.server.get_sandbox_id_from_metadata",
|
|
new_callable=AsyncMock,
|
|
return_value=SANDBOX_CREATING,
|
|
),
|
|
patch("agent.server.client.threads.get", new_callable=AsyncMock, side_effect=threads),
|
|
patch("agent.server.asyncio.sleep", new_callable=AsyncMock),
|
|
patch("agent.server.create_sandbox", return_value=existing_backend) as connect_sandbox,
|
|
patch("agent.server._create_sandbox_with_proxy", new_callable=AsyncMock) as create_sandbox,
|
|
patch("agent.server.check_or_recreate_sandbox", side_effect=passthrough),
|
|
patch("agent.server._refresh_github_proxy_or_recreate", side_effect=passthrough),
|
|
patch("agent.server._configure_git_identity", new_callable=AsyncMock),
|
|
patch("agent.server.client.threads.update", new_callable=AsyncMock) as update_thread,
|
|
):
|
|
result = await ensure_sandbox_for_thread(thread_id)
|
|
|
|
assert result.id == "sandbox-existing"
|
|
# Connected to the worker's sandbox; no second sandbox was created.
|
|
connect_sandbox.assert_called_once_with("sandbox-existing")
|
|
create_sandbox.assert_not_awaited()
|
|
# The fresh sentinel was never reset.
|
|
for call in update_thread.await_args_list:
|
|
assert call.kwargs["metadata"] != {"sandbox_id": None, "sandbox_creating_at": None}
|
|
|
|
assert SANDBOX_CREATION_TIMEOUT > 0
|
|
SANDBOX_BACKENDS.clear()
|