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

137 lines
5.3 KiB
Python

"""Multi-tenancy: the single most important guarantee in the product.
A member of organization A must never be able to read or mutate anything that
belongs to organization B — not by listing, not by guessing an id.
"""
from __future__ import annotations
from httpx import AsyncClient
from tests.conftest import auth_header, signup
async def _create_org(client: AsyncClient, token: str, name: str) -> dict:
response = await client.post(
"/api/organizations", json={"name": name}, headers=auth_header(token)
)
assert response.status_code == 201, response.text
return response.json()
async def test_create_organization_makes_caller_the_owner(client: AsyncClient):
token = (await signup(client, "owner-a@example.com"))["access_token"]
org = await _create_org(client, token, "شرکت آلفا")
assert org["role"] == "owner"
assert org["slug"]
assert org["owner_user_id"]
async def test_organization_slug_is_unique_across_tenants(client: AsyncClient):
token_a = (await signup(client, "slug-a@example.com"))["access_token"]
token_b = (await signup(client, "slug-b@example.com"))["access_token"]
first = await _create_org(client, token_a, "My Company")
second = await _create_org(client, token_b, "My Company")
assert first["slug"] != second["slug"]
assert second["slug"].startswith(first["slug"])
async def test_list_only_returns_own_organizations(client: AsyncClient):
token_a = (await signup(client, "list-a@example.com"))["access_token"]
token_b = (await signup(client, "list-b@example.com"))["access_token"]
org_a = await _create_org(client, token_a, "شرکت الف")
await _create_org(client, token_b, "شرکت ب")
listing = await client.get("/api/organizations", headers=auth_header(token_a))
assert listing.status_code == 200
ids = [o["id"] for o in listing.json()]
assert ids == [org_a["id"]]
async def test_foreign_organization_is_not_readable(client: AsyncClient):
token_a = (await signup(client, "iso-a@example.com"))["access_token"]
token_b = (await signup(client, "iso-b@example.com"))["access_token"]
org_a = await _create_org(client, token_a, "سازمان محرمانه")
# B knows A's id but has no membership.
response = await client.get(f"/api/organizations/{org_a['id']}", headers=auth_header(token_b))
assert response.status_code == 404
assert response.json()["error"]["code"] == "not_found"
async def test_foreign_member_list_is_not_readable(client: AsyncClient):
token_a = (await signup(client, "mem-a@example.com"))["access_token"]
token_b = (await signup(client, "mem-b@example.com"))["access_token"]
org_a = await _create_org(client, token_a, "سازمان با اعضا")
response = await client.get(
f"/api/organizations/{org_a['id']}/members", headers=auth_header(token_b)
)
assert response.status_code == 404
async def test_foreign_invite_is_rejected(client: AsyncClient):
token_a = (await signup(client, "inv-a@example.com"))["access_token"]
token_b = (await signup(client, "inv-b@example.com"))["access_token"]
org_a = await _create_org(client, token_a, "سازمان دعوت")
response = await client.post(
f"/api/organizations/{org_a['id']}/invites",
json={"email": "attacker@example.com", "role": "admin"},
headers=auth_header(token_b),
)
assert response.status_code == 404
async def test_owner_can_list_members_and_see_themselves(client: AsyncClient):
token = (await signup(client, "solo@example.com", name="تک‌نفره"))["access_token"]
org = await _create_org(client, token, "تک‌نفره")
response = await client.get(
f"/api/organizations/{org['id']}/members", headers=auth_header(token)
)
assert response.status_code == 200
members = response.json()
assert len(members) == 1
assert members[0]["role"] == "owner"
assert members[0]["email"] == "solo@example.com"
async def test_invite_returns_token_only_to_the_inviter(client: AsyncClient):
token = (await signup(client, "inviter@example.com"))["access_token"]
org = await _create_org(client, token, "دعوت‌کننده")
response = await client.post(
f"/api/organizations/{org['id']}/invites",
json={"email": "newbie@example.com", "role": "member"},
headers=auth_header(token),
)
assert response.status_code == 201
body = response.json()
assert body["invite_token"]
assert body["email"] == "newbie@example.com"
async def test_cannot_invite_as_owner(client: AsyncClient):
token = (await signup(client, "noowner@example.com"))["access_token"]
org = await _create_org(client, token, "بدون مالک دوم")
response = await client.post(
f"/api/organizations/{org['id']}/invites",
json={"email": "wannabe@example.com", "role": "owner"},
headers=auth_header(token),
)
assert response.status_code == 422
async def test_audit_log_records_organization_creation(client: AsyncClient):
token = (await signup(client, "audited@example.com"))["access_token"]
org = await _create_org(client, token, "سازمان دارای audit")
response = await client.get(
f"/api/organizations/{org['id']}/audit", headers=auth_header(token)
)
assert response.status_code == 200
actions = [event["action"] for event in response.json()]
assert "organization.create" in actions