"""/api/v1/me/team/{footprint,cloud} contract tests (PR-D). Asserts: * Both endpoints require auth (anonymous → 401) * Empty subordinates set returns 200 OK with empty rows (NOT 403) * Footprint counts include assets / seats / api_keys / cloud_grants / scopes / dns / cdn / domains for direct + transitive reports * Cloud endpoint enriches every row with the subordinate owner """ from __future__ import annotations from datetime import UTC, datetime from decimal import Decimal from uuid import uuid4 import pytest from app.domains.apikey.models import ApiKeyAssignment, ApiKeyProvider from app.domains.asset.models import ( AssetItem, AssetSoftwareCatalog, SoftwareLicensePlan, SoftwareSeatAllocation, ) from app.domains.cmdb.models import ( CmdbCloudAccessGrant, CmdbCloudScope, CmdbDnsRecord, CmdbDomain, ) from app.identity.person_models import IdentityPerson from app.integrations.models import Integration def _mkperson(manager_id=None) -> IdentityPerson: return IdentityPerson( source_system="test", hris_person_id=f"P{uuid4().hex[:6]}", employee_no=f"E{uuid4().hex[:6]}", work_email=f"{uuid4().hex[:8]}@test.local", display_name=f"Person {uuid4().hex[:4]}", employment_status="active", manager_person_id=manager_id, ) # ── RBAC ───────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_employee_with_no_reports_gets_empty_200(client, employee_user): """Leaf-node employee: 200 OK with empty rows, not 403.""" _, token = employee_user resp = await client.get( "/api/v1/me/team/footprint", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 body = resp.json() assert body["success"] is True assert body["data"]["subordinates_count"] == 0 assert body["data"]["rows"] == [] # ── Footprint counts ──────────────────────────────────────────── @pytest.mark.asyncio async def test_footprint_counts_assets_seats_apikeys_for_direct_report( client, admin_user, db_session, ): admin, token = admin_user # 1 direct report. report = _mkperson(manager_id=admin.id) db_session.add(report) await db_session.flush() # 1 asset assigned to them. db_session.add(AssetItem( id=uuid4(), asset_tag=f"A-{uuid4().hex[:6]}", asset_name="Test Laptop", category="hardware", status="claimed", # was "in_use" — never a valid asset status (2026-08-07) assigned_to=report.id, )) # 1 active seat allocation. software = AssetSoftwareCatalog( id=uuid4(), name=f"SW {uuid4().hex[:6]}", status="active", category="productivity", ) db_session.add(software) plan = SoftwareLicensePlan( id=uuid4(), software_id=software.id, name="Std", price=Decimal("0"), billing_cycle="monthly", currency="USD", max_seats=None, status="active", ) db_session.add(plan) await db_session.flush() db_session.add(SoftwareSeatAllocation( id=uuid4(), plan_id=plan.id, user_id=report.id, allocated_at=datetime.now(UTC), status="active", )) # 1 active api key assignment (needs a provider). provider = ApiKeyProvider( id=uuid4(), name=f"prov-{uuid4().hex[:6]}", ) db_session.add(provider) await db_session.flush() db_session.add(ApiKeyAssignment( id=uuid4(), provider_id=provider.id, assigned_to=report.id, request_number=f"AK-{uuid4().hex[:8]}", project_name="test-proj", reason="testing", monthly_budget_usd=Decimal("0"), status="active", )) await db_session.flush() resp = await client.get( "/api/v1/me/team/footprint", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 rows = resp.json()["data"]["rows"] assert len(rows) == 1 row = rows[0] assert row["person_id"] == str(report.id) f = row["footprint"] assert f["assets"] >= 1 assert f["seats"] >= 1 assert f["api_keys"] >= 1 @pytest.mark.asyncio async def test_footprint_counts_cloud_resources_owned_by_report( client, admin_user, db_session, ): admin, token = admin_user report = _mkperson(manager_id=admin.id) db_session.add(report) await db_session.flush() # Integration + scope owned by the subordinate. integ = Integration( id=uuid4(), name=f"i-{uuid4().hex[:6]}", slug=f"i-{uuid4().hex[:6]}", kind="cloud", provider="aws", enabled=True, config_public={}, ) db_session.add(integ) await db_session.flush() scope = CmdbCloudScope( id=uuid4(), integration_id=str(integ.id), scope_type="aws_account", external_id=f"acct-{uuid4().hex[:6]}", display_name="Sub's scope", owner_person_id=str(report.id), status="active", ) db_session.add(scope) await db_session.flush() # 1 root domain + 1 DNS record owned by sub. db_session.add(CmdbDomain( id=uuid4(), integration_id=str(integ.id), domain_name=f"sub-{uuid4().hex[:6]}.example.com", owner_person_id=str(report.id), )) db_session.add(CmdbDnsRecord( id=uuid4(), integration_id=str(integ.id), zone_name="example.com", record_id=f"dns-{uuid4().hex[:6]}", name=f"sub-{uuid4().hex[:6]}", record_type="A", value="1.2.3.4", owner_person_id=str(report.id), )) # 1 active cloud grant assigned to sub. db_session.add(CmdbCloudAccessGrant( id=uuid4(), scope_id=str(scope.id), person_id=str(report.id), role_key="reader", status="active", granted_at=datetime.now(UTC), )) await db_session.flush() resp = await client.get( "/api/v1/me/team/footprint", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 rows = resp.json()["data"]["rows"] row = next(r for r in rows if r["person_id"] == str(report.id)) f = row["footprint"] assert f["scopes_owned"] >= 1 assert f["domains_owned"] >= 1 assert f["dns_owned"] >= 1 assert f["cloud_grants"] >= 1 @pytest.mark.asyncio async def test_footprint_excludes_left_report_by_default( client, admin_user, db_session, ): """A leaver keeps their manager pointer + a non-deleted row, but must NOT count as a current report. Default footprint hides them; the ``include_left`` flag opts them back in (e.g. for re-assignment).""" admin, token = admin_user active = _mkperson(manager_id=admin.id) left = _mkperson(manager_id=admin.id) left.employment_status = "left" db_session.add_all([active, left]) await db_session.flush() # Default: leaver hidden, headcount excludes them. resp = await client.get( "/api/v1/me/team/footprint", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 data = resp.json()["data"] ids = {r["person_id"] for r in data["rows"]} assert str(active.id) in ids assert str(left.id) not in ids assert data["subordinates_count"] == len(data["rows"]) # include_left=true: leaver reappears, tagged 'left'. resp = await client.get( "/api/v1/me/team/footprint?include_left=true", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 data = resp.json()["data"] by_id = {r["person_id"]: r for r in data["rows"]} assert str(left.id) in by_id assert by_id[str(left.id)]["employment_status"] == "left" @pytest.mark.asyncio async def test_team_cloud_excludes_left_report_by_default( client, admin_user, db_session, ): """Sibling surface: a leaver's owned cloud resources are hidden from the team-cloud view by default, revealed via ``include_left``.""" admin, token = admin_user left = _mkperson(manager_id=admin.id) left.employment_status = "left" db_session.add(left) await db_session.flush() integ = Integration( id=uuid4(), name=f"i-{uuid4().hex[:6]}", slug=f"i-{uuid4().hex[:6]}", kind="cloud", provider="aws", enabled=True, config_public={}, ) db_session.add(integ) await db_session.flush() scope = CmdbCloudScope( id=uuid4(), integration_id=str(integ.id), scope_type="aws_account", external_id=f"acct-{uuid4().hex[:6]}", display_name="Owned by leaver", owner_person_id=str(left.id), status="active", ) db_session.add(scope) await db_session.flush() resp = await client.get( "/api/v1/me/team/cloud", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 assert not any( s["owner"]["id"] == str(left.id) for s in resp.json()["data"]["scopes"] ) resp = await client.get( "/api/v1/me/team/cloud?include_left=true", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 assert any( s["owner"]["id"] == str(left.id) for s in resp.json()["data"]["scopes"] ) @pytest.mark.asyncio async def test_team_cloud_returns_enriched_resources( client, admin_user, db_session, ): admin, token = admin_user report = _mkperson(manager_id=admin.id) db_session.add(report) await db_session.flush() integ = Integration( id=uuid4(), name=f"i-{uuid4().hex[:6]}", slug=f"i-{uuid4().hex[:6]}", kind="cloud", provider="aws", enabled=True, config_public={}, ) db_session.add(integ) await db_session.flush() scope = CmdbCloudScope( id=uuid4(), integration_id=str(integ.id), scope_type="aws_account", external_id=f"acct-{uuid4().hex[:6]}", display_name="Owned by sub", owner_person_id=str(report.id), status="active", ) db_session.add(scope) await db_session.flush() db_session.add(CmdbCloudAccessGrant( id=uuid4(), scope_id=str(scope.id), person_id=str(report.id), role_key="reader", status="active", granted_at=datetime.now(UTC), )) await db_session.flush() resp = await client.get( "/api/v1/me/team/cloud", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 body = resp.json()["data"] assert any(g["assignee"]["id"] == str(report.id) for g in body["grants"]) assert any(s["owner"]["id"] == str(report.id) for s in body["scopes"]) # ── Team API keys (/me/team/api-keys) ──────────────────────────── @pytest.mark.asyncio async def test_team_api_keys_lists_held_key_with_bailian_spend( client, admin_user, db_session, ): """A held Bailian key surfaces with its real month-to-date spend (summed from ``bailian_usage_daily`` via the same dispatcher the per-AK detail page uses) and rolls into the team summary total.""" from datetime import datetime from zoneinfo import ZoneInfo from app.domains.bailian.models import BailianApiKey, BailianUsageDaily admin, token = admin_user report = _mkperson(manager_id=admin.id) db_session.add(report) await db_session.flush() integ = Integration( id=uuid4(), name=f"i-{uuid4().hex[:6]}", slug=f"i-{uuid4().hex[:6]}", kind="cloud", provider="aliyun", enabled=True, config_public={}, ) db_session.add(integ) # "Bailian" in the provider name → bucket=bailian per classify_bucket. provider = ApiKeyProvider(id=uuid4(), name=f"Bailian {uuid4().hex[:6]}") db_session.add(provider) await db_session.flush() assignment = ApiKeyAssignment( id=uuid4(), provider_id=provider.id, assigned_to=report.id, request_number=f"AK-{uuid4().hex[:8]}", project_name="rag-pipeline", reason="testing", monthly_budget_usd=Decimal("100"), status="active", ) db_session.add(assignment) await db_session.flush() bkey = BailianApiKey( id=uuid4(), integration_id=str(integ.id), api_key_id=f"sk-{uuid4().hex[:8]}", display_name="rag key", assignment_id=assignment.id, ) db_session.add(bkey) await db_session.flush() today_sh = datetime.now(ZoneInfo("Asia/Shanghai")).date() db_session.add(BailianUsageDaily( id=uuid4(), integration_id=str(integ.id), api_key_pk=bkey.id, usage_date=today_sh, model_name="qwen-max", cost_usd=Decimal("12.50"), )) await db_session.flush() resp = await client.get( "/api/v1/me/team/api-keys", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 data = resp.json()["data"] row = next(r for r in data["api_keys"] if r["id"] == str(assignment.id)) assert row["owner"]["id"] == str(report.id) assert row["bucket"] == "bailian" assert row["data_available"] is True assert row["mtd_usd"] == pytest.approx(12.50) assert row["monthly_budget_usd"] == pytest.approx(100.0) assert row["utilization_pct"] == pytest.approx(12.5) assert row["over_budget"] is False assert data["summary"]["total_mtd_usd"] == pytest.approx(12.50) assert data["summary"]["count"] >= 1 @pytest.mark.asyncio async def test_team_api_keys_excludes_revoked_and_left( client, admin_user, db_session, ): """Revoked keys (dead secret) and keys held by a leaver (hidden by default) must not appear; an unknown-provider key surfaces with ``data_available=False`` and counts as unattributed.""" admin, token = admin_user active = _mkperson(manager_id=admin.id) left = _mkperson(manager_id=admin.id) left.employment_status = "left" db_session.add_all([active, left]) provider = ApiKeyProvider(id=uuid4(), name=f"prov-{uuid4().hex[:6]}") db_session.add(provider) await db_session.flush() def _ak(person, status): return ApiKeyAssignment( id=uuid4(), provider_id=provider.id, assigned_to=person.id, request_number=f"AK-{uuid4().hex[:8]}", project_name="p", reason="r", monthly_budget_usd=Decimal("0"), status=status, ) held = _ak(active, "active") revoked = _ak(active, "revoked") leaver_key = _ak(left, "active") db_session.add_all([held, revoked, leaver_key]) await db_session.flush() resp = await client.get( "/api/v1/me/team/api-keys", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 data = resp.json()["data"] ids = {r["id"] for r in data["api_keys"]} assert str(held.id) in ids assert str(revoked.id) not in ids assert str(leaver_key.id) not in ids # Unknown provider → spend can't be attributed. held_row = next(r for r in data["api_keys"] if r["id"] == str(held.id)) assert held_row["data_available"] is False assert data["summary"]["data_unavailable_count"] >= 1 # include_left=true surfaces the leaver's still-held key. resp = await client.get( "/api/v1/me/team/api-keys?include_left=true", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 ids = {r["id"] for r in resp.json()["data"]["api_keys"]} assert str(leaver_key.id) in ids