from __future__ import annotations import asyncio import subprocess from pathlib import Path import pytest from codexpi.commands import CommandResult from codexpi.github_service import ( GitHubError, connect_repository, generate_key, ssh_environment, status, test_connection as verify_connection, validate_repository_url, ) def test_github_key_is_generated_with_private_permissions(tmp_path: Path): value = generate_key(tmp_path) key = tmp_path / "github" / "id_ed25519" assert value["key_ready"] is True assert value["public_key"].startswith("ssh-ed25519 ") assert key.stat().st_mode & 0o777 == 0o600 assert "IdentitiesOnly=yes" in ssh_environment(tmp_path)["GIT_SSH_COMMAND"] def test_github_repository_url_only_accepts_github_ssh(): assert validate_repository_url("git@github.com:openai/codex.git") == "git@github.com:openai/codex.git" assert validate_repository_url("ssh://git@github.com/openai/codex.git").startswith("ssh://") for value in ("https://github.com/openai/codex.git", "git@example.com:openai/codex.git", "-bad"): with pytest.raises(GitHubError): validate_repository_url(value) def test_github_connection_recognizes_authenticated_account(tmp_path: Path, monkeypatch): generate_key(tmp_path) async def fake_command(*_args, **_kwargs): return CommandResult([], 1, "", "Hi octocat! You've successfully authenticated, but GitHub does not provide shell access.") monkeypatch.setattr("codexpi.github_service.run_command", fake_command) value = asyncio.run(verify_connection(tmp_path)) assert value["account"] == "octocat" assert status(tmp_path)["account"] == "octocat" def test_connect_repository_initializes_git_and_sets_origin(tmp_path: Path, monkeypatch): data = tmp_path / "data" generate_key(data) repo = tmp_path / "repo" repo.mkdir() async def fake_command(*_args, **_kwargs): return CommandResult([], 0, "", "") monkeypatch.setattr("codexpi.github_service.run_command", fake_command) value = asyncio.run(connect_repository(repo, data, "git@github.com:octocat/demo.git")) remote = subprocess.run(["git", "remote", "get-url", "origin"], cwd=repo, check=True, capture_output=True, text=True) assert value["is_repository"] is True assert remote.stdout.strip() == "git@github.com:octocat/demo.git" def test_connect_repository_does_not_mix_an_existing_remote_history(tmp_path: Path, monkeypatch): data = tmp_path / "data" generate_key(data) repo = tmp_path / "plain-project" repo.mkdir() async def fake_command(*_args, **_kwargs): return CommandResult([], 0, "abc123\trefs/heads/main\n", "") monkeypatch.setattr("codexpi.github_service.run_command", fake_command) with pytest.raises(GitHubError, match="possède déjà un historique"): asyncio.run(connect_repository(repo, data, "git@github.com:octocat/demo.git")) assert not (repo / ".git").exists() def test_github_endpoints_require_csrf_and_connect_project(logged_in, monkeypatch): client, app, projects, _, headers = logged_in repo = projects / "github-project" repo.mkdir() project = client.post("/api/projects", headers=headers, json={"name": "GitHub", "path": str(repo)}).json() monkeypatch.setattr("codexpi.main.generate_github_key", lambda _data: {"key_ready": True, "public_key": "ssh-ed25519 test", "account": None, "verified_at": None}) async def fake_connect(root, _data, url): return {"is_repository": True, "remote_url": url, "branch": "main", "files": [], "history": []} monkeypatch.setattr("codexpi.main.connect_repository", fake_connect) assert client.post("/api/github/key").status_code == 403 assert client.post("/api/github/key", headers=headers).json()["key_ready"] is True response = client.post( f"/api/projects/{project['id']}/github", headers=headers, json={"repository_url": "git@github.com:octocat/demo.git"}, ) assert response.status_code == 200 assert response.json()["remote_url"] == "git@github.com:octocat/demo.git"