""" Unit tests for talkgroup name resolution. From the 2026-08-23 correlation dump: 84 of 100 incidents were titled "Ems — TGID 9048" rather than "Ems — Ossining Police Dispatch", because /upload took `talkgroup_name` from a multipart form field the node only fills when OP25 already had the name — and never fell back to the system config the way mqtt_handler's call_start path did. server-26#34. resolve() is the single implementation both paths now share. These tests pin its preference order, since the whole bug was one caller skipping a step. """ import pytest from unittest.mock import AsyncMock, patch from app.internal import talkgroups SYSTEM = { "config": { "talkgroups": [ {"id": 9048, "name": "Ossining - Police Dispatch"}, {"id": 9600, "name": "MTA PD Districts 6/7/11 - Police Dispatch"}, {"id": 9563, "name": ""}, # present but unnamed {"id": "9211", "name": "Ardsley"}, # id stored as a string ] } } def _system(doc=SYSTEM): return patch.object(talkgroups.fstore, "doc_get_cached", AsyncMock(return_value=doc)) @pytest.mark.asyncio async def test_hint_wins_over_everything(): """OP25 knew the name — no Firestore read at all.""" with patch.object(talkgroups.fstore, "doc_get_cached", AsyncMock()) as m: got = await talkgroups.resolve("sys-1", 9048, hint="Whatever OP25 Said") assert got == "Whatever OP25 Said" m.assert_not_awaited() @pytest.mark.asyncio async def test_call_doc_used_before_system_config(): """The call document already carries the name written at call_start.""" with patch.object(talkgroups.fstore, "doc_get_cached", AsyncMock()) as m: got = await talkgroups.resolve( "sys-1", 9048, hint=None, call_doc={"talkgroup_name": "From Call Doc"} ) assert got == "From Call Doc" m.assert_not_awaited() @pytest.mark.asyncio async def test_falls_back_to_system_config(): """The case that was broken: nothing upstream knew the name, C2 did.""" with _system(): got = await talkgroups.resolve("sys-1", 9048, hint=None, call_doc={}) assert got == "Ossining - Police Dispatch" @pytest.mark.asyncio async def test_empty_call_doc_name_does_not_block_the_lookup(): """A falsy talkgroup_name on the call doc must not short-circuit.""" with _system(): got = await talkgroups.resolve( "sys-1", 9600, hint=None, call_doc={"talkgroup_name": ""} ) assert got == "MTA PD Districts 6/7/11 - Police Dispatch" @pytest.mark.asyncio async def test_string_talkgroup_ids_in_config_still_match(): with _system(): assert await talkgroups.resolve("sys-1", 9211) == "Ardsley" @pytest.mark.asyncio @pytest.mark.parametrize( "system_id, tgid", [(None, 9048), ("sys-1", None), (None, None)], ) async def test_missing_inputs_return_none_without_reading(system_id, tgid): with patch.object(talkgroups.fstore, "doc_get_cached", AsyncMock()) as m: assert await talkgroups.resolve(system_id, tgid) is None m.assert_not_awaited() @pytest.mark.asyncio async def test_unknown_talkgroup_returns_none_so_caller_keeps_tgid_fallback(): with _system(): assert await talkgroups.resolve("sys-1", 1234) is None @pytest.mark.asyncio async def test_named_entry_with_empty_string_returns_none(): """An entry that exists but has no name is not a name.""" with _system(): assert await talkgroups.resolve("sys-1", 9563) is None @pytest.mark.asyncio async def test_missing_system_document_returns_none(): with _system(doc=None): assert await talkgroups.resolve("sys-1", 9048) is None @pytest.mark.asyncio async def test_unparseable_talkgroup_id_returns_none(): with _system(): assert await talkgroups.resolve("sys-1", "not-a-number") is None