mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
feat(backend): Include owner_type in the Get Repositories API response. (#9763)
This commit is contained in:
@@ -1827,6 +1827,11 @@
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"owner_type": {
|
||||
"type": "string",
|
||||
"enum": ["user", "organization"],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ from openhands.integrations.service_types import (
|
||||
BaseGitService,
|
||||
Branch,
|
||||
GitService,
|
||||
OwnerType,
|
||||
ProviderType,
|
||||
Repository,
|
||||
RequestMethod,
|
||||
@@ -246,6 +247,11 @@ class BitBucketService(BaseGitService, GitService):
|
||||
is_public=repo.get('is_private', True) is False,
|
||||
stargazers_count=None, # Bitbucket doesn't have stars
|
||||
pushed_at=repo.get('updated_on'),
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('workspace', {}).get('is_private') is False
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -287,6 +293,11 @@ class BitBucketService(BaseGitService, GitService):
|
||||
is_public=data.get('is_private', True) is False,
|
||||
stargazers_count=None, # Bitbucket doesn't have stars
|
||||
pushed_at=data.get('updated_on'),
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if data.get('workspace', {}).get('is_private') is False
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
|
||||
async def get_branches(self, repository: str) -> list[Branch]:
|
||||
|
||||
@@ -15,6 +15,7 @@ from openhands.integrations.service_types import (
|
||||
BaseGitService,
|
||||
Branch,
|
||||
GitService,
|
||||
OwnerType,
|
||||
ProviderType,
|
||||
Repository,
|
||||
RequestMethod,
|
||||
@@ -236,6 +237,11 @@ class GitHubService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('stargazers_count'),
|
||||
git_provider=ProviderType.GITHUB,
|
||||
is_public=not repo.get('private', True),
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('owner', {}).get('type') == 'Organization'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
for repo in all_repos
|
||||
]
|
||||
@@ -269,6 +275,11 @@ class GitHubService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('stargazers_count'),
|
||||
git_provider=ProviderType.GITHUB,
|
||||
is_public=True,
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('owner', {}).get('type') == 'Organization'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
for repo in repo_items
|
||||
]
|
||||
@@ -414,6 +425,11 @@ class GitHubService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('stargazers_count'),
|
||||
git_provider=ProviderType.GITHUB,
|
||||
is_public=not repo.get('private', True),
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('owner', {}).get('type') == 'Organization'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
|
||||
async def get_branches(self, repository: str) -> list[Branch]:
|
||||
|
||||
@@ -8,6 +8,7 @@ from openhands.integrations.service_types import (
|
||||
BaseGitService,
|
||||
Branch,
|
||||
GitService,
|
||||
OwnerType,
|
||||
ProviderType,
|
||||
Repository,
|
||||
RequestMethod,
|
||||
@@ -214,6 +215,11 @@ class GitLabService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('star_count'),
|
||||
git_provider=ProviderType.GITLAB,
|
||||
is_public=True,
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('namespace', {}).get('kind') == 'group'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
for repo in response
|
||||
]
|
||||
@@ -265,6 +271,11 @@ class GitLabService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('star_count'),
|
||||
git_provider=ProviderType.GITLAB,
|
||||
is_public=repo.get('visibility') == 'public',
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('namespace', {}).get('kind') == 'group'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
for repo in all_repos
|
||||
]
|
||||
@@ -415,6 +426,11 @@ class GitLabService(BaseGitService, GitService):
|
||||
stargazers_count=repo.get('star_count'),
|
||||
git_provider=ProviderType.GITLAB,
|
||||
is_public=repo.get('visibility') == 'public',
|
||||
owner_type=(
|
||||
OwnerType.ORGANIZATION
|
||||
if repo.get('namespace', {}).get('kind') == 'group'
|
||||
else OwnerType.USER
|
||||
),
|
||||
)
|
||||
|
||||
async def get_branches(self, repository: str) -> list[Branch]:
|
||||
|
||||
@@ -24,6 +24,11 @@ class TaskType(str, Enum):
|
||||
OPEN_PR = 'OPEN_PR'
|
||||
|
||||
|
||||
class OwnerType(str, Enum):
|
||||
USER = 'user'
|
||||
ORGANIZATION = 'organization'
|
||||
|
||||
|
||||
class SuggestedTask(BaseModel):
|
||||
git_provider: ProviderType
|
||||
task_type: TaskType
|
||||
@@ -32,17 +37,7 @@ class SuggestedTask(BaseModel):
|
||||
title: str
|
||||
|
||||
def get_provider_terms(self) -> dict:
|
||||
if self.git_provider == ProviderType.GITLAB:
|
||||
return {
|
||||
'requestType': 'Merge Request',
|
||||
'requestTypeShort': 'MR',
|
||||
'apiName': 'GitLab API',
|
||||
'tokenEnvVar': 'GITLAB_TOKEN',
|
||||
'ciSystem': 'CI pipelines',
|
||||
'ciProvider': 'GitLab',
|
||||
'requestVerb': 'merge request',
|
||||
}
|
||||
elif self.git_provider == ProviderType.GITHUB:
|
||||
if self.git_provider == ProviderType.GITHUB:
|
||||
return {
|
||||
'requestType': 'Pull Request',
|
||||
'requestTypeShort': 'PR',
|
||||
@@ -52,6 +47,16 @@ class SuggestedTask(BaseModel):
|
||||
'ciProvider': 'GitHub',
|
||||
'requestVerb': 'pull request',
|
||||
}
|
||||
elif self.git_provider == ProviderType.GITLAB:
|
||||
return {
|
||||
'requestType': 'Merge Request',
|
||||
'requestTypeShort': 'MR',
|
||||
'apiName': 'GitLab API',
|
||||
'tokenEnvVar': 'GITLAB_TOKEN',
|
||||
'ciSystem': 'CI pipelines',
|
||||
'ciProvider': 'GitLab',
|
||||
'requestVerb': 'merge request',
|
||||
}
|
||||
elif self.git_provider == ProviderType.BITBUCKET:
|
||||
return {
|
||||
'requestType': 'Pull Request',
|
||||
@@ -117,6 +122,9 @@ class Repository(BaseModel):
|
||||
stargazers_count: int | None = None
|
||||
link_header: str | None = None
|
||||
pushed_at: str | None = None # ISO 8601 format date string
|
||||
owner_type: OwnerType | None = (
|
||||
None # Whether the repository is owned by a user or organization
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationError(ValueError):
|
||||
|
||||
@@ -9,8 +9,8 @@ from pydantic import SecretStr
|
||||
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitBucketService
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.integrations.service_types import OwnerType, Repository
|
||||
from openhands.integrations.service_types import ProviderType as ServiceProviderType
|
||||
from openhands.integrations.service_types import Repository
|
||||
from openhands.integrations.utils import validate_provider_token
|
||||
from openhands.resolver.interfaces.bitbucket import BitbucketIssueHandler
|
||||
from openhands.resolver.interfaces.issue import Issue
|
||||
@@ -592,6 +592,167 @@ async def test_validate_provider_token_with_empty_tokens():
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bitbucket_get_repositories_with_user_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for user repositories."""
|
||||
service = BitBucketService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for user repositories (private workspace)
|
||||
mock_workspaces = [{'slug': 'test-user', 'name': 'Test User'}]
|
||||
mock_repos = [
|
||||
{
|
||||
'uuid': 'repo-1',
|
||||
'slug': 'user-repo1',
|
||||
'workspace': {'slug': 'test-user', 'is_private': True},
|
||||
'is_private': False,
|
||||
'updated_on': '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'uuid': 'repo-2',
|
||||
'slug': 'user-repo2',
|
||||
'workspace': {'slug': 'test-user', 'is_private': True},
|
||||
'is_private': True,
|
||||
'updated_on': '2023-01-02T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_fetch_paginated_data') as mock_fetch:
|
||||
mock_fetch.side_effect = [mock_workspaces, mock_repos]
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for user repositories (private workspace)
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ServiceProviderType.BITBUCKET
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bitbucket_get_repositories_with_organization_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for organization repositories."""
|
||||
service = BitBucketService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for organization repositories (public workspace)
|
||||
mock_workspaces = [{'slug': 'test-org', 'name': 'Test Organization'}]
|
||||
mock_repos = [
|
||||
{
|
||||
'uuid': 'repo-3',
|
||||
'slug': 'org-repo1',
|
||||
'workspace': {'slug': 'test-org', 'is_private': False},
|
||||
'is_private': False,
|
||||
'updated_on': '2023-01-03T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'uuid': 'repo-4',
|
||||
'slug': 'org-repo2',
|
||||
'workspace': {'slug': 'test-org', 'is_private': False},
|
||||
'is_private': True,
|
||||
'updated_on': '2023-01-04T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_fetch_paginated_data') as mock_fetch:
|
||||
mock_fetch.side_effect = [mock_workspaces, mock_repos]
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for organization repositories (public workspace)
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.ORGANIZATION
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ServiceProviderType.BITBUCKET
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bitbucket_get_repositories_mixed_owner_types():
|
||||
"""Test that get_repositories correctly handles mixed user and organization repositories."""
|
||||
service = BitBucketService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with mixed workspace types
|
||||
mock_workspaces = [
|
||||
{'slug': 'test-user', 'name': 'Test User'},
|
||||
{'slug': 'test-org', 'name': 'Test Organization'},
|
||||
]
|
||||
|
||||
# First workspace (user) repositories
|
||||
mock_user_repos = [
|
||||
{
|
||||
'uuid': 'repo-1',
|
||||
'slug': 'user-repo',
|
||||
'workspace': {'slug': 'test-user', 'is_private': True},
|
||||
'is_private': False,
|
||||
'updated_on': '2023-01-01T00:00:00Z',
|
||||
}
|
||||
]
|
||||
|
||||
# Second workspace (organization) repositories
|
||||
mock_org_repos = [
|
||||
{
|
||||
'uuid': 'repo-2',
|
||||
'slug': 'org-repo',
|
||||
'workspace': {'slug': 'test-org', 'is_private': False},
|
||||
'is_private': False,
|
||||
'updated_on': '2023-01-02T00:00:00Z',
|
||||
}
|
||||
]
|
||||
|
||||
with patch.object(service, '_fetch_paginated_data') as mock_fetch:
|
||||
mock_fetch.side_effect = [mock_workspaces, mock_user_repos, mock_org_repos]
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got repositories from both workspaces
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for each repository
|
||||
user_repo = next(repo for repo in repositories if 'user-repo' in repo.full_name)
|
||||
org_repo = next(repo for repo in repositories if 'org-repo' in repo.full_name)
|
||||
|
||||
assert user_repo.owner_type == OwnerType.USER
|
||||
assert org_repo.owner_type == OwnerType.ORGANIZATION
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bitbucket_get_repositories_owner_type_fallback():
|
||||
"""Test that owner_type defaults to USER when workspace is private."""
|
||||
service = BitBucketService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with private workspace (should default to USER)
|
||||
mock_workspaces = [{'slug': 'test-user', 'name': 'Test User'}]
|
||||
mock_repos = [
|
||||
{
|
||||
'uuid': 'repo-1',
|
||||
'slug': 'user-repo',
|
||||
'workspace': {'slug': 'test-user', 'is_private': True}, # Private workspace
|
||||
'is_private': False,
|
||||
'updated_on': '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'uuid': 'repo-2',
|
||||
'slug': 'another-user-repo',
|
||||
'workspace': {'slug': 'test-user', 'is_private': True}, # Private workspace
|
||||
'is_private': True,
|
||||
'updated_on': '2023-01-02T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_fetch_paginated_data') as mock_fetch:
|
||||
mock_fetch.side_effect = [mock_workspaces, mock_repos]
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify all repositories default to USER owner_type for private workspaces
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
|
||||
|
||||
# Setup.py Bitbucket Token Tests
|
||||
@patch('openhands.core.setup.call_async_from_sync')
|
||||
@patch('openhands.core.setup.get_file_store')
|
||||
|
||||
@@ -5,7 +5,13 @@ import pytest
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.integrations.github.github_service import GitHubService
|
||||
from openhands.integrations.service_types import AuthenticationError
|
||||
from openhands.integrations.service_types import (
|
||||
AuthenticationError,
|
||||
OwnerType,
|
||||
ProviderType,
|
||||
Repository,
|
||||
)
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -79,3 +85,162 @@ async def test_github_service_fetch_data():
|
||||
|
||||
with pytest.raises(AuthenticationError):
|
||||
_ = await service._make_request('https://api.github.com/user')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_github_get_repositories_with_user_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for user repositories."""
|
||||
service = GitHubService(user_id=None, token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for user repositories
|
||||
mock_repo_data = [
|
||||
{
|
||||
'id': 123,
|
||||
'full_name': 'test-user/test-repo',
|
||||
'private': False,
|
||||
'stargazers_count': 10,
|
||||
'owner': {'type': 'User'}, # User repository
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'full_name': 'test-user/another-repo',
|
||||
'private': True,
|
||||
'stargazers_count': 5,
|
||||
'owner': {'type': 'User'}, # User repository
|
||||
},
|
||||
]
|
||||
|
||||
with (
|
||||
patch.object(service, '_fetch_paginated_repos', return_value=mock_repo_data),
|
||||
patch.object(service, 'get_installation_ids', return_value=[123]),
|
||||
):
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for user repositories
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ProviderType.GITHUB
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_github_get_repositories_with_organization_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for organization repositories."""
|
||||
service = GitHubService(user_id=None, token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for organization repositories
|
||||
mock_repo_data = [
|
||||
{
|
||||
'id': 789,
|
||||
'full_name': 'test-org/org-repo',
|
||||
'private': False,
|
||||
'stargazers_count': 25,
|
||||
'owner': {'type': 'Organization'}, # Organization repository
|
||||
},
|
||||
{
|
||||
'id': 101,
|
||||
'full_name': 'test-org/another-org-repo',
|
||||
'private': True,
|
||||
'stargazers_count': 15,
|
||||
'owner': {'type': 'Organization'}, # Organization repository
|
||||
},
|
||||
]
|
||||
|
||||
with (
|
||||
patch.object(service, '_fetch_paginated_repos', return_value=mock_repo_data),
|
||||
patch.object(service, 'get_installation_ids', return_value=[123]),
|
||||
):
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for organization repositories
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.ORGANIZATION
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ProviderType.GITHUB
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_github_get_repositories_mixed_owner_types():
|
||||
"""Test that get_repositories correctly handles mixed user and organization repositories."""
|
||||
service = GitHubService(user_id=None, token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with mixed owner types
|
||||
mock_repo_data = [
|
||||
{
|
||||
'id': 123,
|
||||
'full_name': 'test-user/user-repo',
|
||||
'private': False,
|
||||
'stargazers_count': 10,
|
||||
'owner': {'type': 'User'}, # User repository
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'full_name': 'test-org/org-repo',
|
||||
'private': True,
|
||||
'stargazers_count': 25,
|
||||
'owner': {'type': 'Organization'}, # Organization repository
|
||||
},
|
||||
]
|
||||
|
||||
with (
|
||||
patch.object(service, '_fetch_paginated_repos', return_value=mock_repo_data),
|
||||
patch.object(service, 'get_installation_ids', return_value=[123]),
|
||||
):
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for each repository
|
||||
user_repo = next(repo for repo in repositories if 'user-repo' in repo.full_name)
|
||||
org_repo = next(repo for repo in repositories if 'org-repo' in repo.full_name)
|
||||
|
||||
assert user_repo.owner_type == OwnerType.USER
|
||||
assert org_repo.owner_type == OwnerType.ORGANIZATION
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_github_get_repositories_owner_type_fallback():
|
||||
"""Test that owner_type defaults to USER when owner type is not 'Organization'."""
|
||||
service = GitHubService(user_id=None, token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with missing or unexpected owner type
|
||||
mock_repo_data = [
|
||||
{
|
||||
'id': 123,
|
||||
'full_name': 'test-user/test-repo',
|
||||
'private': False,
|
||||
'stargazers_count': 10,
|
||||
'owner': {'type': 'User'}, # Explicitly User
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'full_name': 'test-user/another-repo',
|
||||
'private': True,
|
||||
'stargazers_count': 5,
|
||||
'owner': {'type': 'Bot'}, # Unexpected type
|
||||
},
|
||||
{
|
||||
'id': 789,
|
||||
'full_name': 'test-user/third-repo',
|
||||
'private': False,
|
||||
'stargazers_count': 15,
|
||||
'owner': {}, # Missing type
|
||||
},
|
||||
]
|
||||
|
||||
with (
|
||||
patch.object(service, '_fetch_paginated_repos', return_value=mock_repo_data),
|
||||
patch.object(service, 'get_installation_ids', return_value=[123]),
|
||||
):
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify all repositories default to USER owner_type
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
|
||||
169
tests/unit/test_gitlab.py
Normal file
169
tests/unit/test_gitlab.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""Tests for GitLab integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabService
|
||||
from openhands.integrations.service_types import OwnerType, ProviderType, Repository
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gitlab_get_repositories_with_user_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for user repositories."""
|
||||
service = GitLabService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for user repositories (namespace kind = 'user')
|
||||
mock_repos = [
|
||||
{
|
||||
'id': 123,
|
||||
'path_with_namespace': 'test-user/user-repo1',
|
||||
'star_count': 10,
|
||||
'visibility': 'public',
|
||||
'namespace': {'kind': 'user'}, # User namespace
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'path_with_namespace': 'test-user/user-repo2',
|
||||
'star_count': 5,
|
||||
'visibility': 'private',
|
||||
'namespace': {'kind': 'user'}, # User namespace
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_make_request') as mock_request:
|
||||
# Mock the pagination response
|
||||
mock_request.side_effect = [(mock_repos, {'Link': ''})] # No next page
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for user repositories
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ProviderType.GITLAB
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gitlab_get_repositories_with_organization_owner_type():
|
||||
"""Test that get_repositories correctly sets owner_type field for organization repositories."""
|
||||
service = GitLabService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data for organization repositories (namespace kind = 'group')
|
||||
mock_repos = [
|
||||
{
|
||||
'id': 789,
|
||||
'path_with_namespace': 'test-org/org-repo1',
|
||||
'star_count': 25,
|
||||
'visibility': 'public',
|
||||
'namespace': {'kind': 'group'}, # Organization/Group namespace
|
||||
},
|
||||
{
|
||||
'id': 101,
|
||||
'path_with_namespace': 'test-org/org-repo2',
|
||||
'star_count': 15,
|
||||
'visibility': 'private',
|
||||
'namespace': {'kind': 'group'}, # Organization/Group namespace
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_make_request') as mock_request:
|
||||
# Mock the pagination response
|
||||
mock_request.side_effect = [(mock_repos, {'Link': ''})] # No next page
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for organization repositories
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.ORGANIZATION
|
||||
assert isinstance(repo, Repository)
|
||||
assert repo.git_provider == ProviderType.GITLAB
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gitlab_get_repositories_mixed_owner_types():
|
||||
"""Test that get_repositories correctly handles mixed user and organization repositories."""
|
||||
service = GitLabService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with mixed namespace types
|
||||
mock_repos = [
|
||||
{
|
||||
'id': 123,
|
||||
'path_with_namespace': 'test-user/user-repo',
|
||||
'star_count': 10,
|
||||
'visibility': 'public',
|
||||
'namespace': {'kind': 'user'}, # User namespace
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'path_with_namespace': 'test-org/org-repo',
|
||||
'star_count': 25,
|
||||
'visibility': 'public',
|
||||
'namespace': {'kind': 'group'}, # Organization/Group namespace
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_make_request') as mock_request:
|
||||
# Mock the pagination response
|
||||
mock_request.side_effect = [(mock_repos, {'Link': ''})] # No next page
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify we got the expected number of repositories
|
||||
assert len(repositories) == 2
|
||||
|
||||
# Verify owner_type is correctly set for each repository
|
||||
user_repo = next(repo for repo in repositories if 'user-repo' in repo.full_name)
|
||||
org_repo = next(repo for repo in repositories if 'org-repo' in repo.full_name)
|
||||
|
||||
assert user_repo.owner_type == OwnerType.USER
|
||||
assert org_repo.owner_type == OwnerType.ORGANIZATION
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gitlab_get_repositories_owner_type_fallback():
|
||||
"""Test that owner_type defaults to USER when namespace kind is not 'group'."""
|
||||
service = GitLabService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock repository data with missing or unexpected namespace kind
|
||||
mock_repos = [
|
||||
{
|
||||
'id': 123,
|
||||
'path_with_namespace': 'test-user/user-repo1',
|
||||
'star_count': 10,
|
||||
'visibility': 'public',
|
||||
'namespace': {'kind': 'user'}, # Explicitly user
|
||||
},
|
||||
{
|
||||
'id': 456,
|
||||
'path_with_namespace': 'test-user/user-repo2',
|
||||
'star_count': 5,
|
||||
'visibility': 'private',
|
||||
'namespace': {'kind': 'unknown'}, # Unexpected kind
|
||||
},
|
||||
{
|
||||
'id': 789,
|
||||
'path_with_namespace': 'test-user/user-repo3',
|
||||
'star_count': 15,
|
||||
'visibility': 'public',
|
||||
'namespace': {}, # Missing kind
|
||||
},
|
||||
]
|
||||
|
||||
with patch.object(service, '_make_request') as mock_request:
|
||||
# Mock the pagination response
|
||||
mock_request.side_effect = [(mock_repos, {'Link': ''})] # No next page
|
||||
|
||||
repositories = await service.get_repositories('pushed', AppMode.SAAS)
|
||||
|
||||
# Verify all repositories default to USER owner_type
|
||||
for repo in repositories:
|
||||
assert repo.owner_type == OwnerType.USER
|
||||
Reference in New Issue
Block a user