mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(forge): Phase 4 — GitLab + Gitea repo-provisioning parity (#581)
GitLab's create_org_repo (services/forge/gitlab.py) replaces the Phase-3
synthetic 501 with a real implementation: resolves org (a group's full
path, subgroups included) to a numeric namespace id via GET
/groups/{path}, falling back to the token's own namespace on a 404
(personal-namespace projects); POSTs /projects with the
name/path/description/visibility/initialize_with_readme payload
(visibility mapped private->"private"/"internal"); reshapes the 201
onto the GitHub fields callers read (full_name/clone_url/html_url) and
GitLab's duplicate-path 400 "has already been taken" onto GitHub's 422
shape, text preserved. Gitea's create_org_repo was already real but
untested — added transport-level coverage.
GitHubProvisioningService (services/github_provisioning.py) is now
provider-aware: ROBOCO_PROVISIONING_PROVIDER (github default / gitlab /
gitea) and ROBOCO_PROVISIONING_HOST (self-hosted instance, required for
gitlab/gitea or the service stays disabled exactly like a missing
token/org) pick the target forge; the class/factory names stay
GitHub-flavored for backward compatibility (pitch.py and existing
imports untouched). A shared _is_already_exists() helper recognizes
GitHub's "already exists" (422), Gitea's (409/422 "already exists"),
and GitLab's reshaped "has already been taken" (422). The existing-repo
re-fetch now builds a provider-aware RepoRef (GitLab packs org/name
into the owner field; GitHub/Gitea keep the owner,repo pair). Default
behavior (no new env set) is byte-for-byte the Phase-1 GitHub path,
pinned by a regression test.
Gates: ruff format/check clean, mypy roboco/+tests/ clean (1235 files),
xenon A/A/B clean, targeted suite (forge + provisioning + pitch) 79/79
green.
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -263,3 +263,35 @@ async def test_slash_branch_segments_are_url_encoded(
|
||||
|
||||
assert "/commits/feature%2Fbackend%2FABC/status" in str(recorder.requests[0].url)
|
||||
assert "/branches/feature%2Fbackend%2FABC" in str(recorder.requests[1].url)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_org_repo_posts_to_org_repos_with_token_auth(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
recorder = _Recorder(
|
||||
lambda _r: httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"full_name": "acme/widgets",
|
||||
"clone_url": "https://gitea.example.com/acme/widgets.git",
|
||||
"html_url": "https://gitea.example.com/acme/widgets",
|
||||
},
|
||||
)
|
||||
)
|
||||
_patch_client(monkeypatch, recorder)
|
||||
|
||||
resp = await GiteaProvider("gitea.example.com").create_org_repo(
|
||||
"SECRET", "acme", name="widgets", description="d", private=True, auto_init=True
|
||||
)
|
||||
|
||||
request = recorder.requests[0]
|
||||
assert request.method == "POST"
|
||||
assert str(request.url) == "https://gitea.example.com/api/v1/orgs/acme/repos"
|
||||
assert request.headers["Authorization"] == "token SECRET"
|
||||
body = request.content
|
||||
assert b'"name": "widgets"' in body or b'"name":"widgets"' in body
|
||||
assert b'"description": "d"' in body or b'"description":"d"' in body
|
||||
assert b'"private": true' in body or b'"private":true' in body
|
||||
assert b'"auto_init": true' in body or b'"auto_init":true' in body
|
||||
assert resp.json()["full_name"] == "acme/widgets"
|
||||
|
||||
Reference in New Issue
Block a user