""" Unit tests for cross-node duplicate detection. Fixture timings come from real production data: node-002 and node-PI-2 both recorded TG 9048 on 2026-08-16, starting ~1.1s apart. """ import pytest from datetime import datetime, timezone, timedelta from app.internal.dedup import _parse_dt, _is_canonical, find_duplicate_of BASE = datetime(2026, 8, 16, 19, 31, 46, tzinfo=timezone.utc) def _query_returning(*calls): """Stand-in for fstore.collection_where.""" async def _q(_collection, _conditions): return list(calls) return _q def _query_raising(exc): async def _q(_collection, _conditions): raise exc return _q def _call(call_id, node_id, offset_seconds=0.0, talkgroup_id=9048, **extra): return { "call_id": call_id, "node_id": node_id, "system_id": "sys-1", "talkgroup_id": talkgroup_id, "started_at": BASE + timedelta(seconds=offset_seconds), **extra, } # --------------------------------------------------------------------------- # Timestamp parsing — Firestore returns three different shapes # --------------------------------------------------------------------------- def test_parse_dt_accepts_aware_datetime(): assert _parse_dt(BASE) == BASE def test_parse_dt_assumes_utc_for_naive_datetime(): naive = datetime(2026, 8, 16, 19, 31, 46) assert _parse_dt(naive) == BASE def test_parse_dt_accepts_iso_string_with_z(): assert _parse_dt("2026-08-16T19:31:46Z") == BASE def test_parse_dt_returns_none_for_junk(): assert _parse_dt("not a date") is None assert _parse_dt(None) is None # --------------------------------------------------------------------------- # Canonical selection # --------------------------------------------------------------------------- def test_earlier_start_wins(): early = _call("a", "node-002", 0.0) late = _call("b", "node-PI-2", 1.1) assert _is_canonical(early, [late]) is True assert _is_canonical(late, [early]) is False def test_identical_starts_break_tie_on_call_id(): first = _call("aaa", "node-002", 0.0) second = _call("bbb", "node-PI-2", 0.0) assert _is_canonical(first, [second]) is True assert _is_canonical(second, [first]) is False def test_both_nodes_reach_the_same_verdict(): """The whole point: the decision must not depend on upload order.""" a = _call("a", "node-002", 0.0) b = _call("b", "node-PI-2", 1.1) verdicts = [_is_canonical(a, [b]), _is_canonical(b, [a])] assert verdicts.count(True) == 1, "exactly one recording must be canonical" # --------------------------------------------------------------------------- # find_duplicate_of # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_returns_canonical_id_for_later_recording(): canonical = _call("canon", "node-002", 0.0) later = _call("later", "node-PI-2", 1.1) q = _query_returning(canonical, later) assert await find_duplicate_of(later, query=q) == "canon" @pytest.mark.asyncio async def test_returns_none_for_the_canonical_recording(): canonical = _call("canon", "node-002", 0.0) later = _call("later", "node-PI-2", 1.1) q = _query_returning(canonical, later) assert await find_duplicate_of(canonical, query=q) is None @pytest.mark.asyncio async def test_same_node_is_never_a_duplicate(): """Back-to-back transmissions from one node are real, separate calls.""" first = _call("a", "node-002", 0.0) second = _call("b", "node-002", 2.0) q = _query_returning(first, second) assert await find_duplicate_of(second, query=q) is None @pytest.mark.asyncio async def test_different_talkgroup_is_not_a_duplicate(): other_tg = _call("a", "node-002", 0.0, talkgroup_id=9600) mine = _call("b", "node-PI-2", 1.0, talkgroup_id=9048) q = _query_returning(other_tg, mine) assert await find_duplicate_of(mine, query=q) is None @pytest.mark.asyncio async def test_never_chains_onto_another_duplicate(): """A third node must point at the original, not at a duplicate of it.""" canonical = _call("canon", "node-002", 0.0) already_dupe = _call("dupe", "node-PI-2", 0.5, duplicate_of="canon") third = _call("third", "node-003", 1.0) q = _query_returning(canonical, already_dupe, third) assert await find_duplicate_of(third, query=q) == "canon" @pytest.mark.asyncio async def test_no_match_returns_none(): lonely = _call("only", "node-002", 0.0) q = _query_returning(lonely) assert await find_duplicate_of(lonely, query=q) is None @pytest.mark.asyncio async def test_missing_identifiers_skip_the_check(): called = False async def _q(_collection, _conditions): nonlocal called called = True return [] incomplete = {"call_id": "x", "node_id": "node-002", "started_at": BASE} assert await find_duplicate_of(incomplete, query=_q) is None assert called is False, "must bail out before querying" @pytest.mark.asyncio async def test_query_failure_never_blocks_the_upload(): call = _call("a", "node-002", 0.0) q = _query_raising(RuntimeError("firestore down")) assert await find_duplicate_of(call, query=q) is None