139 lines
7.1 KiB
Python
139 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from codexpi.commands import CommandResult
|
|
from codexpi.gitea_service import GiteaError, askpass_value, connect, connect_repository, create_repository, namespaces, normalize_server_url
|
|
from codexpi.git_service import GitError, _validate_remote
|
|
|
|
|
|
def test_gitea_url_requires_explicit_http():
|
|
assert normalize_server_url("https://gitea.local/") == "https://gitea.local"
|
|
assert normalize_server_url("http://gitea.local", True) == "http://gitea.local"
|
|
with pytest.raises(GiteaError, match="HTTP"):
|
|
normalize_server_url("http://gitea.local")
|
|
with pytest.raises(GiteaError):
|
|
normalize_server_url("https://user:secret@gitea.local")
|
|
|
|
|
|
def test_plain_http_remote_is_only_allowed_for_explicit_gitea_flow():
|
|
with pytest.raises(GitError, match="HTTPS ou SSH"):
|
|
_validate_remote("http://gitea.local/alice/demo.git")
|
|
assert _validate_remote("http://gitea.local/alice/demo.git", allow_http=True).startswith("http://")
|
|
with pytest.raises(GitError, match="jeton"):
|
|
_validate_remote("http://alice:token@gitea.local/alice/demo.git", allow_http=True)
|
|
|
|
|
|
def test_gitea_connect_registers_key_and_encrypts_token(tmp_path: Path, monkeypatch):
|
|
calls: list[tuple[str, str]] = []
|
|
|
|
def fake_request(_config, path, method="GET", payload=None):
|
|
calls.append((method, path))
|
|
if path == "user":
|
|
return {"login": "alice", "full_name": "Alice"}
|
|
if path.startswith("user/keys?"):
|
|
return []
|
|
assert payload["key"].startswith("ssh-ed25519")
|
|
return {"id": 1}
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service._request", fake_request)
|
|
monkeypatch.setattr("codexpi.gitea_service.generate_key", lambda _data: {"public_key": "ssh-ed25519 AAAATEST codexpi"})
|
|
value = connect(tmp_path, "secret", "https://gitea.local", "top-secret-token")
|
|
encrypted = (tmp_path / "gitea" / "connection.json").read_bytes()
|
|
assert value["account"] == "alice" and "token" not in value
|
|
assert b"top-secret-token" not in encrypted
|
|
assert ("POST", "user/keys") in calls
|
|
|
|
|
|
def test_gitea_namespaces_and_organization_repository(tmp_path: Path, monkeypatch):
|
|
def initial(_config, path, method="GET", payload=None):
|
|
if path == "user":
|
|
return {"login": "alice"}
|
|
return [{"key": "ssh-ed25519 AAAATEST"}]
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service._request", initial)
|
|
monkeypatch.setattr("codexpi.gitea_service.generate_key", lambda _data: {"public_key": "ssh-ed25519 AAAATEST"})
|
|
connect(tmp_path, "secret", "https://gitea.local", "token")
|
|
|
|
def api_request(config, path, method="GET", payload=None):
|
|
assert config["token"] == "token"
|
|
if path == "user":
|
|
return {"login": "alice", "full_name": "Alice"}
|
|
if path.startswith("user/orgs?"):
|
|
return [{"username": "team", "full_name": "The Team"}]
|
|
assert path == "orgs/team/repos" and method == "POST" and payload["private"] is True
|
|
return {"id": 3, "name": "demo", "full_name": "team/demo", "private": True, "html_url": "https://gitea.local/team/demo", "ssh_url": "git@gitea.local:team/demo.git"}
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service._request", api_request)
|
|
assert [item["kind"] for item in namespaces(tmp_path, "secret")] == ["user", "organization"]
|
|
value = create_repository(tmp_path, "secret", "demo", "team", "organization", True)
|
|
assert value["full_name"] == "team/demo"
|
|
assert value["http_url"] == "https://gitea.local/team/demo.git"
|
|
|
|
|
|
def test_gitea_askpass_reads_encrypted_credentials_only_for_configured_server(tmp_path: Path, monkeypatch):
|
|
def fake_request(_config, path, method="GET", payload=None):
|
|
return {"login": "alice"} if path == "user" else [{"key": "ssh-ed25519 AAAATEST"}]
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service._request", fake_request)
|
|
monkeypatch.setattr("codexpi.gitea_service.generate_key", lambda _data: {"public_key": "ssh-ed25519 AAAATEST"})
|
|
(tmp_path / "secret_key").write_text("secret", encoding="utf-8")
|
|
connect(tmp_path, "secret", "https://gitea.local", "token-value")
|
|
assert askpass_value(tmp_path, "Username for 'https://gitea.local':") == "alice"
|
|
assert askpass_value(tmp_path, "Password for 'https://gitea.local':") == "token-value"
|
|
assert askpass_value(tmp_path, "Password for 'https://evil.example':") == ""
|
|
|
|
|
|
def test_gitea_repository_is_initialized_and_linked(tmp_path: Path, monkeypatch):
|
|
data = tmp_path / "data"
|
|
key = data / "github" / "id_ed25519"
|
|
key.parent.mkdir(parents=True)
|
|
key.write_text("private", encoding="utf-8")
|
|
root = tmp_path / "project"
|
|
root.mkdir()
|
|
|
|
async def fake_command(*_args, **_kwargs):
|
|
return CommandResult([], 0, "", "")
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service.run_command", fake_command)
|
|
result = asyncio.run(connect_repository(root, data, "git@gitea.local:alice/demo.git"))
|
|
assert result["remote_url"] == "git@gitea.local:alice/demo.git"
|
|
|
|
|
|
def test_gitea_http_repository_is_linked_when_local_http_was_authorized(tmp_path: Path, monkeypatch):
|
|
data = tmp_path / "data"
|
|
(data / "gitea").mkdir(parents=True)
|
|
(data / "gitea" / "connection.json").write_text("encrypted", encoding="utf-8")
|
|
root = tmp_path / "project"
|
|
root.mkdir()
|
|
|
|
async def fake_command(*_args, **_kwargs):
|
|
return CommandResult([], 0, "", "")
|
|
|
|
monkeypatch.setattr("codexpi.gitea_service.run_command", fake_command)
|
|
result = asyncio.run(connect_repository(root, data, "http://gitea.local/alice/demo.git", use_credentials=True))
|
|
assert result["remote_url"] == "http://gitea.local/alice/demo.git"
|
|
|
|
|
|
def test_gitea_endpoints_require_csrf_and_create_repository(logged_in, monkeypatch):
|
|
client, _app, projects, _, headers = logged_in
|
|
root = projects / "gitea-project"
|
|
root.mkdir()
|
|
project = client.post("/api/projects", headers=headers, json={"name": "Gitea", "path": str(root)}).json()
|
|
monkeypatch.setattr("codexpi.main.connect_gitea", lambda *_args: {"configured": True, "server_url": "https://gitea.local", "account": "alice", "name": "Alice", "verified_at": "now", "verify_tls": True})
|
|
monkeypatch.setattr("codexpi.main.create_gitea_repository", lambda *_args: {"name": "demo", "full_name": "alice/demo", "web_url": "https://gitea.local/alice/demo", "ssh_url": "git@gitea.local:alice/demo.git", "http_url": "https://gitea.local/alice/demo.git"})
|
|
|
|
async def fake_link(*_args, **_kwargs):
|
|
return {"is_repository": True, "remote_url": "git@gitea.local:alice/demo.git"}
|
|
|
|
monkeypatch.setattr("codexpi.main.connect_gitea_repository", fake_link)
|
|
body = {"server_url": "https://gitea.local", "token": "secret"}
|
|
assert client.post("/api/gitea/connect", json=body).status_code == 403
|
|
assert client.post("/api/gitea/connect", headers=headers, json=body).status_code == 200
|
|
response = client.post(f"/api/projects/{project['id']}/gitea/repositories", headers=headers, json={"name": "demo", "owner": "alice", "owner_kind": "user", "private": True})
|
|
assert response.status_code == 201
|
|
assert response.json()["repository"]["full_name"] == "alice/demo"
|