"""Self-service alias list / delete endpoints + admin variants. The Google Workspace driver is patched: tests assert RBAC + DB side-effects without hitting Google. Three flows tested: 1. Employee can list and delete their own aliases. 2. Employee CANNOT delete someone else's alias (403). 3. Admin (HR / global) CAN delete anyone's alias. 4. ``editable=False`` aliases (Google nonEditableAliases) are surfaced but rejected for deletion with code ``ALIAS_NOT_EDITABLE``. """ from __future__ import annotations from unittest.mock import AsyncMock, patch import pytest from httpx import AsyncClient from app.identity.person_models import IdentityPerson @pytest.fixture(autouse=True) def patch_google_driver(): """Replace the EmailGroupService.get_user_profile + remove_alias hooks used by the alias-list / delete paths. The driver itself is constructed lazily; we patch ``_get_driver`` on EmailGroupService to return an AsyncMock with the methods the alias surface uses. """ fake_driver = AsyncMock() fake_driver.get_user_profile.return_value = { "primaryEmail": "shuai.feng@pingcap.com", "aliases": ["shuai.f@pingcap.com", "sfeng@pingcap.com"], "nonEditableAliases": ["shuai.feng@pingcap-c.com"], } fake_driver.remove_alias.return_value = type( "R", (), {"ok": True, "data": {"already_absent": False}, "error_message": None}, )() with patch( "app.domains.email_group.service.EmailGroupService._get_driver", return_value=fake_driver, ): yield fake_driver @pytest.mark.asyncio async def test_employee_lists_own_aliases( client: AsyncClient, db_session, employee_user, ): user, token = employee_user # Pre-existing identity_persons row uses the conftest stock value; # set work_email so the Google lookup matches. user_row = await db_session.get(IdentityPerson, user.id) user_row.work_email = "shuai.feng@pingcap.com" await db_session.commit() r = await client.get( "/api/v1/me/email-aliases", headers={"Authorization": f"Bearer {token}"}, ) assert r.status_code == 200, r.text data = r.json()["data"] assert data["primary_email"] == "shuai.feng@pingcap.com" aliases = {(a["alias_email"], a["editable"]) for a in data["aliases"]} assert ("shuai.f@pingcap.com", True) in aliases assert ("sfeng@pingcap.com", True) in aliases assert ("shuai.feng@pingcap-c.com", False) in aliases @pytest.mark.asyncio async def test_employee_deletes_own_alias( client: AsyncClient, db_session, employee_user, patch_google_driver, ): user, token = employee_user user_row = await db_session.get(IdentityPerson, user.id) user_row.work_email = "shuai.feng@pingcap.com" await db_session.commit() r = await client.request( "DELETE", "/api/v1/me/email-aliases", headers={"Authorization": f"Bearer {token}"}, json={"alias_email": "shuai.f@pingcap.com"}, ) assert r.status_code == 200, r.text body = r.json()["data"] assert body["alias_email"] == "shuai.f@pingcap.com" # Driver was actually called. assert patch_google_driver.remove_alias.await_count == 1 @pytest.mark.asyncio async def test_delete_non_editable_alias_rejected( client: AsyncClient, db_session, employee_user, ): user, token = employee_user user_row = await db_session.get(IdentityPerson, user.id) user_row.work_email = "shuai.feng@pingcap.com" await db_session.commit() r = await client.request( "DELETE", "/api/v1/me/email-aliases", headers={"Authorization": f"Bearer {token}"}, json={"alias_email": "shuai.feng@pingcap-c.com"}, ) assert r.status_code == 409 assert r.json()["errors"][0]["code"] == "ALIAS_NOT_EDITABLE" @pytest.mark.asyncio async def test_employee_cannot_delete_someone_elses_alias( client: AsyncClient, db_session, employee_user, admin_user, ): _, employee_token = employee_user admin, _ = admin_user # Admin's mailbox; employee tries to delete their alias. admin_row = await db_session.get(IdentityPerson, admin.id) admin_row.work_email = "admin@test.com" await db_session.commit() r = await client.request( "DELETE", f"/api/v1/users/{admin.id}/email-aliases", headers={"Authorization": f"Bearer {employee_token}"}, json={"alias_email": "admin.alias@test.com"}, ) assert r.status_code == 403 @pytest.mark.asyncio async def test_admin_can_delete_others_alias( client: AsyncClient, db_session, employee_user, admin_user, patch_google_driver, ): employee, _ = employee_user _, admin_token = admin_user employee_row = await db_session.get(IdentityPerson, employee.id) employee_row.work_email = "shuai.feng@pingcap.com" await db_session.commit() r = await client.request( "DELETE", f"/api/v1/users/{employee.id}/email-aliases", headers={"Authorization": f"Bearer {admin_token}"}, json={ "alias_email": "shuai.f@pingcap.com", "reason": "consolidating duplicate identities", }, ) assert r.status_code == 200, r.text assert patch_google_driver.remove_alias.await_count == 1