"""Tests for SAMTunnelSession — TDD: tests before implementation. Uses MockSAMServer from conftest for protocol-level testing. """ import asyncio import pytest from i2p_apps.i2ptunnel.sam_session import SAMTunnelSession, SessionFactory @pytest.mark.asyncio class TestSAMTunnelSession: async def test_create_session(self, mock_sam_server): """Session creation via HELLO + SESSION CREATE.""" session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="test-session") assert session.destination is not None assert len(session.destination) > 0 await session.close() async def test_naming_lookup_found(self, mock_sam_server): mock_sam_server.add_name("forum.i2p", "B" * 516) session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="lookup-test") dest = await session.lookup("forum.i2p") assert dest == "B" * 516 await session.close() async def test_naming_lookup_not_found(self, mock_sam_server): session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="lookup-miss") dest = await session.lookup("nonexistent.i2p") assert dest is None await session.close() async def test_connect_returns_streams(self, mock_sam_server): """STREAM CONNECT returns reader/writer pair.""" session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="connect-test") reader, writer = await session.connect("C" * 516) assert reader is not None assert writer is not None writer.close() await writer.wait_closed() await session.close() async def test_connect_echo(self, mock_sam_server): """MockSAMServer echoes data after STREAM CONNECT.""" session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="echo-test") reader, writer = await session.connect("D" * 516) writer.write(b"ping") await writer.drain() # Read echo before closing — server echoes data back data = await asyncio.wait_for(reader.read(4096), timeout=2.0) assert data == b"ping" writer.close() await session.close() async def test_session_with_private_key(self, mock_sam_server): """Session creation with a private key.""" session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="key-test", private_key="E" * 516) assert session.destination is not None await session.close() async def test_close_idempotent(self, mock_sam_server): session = SAMTunnelSession(mock_sam_server.host, mock_sam_server.port) await session.open(nickname="close-test") await session.close() await session.close() # should not raise @pytest.mark.asyncio class TestSessionFactory: async def test_create_session(self, mock_sam_server): factory = SessionFactory(mock_sam_server.host, mock_sam_server.port) session = await factory.create(nickname="factory-test") assert session.destination is not None await factory.release(session) async def test_shared_session(self, mock_sam_server): """Shared sessions reuse the same SAM connection.""" factory = SessionFactory(mock_sam_server.host, mock_sam_server.port) s1 = await factory.get_shared("shared-group") s2 = await factory.get_shared("shared-group") assert s1 is s2 # same object await factory.release_shared("shared-group")