diff --git a/bot/chorus_bot/backend_client.py b/bot/chorus_bot/backend_client.py index 96bc6d8..6932136 100644 --- a/bot/chorus_bot/backend_client.py +++ b/bot/chorus_bot/backend_client.py @@ -14,6 +14,7 @@ class BackendClient: captured_at: datetime, discord_message_id: str | None, sender_id: str | None = None, + direction: str = "inbound", ) -> dict: payload = { "modality": modality, @@ -21,6 +22,7 @@ class BackendClient: "captured_at": captured_at.isoformat(), "discord_message_id": discord_message_id, "sender_id": sender_id, + "direction": direction, } async with httpx.AsyncClient() as client: resp = await client.post(f"{self._base_url}/items", json=payload, timeout=10) diff --git a/bot/chorus_bot/cogs/manual_share.py b/bot/chorus_bot/cogs/manual_share.py index 8741d2a..f5e9c90 100644 --- a/bot/chorus_bot/cogs/manual_share.py +++ b/bot/chorus_bot/cogs/manual_share.py @@ -1,6 +1,6 @@ import discord from discord.ext import commands -from chorus_bot.modality_map import modality_for_channel +from chorus_bot.modality_map import modality_for_channel, direction_for_modality from chorus_bot.ping import build_ping_embed @@ -32,6 +32,7 @@ class ManualShareCog(commands.Cog): snippet=snippet, raw_content=raw_content, captured_at=message.created_at, + direction=direction_for_modality(modality), ) inbox = self.bot.get_channel(self.config.inbox_channel_id) diff --git a/bot/chorus_bot/cogs/thread_ingest.py b/bot/chorus_bot/cogs/thread_ingest.py index 717c608..6278618 100644 --- a/bot/chorus_bot/cogs/thread_ingest.py +++ b/bot/chorus_bot/cogs/thread_ingest.py @@ -31,6 +31,7 @@ class ThreadIngestCog(commands.Cog): captured_at=parsed["captured_at"], discord_message_id=str(thread.id), sender_id=parsed["sender_id"], + direction=parsed["direction"], ) await thread.send( "Logged in Chorus - open the app to fill in details and track this one." diff --git a/bot/tests/test_manual_share.py b/bot/tests/test_manual_share.py new file mode 100644 index 0000000..3ca7747 --- /dev/null +++ b/bot/tests/test_manual_share.py @@ -0,0 +1,51 @@ +from unittest.mock import AsyncMock, MagicMock +import pytest +import discord +from chorus_bot.cogs.manual_share import ManualShareCog +from chorus_bot.ping import parse_ping_embed + + +class _FakeConfig: + inbox_channel_id = 111 + triage_role_id = 222 + + +def _make_message(*, channel_name, content="hello", author_bot=False, channel_id=999): + message = MagicMock(spec=discord.Message) + message.author.bot = author_bot + message.channel.id = channel_id + message.channel.name = channel_name + message.content = content + message.created_at = __import__("datetime").datetime(2026, 7, 13, 7, 0, 0) + return message + + +@pytest.mark.asyncio +async def test_leads_found_channel_forwards_with_outbound_direction(): + bot = MagicMock() + inbox = AsyncMock() + bot.get_channel.return_value = inbox + cog = ManualShareCog(bot, _FakeConfig()) + + message = _make_message(channel_name="leads-found", content="Neighbor posting about book giveaway") + await cog.on_message(message) + + sent_embed = inbox.send.call_args.kwargs["embed"] + parsed = parse_ping_embed(sent_embed) + assert parsed["direction"] == "outbound" + assert parsed["modality"] == "lead_found" + + +@pytest.mark.asyncio +async def test_existing_modality_channel_still_forwards_with_inbound_direction(): + bot = MagicMock() + inbox = AsyncMock() + bot.get_channel.return_value = inbox + cog = ManualShareCog(bot, _FakeConfig()) + + message = _make_message(channel_name="voice", content="call about donation") + await cog.on_message(message) + + sent_embed = inbox.send.call_args.kwargs["embed"] + parsed = parse_ping_embed(sent_embed) + assert parsed["direction"] == "inbound" diff --git a/bot/tests/test_thread_ingest.py b/bot/tests/test_thread_ingest.py new file mode 100644 index 0000000..05b439d --- /dev/null +++ b/bot/tests/test_thread_ingest.py @@ -0,0 +1,37 @@ +from unittest.mock import AsyncMock, MagicMock +import pytest +import discord +from chorus_bot.cogs.thread_ingest import ThreadIngestCog +from chorus_bot.ping import build_ping_embed + + +class _FakeConfig: + inbox_channel_id = 111 + + +@pytest.mark.asyncio +async def test_thread_create_passes_direction_to_backend_client(): + bot = MagicMock() + backend_client = AsyncMock() + cog = ThreadIngestCog(bot, _FakeConfig(), backend_client) + + embed = build_ping_embed( + modality="lead_found", sender_id=None, snippet="found post", + raw_content="Someone posting about book donation", + captured_at=__import__("datetime").datetime(2026, 7, 13, 7, 0, 0, + tzinfo=__import__("datetime").timezone.utc), + direction="outbound", + ) + parent_message = MagicMock() + parent_message.embeds = [embed] + + thread = MagicMock(spec=discord.Thread) + thread.id = 555 + thread.parent_id = 111 + thread.parent.fetch_message = AsyncMock(return_value=parent_message) + thread.send = AsyncMock() + + await cog.on_thread_create(thread) + + backend_client.create_item.assert_awaited_once() + assert backend_client.create_item.call_args.kwargs["direction"] == "outbound"