"""Tests for SessionTag and Payload data structures. TDD: these tests are written before the implementation. """ from __future__ import annotations import io import os import struct import pytest from i2p_data.session import SessionTag, Payload # --------------------------------------------------------------------------- # SessionTag # --------------------------------------------------------------------------- class TestSessionTag: """Tests for the 32-byte SessionTag.""" def test_construction_valid(self): data = os.urandom(32) tag = SessionTag(data) assert tag.to_bytes() == data def test_construction_wrong_size_short(self): with pytest.raises(ValueError, match="32 bytes"): SessionTag(b"\x00" * 31) def test_construction_wrong_size_long(self): with pytest.raises(ValueError, match="32 bytes"): SessionTag(b"\x00" * 33) def test_construction_empty(self): with pytest.raises(ValueError, match="32 bytes"): SessionTag(b"") def test_from_bytes(self): data = os.urandom(32) tag = SessionTag.from_bytes(data) assert tag.to_bytes() == data def test_from_bytes_wrong_size(self): with pytest.raises(ValueError, match="32 bytes"): SessionTag.from_bytes(b"\x00" * 10) def test_random(self): tag = SessionTag.random() assert len(tag.to_bytes()) == 32 def test_random_unique(self): """Two random tags should (almost certainly) differ.""" t1 = SessionTag.random() t2 = SessionTag.random() assert t1 != t2 def test_round_trip(self): tag = SessionTag.random() restored = SessionTag.from_bytes(tag.to_bytes()) assert restored == tag def test_equality_same_data(self): data = os.urandom(32) assert SessionTag(data) == SessionTag(data) def test_equality_different_data(self): assert SessionTag(b"\x00" * 32) != SessionTag(b"\x01" * 32) def test_equality_wrong_type(self): tag = SessionTag(b"\x00" * 32) assert tag != "not a tag" assert tag != 42 def test_hash_same_data(self): data = os.urandom(32) assert hash(SessionTag(data)) == hash(SessionTag(data)) def test_hash_usable_in_set(self): data = os.urandom(32) s = {SessionTag(data), SessionTag(data)} assert len(s) == 1 def test_repr(self): tag = SessionTag(b"\xab\xcd" + b"\x00" * 30) r = repr(tag) assert "SessionTag" in r assert "abcd" in r # --------------------------------------------------------------------------- # Payload # --------------------------------------------------------------------------- class TestPayload: """Tests for the length-prefixed Payload.""" def test_construction(self): p = Payload(b"hello") assert len(p) == 5 def test_to_bytes_length_prefix(self): p = Payload(b"hello") wire = p.to_bytes() assert wire[:4] == struct.pack("!I", 5) assert wire[4:] == b"hello" def test_from_bytes_round_trip(self): original = Payload(b"test data") wire = original.to_bytes() restored = Payload.from_bytes(wire) assert restored == original def test_from_stream_round_trip(self): original = Payload(b"stream test") wire = original.to_bytes() stream = io.BytesIO(wire) restored = Payload.from_stream(stream) assert restored == original def test_empty_payload(self): p = Payload(b"") assert len(p) == 0 wire = p.to_bytes() assert wire == struct.pack("!I", 0) restored = Payload.from_bytes(wire) assert restored == p def test_large_payload(self): data = os.urandom(100_000) p = Payload(data) assert len(p) == 100_000 wire = p.to_bytes() restored = Payload.from_bytes(wire) assert restored == p def test_from_bytes_truncated_header(self): with pytest.raises(ValueError, match="[Tt]runcated|[Nn]eed|bytes"): Payload.from_bytes(b"\x00\x00") def test_from_bytes_truncated_body(self): # Header says 10 bytes, but only 3 are present wire = struct.pack("!I", 10) + b"\x00\x00\x00" with pytest.raises(ValueError, match="[Tt]runcated|[Nn]eed|bytes"): Payload.from_bytes(wire) def test_from_stream_truncated_header(self): stream = io.BytesIO(b"\x00\x00") with pytest.raises(ValueError, match="[Tt]runcated|[Nn]eed|bytes"): Payload.from_stream(stream) def test_from_stream_truncated_body(self): wire = struct.pack("!I", 10) + b"\x00\x00\x00" stream = io.BytesIO(wire) with pytest.raises(ValueError, match="[Tt]runcated|[Nn]eed|bytes"): Payload.from_stream(stream) def test_equality_same(self): assert Payload(b"abc") == Payload(b"abc") def test_equality_different(self): assert Payload(b"abc") != Payload(b"xyz") def test_equality_wrong_type(self): assert Payload(b"abc") != "abc" def test_len(self): assert len(Payload(b"1234")) == 4