"""Tests for SAM v3.3 text protocol parser and formatter.""" import pytest from i2p_sam.protocol import SAMCommand, SAMReply, SUPPORTED_VERSIONS, MAX_VERSION class TestSAMCommandParse: """Tests for SAMCommand.parse().""" def test_parse_hello(self): """Parse HELLO VERSION MIN=3.0 MAX=3.3.""" cmd = SAMCommand.parse("HELLO VERSION MIN=3.0 MAX=3.3") assert cmd.verb == "HELLO" assert cmd.opcode == "VERSION" assert cmd.params["MIN"] == "3.0" assert cmd.params["MAX"] == "3.3" def test_parse_session_create(self): """Parse SESSION CREATE with multiple params.""" cmd = SAMCommand.parse("SESSION CREATE ID=test STYLE=STREAM DESTINATION=TRANSIENT") assert cmd.verb == "SESSION" assert cmd.opcode == "CREATE" assert cmd.params["ID"] == "test" assert cmd.params["STYLE"] == "STREAM" assert cmd.params["DESTINATION"] == "TRANSIENT" def test_parse_stream_connect(self): """Parse STREAM CONNECT with base64 destination.""" dest_b64 = "AAAA" * 86 + "AA" # ~346 chars, typical base64 dest cmd = SAMCommand.parse(f"STREAM CONNECT ID=test DESTINATION={dest_b64}") assert cmd.verb == "STREAM" assert cmd.opcode == "CONNECT" assert cmd.params["ID"] == "test" assert cmd.params["DESTINATION"] == dest_b64 def test_parse_ping(self): """Parse PING with data payload.""" cmd = SAMCommand.parse("PING keepalive123") assert cmd.verb == "PING" assert cmd.opcode == "keepalive123" def test_parse_quoted_values(self): """Handle KEY="value with spaces" in params.""" cmd = SAMCommand.parse('SESSION CREATE ID="my session" STYLE=STREAM DESTINATION=TRANSIENT') assert cmd.params["ID"] == "my session" assert cmd.params["STYLE"] == "STREAM" def test_parse_case_insensitive_keys(self): """Keys are normalized to uppercase.""" cmd = SAMCommand.parse("HELLO VERSION min=3.0 max=3.3") assert "MIN" in cmd.params assert "MAX" in cmd.params def test_parse_empty_raises(self): """Empty or whitespace-only input raises ValueError.""" with pytest.raises(ValueError): SAMCommand.parse("") with pytest.raises(ValueError): SAMCommand.parse(" ") def test_parse_verb_only(self): """Single-word command gets empty opcode and params.""" cmd = SAMCommand.parse("QUIT") assert cmd.verb == "QUIT" assert cmd.opcode == "" assert cmd.params == {} class TestSAMCommandStr: """Tests for SAMCommand.__str__().""" def test_command_str_roundtrip(self): """Parse then format, verify consistency.""" original = "HELLO VERSION MIN=3.0 MAX=3.3" cmd = SAMCommand.parse(original) formatted = str(cmd) # Re-parse the formatted string cmd2 = SAMCommand.parse(formatted) assert cmd2.verb == cmd.verb assert cmd2.opcode == cmd.opcode assert cmd2.params == cmd.params def test_str_no_params(self): """Format command with no params.""" cmd = SAMCommand("QUIT", "", {}) assert str(cmd).strip() == "QUIT" class TestSAMReply: """Tests for SAMReply static methods.""" def test_hello_ok(self): assert SAMReply.hello_ok() == "HELLO REPLY RESULT=OK VERSION=3.3\n" assert SAMReply.hello_ok("3.1") == "HELLO REPLY RESULT=OK VERSION=3.1\n" def test_hello_noversion(self): assert SAMReply.hello_noversion() == "HELLO REPLY RESULT=NOVERSION\n" def test_session_ok(self): reply = SAMReply.session_ok("AAAA") assert reply == "SESSION STATUS RESULT=OK DESTINATION=AAAA\n" def test_session_error(self): reply = SAMReply.session_error("DUPLICATED_ID", "Already exists") assert "RESULT=DUPLICATED_ID" in reply assert "MESSAGE=" in reply assert reply.endswith("\n") def test_stream_ok(self): assert SAMReply.stream_ok() == "STREAM STATUS RESULT=OK\n" def test_stream_error(self): reply = SAMReply.stream_error("CANT_REACH_PEER", "Timeout") assert "RESULT=CANT_REACH_PEER" in reply assert reply.endswith("\n") def test_dest_reply(self): reply = SAMReply.dest_reply("test.i2p", "AAAA") assert reply == "DEST REPLY RESULT=OK NAME=test.i2p DESTINATION=AAAA\n" def test_dest_not_found(self): reply = SAMReply.dest_not_found("unknown.i2p") assert "KEY_NOT_FOUND" in reply assert "unknown.i2p" in reply def test_naming_reply(self): reply = SAMReply.naming_reply("test.i2p", "AAAA") assert reply == "NAMING REPLY RESULT=OK NAME=test.i2p VALUE=AAAA\n" def test_pong(self): assert SAMReply.pong("keepalive123") == "PONG keepalive123\n" class TestConstants: """Tests for SAM protocol constants.""" def test_supported_versions(self): assert "3.0" in SUPPORTED_VERSIONS assert "3.3" in SUPPORTED_VERSIONS assert MAX_VERSION == "3.3"