"""Inbound dispatch + dedup tests for PhotonAdapter. These bypass the loopback HTTP stream — they call ``_dispatch_inbound`` / ``_on_inbound_line`` / ``_is_duplicate`` directly, exercising the sidecar-event parsing without spawning the Node sidecar or binding ports. """ from __future__ import annotations import json from typing import Any, Dict, List import pytest from gateway.config import Platform, PlatformConfig from gateway.platforms.base import MessageEvent, MessageType from plugins.platforms.photon.adapter import PhotonAdapter def _make_adapter(monkeypatch: pytest.MonkeyPatch) -> PhotonAdapter: monkeypatch.setenv("PHOTON_PROJECT_ID", "test-project-id") monkeypatch.setenv("PHOTON_PROJECT_SECRET", "test-project-secret") cfg = PlatformConfig(enabled=True, token="", extra={}) return PhotonAdapter(cfg) def _capture(adapter: PhotonAdapter, monkeypatch: pytest.MonkeyPatch) -> List[MessageEvent]: captured: List[MessageEvent] = [] async def fake_handle(event: MessageEvent) -> None: captured.append(event) monkeypatch.setattr(adapter, "handle_message", fake_handle) return captured def _dm_event(text: str, msg_id: str = "spc-msg-abc") -> Dict[str, Any]: return { "messageId": msg_id, "platform": "iMessage", "space": {"id": "+15551234567", "type": "dm", "phone": "+15551234567"}, "sender": {"id": "+15551234567"}, "content": {"type": "text", "text": text}, "timestamp": "2026-05-14T19:06:32.000Z", } @pytest.mark.asyncio async def test_dispatch_text_dm(monkeypatch: pytest.MonkeyPatch) -> None: adapter = _make_adapter(monkeypatch) captured = _capture(adapter, monkeypatch) await adapter._dispatch_inbound(_dm_event("hello world")) assert len(captured) == 1 event = captured[0] assert event.text == "hello world" assert event.message_type == MessageType.TEXT assert event.message_id == "spc-msg-abc" src = event.source assert src is not None assert src.platform == Platform("photon") assert src.chat_id == "+15551234567" assert src.chat_type == "dm" assert src.user_id == "+15551234567" @pytest.mark.asyncio async def test_dispatch_group_type(monkeypatch: pytest.MonkeyPatch) -> None: adapter = _make_adapter(monkeypatch) captured = _capture(adapter, monkeypatch) event = { "messageId": "spc-msg-grp", "space": {"id": "group-guid-xyz", "type": "group", "phone": None}, "sender": {"id": "+15551234567"}, "content": {"type": "text", "text": "hi group"}, "timestamp": "2026-05-14T19:06:32.000Z", } await adapter._dispatch_inbound(event) assert captured[0].source.chat_type == "group" @pytest.mark.asyncio async def test_dispatch_attachment_surfaces_marker(monkeypatch: pytest.MonkeyPatch) -> None: adapter = _make_adapter(monkeypatch) captured = _capture(adapter, monkeypatch) event = { "messageId": "spc-msg-att", "space": {"id": "+15551234567", "type": "dm", "phone": "+15551234567"}, "sender": {"id": "+15551234567"}, "content": { "type": "attachment", "name": "IMG_4127.HEIC", "mimeType": "image/heic", "size": 12345, }, "timestamp": "2026-05-14T19:06:32.000Z", } await adapter._dispatch_inbound(event) assert len(captured) == 1 assert "Photon attachment received" in captured[0].text assert "IMG_4127.HEIC" in captured[0].text assert captured[0].message_type == MessageType.PHOTO @pytest.mark.asyncio async def test_on_inbound_line_dispatches_and_dedups( monkeypatch: pytest.MonkeyPatch, ) -> None: adapter = _make_adapter(monkeypatch) captured = _capture(adapter, monkeypatch) line = json.dumps(_dm_event("ping", msg_id="dup-1")) await adapter._on_inbound_line(line) await adapter._on_inbound_line(line) # same messageId -> deduped assert len(captured) == 1 assert captured[0].text == "ping" @pytest.mark.asyncio async def test_on_inbound_line_ignores_bad_json(monkeypatch: pytest.MonkeyPatch) -> None: adapter = _make_adapter(monkeypatch) captured = _capture(adapter, monkeypatch) await adapter._on_inbound_line("{not json") assert captured == [] def test_is_duplicate_window(monkeypatch: pytest.MonkeyPatch) -> None: adapter = _make_adapter(monkeypatch) assert adapter._is_duplicate("id-1") is False assert adapter._is_duplicate("id-1") is True assert adapter._is_duplicate("id-2") is False assert adapter._is_duplicate("id-1") is True # still dup def test_check_requirements_without_node(monkeypatch: pytest.MonkeyPatch) -> None: # If no node binary on PATH the adapter should refuse to start. from plugins.platforms.photon import adapter as adapter_mod monkeypatch.setattr(adapter_mod.shutil, "which", lambda _name: None) assert adapter_mod.check_requirements() is False