from __future__ import annotations import inspect import uuid import pytest from app.domains.data_access import constants as c from app.domains.data_access.models import ( OWNER_RESOLUTION_FALLBACK, OWNER_RESOLUTION_RESOLVED, OWNER_RESOLUTION_UNRESOLVED, OWNER_SOURCE_BOOTSTRAP, RESOURCE_STATUS_ACTIVE, RESOURCE_STATUS_SUSPENDED, DataAccessResource, ) from app.domains.data_access.service import ( DataAccessResourceValidationError, DataAccessService, ) from app.integrations.models import Integration async def _integration_id(db_session) -> str: row = Integration( kind=c.DOMAIN_DATA_ACCESS, provider=c.PROVIDER_BIGQUERY, name="Data Access BigQuery", slug=f"dax-bq-{uuid.uuid4().hex[:8]}", enabled=True, is_primary=False, ) db_session.add(row) await db_session.flush() return str(row.id) def _resource( *, integration_id: str, provider: str = c.PROVIDER_BIGQUERY, resource_type: str = c.RESOURCE_TYPE_DATASET, project_id: str = "helix-data-prod", dataset_id: str = "finance_2026", requestable: bool = True, status: str = RESOURCE_STATUS_ACTIVE, owner_person_id: str | None = None, owner_resolution_status: str = OWNER_RESOLUTION_UNRESOLVED, ) -> DataAccessResource: return DataAccessResource( integration_id=integration_id, provider=provider, resource_type=resource_type, project_id=project_id, dataset_id=dataset_id, display_name=f"{project_id}.{dataset_id}", requestable=requestable, status=status, owner_person_id=owner_person_id, owner_resolution_status=owner_resolution_status, owner_source=OWNER_SOURCE_BOOTSTRAP, source_version="bootstrap-2026-06-10", source_etag=f"etag-{dataset_id}", ) async def _add_resource( db_session, *, dataset_id: str, **kwargs, ) -> DataAccessResource: integration_id = await _integration_id(db_session) resource = _resource( integration_id=integration_id, dataset_id=dataset_id, **kwargs, ) db_session.add(resource) await db_session.flush() return resource async def _assert_rejected( db_session, resource_id: uuid.UUID, reason: str, *, require_resource_owner_authority: bool = False, ) -> DataAccessResourceValidationError: with pytest.raises(DataAccessResourceValidationError) as exc_info: await DataAccessService().validate_requestable_bigquery_dataset_resource( db_session, resource_id, require_resource_owner_authority=require_resource_owner_authority, ) exc = exc_info.value assert exc.reason == reason assert exc.code == f"DATA_ACCESS_{reason.upper()}" assert exc.field == "resource_id" assert exc.status_code == 422 return exc async def test_validator_accepts_active_requestable_bigquery_dataset( db_session, employee_user, ) -> None: owner, _ = employee_user resource = await _add_resource( db_session, dataset_id="finance_valid", owner_person_id=str(owner.id), owner_resolution_status=OWNER_RESOLUTION_RESOLVED, ) result = await DataAccessService().validate_requestable_bigquery_dataset_resource( db_session, resource.id, require_resource_owner_authority=True, ) assert result.resource_id == resource.id assert result.provider == c.PROVIDER_BIGQUERY assert result.resource_type == c.RESOURCE_TYPE_DATASET assert result.project_id == "helix-data-prod" assert result.dataset_id == "finance_valid" assert result.owner_person_id == str(owner.id) assert result.owner_resolution_status == OWNER_RESOLUTION_RESOLVED assert result.owner_authority_enabled is True assert result.source_version == "bootstrap-2026-06-10" assert result.source_etag == "etag-finance_valid" async def test_validator_rejects_non_federated_resource(db_session) -> None: resource_id = uuid.uuid4() exc = await _assert_rejected( db_session, resource_id, "non_federated_resource", ) assert exc.resource_id == resource_id async def test_validator_rejects_non_bigquery_provider(db_session) -> None: resource = await _add_resource( db_session, dataset_id="finance_wrong_provider", provider="snowflake", ) await _assert_rejected(db_session, resource.id, "unsupported_provider") async def test_validator_rejects_table_resources_before_feishu( db_session, ) -> None: resource = await _add_resource( db_session, dataset_id="finance_table", resource_type="table", ) await _assert_rejected(db_session, resource.id, "unsupported_resource_type") async def test_validator_rejects_inactive_resources_before_feishu( db_session, ) -> None: resource = await _add_resource( db_session, dataset_id="finance_inactive", status=RESOURCE_STATUS_SUSPENDED, ) await _assert_rejected(db_session, resource.id, "inactive_resource") async def test_validator_rejects_non_requestable_resources_before_feishu( db_session, ) -> None: resource = await _add_resource( db_session, dataset_id="finance_hidden", requestable=False, ) await _assert_rejected(db_session, resource.id, "resource_not_requestable") @pytest.mark.parametrize( ("owner_resolution_status", "owner_person_id"), [ (OWNER_RESOLUTION_UNRESOLVED, None), (OWNER_RESOLUTION_FALLBACK, None), (OWNER_RESOLUTION_RESOLVED, None), ], ) async def test_validator_fails_closed_when_resource_owner_decision_required( db_session, owner_resolution_status: str, owner_person_id: str | None, ) -> None: resource = await _add_resource( db_session, dataset_id=f"finance_{owner_resolution_status}", owner_person_id=owner_person_id, owner_resolution_status=owner_resolution_status, ) await _assert_rejected( db_session, resource.id, "resource_owner_authority_unavailable", require_resource_owner_authority=True, ) def test_validator_uses_projected_resource_owner_authority_only() -> None: source = inspect.getsource( DataAccessService.validate_requestable_bigquery_dataset_resource, ) assert "resource_owner_authority_enabled" in source assert c.RUNTIME_OWNER_AUTHORITY_FIELD in source for field in c.FORBIDDEN_RUNTIME_OWNER_AUTHORITY_FIELDS: assert field not in source