نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user