mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
fix: refresh provider tokens proactively and update git URLs on resume (#11296)
This commit is contained in:
@@ -8,7 +8,11 @@ from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.events.action import Action
|
||||
from openhands.events.action.commands import CmdRunAction
|
||||
from openhands.events.observation import NullObservation, Observation
|
||||
from openhands.events.observation import (
|
||||
CmdOutputObservation,
|
||||
NullObservation,
|
||||
Observation,
|
||||
)
|
||||
from openhands.events.stream import EventStream
|
||||
from openhands.integrations.provider import ProviderHandler, ProviderToken, ProviderType
|
||||
from openhands.integrations.service_types import AuthenticationError, Repository
|
||||
@@ -73,6 +77,36 @@ class MockRuntime(Runtime):
|
||||
|
||||
def run_action(self, action: Action) -> Observation:
|
||||
self.run_action_calls.append(action)
|
||||
# Return a mock git remote URL for git remote get-url commands
|
||||
# Use an OLD token to simulate token refresh scenario
|
||||
if (
|
||||
isinstance(action, CmdRunAction)
|
||||
and 'git remote get-url origin' in action.command
|
||||
):
|
||||
# Extract provider from previous clone command
|
||||
if len(self.run_action_calls) > 0:
|
||||
clone_cmd = (
|
||||
self.run_action_calls[0].command if self.run_action_calls else ''
|
||||
)
|
||||
if 'github.com' in clone_cmd:
|
||||
mock_url = 'https://old_github_token@github.com/owner/repo.git'
|
||||
elif 'gitlab.com' in clone_cmd:
|
||||
mock_url = (
|
||||
'https://oauth2:old_gitlab_token@gitlab.com/owner/repo.git'
|
||||
)
|
||||
else:
|
||||
mock_url = 'https://github.com/owner/repo.git'
|
||||
return CmdOutputObservation(
|
||||
content=mock_url, command_id=-1, command='', exit_code=0
|
||||
)
|
||||
# Return success for git remote set-url commands
|
||||
if (
|
||||
isinstance(action, CmdRunAction)
|
||||
and 'git remote set-url origin' in action.command
|
||||
):
|
||||
return CmdOutputObservation(
|
||||
content='', command_id=-1, command='', exit_code=0
|
||||
)
|
||||
return NullObservation(content='')
|
||||
|
||||
def call_tool_mcp(self, action):
|
||||
@@ -330,22 +364,29 @@ async def test_clone_or_init_repo_github_with_token(temp_dir, monkeypatch):
|
||||
|
||||
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
|
||||
|
||||
# Verify that git clone and checkout were called as separate commands
|
||||
assert len(runtime.run_action_calls) == 2
|
||||
# Verify that git clone, checkout, and git remote URL update were called
|
||||
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
|
||||
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
|
||||
|
||||
# Check that the first command is the git clone with the correct URL format with token
|
||||
clone_cmd = runtime.run_action_calls[0].command
|
||||
assert (
|
||||
f'git clone https://{github_token}@github.com/owner/repo.git repo' in clone_cmd
|
||||
)
|
||||
assert f'https://{github_token}@github.com/owner/repo.git' in clone_cmd
|
||||
expected_repo_path = str(runtime.workspace_root / 'repo')
|
||||
assert expected_repo_path in clone_cmd
|
||||
|
||||
# Check that the second command is the checkout
|
||||
checkout_cmd = runtime.run_action_calls[1].command
|
||||
assert 'cd repo' in checkout_cmd
|
||||
assert f'cd {expected_repo_path}' in checkout_cmd
|
||||
assert 'git checkout -b openhands-workspace-' in checkout_cmd
|
||||
|
||||
# Check that the third command sets the remote URL immediately after clone
|
||||
set_url_cmd = runtime.run_action_calls[2].command
|
||||
assert f'cd {expected_repo_path}' in set_url_cmd
|
||||
assert 'git remote set-url origin' in set_url_cmd
|
||||
assert github_token in set_url_cmd
|
||||
|
||||
assert result == 'repo'
|
||||
|
||||
|
||||
@@ -363,20 +404,28 @@ async def test_clone_or_init_repo_github_no_token(temp_dir, monkeypatch):
|
||||
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
|
||||
result = await runtime.clone_or_init_repo(None, 'owner/repo', None)
|
||||
|
||||
# Verify that git clone and checkout were called as separate commands
|
||||
assert len(runtime.run_action_calls) == 2
|
||||
# Verify that git clone, checkout, and remote update were called
|
||||
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
|
||||
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
|
||||
|
||||
# Check that the first command is the git clone with the correct URL format without token
|
||||
clone_cmd = runtime.run_action_calls[0].command
|
||||
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
|
||||
expected_repo_path = str(runtime.workspace_root / 'repo')
|
||||
assert 'git clone https://github.com/owner/repo.git' in clone_cmd
|
||||
assert expected_repo_path in clone_cmd
|
||||
|
||||
# Check that the second command is the checkout
|
||||
checkout_cmd = runtime.run_action_calls[1].command
|
||||
assert 'cd repo' in checkout_cmd
|
||||
assert f'cd {expected_repo_path}' in checkout_cmd
|
||||
assert 'git checkout -b openhands-workspace-' in checkout_cmd
|
||||
|
||||
# Check that the third command sets the remote URL after clone
|
||||
set_url_cmd = runtime.run_action_calls[2].command
|
||||
assert f'cd {expected_repo_path}' in set_url_cmd
|
||||
assert 'git remote set-url origin' in set_url_cmd
|
||||
|
||||
assert result == 'repo'
|
||||
|
||||
|
||||
@@ -403,23 +452,29 @@ async def test_clone_or_init_repo_gitlab_with_token(temp_dir, monkeypatch):
|
||||
|
||||
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
|
||||
|
||||
# Verify that git clone and checkout were called as separate commands
|
||||
assert len(runtime.run_action_calls) == 2
|
||||
# Verify that git clone, checkout, and git remote URL update were called
|
||||
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
|
||||
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
|
||||
|
||||
# Check that the first command is the git clone with the correct URL format with token
|
||||
clone_cmd = runtime.run_action_calls[0].command
|
||||
assert (
|
||||
f'git clone https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git repo'
|
||||
in clone_cmd
|
||||
)
|
||||
expected_repo_path = str(runtime.workspace_root / 'repo')
|
||||
assert f'https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git' in clone_cmd
|
||||
assert expected_repo_path in clone_cmd
|
||||
|
||||
# Check that the second command is the checkout
|
||||
checkout_cmd = runtime.run_action_calls[1].command
|
||||
assert 'cd repo' in checkout_cmd
|
||||
assert f'cd {expected_repo_path}' in checkout_cmd
|
||||
assert 'git checkout -b openhands-workspace-' in checkout_cmd
|
||||
|
||||
# Check that the third command sets the remote URL immediately after clone
|
||||
set_url_cmd = runtime.run_action_calls[2].command
|
||||
assert f'cd {expected_repo_path}' in set_url_cmd
|
||||
assert 'git remote set-url origin' in set_url_cmd
|
||||
assert gitlab_token in set_url_cmd
|
||||
|
||||
assert result == 'repo'
|
||||
|
||||
|
||||
@@ -437,18 +492,24 @@ async def test_clone_or_init_repo_with_branch(temp_dir, monkeypatch):
|
||||
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
|
||||
result = await runtime.clone_or_init_repo(None, 'owner/repo', 'feature-branch')
|
||||
|
||||
# Verify that git clone and checkout were called as separate commands
|
||||
assert len(runtime.run_action_calls) == 2
|
||||
# Verify that git clone, checkout, and remote update were called
|
||||
assert len(runtime.run_action_calls) == 3 # clone, checkout, set-url
|
||||
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
|
||||
assert isinstance(runtime.run_action_calls[2], CmdRunAction)
|
||||
|
||||
# Check that the first command is the git clone
|
||||
clone_cmd = runtime.run_action_calls[0].command
|
||||
expected_repo_path = str(runtime.workspace_root / 'repo')
|
||||
assert 'git clone https://github.com/owner/repo.git' in clone_cmd
|
||||
assert expected_repo_path in clone_cmd
|
||||
|
||||
# Check that the second command contains the correct branch checkout
|
||||
checkout_cmd = runtime.run_action_calls[1].command
|
||||
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
|
||||
assert 'cd repo' in checkout_cmd
|
||||
assert f'cd {expected_repo_path}' in checkout_cmd
|
||||
assert 'git checkout feature-branch' in checkout_cmd
|
||||
set_url_cmd = runtime.run_action_calls[2].command
|
||||
assert f'cd {expected_repo_path}' in set_url_cmd
|
||||
assert 'git remote set-url origin' in set_url_cmd
|
||||
assert 'git checkout -b' not in checkout_cmd # Should not create a new branch
|
||||
assert result == 'repo'
|
||||
|
||||
Reference in New Issue
Block a user