"""Project Git repository endpoints. These endpoints had **no test coverage** and were broken at runtime: the domain entity and the infrastructure repository that manages ``Project`` rows were both named ``ProjectRepository``, so the infrastructure class shadowed the entity and ``ensure_repository`` constructed it with ``project_id=`` — a ``TypeError`` that turned every call into a 500. The tests below exist to keep that from regressing, and to pin the security properties the module promises: repositories are always private, and the Git password never reaches a response body. """ from __future__ import annotations import json from httpx import AsyncClient from tests.conftest import auth_header, signup async def _org_and_project(client: AsyncClient, token: str) -> tuple[str, str]: org = await client.post( "/api/organizations", json={"name": "شرکت مخزن"}, headers=auth_header(token) ) assert org.status_code == 201, org.text org_id = org.json()["id"] project = await client.post( f"/api/organizations/{org_id}/projects", json={"name": "پروژهٔ آزمایشی", "description": "برای تست مخزن"}, headers=auth_header(token), ) assert project.status_code == 201, project.text return org_id, project.json()["id"] async def test_ensure_repository_creates_a_private_repo(client: AsyncClient): """The exact path that used to raise TypeError -> 500.""" token = (await signup(client, "repo-owner@example.com"))["access_token"] org_id, project_id = await _org_and_project(client, token) base = f"/api/organizations/{org_id}/projects/{project_id}" response = await client.post(f"{base}/repo/ensure", headers=auth_header(token)) assert response.status_code in (200, 201), response.text body = response.json() assert body["owner"] assert body["repo_name"] assert body["clone_url"] # A tenant repository must never be created public. assert body.get("private", True) is True async def test_repo_status_is_reported_after_ensure(client: AsyncClient): token = (await signup(client, "repo-status@example.com"))["access_token"] org_id, project_id = await _org_and_project(client, token) base = f"/api/organizations/{org_id}/projects/{project_id}" # Before ensure, the platform has no record of a repository. before = await client.get(f"{base}/repo/status", headers=auth_header(token)) assert before.status_code == 200, before.text assert before.json()["exists"] is False await client.post(f"{base}/repo/ensure", headers=auth_header(token)) after = await client.get(f"{base}/repo/status", headers=auth_header(token)) assert after.status_code == 200, after.text assert after.json()["exists"] is True async def test_git_password_never_leaves_the_api(client: AsyncClient): """`ensure_credentials` returns plaintext to the service layer only. The regression this guards against is a future refactor adding the credential object straight to a response model. """ token = (await signup(client, "repo-secret@example.com"))["access_token"] org_id, project_id = await _org_and_project(client, token) base = f"/api/organizations/{org_id}/projects/{project_id}" await client.post(f"{base}/repo/ensure", headers=auth_header(token)) for path in (f"{base}/repo", f"{base}/repo/status"): response = await client.get(path, headers=auth_header(token)) assert response.status_code == 200, response.text payload = json.dumps(response.json()) for forbidden in ("password", "encrypted_password", "token"): assert forbidden not in payload.lower(), f"{forbidden} leaked via {path}" async def test_repository_of_another_tenant_is_not_reachable(client: AsyncClient): """Tenant isolation must hold on the repo sub-resources too.""" token_a = (await signup(client, "repo-a@example.com"))["access_token"] token_b = (await signup(client, "repo-b@example.com"))["access_token"] org_a, project_a = await _org_and_project(client, token_a) # B is authenticated but not a member of A's organization. response = await client.post( f"/api/organizations/{org_a}/projects/{project_a}/repo/ensure", headers=auth_header(token_b), ) # 404 rather than 403, so the API does not confirm the project exists. assert response.status_code == 404, response.text async def test_ensure_repository_is_idempotent(client: AsyncClient): token = (await signup(client, "repo-idem@example.com"))["access_token"] org_id, project_id = await _org_and_project(client, token) base = f"/api/organizations/{org_id}/projects/{project_id}" first = await client.post(f"{base}/repo/ensure", headers=auth_header(token)) second = await client.post(f"{base}/repo/ensure", headers=auth_header(token)) assert first.status_code in (200, 201), first.text assert second.status_code in (200, 201), second.text assert first.json()["repo_name"] == second.json()["repo_name"]