"""Unified self-service server visibility (2026-07-22 contract). A non-admin sees servers they (or their reports) own at server level PLUS every server attributed to a cloud subscription they own — via the account stamp each sync driver writes onto ``raw_data`` (``SCOPE_ACCOUNT_RAW_KEYS``) or the cost-fact link. The contract is shared by the servers list/detail, the efficiency surface, and the "my" analytics, so a machine never shows on one page and 404s on the next. See ``app/domains/cmdb/visibility.py``. """ from __future__ import annotations import uuid from datetime import UTC, date, datetime, timedelta import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from app.domains.cmdb.models import ( CmdbCloudScope, CmdbCostFact, CmdbServer, CmdbServerEfficiency, ) from app.integrations.models import Integration async def _integration(db: AsyncSession, provider: str = "aws") -> Integration: integ = Integration( id=uuid.uuid4(), kind="cloud", provider=provider, name=f"{provider}-vis", slug=f"{provider}-{uuid.uuid4().hex[:6]}", enabled=True, is_primary=False, capabilities=["servers", "metrics"], ) db.add(integ) await db.flush() return integ async def _scope(db: AsyncSession, integ: Integration, **kw) -> CmdbCloudScope: d = { "id": uuid.uuid4(), "integration_id": str(integ.id), "scope_type": kw.pop("scope_type", "aws_account"), "external_id": kw.pop("external_id", f"acct-{uuid.uuid4().hex[:8]}"), "display_name": kw.pop("display_name", "sub-vis"), } d.update(kw) s = CmdbCloudScope(**d) db.add(s) await db.flush() return s async def _server(db: AsyncSession, integ: Integration, **kw) -> CmdbServer: now = datetime.now(UTC) d = { "id": uuid.uuid4(), "instance_id": kw.pop("instance_id", f"i-{uuid.uuid4().hex[:10]}"), "name": kw.pop("name", "srv"), "status": kw.pop("status", "running"), "region": "us-east-1", "integration_id": str(integ.id), "first_seen_at": now - timedelta(days=100), "last_synced_at": now, } d.update(kw) s = CmdbServer(**d) db.add(s) await db.flush() return s async def _efficiency(db, integ, server, **kw) -> CmdbServerEfficiency: d = { "id": uuid.uuid4(), "server_id": str(server.id), "integration_id": str(integ.id), "verdict": kw.pop("verdict", "oversized"), "recommendation": kw.pop("recommendation", "downsize"), "computed_at": datetime.now(UTC), } d.update(kw) e = CmdbServerEfficiency(**d) db.add(e) await db.flush() return e async def _cost_fact(db, integ, scope, server, amount=10): f = CmdbCostFact( fact_key=uuid.uuid4().hex, date=date.today(), integration_id=str(integ.id), scope_id=str(scope.id), resource_id=server.instance_id, resource_type="Instance", service="AmazonEC2", amount_usd=amount, currency="USD", ) db.add(f) await db.flush() return f def _pid(employee_user) -> str: person = ( employee_user[0] if isinstance(employee_user, tuple) else employee_user ) return str(person.id) # ── efficiency surface ─────────────────────────────────────────────── @pytest.mark.asyncio async def test_efficiency_includes_owned_scope_servers( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): """A subscription owner sees the subscription's servers on the top-level efficiency page even with no server-level owner set.""" pid = _pid(employee_user) integ = await _integration(db_session) my_scope = await _scope(db_session, integ, owner_person_id=pid) other_scope = await _scope(db_session, integ) mine = await _server(db_session, integ, name="scope-owned") foreign = await _server(db_session, integ, name="foreign") await _cost_fact(db_session, integ, my_scope, mine) await _cost_fact(db_session, integ, other_scope, foreign) await _efficiency(db_session, integ, mine, estimated_monthly_savings_usd=40) await _efficiency(db_session, integ, foreign, estimated_monthly_savings_usd=99) await db_session.commit() r = await client.get( "/api/v1/cmdb/efficiency/instances", headers=employee_headers, ) assert r.status_code == 200 ids = {i["server_id"] for i in r.json()["data"]} assert str(mine.id) in ids assert str(foreign.id) not in ids # The row carries the inherited-attribution fields for the 归属 tag. row = next(i for i in r.json()["data"] if i["server_id"] == str(mine.id)) assert row["owner_inherited"] is True assert row["inherited_from_scope_id"] == str(my_scope.id) # Summary counts the same set. rs = await client.get( "/api/v1/cmdb/efficiency/summary", headers=employee_headers, ) assert rs.status_code == 200 assert rs.json()["data"]["totals"]["servers"] == 1 # Detail: mine 200, foreign 404 (non-oracle). rd = await client.get( f"/api/v1/cmdb/efficiency/instances/{mine.id}", headers=employee_headers, ) assert rd.status_code == 200 rf = await client.get( f"/api/v1/cmdb/efficiency/instances/{foreign.id}", headers=employee_headers, ) assert rf.status_code == 404 @pytest.mark.asyncio async def test_efficiency_gcp_project_link_visibility( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): """GCP servers attach to their scope via raw_data._project_id (no per-resource cost lines on GCP) — the owner still sees them.""" pid = _pid(employee_user) integ = await _integration(db_session, provider="gcp") my_scope = await _scope( db_session, integ, scope_type="gcp_project", external_id="proj-mine", owner_person_id=pid, ) await _scope(db_session, integ, scope_type="gcp_project", external_id="proj-other") mine = await _server( db_session, integ, name="gcp-mine", raw_data={"_project_id": "proj-mine"}, ) foreign = await _server( db_session, integ, name="gcp-other", raw_data={"_project_id": "proj-other"}, ) await _efficiency(db_session, integ, mine) await _efficiency(db_session, integ, foreign) await db_session.commit() r = await client.get( "/api/v1/cmdb/efficiency/instances", headers=employee_headers, ) assert r.status_code == 200 ids = {i["server_id"] for i in r.json()["data"]} assert str(mine.id) in ids assert str(foreign.id) not in ids assert my_scope.external_id == "proj-mine" @pytest.mark.asyncio async def test_scope_owner_can_triage_attributed_server( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): pid = _pid(employee_user) integ = await _integration(db_session) my_scope = await _scope(db_session, integ, owner_person_id=pid) srv = await _server(db_session, integ) await _cost_fact(db_session, integ, my_scope, srv) await _efficiency(db_session, integ, srv, verdict="idle", recommendation="stop") other = await _server(db_session, integ) await _efficiency(db_session, integ, other, verdict="idle", recommendation="stop") await db_session.commit() ok = await client.post( f"/api/v1/cmdb/efficiency/instances/{srv.id}/triage", json={"action": "acknowledge"}, headers=employee_headers, ) assert ok.status_code == 200 assert ok.json()["data"]["triage_status"] == "acknowledged" denied = await client.post( f"/api/v1/cmdb/efficiency/instances/{other.id}/triage", json={"action": "acknowledge"}, headers=employee_headers, ) assert denied.status_code == 404 # ── servers list / detail ──────────────────────────────────────────── @pytest.mark.asyncio async def test_servers_list_and_detail_include_owned_scope_servers( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): pid = _pid(employee_user) integ = await _integration(db_session) my_scope = await _scope(db_session, integ, owner_person_id=pid) other_scope = await _scope(db_session, integ) _direct = await _server(db_session, integ, name="direct", owner_person_id=pid) via_scope = await _server(db_session, integ, name="via-scope") foreign = await _server(db_session, integ, name="foreign") await _cost_fact(db_session, integ, my_scope, via_scope) await _cost_fact(db_session, integ, other_scope, foreign) await db_session.commit() r = await client.get("/api/v1/cmdb/servers", headers=employee_headers) assert r.status_code == 200 names = {row["name"] for row in r.json()["data"]} assert {"direct", "via-scope"} <= names assert "foreign" not in names # The scope-attributed row carries the inherited-owner display fields. row = next(x for x in r.json()["data"] if x["name"] == "via-scope") assert row.get("owner_inherited") is True assert row.get("inherited_from_scope_id") == str(my_scope.id) # Detail: scope-attributed server opens; foreign 404s. rd = await client.get( f"/api/v1/cmdb/servers/{via_scope.id}", headers=employee_headers, ) assert rd.status_code == 200 rf = await client.get( f"/api/v1/cmdb/servers/{foreign.id}", headers=employee_headers, ) assert rf.status_code == 404 @pytest.mark.asyncio async def test_my_analytics_server_count_matches_visibility( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): pid = _pid(employee_user) integ = await _integration(db_session) my_scope = await _scope(db_session, integ, owner_person_id=pid) await _server(db_session, integ, name="direct", owner_person_id=pid) via_scope = await _server(db_session, integ, name="via-scope") await _server(db_session, integ, name="foreign") await _cost_fact(db_session, integ, my_scope, via_scope) await db_session.commit() r = await client.get("/api/v1/cmdb/analytics/my", headers=employee_headers) assert r.status_code == 200 data = r.json()["data"] assert data["totals"]["servers"] == 2 @pytest.mark.asyncio async def test_aws_and_azure_owners_see_their_subscription_servers( client: AsyncClient, db_session: AsyncSession, employee_user, employee_headers, ): """The account stamp — not just GCP's — attributes a server to its scope. The stamp link used to read ``_project_id`` alone, so AWS and Azure fell through to the cost-fact link, which cannot resolve for either: AWS cost lines are aggregated per account (``resource_id`` = ``aws-account::``) and Azure's is the ARM path, neither of which equals an instance id. Net effect on production 2026-08-18: a subscription owner could not see the 5136 AWS + 11 Azure machines in their OWN subscription — the 2026-07-22 contract was undelivered for two clouds while GCP worked. """ pid = _pid(employee_user) aws = await _integration(db_session, provider="aws") azure = await _integration(db_session, provider="azure") aws_scope = await _scope( db_session, aws, external_id="811111111111", owner_person_id=pid, ) other_aws_scope = await _scope(db_session, aws, external_id="822222222222") azure_sub = "b1111111-2222-3333-4444-555555555555" azure_scope = await _scope( db_session, azure, scope_type="azure_subscription", external_id=azure_sub, owner_person_id=pid, ) # Mine, attributed only by the driver's account stamp — no cost fact. aws_mine = await _server( db_session, aws, name="aws-mine", raw_data={"_aws_account_id": "811111111111"}, ) azure_mine = await _server( db_session, azure, name="azure-mine", raw_data={"_subscription_id": azure_sub}, ) # Someone else's subscription, same integration — must stay hidden. aws_foreign = await _server( db_session, aws, name="aws-foreign", raw_data={"_aws_account_id": "822222222222"}, ) # No stamp at all and no cost line (the UCloud shape) — stays hidden. unattributable = await _server( db_session, aws, name="no-stamp", raw_data={"InstanceId": "nothing"}, ) await db_session.commit() assert other_aws_scope is not None and azure_scope is not None r = await client.get( "/api/v1/cmdb/servers?page_size=200", headers=employee_headers, ) assert r.status_code == 200 names = {row["name"] for row in r.json()["data"]} assert {"aws-mine", "azure-mine"} <= names assert "aws-foreign" not in names assert "no-stamp" not in names # Inherited-owner display fields ride along on the stamp link too. row = next(x for x in r.json()["data"] if x["name"] == "aws-mine") assert row.get("owner_inherited") is True assert row.get("inherited_from_scope_id") == str(aws_scope.id) # Detail parity: mine opens, the neighbouring subscription's 404s. assert (await client.get( f"/api/v1/cmdb/servers/{aws_mine.id}", headers=employee_headers, )).status_code == 200 assert (await client.get( f"/api/v1/cmdb/servers/{azure_mine.id}", headers=employee_headers, )).status_code == 200 assert (await client.get( f"/api/v1/cmdb/servers/{aws_foreign.id}", headers=employee_headers, )).status_code == 404 assert (await client.get( f"/api/v1/cmdb/servers/{unattributable.id}", headers=employee_headers, )).status_code == 404