|
|
|
|
@@ -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'
|
|
|
|
|
|