"""Tests for net.i2p.util Tier 3 — HTTP client & SSL. TDD — tests for I2PSSLSocketFactory, EepGet, SSLEepGet. Uses a local test HTTP server, no external network access. """ from __future__ import annotations import http.server import json import ssl import tempfile import threading import os import pytest from i2p_util.ssl_factory import create_ssl_context from i2p_util.eep_get import EepGet, EepGetResult # --------------------------------------------------------------------------- # SSL Context Factory # --------------------------------------------------------------------------- class TestSSLFactory: """SSL context creation.""" def test_create_default_context(self): ctx = create_ssl_context() assert isinstance(ctx, ssl.SSLContext) def test_minimum_tls_version(self): ctx = create_ssl_context() assert ctx.minimum_version >= ssl.TLSVersion.TLSv1_2 def test_hostname_check_disabled_for_i2p(self): ctx = create_ssl_context(verify_hostname=False) assert ctx.check_hostname is False # --------------------------------------------------------------------------- # Test HTTP server fixture # --------------------------------------------------------------------------- class _TestHandler(http.server.BaseHTTPRequestHandler): """Minimal test HTTP server.""" def do_GET(self): if self.path == "/hello": body = b"Hello, World!" self.send_response(200) self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) elif self.path == "/redirect": self.send_response(302) self.send_header("Location", "/hello") self.end_headers() elif self.path == "/large": body = b"X" * 10000 self.send_response(200) self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) elif self.path == "/not-modified": ims = self.headers.get("If-Modified-Since") if ims: self.send_response(304) self.end_headers() else: body = b"fresh content" self.send_response(200) self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) else: self.send_response(404) self.end_headers() def log_message(self, format, *args): pass # suppress logs @pytest.fixture(scope="module") def test_server(): """Start a local HTTP server for testing.""" server = http.server.HTTPServer(("127.0.0.1", 0), _TestHandler) port = server.server_address[1] thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() yield f"http://127.0.0.1:{port}" server.shutdown() # --------------------------------------------------------------------------- # EepGet # --------------------------------------------------------------------------- class TestEepGet: """HTTP client for I2P.""" def test_fetch_success(self, test_server): eg = EepGet() result = eg.fetch(f"{test_server}/hello") assert result.success is True assert result.data == b"Hello, World!" assert result.status_code == 200 def test_fetch_404(self, test_server): eg = EepGet() result = eg.fetch(f"{test_server}/missing") assert result.status_code == 404 def test_fetch_follow_redirect(self, test_server): eg = EepGet() result = eg.fetch(f"{test_server}/redirect") assert result.success is True assert result.data == b"Hello, World!" def test_fetch_to_file(self, test_server): eg = EepGet() with tempfile.NamedTemporaryFile(delete=False) as f: tmp_path = f.name try: result = eg.fetch(f"{test_server}/hello", output_file=tmp_path) assert result.success is True with open(tmp_path, "rb") as f: assert f.read() == b"Hello, World!" finally: os.unlink(tmp_path) def test_fetch_large(self, test_server): eg = EepGet() result = eg.fetch(f"{test_server}/large") assert result.success is True assert len(result.data) == 10000 def test_progress_callback(self, test_server): progress_calls = [] def on_progress(downloaded, total): progress_calls.append((downloaded, total)) eg = EepGet() result = eg.fetch(f"{test_server}/large", progress_callback=on_progress) assert result.success is True assert len(progress_calls) > 0 def test_timeout(self): """Connection to non-routable address should timeout.""" eg = EepGet(connect_timeout=0.5) result = eg.fetch("http://192.0.2.1:1/timeout") assert result.success is False def test_if_modified_since(self, test_server): eg = EepGet() result = eg.fetch( f"{test_server}/not-modified", if_modified_since="Thu, 01 Jan 2099 00:00:00 GMT", ) assert result.status_code == 304 def test_user_agent(self, test_server): """Default User-Agent should be 'I2P'.""" eg = EepGet() assert eg.user_agent == "I2P" # --------------------------------------------------------------------------- # EepGetResult # --------------------------------------------------------------------------- class TestEepGetResult: """Result object from EepGet.""" def test_success_result(self): r = EepGetResult(success=True, status_code=200, data=b"ok") assert r.success assert r.content_length == 2 def test_failure_result(self): r = EepGetResult(success=False, status_code=0, data=b"", error="timeout") assert not r.success assert r.error == "timeout"