''' Multiaddr construction, parsing, and round-trip tests for `tractor.discovery._multiaddr.mk_maddr()` and `tractor.discovery._multiaddr.parse_maddr()`. ''' from pathlib import Path from types import SimpleNamespace import pytest from multiaddr import Multiaddr from tractor.ipc._tcp import TCPAddress from tractor.ipc._uds import UDSAddress from tractor.discovery._multiaddr import ( mk_maddr, parse_maddr, _tpt_proto_to_maddr, _maddr_to_tpt_proto, ) from tractor.discovery._addr import wrap_address def test_tpt_proto_to_maddr_mapping(): ''' `_tpt_proto_to_maddr` maps all supported `proto_key` values to their correct multiaddr protocol names. ''' assert _tpt_proto_to_maddr['tcp'] == 'tcp' assert _tpt_proto_to_maddr['uds'] == 'unix' assert len(_tpt_proto_to_maddr) == 2 def test_mk_maddr_tcp_ipv4(): ''' `mk_maddr()` on a `TCPAddress` with an IPv4 host produces the correct `/ip4//tcp/` multiaddr. ''' addr = TCPAddress('127.0.0.1', 1234) result: Multiaddr = mk_maddr(addr) assert isinstance(result, Multiaddr) assert str(result) == '/ip4/127.0.0.1/tcp/1234' protos = result.protocols() assert protos[0].name == 'ip4' assert protos[1].name == 'tcp' assert result.value_for_protocol('ip4') == '127.0.0.1' assert result.value_for_protocol('tcp') == '1234' def test_mk_maddr_tcp_ipv6(): ''' `mk_maddr()` on a `TCPAddress` with an IPv6 host produces the correct `/ip6//tcp/` multiaddr. ''' addr = TCPAddress('::1', 5678) result: Multiaddr = mk_maddr(addr) assert str(result) == '/ip6/::1/tcp/5678' protos = result.protocols() assert protos[0].name == 'ip6' assert protos[1].name == 'tcp' def test_mk_maddr_uds(): ''' `mk_maddr()` on a `UDSAddress` produces a `/unix/` multiaddr containing the full socket path. ''' # NOTE, use an absolute `filedir` to match real runtime # UDS paths; `mk_maddr()` strips the leading `/` to avoid # the double-slash `/unix//run/..` that py-multiaddr # rejects as "empty protocol path". filedir = '/tmp/tractor_test' filename = 'test_sock.sock' addr = UDSAddress( filedir=filedir, filename=filename, ) result: Multiaddr = mk_maddr(addr) assert isinstance(result, Multiaddr) result_str: str = str(result) assert result_str.startswith('/unix/') # verify the leading `/` was stripped to avoid double-slash assert '/unix/tmp/tractor_test/' in result_str sockpath_rel: str = str( Path(filedir) / filename ).lstrip('/') unix_val: str = result.value_for_protocol('unix') assert unix_val.endswith(sockpath_rel) def test_mk_maddr_unsupported_proto_key(): ''' `mk_maddr()` raises `ValueError` for an unsupported `proto_key`. ''' fake_addr = SimpleNamespace(proto_key='quic') with pytest.raises( ValueError, match='Unsupported proto_key', ): mk_maddr(fake_addr) @pytest.mark.parametrize( 'addr', [ pytest.param( TCPAddress('127.0.0.1', 9999), id='tcp-ipv4', ), pytest.param( UDSAddress( filedir='/tmp/tractor_rt', filename='roundtrip.sock', ), id='uds', ), ], ) def test_mk_maddr_roundtrip(addr): ''' `mk_maddr()` output is valid multiaddr syntax that the library can re-parse back into an equivalent `Multiaddr`. ''' maddr: Multiaddr = mk_maddr(addr) reparsed = Multiaddr(str(maddr)) assert reparsed == maddr assert str(reparsed) == str(maddr) # ------ parse_maddr() tests ------ def test_maddr_to_tpt_proto_mapping(): ''' `_maddr_to_tpt_proto` is the exact inverse of `_tpt_proto_to_maddr`. ''' assert _maddr_to_tpt_proto == { 'tcp': 'tcp', 'unix': 'uds', } def test_parse_maddr_tcp_ipv4(): ''' `parse_maddr()` on an IPv4 TCP multiaddr string produce a `TCPAddress` with the correct host and port. ''' result = parse_maddr('/ip4/127.0.0.1/tcp/1234') assert isinstance(result, TCPAddress) assert result.unwrap() == ('127.0.0.1', 1234) def test_parse_maddr_tcp_ipv6(): ''' `parse_maddr()` on an IPv6 TCP multiaddr string produce a `TCPAddress` with the correct host and port. ''' result = parse_maddr('/ip6/::1/tcp/5678') assert isinstance(result, TCPAddress) assert result.unwrap() == ('::1', 5678) def test_parse_maddr_uds(): ''' `parse_maddr()` on a `/unix/...` multiaddr string produce a `UDSAddress` with the correct dir and filename, preserving absolute path semantics. ''' result = parse_maddr('/unix/tmp/tractor_test/test.sock') assert isinstance(result, UDSAddress) filedir, filename = result.unwrap() assert filename == 'test.sock' assert str(filedir) == '/tmp/tractor_test' def test_parse_maddr_unsupported(): ''' `parse_maddr()` raise `ValueError` for an unsupported protocol combination like UDP. ''' with pytest.raises( ValueError, match='Unsupported multiaddr protocol combo', ): parse_maddr('/ip4/127.0.0.1/udp/1234') @pytest.mark.parametrize( 'addr', [ pytest.param( TCPAddress('127.0.0.1', 9999), id='tcp-ipv4', ), pytest.param( UDSAddress( filedir='/tmp/tractor_rt', filename='roundtrip.sock', ), id='uds', ), ], ) def test_parse_maddr_roundtrip(addr): ''' Full round-trip: `addr -> mk_maddr -> str -> parse_maddr` produce an `Address` whose `.unwrap()` matches the original. ''' maddr: Multiaddr = mk_maddr(addr) maddr_str: str = str(maddr) parsed = parse_maddr(maddr_str) assert type(parsed) is type(addr) assert parsed.unwrap() == addr.unwrap() def test_wrap_address_maddr_str(): ''' `wrap_address()` accept a multiaddr-format string and return the correct `Address` type. ''' result = wrap_address('/ip4/127.0.0.1/tcp/9999') assert isinstance(result, TCPAddress) assert result.unwrap() == ('127.0.0.1', 9999)