"""Who receives the bounty desk's alerts — configurable, defaulting to 安全员. Before this, the audience was hardcoded to ``global_admin`` + ``security_officer``: in production that is eight people when four of them do the work, so every researcher chase interrupted four people who cannot action it. An audience wider than the people who act on it is how a channel gets muted. """ from __future__ import annotations import uuid import pytest from sqlalchemy import select from app.auth.models import AuthRole, AuthUserRole from app.domains.bounty import config_service as cfg from app.domains.bounty import notify as bnotify from app.identity.person_models import IdentityPerson pytestmark = pytest.mark.asyncio BASE = "/api/v1/bounty" async def _person(db, email, *, role=None, status="active"): person = IdentityPerson( id=uuid.uuid4(), work_email=email, display_name=email.split("@")[0], employment_status=status, source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:8]}", ) db.add(person) await db.flush() if role: role_id = (await db.execute( select(AuthRole.id).where(AuthRole.name == role), )).scalar_one_or_none() if role_id is None: role_id = uuid.uuid4() db.add(AuthRole(id=role_id, name=role, is_system=True)) await db.flush() db.add(AuthUserRole(user_id=person.id, role_id=role_id)) await db.flush() return person async def test_the_default_audience_is_the_security_officers_only(db_session): """安全员 IS the desk. A global admin seat is not a reason to receive every researcher chase — that widened the audience to eight in production for work four people do.""" officer = await _person(db_session, "officer@test.com", role="security_officer") await _person(db_session, "itadmin@test.com", role="global_admin") people = await bnotify._security_operators(db_session) emails = {p.work_email for p in people} assert "officer@test.com" in emails assert "itadmin@test.com" not in emails, ( "global_admin is no longer in the default audience" ) assert officer.id in {p.id for p in people} async def test_a_configured_list_replaces_the_role_default(db_session): """Naming people in 配置 is the whole point: the audience is an operational choice, not a property of the RBAC model.""" await _person(db_session, "officer2@test.com", role="security_officer") named = await _person(db_session, "named@test.com") await cfg.update_alert_recipients(db_session, [named.id], actor_id=None) people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["named@test.com"], ( "the configured list wins outright — including over the role" ) async def test_an_empty_list_means_the_default_not_nobody(db_session): """A config surface whose empty state silences the channel is how a queue goes unwatched. Muting is done by disabling the poll job.""" await _person(db_session, "officer3@test.com", role="security_officer") await cfg.update_alert_recipients(db_session, [], actor_id=None) people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["officer3@test.com"] async def test_a_recipient_who_left_is_dropped(db_session): """An offboarded person's row must not absorb an alert — same rule the role path already applied.""" await _person(db_session, "officer4@test.com", role="security_officer") staying = await _person(db_session, "staying@test.com") leaver = await _person(db_session, "gone@test.com", status="left") await cfg.update_alert_recipients( db_session, [leaver.id, staying.id], actor_id=None, ) people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["staying@test.com"] async def test_when_every_named_recipient_has_left_the_role_takes_over( db_session, ): """"The configured people are all gone" and "nobody should be told" are different states, and only one of them is ever intended. Falling through to the role keeps the desk reachable.""" await _person(db_session, "officer5@test.com", role="security_officer") leaver = await _person(db_session, "gone2@test.com", status="left") await cfg.update_alert_recipients(db_session, [leaver.id], actor_id=None) people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["officer5@test.com"], ( "a list that resolves to nobody must not silence the channel" ) async def test_the_configured_order_is_preserved(db_session): """The console lists them in the order they were named; the send order has to agree, or a reader comparing the two wonders what got dropped.""" a = await _person(db_session, "a@test.com") b = await _person(db_session, "b@test.com") c = await _person(db_session, "c@test.com") await cfg.update_alert_recipients(db_session, [c.id, a.id, b.id], actor_id=None) people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["c@test.com", "a@test.com", "b@test.com"] async def test_the_endpoint_reports_who_actually_gets_told( client, db_session, admin_headers, ): """Resolved people, not stored ids: a stored id whose person has left answers "who gets told" wrongly, and an empty box reads as "nobody".""" await _person(db_session, "officer6@test.com", role="security_officer") await db_session.commit() res = await client.get(f"{BASE}/config/alert-recipients", headers=admin_headers) assert res.status_code == 200, res.text data = res.json()["data"] assert data["is_default"] is True assert data["default_roles"] == ["security_officer"] assert "officer6@test.com" in {r["email"] for r in data["recipients"]} named = (await db_session.execute(select(IdentityPerson).where( IdentityPerson.work_email == "officer6@test.com"))).scalars().one() res = await client.put( f"{BASE}/config/alert-recipients", json={"person_ids": [str(named.id)]}, headers=admin_headers, ) assert res.status_code == 200, res.text data = res.json()["data"] assert data["is_default"] is False assert data["configured_count"] == 1 async def test_a_malformed_stored_id_does_not_take_the_list_down(db_session): """Hand-edited settings happen. One bad id must cost that id, not the whole desk's alerts.""" officer = await _person(db_session, "officer7@test.com", role="security_officer") good = await _person(db_session, "good@test.com") await cfg._put_setting( db_session, "bounty.alert_recipients", {"person_ids": ["not-a-uuid", str(good.id)]}, actor_id=None, ) assert await cfg.get_alert_recipient_ids(db_session) == [good.id] people = await bnotify._security_operators(db_session) assert [p.work_email for p in people] == ["good@test.com"] assert officer is not None