"""Legacy-billing units are not cloud accounts. Prod 2026-08-05 04:11:25 UTC: a one-off script created three "(legacy billing)" integrations (KSCloud / UCloud / AWS China, all ``enabled=0``, ``capabilities=[]``) plus **37 scope rows in that same second**, typed ``standalone_iam``. No audit rows, no commit that day, nothing in docs, and the bills they were made for were never imported. So 云订阅 listed 37 subscriptions that are not accounts — 金山云 is project-based, not multi-account, and the names are PingCAP cost centres (``C0801-RDG-TiDB_Cloud``, ``默认项目``, ``Innovation-Project``). 30 of them carry an owner, which made them candidates for owner-confirmation cards too. ``legacy_billing_unit`` is deliberately absent from ``TENANT_LEVEL_SCOPE_TYPES``, so one fact removes them from the requestable picker, every per-scope driver fan-out and the grant gate. The rows stay: the historical bills are still meant to be imported. """ from __future__ import annotations import uuid import pytest from sqlalchemy.ext.asyncio import AsyncSession from app.domains.cmdb.models import ( SCOPE_STATUS_ACTIVE, SCOPE_TYPE_LEGACY_BILLING_UNIT, SCOPE_TYPE_STANDALONE_IAM, TENANT_LEVEL_SCOPE_TYPES, CmdbCloudScope, ) from app.integrations.models import Integration async def _integration( db: AsyncSession, *, name: str, enabled: bool = True, ) -> Integration: row = Integration( id=uuid.uuid4(), kind="cloud", provider="ksyun", name=name, slug=f"ks-{uuid.uuid4().hex[:6]}", enabled=enabled, is_primary=False, capabilities=[], ) db.add(row) await db.commit() return row async def _scope( db: AsyncSession, integration: Integration, *, external_id: str, scope_type: str = SCOPE_TYPE_STANDALONE_IAM, ) -> CmdbCloudScope: row = CmdbCloudScope( id=uuid.uuid4(), integration_id=str(integration.id), scope_type=scope_type, external_id=external_id, display_name=external_id, status=SCOPE_STATUS_ACTIVE, environment="dev", ) db.add(row) await db.commit() return row def test_legacy_billing_is_not_a_tenant_level_scope() -> None: """The single fact everything else follows from: the requestable picker, the grants/roles/billing/cost fan-outs and the grant gate all filter on this set.""" assert SCOPE_TYPE_LEGACY_BILLING_UNIT not in TENANT_LEVEL_SCOPE_TYPES @pytest.mark.asyncio async def test_grant_gate_refuses_a_legacy_billing_unit( db_session: AsyncSession, ) -> None: from app.core.exceptions import AppException from app.domains.cmdb.lifecycle_service import ( _refuse_ungrantable_scope_type, ) integration = await _integration( db_session, name="KSCloud (legacy billing)", enabled=False, ) scope = await _scope( db_session, integration, external_id="ks-tidbcloud-dev", scope_type=SCOPE_TYPE_LEGACY_BILLING_UNIT, ) with pytest.raises(AppException): _refuse_ungrantable_scope_type(scope) @pytest.mark.asyncio async def test_employee_directory_hides_them_too( db_session: AsyncSession, client, employee_user, ) -> None: """The employee-facing directory must agree with the admin list. ``/cmdb/scopes`` has hidden ``legacy_billing_unit`` by default since migration 466, but ``/my-subscriptions?include_requestable=true`` (我的云订阅) filtered only on ``!= standalone_iam`` — so the same owner filter answered **7** for a global_admin and **12** for an employee on the same person (prod, 2026-09-07: 5 legacy units + one closed row the directory already drops). Worse than a count mismatch: those rows carry ``environment='dev'``, so ``requestable`` came back true and the employee got a 快速申请 button on a scope ``_refuse_ungrantable_scope_type`` rejects *after* they tap it. ``azure_resource_group`` is the same class of row (RG name where a subscription GUID belongs) and is covered by the same predicate. """ from app.domains.cmdb.models import SCOPE_TYPE_AZURE_RESOURCE_GROUP from app.identity.person_models import IdentityPerson _, token = employee_user owner = IdentityPerson( id=uuid.uuid4(), work_email=f"owner-{uuid.uuid4().hex[:6]}@pingcap.com", display_name="Owner", employment_status="active", source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:8]}", ) db_session.add(owner) await db_session.commit() integration = await _integration( db_session, name="KSCloud (legacy billing)", enabled=False, ) real = await _integration(db_session, name="腾讯云") ungrantable = [ await _scope( db_session, integration, external_id="ks-default", scope_type=SCOPE_TYPE_LEGACY_BILLING_UNIT, ), await _scope( db_session, integration, external_id="helix-aoai-rg", scope_type=SCOPE_TYPE_AZURE_RESOURCE_GROUP, ), ] grantable = await _scope( db_session, real, external_id="100002610019", scope_type="aliyun_account", ) for row in [*ungrantable, grantable]: row.owner_person_id = str(owner.id) await db_session.commit() resp = await client.get( "/api/v1/cmdb/my-subscriptions?include_requestable=true", headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200, resp.text ids = [r["external_id"] for r in resp.json()["data"]] # The real sub-account still surfaces — this is a type filter, not a # blanket narrowing of what employees may discover. assert "100002610019" in ids assert "ks-default" not in ids assert "helix-aoai-rg" not in ids @pytest.mark.asyncio async def test_scope_list_hides_them_by_default_and_can_show_them( db_session: AsyncSession, client, admin_headers: dict[str, str], ) -> None: """Hidden, not deleted — the bills are still meant to be imported, so an admin has to be able to find these rows on purpose.""" integration = await _integration( db_session, name="KSCloud (legacy billing)", enabled=False, ) await _scope( db_session, integration, external_id="ks-default", scope_type=SCOPE_TYPE_LEGACY_BILLING_UNIT, ) real = await _integration(db_session, name="腾讯云") await _scope(db_session, real, external_id="100002610019") default = await client.get( "/api/v1/cmdb/scopes?page_size=200", headers=admin_headers, ) ids = [r["external_id"] for r in default.json()["data"]] assert "100002610019" in ids assert "ks-default" not in ids included = await client.get( "/api/v1/cmdb/scopes?page_size=200&include_legacy_billing=true", headers=admin_headers, ) ids2 = [r["external_id"] for r in included.json()["data"]] assert "ks-default" in ids2 @pytest.mark.asyncio async def test_owner_confirmation_never_asks_about_one( db_session: AsyncSession, ) -> None: """30 of the 37 imported rows carry an owner. Asking them to confirm access on a cost-allocation row is a card nobody can act on.""" from app.domains.cmdb.owner_confirmation_service import ( OwnerConfirmationService, ) from app.identity.person_models import IdentityPerson owner = IdentityPerson( id=uuid.uuid4(), work_email=f"owner-{uuid.uuid4().hex[:6]}@pingcap.com", display_name="Owner", employment_status="active", source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:8]}", ) db_session.add(owner) await db_session.commit() integration = await _integration( db_session, name="UCloud (legacy billing)", enabled=False, ) scope = await _scope( db_session, integration, external_id="uc-legacy-1", scope_type=SCOPE_TYPE_LEGACY_BILLING_UNIT, ) scope.owner_person_id = str(owner.id) await db_session.commit() service = OwnerConfirmationService(db_session) due = await service.list_scopes_due_semiannual() assert all(str(s.id) != str(scope.id) for s in due)