"""Tests para fetch_http_fingerprint. Levanta un http.server.HTTPServer local en 127.0.0.1 en un puerto efimero, servido por un thread, con headers fake (Server, X-Powered-By, Set-Cookie) y un HTML con Hola. NO toca red externa. """ import threading from http.server import BaseHTTPRequestHandler, HTTPServer from fetch_http_fingerprint import fetch_http_fingerprint _HTML = b"Holaok" class _FakeHandler(BaseHTTPRequestHandler): def do_GET(self): # noqa: N802 (firma de BaseHTTPRequestHandler) self.send_response(200) self.send_header("Server", "TestServer/1.0") self.send_header("X-Powered-By", "PHP/8.1") self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Set-Cookie", "PHPSESSID=secret_value_no_capturar; Path=/") self.end_headers() self.wfile.write(_HTML) def log_message(self, *args): # silencia el logging del server en los tests pass def _start_server(): server = HTTPServer(("127.0.0.1", 0), _FakeHandler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() return server, thread def test_status_ok_y_status_code_200(): server, thread = _start_server() try: port = server.server_address[1] res = fetch_http_fingerprint(f"http://127.0.0.1:{port}/") assert res["status"] == "ok", res assert res["status_code"] == 200, res["status_code"] finally: server.shutdown() thread.join(timeout=2) def test_headers_normalizados_lowercase(): server, thread = _start_server() try: port = server.server_address[1] res = fetch_http_fingerprint(f"http://127.0.0.1:{port}/") assert res["headers"]["server"] == "TestServer/1.0", res["headers"] assert res["server"] == "TestServer/1.0", res["server"] assert res["headers"]["x-powered-by"] == "PHP/8.1", res["headers"] finally: server.shutdown() thread.join(timeout=2) def test_cookies_solo_nombres_no_valores(): server, thread = _start_server() try: port = server.server_address[1] res = fetch_http_fingerprint(f"http://127.0.0.1:{port}/") assert "PHPSESSID" in res["cookies"], res["cookies"] # El valor sensible NUNCA debe aparecer en la salida. assert "secret_value_no_capturar" not in res["raw"], "valor de cookie filtrado en raw" assert all("=" not in c for c in res["cookies"]), res["cookies"] finally: server.shutdown() thread.join(timeout=2) def test_title_extraido(): server, thread = _start_server() try: port = server.server_address[1] res = fetch_http_fingerprint(f"http://127.0.0.1:{port}/") assert res["title"] == "Hola", res["title"] assert res["html_len"] == len(_HTML), res["html_len"] finally: server.shutdown() thread.join(timeout=2) def test_url_vacia_devuelve_error(): res = fetch_http_fingerprint("") assert res["status"] == "error", res assert "url vacia" in res["error"], res["error"] def test_host_inexistente_devuelve_error_sin_lanzar(): # Puerto cerrado en loopback: conexion rechazada, debe devolver error, no lanzar. res = fetch_http_fingerprint("http://127.0.0.1:1/") assert res["status"] == "error", res assert res["url"] == "http://127.0.0.1:1/", res if __name__ == "__main__": test_status_ok_y_status_code_200() test_headers_normalizados_lowercase() test_cookies_solo_nombres_no_valores() test_title_extraido() test_url_vacia_devuelve_error() test_host_inexistente_devuelve_error_sin_lanzar() print("all tests passed")