""" Unit tests for tenant resolution logic. Tests TNNT-02: Inbound messages are resolved to the correct tenant via channel metadata. These tests verify the resolution logic in isolation — no live database needed. The production resolver queries channel_connections; here we mock that lookup. """ from __future__ import annotations import uuid from typing import Optional import pytest from shared.models.message import ChannelType # --------------------------------------------------------------------------- # Minimal in-process tenant resolver for unit testing # --------------------------------------------------------------------------- class ChannelConnectionRecord: """Represents a row from the channel_connections table.""" def __init__(self, tenant_id: uuid.UUID, channel_type: ChannelType, workspace_id: str) -> None: self.tenant_id = tenant_id self.channel_type = channel_type self.workspace_id = workspace_id def resolve_tenant( workspace_id: str, channel_type: ChannelType, connections: list[ChannelConnectionRecord], ) -> Optional[uuid.UUID]: """ Resolve a (workspace_id, channel_type) pair to a tenant_id. This mirrors the logic in packages/router/tenant.py. Returns None if no matching connection is found. """ for conn in connections: if conn.workspace_id == workspace_id and conn.channel_type == channel_type: return conn.tenant_id return None # --------------------------------------------------------------------------- # Test fixtures # --------------------------------------------------------------------------- @pytest.fixture def tenant_a_id() -> uuid.UUID: return uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") @pytest.fixture def tenant_b_id() -> uuid.UUID: return uuid.UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb") @pytest.fixture def connections(tenant_a_id: uuid.UUID, tenant_b_id: uuid.UUID) -> list[ChannelConnectionRecord]: return [ ChannelConnectionRecord(tenant_a_id, ChannelType.SLACK, "T-WORKSPACE-A"), ChannelConnectionRecord(tenant_b_id, ChannelType.SLACK, "T-WORKSPACE-B"), ChannelConnectionRecord(tenant_b_id, ChannelType.TELEGRAM, "tg-chat-12345"), ] # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestTenantResolution: """Tests for tenant resolution from channel workspace IDs.""" def test_slack_workspace_resolves_to_correct_tenant( self, connections: list[ChannelConnectionRecord], tenant_a_id: uuid.UUID, ) -> None: """Known Slack workspace_id must resolve to the correct tenant.""" result = resolve_tenant("T-WORKSPACE-A", ChannelType.SLACK, connections) assert result == tenant_a_id def test_second_slack_workspace_resolves_independently( self, connections: list[ChannelConnectionRecord], tenant_b_id: uuid.UUID, ) -> None: """Two different Slack workspaces must resolve to their respective tenants.""" result = resolve_tenant("T-WORKSPACE-B", ChannelType.SLACK, connections) assert result == tenant_b_id def test_unknown_workspace_id_returns_none( self, connections: list[ChannelConnectionRecord], ) -> None: """Unknown workspace_id must return None — not raise, not return wrong tenant.""" result = resolve_tenant("T-UNKNOWN", ChannelType.SLACK, connections) assert result is None def test_wrong_channel_type_does_not_match( self, connections: list[ChannelConnectionRecord], ) -> None: """Workspace ID from wrong channel type must not match.""" # T-WORKSPACE-A is registered as SLACK — should not match TELEGRAM result = resolve_tenant("T-WORKSPACE-A", ChannelType.TELEGRAM, connections) assert result is None def test_telegram_workspace_resolves_correctly( self, connections: list[ChannelConnectionRecord], tenant_b_id: uuid.UUID, ) -> None: """Telegram channel connections resolve independently from Slack.""" result = resolve_tenant("tg-chat-12345", ChannelType.TELEGRAM, connections) assert result == tenant_b_id def test_empty_connections_returns_none(self) -> None: """Empty connection list must return None for any workspace.""" result = resolve_tenant("T-ANY", ChannelType.SLACK, []) assert result is None def test_resolution_is_channel_type_specific( self, connections: list[ChannelConnectionRecord], ) -> None: """ The same workspace_id string registered on two different channel types must only match the correct channel type. This prevents a Slack workspace ID from accidentally matching a Mattermost workspace with the same string value. """ same_id_connections = [ ChannelConnectionRecord( uuid.UUID("cccccccc-cccc-cccc-cccc-cccccccccccc"), ChannelType.SLACK, "SHARED-ID", ), ChannelConnectionRecord( uuid.UUID("dddddddd-dddd-dddd-dddd-dddddddddddd"), ChannelType.MATTERMOST, "SHARED-ID", ), ] slack_tenant = resolve_tenant("SHARED-ID", ChannelType.SLACK, same_id_connections) mm_tenant = resolve_tenant("SHARED-ID", ChannelType.MATTERMOST, same_id_connections) assert slack_tenant != mm_tenant assert str(slack_tenant) == "cccccccc-cccc-cccc-cccc-cccccccccccc" assert str(mm_tenant) == "dddddddd-dddd-dddd-dddd-dddddddddddd"