from __future__ import annotations import uuid import pytest from app.domains.data_access import constants as c from app.domains.data_access.service import ( DataAccessPrincipalValidationError, DataAccessService, ) from app.domains.email_group.models import EmailGroup def _group( *, email: str, name: str | None = None, status: str = "active", google_group_id: str | None = None, aliases: list[str] | None = None, ) -> EmailGroup: return EmailGroup( email=email, name=name or email.split("@", 1)[0], status=status, google_group_id=google_group_id, aliases=aliases, ) async def _add_group(db_session, **kwargs) -> EmailGroup: group = _group(**kwargs) db_session.add(group) await db_session.flush() return group async def _assert_rejected( db_session, principal: uuid.UUID | str, reason: str, *, principal_type: str = c.PRINCIPAL_TYPE_GOOGLE_GROUP, allow_deleted_rename_bridge: bool = True, ) -> DataAccessPrincipalValidationError: with pytest.raises(DataAccessPrincipalValidationError) as exc_info: await DataAccessService().validate_google_group_principal( db_session, principal, principal_type=principal_type, allow_deleted_rename_bridge=allow_deleted_rename_bridge, ) exc = exc_info.value assert exc.reason == reason assert exc.code == f"DATA_ACCESS_{reason.upper()}" assert exc.field == "principal" assert exc.status_code == 422 return exc async def test_validator_accepts_active_group_by_primary_email( db_session, ) -> None: group = await _add_group( db_session, email="analytics-readers@example.com", google_group_id="google-group-analytics", aliases=["analytics-alias@example.com"], ) result = await DataAccessService().validate_google_group_principal( db_session, "Analytics-Readers@Example.com", ) assert result.principal_email_group_id == group.id assert result.requested_email_group_id == group.id assert result.google_group_id == "google-group-analytics" assert result.primary_email_snapshot == "analytics-readers@example.com" assert result.provider_member == "group:analytics-readers@example.com" assert result.resolved_via == "active_primary_email" async def test_validator_resolves_alias_to_primary_group_snapshot( db_session, ) -> None: group = await _add_group( db_session, email="security-readers@example.com", google_group_id="google-group-security", aliases=["Security-Alias@Example.com"], ) result = await DataAccessService().validate_google_group_principal( db_session, "security-alias@example.com", ) assert result.principal_email_group_id == group.id assert result.primary_email_snapshot == "security-readers@example.com" assert result.provider_member == "group:security-readers@example.com" assert result.provider_member != "group:security-alias@example.com" assert result.resolved_via == "active_alias" @pytest.mark.parametrize("principal_type", ["user", "person", "service_account"]) async def test_validator_rejects_non_group_principal_types( db_session, principal_type: str, ) -> None: await _assert_rejected( db_session, "alice@example.com", "unsupported_principal_type", principal_type=principal_type, ) async def test_validator_rejects_unknown_groups(db_session) -> None: await _assert_rejected( db_session, "unknown-readers@example.com", "unknown_group", ) await _assert_rejected(db_session, uuid.uuid4(), "unknown_group") async def test_validator_rejects_deleted_group_without_active_bridge( db_session, ) -> None: deleted = await _add_group( db_session, email="old-readers@example.com", status="deleted", google_group_id="google-group-old", ) await _assert_rejected(db_session, deleted.id, "deleted_group") async def test_validator_bridges_deleted_group_to_active_same_google_group_id( db_session, ) -> None: deleted = await _add_group( db_session, email="analytics-old@example.com", status="deleted", google_group_id="google-group-rename", ) active = await _add_group( db_session, email="analytics-new@example.com", status="active", google_group_id="google-group-rename", ) result = await DataAccessService().validate_google_group_principal( db_session, deleted.id, ) assert result.requested_email_group_id == deleted.id assert result.principal_email_group_id == active.id assert result.google_group_id == "google-group-rename" assert result.primary_email_snapshot == "analytics-new@example.com" assert result.provider_member == "group:analytics-new@example.com" assert result.resolved_via == "rename_bridge" async def test_validator_rejects_deleted_group_when_bridge_mode_disabled( db_session, ) -> None: deleted = await _add_group( db_session, email="analytics-no-bridge-mode@example.com", status="deleted", google_group_id="google-group-no-bridge-mode", ) await _add_group( db_session, email="analytics-active-bridge@example.com", status="active", google_group_id="google-group-no-bridge-mode", ) await _assert_rejected( db_session, deleted.id, "deleted_group", allow_deleted_rename_bridge=False, ) async def test_validator_rejects_alias_collision(db_session) -> None: await _add_group( db_session, email="analytics-primary@example.com", google_group_id="google-group-analytics-primary", aliases=["shared-alias@example.com"], ) await _add_group( db_session, email="security-primary@example.com", google_group_id="google-group-security-primary", aliases=["shared-alias@example.com"], ) await _assert_rejected( db_session, "shared-alias@example.com", "alias_collision", ) async def test_validator_rejects_active_primary_when_alias_also_matches_other( db_session, ) -> None: await _add_group( db_session, email="platform-readers@example.com", google_group_id="google-group-platform", ) await _add_group( db_session, email="shadow-readers@example.com", google_group_id="google-group-shadow", aliases=["platform-readers@example.com"], ) await _assert_rejected( db_session, "platform-readers@example.com", "alias_collision", ) async def test_validator_rejects_duplicate_active_google_group_id( db_session, ) -> None: first = await _add_group( db_session, email="finance-readers@example.com", google_group_id="google-group-duplicate", ) await _add_group( db_session, email="finance-readers-2@example.com", google_group_id="google-group-duplicate", ) await _assert_rejected( db_session, first.id, "duplicate_active_google_group_id", ) async def test_validator_rejects_deleted_bridge_with_duplicate_active_rows( db_session, ) -> None: deleted = await _add_group( db_session, email="bridge-old@example.com", status="deleted", google_group_id="google-group-bridge-duplicate", ) await _add_group( db_session, email="bridge-new-a@example.com", google_group_id="google-group-bridge-duplicate", ) await _add_group( db_session, email="bridge-new-b@example.com", google_group_id="google-group-bridge-duplicate", ) await _assert_rejected( db_session, deleted.id, "duplicate_active_google_group_id", ) async def test_validator_rejects_missing_google_group_id_ambiguity( db_session, ) -> None: active = await _add_group( db_session, email="unsynced-readers@example.com", google_group_id=None, ) deleted = await _add_group( db_session, email="unsynced-old-readers@example.com", status="deleted", google_group_id=None, ) await _assert_rejected( db_session, active.id, "missing_google_group_id", ) await _assert_rejected( db_session, deleted.id, "missing_google_group_id", )