smolweb/tests/test_gopher.py

69 lines
2.4 KiB
Python
Raw Permalink Normal View History

"""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"