A Python port of the Invisible Internet Project (I2P)
at main 141 lines 5.1 kB view raw
1"""Tests for SAM v3.3 text protocol parser and formatter.""" 2 3import pytest 4 5from i2p_sam.protocol import SAMCommand, SAMReply, SUPPORTED_VERSIONS, MAX_VERSION 6 7 8class TestSAMCommandParse: 9 """Tests for SAMCommand.parse().""" 10 11 def test_parse_hello(self): 12 """Parse HELLO VERSION MIN=3.0 MAX=3.3.""" 13 cmd = SAMCommand.parse("HELLO VERSION MIN=3.0 MAX=3.3") 14 assert cmd.verb == "HELLO" 15 assert cmd.opcode == "VERSION" 16 assert cmd.params["MIN"] == "3.0" 17 assert cmd.params["MAX"] == "3.3" 18 19 def test_parse_session_create(self): 20 """Parse SESSION CREATE with multiple params.""" 21 cmd = SAMCommand.parse("SESSION CREATE ID=test STYLE=STREAM DESTINATION=TRANSIENT") 22 assert cmd.verb == "SESSION" 23 assert cmd.opcode == "CREATE" 24 assert cmd.params["ID"] == "test" 25 assert cmd.params["STYLE"] == "STREAM" 26 assert cmd.params["DESTINATION"] == "TRANSIENT" 27 28 def test_parse_stream_connect(self): 29 """Parse STREAM CONNECT with base64 destination.""" 30 dest_b64 = "AAAA" * 86 + "AA" # ~346 chars, typical base64 dest 31 cmd = SAMCommand.parse(f"STREAM CONNECT ID=test DESTINATION={dest_b64}") 32 assert cmd.verb == "STREAM" 33 assert cmd.opcode == "CONNECT" 34 assert cmd.params["ID"] == "test" 35 assert cmd.params["DESTINATION"] == dest_b64 36 37 def test_parse_ping(self): 38 """Parse PING with data payload.""" 39 cmd = SAMCommand.parse("PING keepalive123") 40 assert cmd.verb == "PING" 41 assert cmd.opcode == "keepalive123" 42 43 def test_parse_quoted_values(self): 44 """Handle KEY="value with spaces" in params.""" 45 cmd = SAMCommand.parse('SESSION CREATE ID="my session" STYLE=STREAM DESTINATION=TRANSIENT') 46 assert cmd.params["ID"] == "my session" 47 assert cmd.params["STYLE"] == "STREAM" 48 49 def test_parse_case_insensitive_keys(self): 50 """Keys are normalized to uppercase.""" 51 cmd = SAMCommand.parse("HELLO VERSION min=3.0 max=3.3") 52 assert "MIN" in cmd.params 53 assert "MAX" in cmd.params 54 55 def test_parse_empty_raises(self): 56 """Empty or whitespace-only input raises ValueError.""" 57 with pytest.raises(ValueError): 58 SAMCommand.parse("") 59 with pytest.raises(ValueError): 60 SAMCommand.parse(" ") 61 62 def test_parse_verb_only(self): 63 """Single-word command gets empty opcode and params.""" 64 cmd = SAMCommand.parse("QUIT") 65 assert cmd.verb == "QUIT" 66 assert cmd.opcode == "" 67 assert cmd.params == {} 68 69 70class TestSAMCommandStr: 71 """Tests for SAMCommand.__str__().""" 72 73 def test_command_str_roundtrip(self): 74 """Parse then format, verify consistency.""" 75 original = "HELLO VERSION MIN=3.0 MAX=3.3" 76 cmd = SAMCommand.parse(original) 77 formatted = str(cmd) 78 # Re-parse the formatted string 79 cmd2 = SAMCommand.parse(formatted) 80 assert cmd2.verb == cmd.verb 81 assert cmd2.opcode == cmd.opcode 82 assert cmd2.params == cmd.params 83 84 def test_str_no_params(self): 85 """Format command with no params.""" 86 cmd = SAMCommand("QUIT", "", {}) 87 assert str(cmd).strip() == "QUIT" 88 89 90class TestSAMReply: 91 """Tests for SAMReply static methods.""" 92 93 def test_hello_ok(self): 94 assert SAMReply.hello_ok() == "HELLO REPLY RESULT=OK VERSION=3.3\n" 95 assert SAMReply.hello_ok("3.1") == "HELLO REPLY RESULT=OK VERSION=3.1\n" 96 97 def test_hello_noversion(self): 98 assert SAMReply.hello_noversion() == "HELLO REPLY RESULT=NOVERSION\n" 99 100 def test_session_ok(self): 101 reply = SAMReply.session_ok("AAAA") 102 assert reply == "SESSION STATUS RESULT=OK DESTINATION=AAAA\n" 103 104 def test_session_error(self): 105 reply = SAMReply.session_error("DUPLICATED_ID", "Already exists") 106 assert "RESULT=DUPLICATED_ID" in reply 107 assert "MESSAGE=" in reply 108 assert reply.endswith("\n") 109 110 def test_stream_ok(self): 111 assert SAMReply.stream_ok() == "STREAM STATUS RESULT=OK\n" 112 113 def test_stream_error(self): 114 reply = SAMReply.stream_error("CANT_REACH_PEER", "Timeout") 115 assert "RESULT=CANT_REACH_PEER" in reply 116 assert reply.endswith("\n") 117 118 def test_dest_reply(self): 119 reply = SAMReply.dest_reply("test.i2p", "AAAA") 120 assert reply == "DEST REPLY RESULT=OK NAME=test.i2p DESTINATION=AAAA\n" 121 122 def test_dest_not_found(self): 123 reply = SAMReply.dest_not_found("unknown.i2p") 124 assert "KEY_NOT_FOUND" in reply 125 assert "unknown.i2p" in reply 126 127 def test_naming_reply(self): 128 reply = SAMReply.naming_reply("test.i2p", "AAAA") 129 assert reply == "NAMING REPLY RESULT=OK NAME=test.i2p VALUE=AAAA\n" 130 131 def test_pong(self): 132 assert SAMReply.pong("keepalive123") == "PONG keepalive123\n" 133 134 135class TestConstants: 136 """Tests for SAM protocol constants.""" 137 138 def test_supported_versions(self): 139 assert "3.0" in SUPPORTED_VERSIONS 140 assert "3.3" in SUPPORTED_VERSIONS 141 assert MAX_VERSION == "3.3"