"""Actual loopback WebSocket checks; no application, database or external calls.""" import importlib.machinery import importlib.util from pathlib import Path import socketserver import threading import unittest HAS_TRANSPORT = all(importlib.util.find_spec(name) for name in ("uvicorn", "wsproto")) @unittest.skipUnless(HAS_TRANSPORT, "Install pinned uvicorn and wsproto to run the transport tests") class WebSocketHealthcheckTests(unittest.TestCase): @classmethod def setUpClass(cls): path = Path(__file__).resolve().parents[1] / "eusrr-deploy-healthcheck" loader = importlib.machinery.SourceFileLoader("deploy_healthcheck", str(path)) cls.probe = importlib.util.module_from_spec(importlib.util.spec_from_loader(loader.name, loader)) loader.exec_module(cls.probe) def test_actual_uvicorn_101_and_asgi_frame(self): self.probe.check_transport() def run_rejection_server(self, status): class Handler(socketserver.BaseRequestHandler): def handle(self): self.request.recv(4096) self.request.sendall( f"HTTP/1.1 {status} Response\r\nContent-Length: 0\r\nConnection: close\r\n\r\n".encode() ) server = socketserver.TCPServer(("127.0.0.1", 0), Handler) thread = threading.Thread(target=server.handle_request, daemon=True) thread.start() self.addCleanup(server.server_close) self.addCleanup(lambda: thread.join(timeout=5)) return server.server_address[1] def test_anonymous_rejection_is_expected(self): self.probe.handshake(self.run_rejection_server(403), "/ws/", 403) def test_http_fallback_404_does_not_pass(self): with self.assertRaisesRegex(RuntimeError, "Unexpected WebSocket HTTP status"): self.probe.handshake(self.run_rejection_server(404), "/ws/", 403) def test_bogus_101_without_accept_does_not_pass(self): with self.assertRaisesRegex(RuntimeError, "Invalid WebSocket upgrade"): self.probe.handshake(self.run_rejection_server(101), "/", 101) if __name__ == "__main__": unittest.main()