- Tables forge_connections/users/workspaces (+ membres, projets, états, audit) ; migration des anciennes bases avec portées (workspace_id, connection_id) par défaut (1, 1). - Jetons chiffrés Fernet (KANBANTE_MASTER_KEY ou clé générée en base), jamais réaffichés. - ForgeRegistry : connexion héritée créée depuis l'environnement, espace Principal, instantanés préfixés par connexion. - GitHubClient complet (Bearer, pagination Link, étiquettes par nom, PATCH archived, branches via git/refs). - Paramètres surchargeables en base (résolveur config, P3).
118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
"""GitHubClient (0.24.0, P1): same protocol as GiteaClient,
|
|
GitHub wire shape underneath. Driven through MockTransport."""
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from app.github_client import GitHubClient
|
|
|
|
|
|
def make_client(handler) -> GitHubClient:
|
|
return GitHubClient(
|
|
base_url="https://api.github.com",
|
|
token="ghp_test",
|
|
cache_ttl=0,
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
|
|
async def test_auth_header_is_bearer():
|
|
seen = {}
|
|
|
|
def handler(request):
|
|
seen["auth"] = request.headers.get("authorization")
|
|
seen["accept"] = request.headers.get("accept")
|
|
return httpx.Response(200, json={"login": "octocat"})
|
|
|
|
client = make_client(handler)
|
|
user = await client.get_user()
|
|
assert user["login"] == "octocat"
|
|
assert seen["auth"] == "Bearer ghp_test"
|
|
assert "application/vnd.github+json" in seen["accept"]
|
|
|
|
|
|
async def test_pagination_follows_link_headers():
|
|
def handler(request):
|
|
if "page=2" in str(request.url):
|
|
return httpx.Response(200, json=[{"full_name": "a/deux"}])
|
|
return httpx.Response(
|
|
200,
|
|
json=[{"full_name": "a/un"}],
|
|
headers={
|
|
"Link": '<https://api.github.com/user/repos?page=2>; rel="next"'
|
|
},
|
|
)
|
|
|
|
client = make_client(handler)
|
|
repos = await client._fetch_search_repos()
|
|
assert [r["full_name"] for r in repos] == ["a/un", "a/deux"]
|
|
|
|
|
|
async def test_labels_are_addressed_by_name():
|
|
seen = {}
|
|
|
|
def handler(request):
|
|
if request.method == "GET" and request.url.path.endswith("/labels"):
|
|
return httpx.Response(
|
|
200, json=[{"id": 991, "name": "kanban:todo", "color": "3b82f6"}]
|
|
)
|
|
if request.method == "POST":
|
|
seen["body"] = request.content.decode()
|
|
return httpx.Response(200, json=[])
|
|
raise AssertionError(request.url)
|
|
|
|
client = make_client(handler)
|
|
label = await client.ensure_label("a", "b", "kanban:todo", "3b82f6")
|
|
assert label["id"] == "kanban:todo" # name, not GitHub's numeric id
|
|
await client.add_issue_label("a", "b", 7, label["id"])
|
|
assert '"kanban:todo"' in seen["body"]
|
|
|
|
|
|
async def test_search_issues_normalizes_repository():
|
|
def handler(request):
|
|
assert "is%3Aissue" in str(request.url) or "is:issue" in str(request.url)
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"items": [
|
|
{
|
|
"number": 3,
|
|
"title": "T",
|
|
"repository_url": "https://api.github.com/repos/a/b",
|
|
}
|
|
]
|
|
},
|
|
)
|
|
|
|
client = make_client(handler)
|
|
issues = await client.search_issues(q="mot", state="open")
|
|
assert issues[0]["repository"]["full_name"] == "a/b"
|
|
|
|
|
|
async def test_set_archived_uses_patch():
|
|
seen = {}
|
|
|
|
def handler(request):
|
|
seen["method"] = request.method
|
|
seen["body"] = request.content.decode()
|
|
return httpx.Response(200, json={"archived": True})
|
|
|
|
client = make_client(handler)
|
|
await client.set_archived("a", "b", True)
|
|
assert seen["method"] == "PATCH"
|
|
assert '"archived": true' in seen["body"] or '"archived":true' in seen["body"]
|
|
|
|
|
|
async def test_starred_roundtrip():
|
|
seen = {}
|
|
|
|
def handler(request):
|
|
seen["method"] = request.method
|
|
seen["path"] = request.url.path
|
|
return httpx.Response(204)
|
|
|
|
client = make_client(handler)
|
|
await client.set_starred("a", "b", True)
|
|
assert seen["method"] == "PUT"
|
|
assert seen["path"] == "/user/starred/a/b"
|