Files
smart-org/apps/api/tests/test_project_repo.py
T
mjalmoudy94 717e53a7ce نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
2026-10-13 00:39:28 +03:30

118 lines
4.9 KiB
Python

"""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"]