Skip to content

Commit d9674ea

Browse files
authored
Add gitea list all repos CLI (#3)
1 parent 7500bf2 commit d9674ea

File tree

9 files changed

+269
-5
lines changed

9 files changed

+269
-5
lines changed

gitea_github_sync/cli.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import click
44
from rich import print
55

6-
from . import github, repository
6+
from . import gitea, github, repository
77

88

99
@click.group()
@@ -37,3 +37,11 @@ def list_all_github_repositories(stats: bool) -> None:
3737
gh = github.get_github()
3838
repos = github.list_all_repositories(gh)
3939
print_repositories(repos, stats)
40+
41+
42+
@click.option("--stats", is_flag=True)
43+
@cli.command()
44+
def list_all_gitea_repositories(stats: bool) -> None:
45+
gt = gitea.get_gitea()
46+
repos = gt.get_repos()
47+
print_repositories(repos, stats)

gitea_github_sync/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
class Config(BaseModel):
1010
github_token: str
11+
gitea_api_url: str
1112
gitea_token: str
1213

1314

gitea_github_sync/gitea.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import Any, Dict, List, Optional
5+
6+
import requests
7+
8+
from gitea_github_sync import config
9+
10+
from .repository import Repository, Visibility
11+
12+
13+
@dataclass(frozen=True)
14+
class Gitea:
15+
api_url: str
16+
api_token: str
17+
18+
def _get_authorization_header(self) -> Dict[str, str]:
19+
return {"Authorization": f"token {self.api_token}"}
20+
21+
def _get_all_pages(self, path: str) -> List[Dict[str, Any]]:
22+
output = []
23+
url: Optional[str] = f"{self.api_url}{path}"
24+
while url is not None:
25+
auth = self._get_authorization_header()
26+
result = requests.get(url, headers=auth)
27+
result.raise_for_status()
28+
data = result.json()
29+
output.extend(data)
30+
31+
url = result.links["next"]["url"] if "next" in result.links else None
32+
return output
33+
34+
def get_repos(self) -> List[Repository]:
35+
36+
repos = self._get_all_pages("/user/repos")
37+
return [
38+
Repository(
39+
repo["full_name"],
40+
visibility=Visibility.PRIVATE if repo["private"] else Visibility.PUBLIC,
41+
)
42+
for repo in repos
43+
]
44+
45+
46+
def get_gitea(conf: Optional[config.Config] = None) -> Gitea:
47+
if conf is None:
48+
conf = config.load_config()
49+
return Gitea(api_url=conf.gitea_api_url, api_token=conf.gitea_token)

poetry.lock

Lines changed: 46 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ click = "^8.1.3"
1919
piny = "^0.6.0"
2020
pydantic = "^1.10.4"
2121
rich = "^13.0.0"
22+
requests = "^2.28.1"
2223

2324

2425
[tool.poetry.group.dev]
@@ -32,6 +33,7 @@ isort = "^5.11.4"
3233
mypy = "^0.991"
3334
tox = "^4.1.1"
3435
pytest-cov = "^4.0.0"
36+
responses = "^0.22.0"
3537

3638
[build-system]
3739
requires = ["poetry-core"]

tests/test_cli.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def repositories_fixture() -> List[Repository]:
2323
@patch("gitea_github_sync.cli.print_repositories", autospec=True)
2424
@patch("gitea_github_sync.cli.github.get_github", autospec=True)
2525
@patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True)
26-
def test_list_all_github_repositories_with_stats(
26+
def test_list_all_github_repositories(
2727
mock_list_all_repositories: MagicMock,
2828
mock_get_github: MagicMock,
2929
mock_print_repositories: MagicMock,
@@ -46,6 +46,31 @@ def test_list_all_github_repositories_with_stats(
4646
mock_print_repositories.assert_called_once_with(repositories_fixture, expected_stat)
4747

4848

49+
@pytest.mark.parametrize("expected_stat", [True, False])
50+
@patch("gitea_github_sync.cli.print_repositories", autospec=True)
51+
@patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True)
52+
def test_list_all_gitea_repositories(
53+
mock_get_gitea: MagicMock,
54+
mock_print_repositories: MagicMock,
55+
expected_stat: bool,
56+
repositories_fixture: List[Repository],
57+
) -> None:
58+
mock_gitea = MagicMock()
59+
mock_get_gitea.return_value = mock_gitea
60+
mock_gitea.get_repos.return_value = repositories_fixture
61+
62+
runner = CliRunner()
63+
command = (
64+
["list-all-gitea-repositories", "--stats"]
65+
if expected_stat
66+
else ["list-all-gitea-repositories"]
67+
)
68+
result = runner.invoke(cli, command)
69+
70+
assert result.exit_code == 0
71+
mock_print_repositories.assert_called_once_with(repositories_fixture, expected_stat)
72+
73+
4974
@patch("sys.stdout", new_callable=StringIO)
5075
def test_print_repositories_without_stats(
5176
stdout: StringIO,

tests/test_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@
88

99
VALID_CONFIG_FILE = """
1010
github_token: some-github-token
11+
gitea_api_url: https://some-gitea-url.com
1112
gitea_token: some-gitea-token
1213
"""
1314

14-
VALID_CONFIG = Config(github_token="some-github-token", gitea_token="some-gitea-token")
15+
VALID_CONFIG = Config(
16+
github_token="some-github-token",
17+
gitea_api_url="https://some-gitea-url.com",
18+
gitea_token="some-gitea-token",
19+
)
1520

1621
DEFAULT_CONFIG_FILE_PATH = config_file_location()
1722

tests/test_gitea.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
import pytest
4+
import responses
5+
from responses import matchers
6+
7+
from gitea_github_sync.config import Config
8+
from gitea_github_sync.gitea import Gitea, get_gitea
9+
from gitea_github_sync.repository import Repository, Visibility
10+
11+
GITEA_BASE_API_URL = "https://gitea.yourinstance.com/api/v1"
12+
GITEA_TOKEN = "your-gitea-token"
13+
14+
15+
@pytest.fixture
16+
def conf_fixture() -> Config:
17+
return Config(
18+
github_token="some-token", gitea_api_url=GITEA_BASE_API_URL, gitea_token=GITEA_TOKEN
19+
)
20+
21+
22+
@pytest.fixture
23+
def gitea_fixture(conf_fixture: Config) -> Gitea:
24+
return Gitea(api_url=conf_fixture.gitea_api_url, api_token=conf_fixture.gitea_token)
25+
26+
27+
@responses.activate
28+
def test_get_repos(gitea_fixture: Gitea) -> None:
29+
30+
json = [
31+
{"full_name": "some-team/a-repo", "private": True},
32+
{"full_name": "some-team/b-repo", "private": False},
33+
]
34+
35+
expected_repos = [
36+
Repository(full_repo_name="some-team/a-repo", visibility=Visibility.PRIVATE),
37+
Repository(full_repo_name="some-team/b-repo", visibility=Visibility.PUBLIC),
38+
]
39+
responses.get(
40+
f"{GITEA_BASE_API_URL}/user/repos",
41+
match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})],
42+
json=json,
43+
)
44+
45+
result = gitea_fixture.get_repos()
46+
assert expected_repos == result
47+
48+
49+
@responses.activate
50+
def test_get_repos_multiple_pages(gitea_fixture: Gitea) -> None:
51+
52+
json_1 = [
53+
{"full_name": "some-team/a-repo", "private": True},
54+
{"full_name": "some-team/b-repo", "private": False},
55+
]
56+
json_2 = [
57+
{"full_name": "some-team/c-repo", "private": True},
58+
{"full_name": "some-team/d-repo", "private": False},
59+
]
60+
json_3 = [
61+
{"full_name": "some-team/e-repo", "private": True},
62+
{"full_name": "some-team/f-repo", "private": False},
63+
]
64+
65+
expected_repos = [
66+
Repository(full_repo_name="some-team/a-repo", visibility=Visibility.PRIVATE),
67+
Repository(full_repo_name="some-team/b-repo", visibility=Visibility.PUBLIC),
68+
Repository(full_repo_name="some-team/c-repo", visibility=Visibility.PRIVATE),
69+
Repository(full_repo_name="some-team/d-repo", visibility=Visibility.PUBLIC),
70+
Repository(full_repo_name="some-team/e-repo", visibility=Visibility.PRIVATE),
71+
Repository(full_repo_name="some-team/f-repo", visibility=Visibility.PUBLIC),
72+
]
73+
responses.get(
74+
f"{GITEA_BASE_API_URL}/user/repos",
75+
match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})],
76+
json=json_1,
77+
headers={
78+
"link": (
79+
f'<{GITEA_BASE_API_URL}/user/repos?page=2>; rel="next",'
80+
+ f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="last"'
81+
)
82+
},
83+
)
84+
responses.get(
85+
f"{GITEA_BASE_API_URL}/user/repos?page=2",
86+
match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})],
87+
json=json_2,
88+
headers={
89+
"link": (
90+
f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="next",'
91+
+ f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="last",'
92+
+ f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="first",'
93+
+ f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="prev"'
94+
)
95+
},
96+
)
97+
responses.get(
98+
f"{GITEA_BASE_API_URL}/user/repos?page=3",
99+
match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})],
100+
json=json_3,
101+
headers={
102+
"link": (
103+
f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="first",'
104+
+ f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="prev"'
105+
)
106+
},
107+
)
108+
109+
result = gitea_fixture.get_repos()
110+
assert expected_repos == result
111+
112+
113+
def test_gitea(gitea_fixture: Gitea, conf_fixture: Config) -> None:
114+
gt = get_gitea(conf_fixture)
115+
116+
assert gt == gitea_fixture
117+
118+
119+
@patch("gitea_github_sync.gitea.config.load_config", autospec=True)
120+
def test_gitea_default_value(
121+
mock_load_config: MagicMock, gitea_fixture: Gitea, conf_fixture: Config
122+
) -> None:
123+
mock_load_config.return_value = conf_fixture
124+
gt = get_gitea()
125+
126+
assert gt == gitea_fixture
127+
mock_load_config.assert_called_once()

tests/test_github.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
from gitea_github_sync.github import get_github, list_all_repositories
1010
from gitea_github_sync.repository import Repository, Visibility
1111

12+
from .test_config import VALID_CONFIG
13+
1214

1315
@pytest.fixture
1416
def conf_fixture() -> Config:
15-
return Config(github_token="some-github-token", gitea_token="some-gitea-token")
17+
return VALID_CONFIG
1618

1719

1820
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)