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"