Initial curated RepoPulse source from 2524ccc3c1c27739996dccb2b7992989f88cd26d
This commit is contained in:
commit
0adf8ef5d9
90 files changed
+14315
No files matched your search
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.gitea import GiteaClient, GiteaError
|
||||
from app.store import ScanStore
|
||||
|
||||
|
||||
def _transport(url: str, headers: dict[str, str]) -> tuple[int, bytes]:
|
||||
assert headers["Authorization"] == "token test-token"
|
||||
if "/user/repos" in url:
|
||||
return 200, json.dumps(
|
||||
[
|
||||
{
|
||||
"id": 64,
|
||||
"name": "RepoPulse",
|
||||
"full_name": "Jens/RepoPulse",
|
||||
"owner": {"login": "Jens"},
|
||||
"description": "Repository intelligence",
|
||||
"default_branch": "main",
|
||||
"clone_url": "https://evil.example/Jens/RepoPulse.git",
|
||||
"private": True,
|
||||
}
|
||||
]
|
||||
).encode()
|
||||
if "/branches/main" in url:
|
||||
return 200, b'{"commit":{"id":"0123456789abcdef0123456789abcdef01234567"}}'
|
||||
raise AssertionError(url)
|
||||
|
||||
|
||||
def test_gitea_discovery_is_bounded_and_preserves_stable_identity() -> None:
|
||||
client = GiteaClient(
|
||||
"http://gitea.local",
|
||||
"test-token",
|
||||
transport=_transport,
|
||||
)
|
||||
|
||||
discovered = client.discover()
|
||||
|
||||
assert len(discovered) == 1
|
||||
repository, revision = discovered[0]
|
||||
assert repository.id == "gitea:64"
|
||||
assert repository.provider_repository_id == "64"
|
||||
assert repository.full_name == "Jens/RepoPulse"
|
||||
assert repository.clone_url == "http://gitea.local/Jens/RepoPulse.git"
|
||||
assert repository.latest_revision == revision
|
||||
|
||||
|
||||
def test_gitea_auth_errors_are_sanitized() -> None:
|
||||
def rejected(_url: str, _headers: dict[str, str]) -> tuple[int, bytes]:
|
||||
return 401, b"secret-bearing upstream response"
|
||||
|
||||
client = GiteaClient("http://gitea.local", "test-token", transport=rejected)
|
||||
|
||||
try:
|
||||
client.list_repositories()
|
||||
except GiteaError as exc:
|
||||
assert exc.code == "authentication"
|
||||
assert "test-token" not in str(exc)
|
||||
assert "secret-bearing" not in str(exc)
|
||||
else:
|
||||
raise AssertionError("authentication error was not raised")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status_code, error_code", [(401, "authentication"), (403, "authentication"), (404, "not_found"), (429, "rate_limited"), (500, "upstream")])
|
||||
def test_gitea_http_statuses_are_classified_without_upstream_body(status_code: int, error_code: str) -> None:
|
||||
client = GiteaClient(
|
||||
"http://gitea.local",
|
||||
"test-token",
|
||||
transport=lambda *_: (status_code, b"token=test-token upstream body"),
|
||||
)
|
||||
|
||||
with pytest.raises(GiteaError) as raised:
|
||||
client.list_repositories()
|
||||
|
||||
assert raised.value.code == error_code
|
||||
assert "test-token" not in str(raised.value)
|
||||
assert "upstream body" not in str(raised.value)
|
||||
|
||||
|
||||
def test_gitea_discovery_paginates_until_a_short_page() -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
def paginated(url: str, headers: dict[str, str]) -> tuple[int, bytes]:
|
||||
calls.append(url)
|
||||
assert headers["Authorization"] == "token test-token"
|
||||
if "/user/repos" in url:
|
||||
if "page=1" in url:
|
||||
payload = [{"id": 64, "owner": {"login": "Jens"}, "name": "One", "default_branch": "main"}]
|
||||
elif "page=2" in url:
|
||||
payload = [{"id": 65, "owner": {"login": "Jens"}, "name": "Two", "default_branch": "trunk"}]
|
||||
else:
|
||||
payload = []
|
||||
return 200, json.dumps(payload).encode()
|
||||
if "/branches/main" in url:
|
||||
return 200, b'{"commit":{"id":"0123456789abcdef0123456789abcdef01234567"}}'
|
||||
if "/branches/trunk" in url:
|
||||
return 200, b'{"commit":{"sha":"abcdef0123456789abcdef0123456789abcdef01"}}'
|
||||
raise AssertionError(url)
|
||||
|
||||
client = GiteaClient("http://gitea.local", "test-token", page_size=1, max_pages=3, transport=paginated)
|
||||
|
||||
discovered = client.discover()
|
||||
|
||||
assert [repository.id for repository, _ in discovered] == ["gitea:64", "gitea:65"]
|
||||
assert len([call for call in calls if "/user/repos" in call]) == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"transport, error_code",
|
||||
[
|
||||
(lambda *_: (200, b"not-json"), "invalid_response"),
|
||||
(lambda *_: (_ for _ in ()).throw(TimeoutError()), "unreachable"),
|
||||
],
|
||||
)
|
||||
def test_gitea_malformed_and_timeout_responses_are_safe(transport, error_code: str) -> None:
|
||||
client = GiteaClient("http://gitea.local", "test-token", transport=transport)
|
||||
|
||||
with pytest.raises(GiteaError) as raised:
|
||||
client.list_repositories()
|
||||
|
||||
assert raised.value.code == error_code
|
||||
|
||||
|
||||
def test_gitea_sync_creates_catalog_without_full_reports(tmp_path: Path) -> None:
|
||||
store = ScanStore(str(tmp_path / "history.db"))
|
||||
client = GiteaClient("http://gitea.local", "test-token", transport=_transport)
|
||||
|
||||
result = client.sync(store)
|
||||
|
||||
assert result.discovered == 1
|
||||
assert result.created == 1
|
||||
repository = store.get_repository("gitea:64")
|
||||
assert repository is not None
|
||||
assert repository.latest_revision == "0123456789abcdef0123456789abcdef01234567"
|
||||
assert repository.description == "Repository intelligence"
|
||||
assert store.repository_scans(repository.id) == []
|
||||
|
||||
|
||||
def test_gitea_outage_preserves_catalog_and_marks_sync_error(tmp_path: Path) -> None:
|
||||
store = ScanStore(str(tmp_path / "history.db"))
|
||||
healthy = GiteaClient("http://gitea.local", "test-token", transport=_transport)
|
||||
healthy.sync(store)
|
||||
|
||||
unavailable = GiteaClient(
|
||||
"http://gitea.local",
|
||||
"test-token",
|
||||
transport=lambda *_: (503, b"upstream details must not escape"),
|
||||
)
|
||||
result = unavailable.sync(store)
|
||||
|
||||
assert result.errors == ["upstream: Gitea returned a server error"]
|
||||
repository = store.get_repository("gitea:64")
|
||||
assert repository is not None
|
||||
assert repository.available is True
|
||||
assert repository.latest_revision == "0123456789abcdef0123456789abcdef01234567"
|
||||
assert repository.sync_error == "upstream: Gitea returned a server error"
|
||||
|
||||
|
||||
def test_complete_sync_marks_missing_repository_unavailable_without_deleting_history(tmp_path: Path) -> None:
|
||||
store = ScanStore(str(tmp_path / "history.db"))
|
||||
GiteaClient("http://gitea.local", "test-token", transport=_transport).sync(store)
|
||||
missing = GiteaClient("http://gitea.local", "test-token", transport=lambda *_: (200, b"[]"))
|
||||
|
||||
result = missing.sync(store)
|
||||
|
||||
assert result.unavailable == 1
|
||||
repository = store.get_repository("gitea:64")
|
||||
assert repository is not None
|
||||
assert repository.available is False
|
||||
assert store.repository_scans(repository.id) == []
|
||||
|
||||
|
||||
def test_branch_resolution_failure_is_partial_and_preserves_other_catalog_entries(tmp_path: Path) -> None:
|
||||
def partial_transport(url: str, _headers: dict[str, str]) -> tuple[int, bytes]:
|
||||
if "/user/repos" in url:
|
||||
return 200, json.dumps(
|
||||
[
|
||||
{"id": 1, "owner": {"login": "Jens"}, "name": "Healthy", "default_branch": "main"},
|
||||
{"id": 2, "owner": {"login": "Jens"}, "name": "Broken", "default_branch": "main"},
|
||||
]
|
||||
).encode()
|
||||
if "/repos/Jens/Healthy/branches/main" in url:
|
||||
return 200, b'{"commit":{"id":"0123456789abcdef0123456789abcdef01234567"}}'
|
||||
if "/repos/Jens/Broken/branches/main" in url:
|
||||
return 500, b"upstream details must remain private"
|
||||
raise AssertionError(url)
|
||||
|
||||
store = ScanStore(str(tmp_path / "history.db"))
|
||||
result = GiteaClient("http://gitea.local", "test-token", transport=partial_transport).sync(store)
|
||||
|
||||
assert result.discovered == 1
|
||||
assert result.partial is True
|
||||
assert result.branch_failures == 1
|
||||
assert result.listing_complete is True
|
||||
assert "Jens/Broken: upstream: Gitea returned a server error" in result.errors
|
||||
assert store.get_repository("gitea:1").latest_revision
|
||||
broken = store.get_repository("gitea:2")
|
||||
assert broken is not None and broken.available is True
|
||||
assert broken.sync_error == "Jens/Broken: upstream: Gitea returned a server error"
|
||||
Reference in new issue
Block a user