"""Gopher listener: the RFC 4266 root-menu compatibility fix, dot-stuffing.""" from __future__ import annotations import socket import threading import pytest from smolweb.servers.gopher import GopherServer from smolweb.site import Site def _free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe: probe.bind(("127.0.0.1", 0)) return probe.getsockname()[1] @pytest.fixture def gopher_server(site: Site): port = _free_port() server = GopherServer(("127.0.0.1", port), site, "localhost") thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() yield server server.shutdown() server.server_close() def _request(server: GopherServer, selector: str) -> bytes: with socket.create_connection(("127.0.0.1", server.port), timeout=5) as sock: sock.sendall(f"{selector}\r\n".encode("utf-8")) chunks = [] while True: chunk = sock.recv(65536) if not chunk: break chunks.append(chunk) return b"".join(chunks) class TestGopherRootMenu: def test_empty_selector_returns_a_one_item_menu(self, gopher_server: GopherServer): # A bare gopher://host/ has no item-type prefix, which RFC 4266 # defines as defaulting to type '1' (menu) -- real clients parse # the response as tab-delimited menu lines, not prose, so the # homepage's own rendered text must never be sent back here. response = _request(gopher_server, "") assert response == f"0Home\t/\tlocalhost\t{gopher_server.port}\r\n.\r\n".encode("utf-8") def test_menu_link_selector_resolves_to_real_content(self, gopher_server: GopherServer): # The menu's own selector ("/") must not trigger the same # menu-synthesis branch -- only the truly empty selector does. response = _request(gopher_server, "/") assert response.endswith(b".\r\n") assert b"Hello." in response assert not response.startswith(b"0Home\t") def test_non_root_selector_is_unaffected(self, gopher_server: GopherServer): response = _request(gopher_server, "about") assert b"Body." in response assert not response.startswith(b"0Home\t") class TestGopherDotStuffing: def test_not_found_response_is_well_formed(self, gopher_server: GopherServer): response = _request(gopher_server, "nope") assert response == b"Not found.\n.\r\n"