import json import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import pytest from control_plane.wgeasy import WgEasyClient, WgEasyError FAKE_CONF = """[Interface] PrivateKey = client-private-key== Address = 10.8.0.5/32 DNS = 1.1.1.1 [Peer] PublicKey = server-public-key== Endpoint = gateway.example.com:51820 AllowedIPs = 10.8.0.0/24 PersistentKeepalive = 25 """ class FakeWgEasyHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass def _json(self, status, payload): body = json.dumps(payload).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def do_POST(self): self.server.requests.append(("POST", self.path)) if self.path == "/api/auth/password": self.send_response(200) self.send_header("Set-Cookie", "wg-easy-session=abc123; Path=/; HttpOnly") self.send_header("Content-Length", "0") self.end_headers() return if self.path == "/api/client": self._json(200, {"success": True, "clientId": "client-1"}) return self._json(404, {"error": "not found"}) def do_GET(self): self.server.requests.append(("GET", self.path)) if self.path == "/api/client/client-1": self._json(200, {"id": "client-1", "publicKey": "client-public-key=="}) return if self.path == "/api/client/client-1/configuration": body = FAKE_CONF.encode() self.send_response(200) self.send_header("Content-Type", "application/octet-stream") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) return self._json(404, {"error": "not found"}) def do_DELETE(self): self.server.requests.append(("DELETE", self.path)) if self.path == "/api/client/client-1": self._json(200, {"success": True}) return self._json(404, {"error": "not found"}) @pytest.fixture def fake_wgeasy(): server = ThreadingHTTPServer(("127.0.0.1", 0), FakeWgEasyHandler) server.requests = [] thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: yield server finally: server.shutdown() thread.join() def _client(server): port = server.server_address[1] return WgEasyClient(f"http://127.0.0.1:{port}", "admin", "hunter2") def test_create_client_logs_in_and_parses_configuration(fake_wgeasy): client = _client(fake_wgeasy) peer = client.create_client("box-1") assert peer["id"] == "client-1" assert peer["public_key"] == "client-public-key==" assert peer["private_key"] == "client-private-key==" assert peer["address"] == "10.8.0.5/32" assert peer["server_public_key"] == "server-public-key==" assert peer["endpoint"] == "gateway.example.com:51820" assert peer["allowed_ips"] == "10.8.0.0/24" methods_and_paths = [(m, p) for m, p in fake_wgeasy.requests] assert ("POST", "/api/auth/password") in methods_and_paths assert ("POST", "/api/client") in methods_and_paths def test_delete_client_swallows_not_found(fake_wgeasy): client = _client(fake_wgeasy) client.delete_client("does-not-exist") # 404 from fake server — must not raise def test_delete_client_calls_endpoint(fake_wgeasy): client = _client(fake_wgeasy) client.delete_client("client-1") assert ("DELETE", "/api/client/client-1") in [(m, p) for m, p in fake_wgeasy.requests] def test_bad_endpoint_raises_wgeasy_error(fake_wgeasy): port = fake_wgeasy.server_address[1] client = WgEasyClient(f"http://127.0.0.1:{port}", "admin", "hunter2") with pytest.raises(WgEasyError): client._authed_request("GET", "/api/does-not-exist")