"""Administrator (view only) role (0.2.45): reads everything on its granted entities, changes nothing anywhere.""" import io from datetime import date from ten31portal.models import ( CapitalAccountStatement, Entity, EntityAccess, EntityType, UserRole, ) from tests.conftest import make_user def _login(client, username, password="password123"): client.post("/api/auth/logout") resp = client.post("/api/auth/login", json={"login": username, "password": password}) assert resp.status_code == 200, resp.text return resp def _setup(session): fund_a = Entity(name="Fund A", type=EntityType.fund) fund_b = Entity(name="Fund B", type=EntityType.fund) session.add(fund_a) session.add(fund_b) session.commit() viewer = make_user(session, username="viewonly", role=UserRole.administrator_viewer, name="Read Only Admin") session.add(EntityAccess(user_id=viewer.id, entity_id=fund_a.id)) lp = make_user(session, username="lp-a", role=UserRole.investor, name="LP Alpha") session.add(EntityAccess(user_id=lp.id, entity_id=fund_a.id)) session.add(CapitalAccountStatement( entity_id=fund_a.id, investor_user_id=lp.id, as_of_date=date(2026, 3, 31), commitment_cents=100_00, beginning_balance_cents=0, contributions_cents=100_00, distributions_cents=0, ending_balance_cents=110_00, )) session.commit() return fund_a, fund_b, viewer, lp def test_viewer_reads_only_their_fund(client, session, approver): fund_a, fund_b, viewer, lp = _setup(session) _login(client, "viewonly") entities = client.get("/api/entities").json() assert [e["name"] for e in entities] == ["Fund A"] assert client.get(f"/api/entities/{fund_a.id}/partners").status_code == 200 assert client.get(f"/api/entities/{fund_b.id}/partners").status_code == 403 assert client.get(f"/api/entities/{fund_a.id}/rounds").status_code == 200 assert client.get(f"/api/entities/{fund_a.id}/holdings").status_code == 200 # Sees every investor's statements within the fund, plus their names. rows = client.get("/api/capital-accounts").json() assert {r["entity_id"] for r in rows} == {fund_a.id} users = client.get("/api/users").json() assert {u["username"] for u in users} == {"lp-a"} docs = client.get("/api/documents").json() assert isinstance(docs, list) def test_viewer_cannot_change_anything(client, session, approver): fund_a, fund_b, viewer, lp = _setup(session) _login(client, "viewonly") # Entity records assert client.patch(f"/api/entities/{fund_a.id}", json={"name": "X"}).status_code == 403 assert client.put( f"/api/entities/{fund_a.id}/partners/{lp.id}/exited", json={"exited_on": "2026-06-30"} ).status_code == 403 assert client.delete(f"/api/entities/{fund_a.id}/partners").status_code == 403 # Documents resp = client.post( "/api/documents", data={"entity_id": str(fund_a.id), "category": "statement"}, files={"file": ("x.pdf", io.BytesIO(b"pdf"), "application/pdf")}, ) assert resp.status_code == 403 # Capital accounts assert client.post("/api/capital-accounts", json={ "entity_id": fund_a.id, "investor_user_id": lp.id, "as_of_date": "2026-06-30", "commitment_dollars": 1, "beginning_balance_dollars": 0, "contributions_dollars": 1, "distributions_dollars": 0, "ending_balance_dollars": 1, }).status_code == 403 # Imports assert client.post( f"/api/import/schedule?entity_id={fund_a.id}", files={"file": ("x.xlsx", io.BytesIO(b"junk"), "application/octet-stream")}, ).status_code == 403 assert client.post( "/api/import/capital-accounts/batch", data={"entity_id": str(fund_a.id)}, files=[("files", ("x.xlsx", io.BytesIO(b"junk"), "application/octet-stream"))], ).status_code == 403 # User management assert client.post("/api/users", json={ "name": "N", "username": "n", "password": "secretpw", "role": "investor", "entity_ids": [fund_a.id], }).status_code == 403 assert client.patch(f"/api/users/{lp.id}", json={"is_active": False}).status_code == 403 assert client.delete(f"/api/users/{lp.id}").status_code == 403 assert client.get("/api/users/access-matrix").status_code == 403 def test_internal_admin_toggles_administrator_level(auth_client, session): admin_acct = make_user(session, username="mgr", role=UserRole.fund_administrator, name="Managing Admin") resp = auth_client.patch(f"/api/users/{admin_acct.id}", json={"role": "administrator_viewer"}) assert resp.status_code == 200, resp.text assert resp.json()["role"] == "administrator_viewer" resp = auth_client.patch(f"/api/users/{admin_acct.id}", json={"role": "fund_administrator"}) assert resp.status_code == 200 assert resp.json()["role"] == "fund_administrator"