mcp-proxy-adapter 6.2.21__py3-none-any.whl → 6.2.23__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,14 +9,14 @@ import json
9
9
  import os
10
10
  import argparse
11
11
  from typing import Dict, Any
12
- def generate_http_simple_config(port: int = 8000) -> Dict[str, Any]:
12
+ def generate_http_simple_config(port: int = 20000, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
13
13
  """Generate HTTP configuration without authorization."""
14
14
  return {
15
15
  "server": {"host": "127.0.0.1", "port": port},
16
16
  "ssl": {"enabled": False},
17
17
  "security": {"enabled": False},
18
18
  "registration": {
19
- "enabled": True,
19
+ "enabled": False,
20
20
  "auth_method": "token",
21
21
  "server_url": "http://127.0.0.1:3004/proxy",
22
22
  "token": {"enabled": True, "token": "proxy_registration_token_123"},
@@ -29,14 +29,25 @@ def generate_http_simple_config(port: int = 8000) -> Dict[str, Any]:
29
29
  },
30
30
  "protocols": {"enabled": True, "allowed_protocols": ["http"]}
31
31
  }
32
- def generate_http_token_config(port: int = 8001) -> Dict[str, Any]:
32
+ def generate_http_token_config(port: int = 20001, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
33
33
  """Generate HTTP configuration with token authorization."""
34
34
  return {
35
35
  "server": {"host": "127.0.0.1", "port": port},
36
36
  "ssl": {"enabled": False},
37
37
  "security": {
38
38
  "enabled": True,
39
- "auth": {"enabled": True, "methods": ["api_key"]},
39
+ "auth": {
40
+ "enabled": True,
41
+ "methods": ["api_key"],
42
+ # Map API tokens to roles for testing
43
+ "api_keys": {
44
+ "test-token-123": "admin",
45
+ "user-token-456": "user",
46
+ "readonly-token-123": "readonly",
47
+ "guest-token-123": "guest",
48
+ "proxy-token-123": "proxy"
49
+ }
50
+ },
40
51
  "permissions": {"enabled": True, "roles_file": "./roles.json"}
41
52
  },
42
53
  "registration": {
@@ -50,7 +61,7 @@ def generate_http_token_config(port: int = 8001) -> Dict[str, Any]:
50
61
  },
51
62
  "protocols": {"enabled": True, "allowed_protocols": ["http"]}
52
63
  }
53
- def generate_https_simple_config(port: int = 8002) -> Dict[str, Any]:
64
+ def generate_https_simple_config(port: int = 20002, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
54
65
  """Generate HTTPS configuration without client certificate verification and authorization."""
55
66
  return {
56
67
  "server": {"host": "127.0.0.1", "port": port},
@@ -71,7 +82,7 @@ def generate_https_simple_config(port: int = 8002) -> Dict[str, Any]:
71
82
  },
72
83
  "protocols": {"enabled": True, "allowed_protocols": ["http", "https"]}
73
84
  }
74
- def generate_https_token_config(port: int = 8003) -> Dict[str, Any]:
85
+ def generate_https_token_config(port: int = 20003, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
75
86
  """Generate HTTPS configuration without client certificate verification with token authorization."""
76
87
  return {
77
88
  "server": {"host": "127.0.0.1", "port": port},
@@ -82,7 +93,17 @@ def generate_https_token_config(port: int = 8003) -> Dict[str, Any]:
82
93
  },
83
94
  "security": {
84
95
  "enabled": True,
85
- "auth": {"enabled": True, "methods": ["api_key"]},
96
+ "auth": {
97
+ "enabled": True,
98
+ "methods": ["api_key"],
99
+ "api_keys": {
100
+ "test-token-123": "admin",
101
+ "user-token-456": "user",
102
+ "readonly-token-123": "readonly",
103
+ "guest-token-123": "guest",
104
+ "proxy-token-123": "proxy"
105
+ }
106
+ },
86
107
  "permissions": {"enabled": True, "roles_file": "./roles.json"}
87
108
  },
88
109
  "registration": {
@@ -96,7 +117,7 @@ def generate_https_token_config(port: int = 8003) -> Dict[str, Any]:
96
117
  },
97
118
  "protocols": {"enabled": True, "allowed_protocols": ["http", "https"]}
98
119
  }
99
- def generate_mtls_no_roles_config(port: int = 8004) -> Dict[str, Any]:
120
+ def generate_mtls_no_roles_config(port: int = 20004, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
100
121
  """Generate mTLS configuration without roles."""
101
122
  return {
102
123
  "server": {"host": "127.0.0.1", "port": port},
@@ -104,7 +125,7 @@ def generate_mtls_no_roles_config(port: int = 8004) -> Dict[str, Any]:
104
125
  "enabled": True,
105
126
  "cert_file": "./certs/localhost_server.crt",
106
127
  "key_file": "./keys/localhost_server.key",
107
- "ca_cert": "./certs/mcp_proxy_adapter_test_ca_ca.crt",
128
+ "ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
108
129
  "verify_client": True
109
130
  },
110
131
  "security": {
@@ -114,7 +135,7 @@ def generate_mtls_no_roles_config(port: int = 8004) -> Dict[str, Any]:
114
135
  },
115
136
  "protocols": {"enabled": True, "allowed_protocols": ["https", "mtls"]}
116
137
  }
117
- def generate_mtls_with_roles_config(port: int = 8005) -> Dict[str, Any]:
138
+ def generate_mtls_with_roles_config(port: int = 20005, certs_dir: str = "./certs", keys_dir: str = "./keys") -> Dict[str, Any]:
118
139
  """Generate mTLS configuration with roles."""
119
140
  return {
120
141
  "server": {"host": "127.0.0.1", "port": port},
@@ -122,7 +143,7 @@ def generate_mtls_with_roles_config(port: int = 8005) -> Dict[str, Any]:
122
143
  "enabled": True,
123
144
  "cert_file": "./certs/localhost_server.crt",
124
145
  "key_file": "./keys/localhost_server.key",
125
- "ca_cert": "./certs/mcp_proxy_adapter_test_ca_ca.crt",
146
+ "ca_cert": "./certs/mcp_proxy_adapter_ca_ca.crt",
126
147
  "verify_client": True
127
148
  },
128
149
  "registration": {
@@ -160,7 +181,7 @@ def generate_roles_config() -> Dict[str, Any]:
160
181
  "heartbeat",
161
182
  "discover"
162
183
  ],
163
- "tokens": []
184
+ "tokens": ["test-token-123"]
164
185
  },
165
186
  "user": {
166
187
  "description": "User role with limited access",
@@ -172,7 +193,7 @@ def generate_roles_config() -> Dict[str, Any]:
172
193
  "heartbeat",
173
194
  "discover"
174
195
  ],
175
- "tokens": []
196
+ "tokens": ["user-token-456"]
176
197
  },
177
198
  "readonly": {
178
199
  "description": "Read-only role",
@@ -180,7 +201,7 @@ def generate_roles_config() -> Dict[str, Any]:
180
201
  "read",
181
202
  "discover"
182
203
  ],
183
- "tokens": []
204
+ "tokens": ["readonly-token-123"]
184
205
  },
185
206
  "guest": {
186
207
  "description": "Guest role with read-only access",
@@ -188,7 +209,7 @@ def generate_roles_config() -> Dict[str, Any]:
188
209
  "read",
189
210
  "discover"
190
211
  ],
191
- "tokens": []
212
+ "tokens": ["guest-token-123"]
192
213
  },
193
214
  "proxy": {
194
215
  "description": "Proxy role for registration",
@@ -198,18 +219,18 @@ def generate_roles_config() -> Dict[str, Any]:
198
219
  "heartbeat",
199
220
  "discover"
200
221
  ],
201
- "tokens": []
222
+ "tokens": ["proxy-token-123"]
202
223
  }
203
224
  }
204
- def generate_all_configs(output_dir: str) -> None:
225
+ def generate_all_configs(output_dir: str, certs_dir: str = "./certs", keys_dir: str = "./keys") -> None:
205
226
  """Generate all 6 configuration types and save them to files."""
206
227
  configs = {
207
- "http_simple": generate_http_simple_config(8000),
208
- "http_token": generate_http_token_config(8001),
209
- "https_simple": generate_https_simple_config(8002),
210
- "https_token": generate_https_token_config(8003),
211
- "mtls_no_roles": generate_mtls_no_roles_config(8004),
212
- "mtls_with_roles": generate_mtls_with_roles_config(8005)
228
+ "http_simple": generate_http_simple_config(20000, certs_dir, keys_dir),
229
+ "http_token": generate_http_token_config(20001, certs_dir, keys_dir),
230
+ "https_simple": generate_https_simple_config(20002, certs_dir, keys_dir),
231
+ "https_token": generate_https_token_config(20003, certs_dir, keys_dir),
232
+ "mtls_no_roles": generate_mtls_no_roles_config(20004, certs_dir, keys_dir),
233
+ "mtls_with_roles": generate_mtls_with_roles_config(20005, certs_dir, keys_dir)
213
234
  }
214
235
  # Ensure output directory exists
215
236
  os.makedirs(output_dir, exist_ok=True)
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Full Test Suite Runner for MCP Proxy Adapter
4
+ This script automatically runs the complete test suite:
5
+ 1. Setup test environment
6
+ 2. Generate configurations
7
+ 3. Create certificates
8
+ 4. Run security tests
9
+
10
+ Author: Vasiliy Zdanovskiy
11
+ email: vasilyvz@gmail.com
12
+ """
13
+ import sys
14
+ import subprocess
15
+ import os
16
+ from pathlib import Path
17
+
18
+
19
+ def run_command(cmd: list, description: str) -> bool:
20
+ """Run a command and return success status."""
21
+ try:
22
+ print(f"\n🚀 {description}")
23
+ print("=" * 60)
24
+
25
+ # Change to script directory if running from package
26
+ script_dir = Path(__file__).parent
27
+ if script_dir.name == "examples":
28
+ os.chdir(script_dir)
29
+
30
+ result = subprocess.run(
31
+ cmd,
32
+ capture_output=False,
33
+ text=True,
34
+ check=True,
35
+ cwd=script_dir
36
+ )
37
+ print(f"✅ {description} completed successfully")
38
+ return True
39
+ except subprocess.CalledProcessError as e:
40
+ print(f"❌ {description} failed:")
41
+ print(f" Command: {' '.join(cmd)}")
42
+ print(f" Error: {e.stderr}")
43
+ return False
44
+ except Exception as e:
45
+ print(f"❌ {description} failed: {e}")
46
+ return False
47
+
48
+
49
+ def main():
50
+ """Run the complete test suite."""
51
+ print("🧪 MCP Proxy Adapter - Full Test Suite")
52
+ print("=" * 60)
53
+
54
+ # Check if we're in the right directory
55
+ current_dir = Path.cwd()
56
+ if not (current_dir / "setup_test_environment.py").exists():
57
+ print("❌ Please run this script from the examples directory")
58
+ return 1
59
+
60
+ success = True
61
+
62
+ # 1. Setup test environment
63
+ if not run_command([
64
+ sys.executable, "-m", "mcp_proxy_adapter.examples.setup_test_environment",
65
+ "--output-dir", "."
66
+ ], "Setting up test environment"):
67
+ success = False
68
+
69
+ # 2. Generate configurations
70
+ if success and not run_command([
71
+ sys.executable, "-m", "mcp_proxy_adapter.examples.generate_test_configs",
72
+ "--output-dir", "configs"
73
+ ], "Generating test configurations"):
74
+ success = False
75
+
76
+ # 3. Create certificates
77
+ if success and not run_command([
78
+ sys.executable, "-m", "mcp_proxy_adapter.examples.create_certificates_simple"
79
+ ], "Creating certificates"):
80
+ success = False
81
+
82
+ # 4. Copy roles.json to root directory
83
+ if success:
84
+ import shutil
85
+ from pathlib import Path
86
+ roles_file = Path("configs/roles.json")
87
+ if roles_file.exists():
88
+ shutil.copy2(roles_file, "roles.json")
89
+ print("✅ Copied roles.json to root directory")
90
+ else:
91
+ success = False
92
+ print("❌ roles.json not found in configs directory")
93
+
94
+ # 5. Run security tests
95
+ if success and not run_command([
96
+ sys.executable, "-m", "mcp_proxy_adapter.examples.run_security_tests"
97
+ ], "Running security tests"):
98
+ success = False
99
+
100
+ # Final result
101
+ print("\n" + "=" * 60)
102
+ if success:
103
+ print("🎉 FULL TEST SUITE COMPLETED SUCCESSFULLY!")
104
+ print("=" * 60)
105
+ print("\n📋 SUMMARY:")
106
+ print("✅ Test environment setup")
107
+ print("✅ Configuration generation")
108
+ print("✅ Certificate creation")
109
+ print("✅ Roles configuration")
110
+ print("✅ Security testing")
111
+ print("\n🚀 All systems are working correctly!")
112
+ return 0
113
+ else:
114
+ print("❌ FULL TEST SUITE FAILED!")
115
+ print("=" * 60)
116
+ print("\n🔧 TROUBLESHOOTING:")
117
+ print("1. Check the error messages above")
118
+ print("2. Ensure you have write permissions")
119
+ print("3. Make sure ports 20000-20010 are free")
120
+ print("4. Check if mcp_security_framework is installed")
121
+ return 1
122
+
123
+
124
+ if __name__ == "__main__":
125
+ exit(main())
@@ -19,7 +19,7 @@ from datetime import datetime, timedelta
19
19
 
20
20
  from fastapi import FastAPI, HTTPException
21
21
  from pydantic import BaseModel
22
- import uvicorn
22
+ from mcp_proxy_adapter.core.server_adapter import UnifiedServerRunner
23
23
 
24
24
 
25
25
  # Simple in-memory storage for registered adapters
@@ -130,13 +130,15 @@ def main() -> None:
130
130
  print(" POST /proxy/heartbeat - Heartbeat from adapter")
131
131
  print("⚡ Press Ctrl+C to stop\n")
132
132
 
133
- # Run server
134
- uvicorn.run(
133
+ # Run server via unified runner (hypercorn under the hood)
134
+ runner = UnifiedServerRunner()
135
+ runner.run_server(
135
136
  app,
136
- host=args.host,
137
- port=args.port,
138
- log_level=args.log_level,
139
- access_log=True
137
+ {
138
+ "host": args.host,
139
+ "port": args.port,
140
+ "log_level": args.log_level,
141
+ },
140
142
  )
141
143
 
142
144
 
@@ -14,11 +14,15 @@ import asyncio
14
14
  import json
15
15
  import os
16
16
  import signal
17
+ import socket
17
18
  import subprocess
18
19
  import sys
19
20
  import time
20
21
  from pathlib import Path
21
- from typing import Dict, List, Optional, Any
22
+ from typing import Dict, List, Optional, Any, Tuple
23
+
24
+ import psutil
25
+ import requests
22
26
  # Import security test client with proper module path
23
27
  from mcp_proxy_adapter.examples.security_test_client import SecurityTestClient, TestResult
24
28
  class SecurityTestRunner:
@@ -27,39 +31,149 @@ class SecurityTestRunner:
27
31
  """Initialize test runner."""
28
32
  self.servers = {}
29
33
  self.proxy_server = None
34
+ self.server_logs = {}
35
+ self.proxy_log = None
30
36
  self.test_results = {}
37
+ # Base and proxy ports
38
+ self.base_port = 20000
39
+ self.proxy_port = 20010
40
+ # Server configurations with ports starting from 20000
31
41
  self.configs = {
32
42
  "basic_http": {
33
43
  "config": "configs/http_simple.json",
34
- "port": 8000,
35
- "url": "http://localhost:8000",
44
+ "port": self.base_port + 0,
45
+ "url": f"http://localhost:{self.base_port + 0}",
36
46
  "auth": "none"
37
47
  },
38
48
  "http_token": {
39
49
  "config": "configs/http_token.json",
40
- "port": 8001,
41
- "url": "http://localhost:8001",
50
+ "port": self.base_port + 1,
51
+ "url": f"http://localhost:{self.base_port + 1}",
42
52
  "auth": "api_key"
43
53
  },
44
54
  "https": {
45
55
  "config": "configs/https_simple.json",
46
- "port": 8002,
47
- "url": "https://localhost:8002",
56
+ "port": self.base_port + 2,
57
+ "url": f"https://localhost:{self.base_port + 2}",
48
58
  "auth": "none"
49
59
  },
50
60
  "https_token": {
51
61
  "config": "configs/https_token.json",
52
- "port": 8003,
53
- "url": "https://localhost:8003",
62
+ "port": self.base_port + 3,
63
+ "url": f"https://localhost:{self.base_port + 3}",
54
64
  "auth": "api_key"
55
65
  },
56
66
  "mtls": {
57
67
  "config": "configs/mtls_no_roles.json",
58
- "port": 8004,
59
- "url": "https://localhost:8004",
68
+ "port": self.base_port + 4,
69
+ "url": f"https://localhost:{self.base_port + 4}",
60
70
  "auth": "certificate"
61
71
  }
62
72
  }
73
+
74
+ def _port_in_use(self, port: int, host: str = "127.0.0.1") -> bool:
75
+ try:
76
+ with socket.create_connection((host, port), timeout=0.5):
77
+ return True
78
+ except Exception:
79
+ return False
80
+
81
+ def _pids_on_port(self, port: int) -> List[int]:
82
+ pids: List[int] = []
83
+ try:
84
+ for proc in psutil.process_iter(attrs=["pid", "connections"]):
85
+ for c in proc.connections(kind="inet"):
86
+ if c.laddr and c.laddr.port == port:
87
+ pids.append(proc.pid)
88
+ break
89
+ except Exception:
90
+ pass
91
+ return list(set(pids))
92
+
93
+ def ensure_ports_free(self, ports: List[int]) -> None:
94
+ for port in ports:
95
+ pids = self._pids_on_port(port)
96
+ for pid in pids:
97
+ try:
98
+ psutil.Process(pid).terminate()
99
+ except Exception:
100
+ pass
101
+ time.sleep(0.3)
102
+ for pid in pids:
103
+ try:
104
+ if psutil.pid_exists(pid):
105
+ psutil.Process(pid).kill()
106
+ except Exception:
107
+ pass
108
+
109
+ def wait_for_http(self, url: str, timeout_sec: float = 8.0) -> bool:
110
+ end = time.time() + timeout_sec
111
+ candidates = ["/health", "/proxy/health"]
112
+ while time.time() < end:
113
+ for path in candidates:
114
+ health_url = url.rstrip("/") + path
115
+ try:
116
+ resp = requests.get(health_url, timeout=1.0, verify=False)
117
+ if resp.status_code == 200:
118
+ return True
119
+ except Exception:
120
+ pass
121
+ time.sleep(0.2)
122
+ return False
123
+
124
+ def wait_for_port(self, port: int, timeout_sec: float = 8.0) -> bool:
125
+ end = time.time() + timeout_sec
126
+ while time.time() < end:
127
+ if self._port_in_use(port):
128
+ return True
129
+ time.sleep(0.2)
130
+ return False
131
+
132
+ def get_all_ports(self) -> List[int]:
133
+ ports = [self.proxy_port]
134
+ for cfg in self.configs.values():
135
+ ports.append(cfg["port"])
136
+ return list(sorted(set(ports)))
137
+
138
+ def _validate_file(self, base: Path, path_value: Optional[str]) -> Tuple[bool, str]:
139
+ if not path_value:
140
+ return True, ""
141
+ p = Path(path_value)
142
+ if not p.is_absolute():
143
+ p = base / p
144
+ return p.exists(), str(p)
145
+
146
+ def validate_config_files(self) -> bool:
147
+ ok = True
148
+ base = Path.cwd()
149
+ missing: List[str] = []
150
+ for name, cfg in self.configs.items():
151
+ cfg_path = Path(cfg["config"]).resolve()
152
+ try:
153
+ with open(cfg_path, "r", encoding="utf-8") as f:
154
+ data = json.load(f)
155
+ ssl = data.get("ssl", {})
156
+ for key in ("cert_file", "key_file", "ca_cert"):
157
+ exists, abs_path = self._validate_file(base, ssl.get(key))
158
+ if ssl.get("enabled") and key in ("cert_file", "key_file") and not exists:
159
+ ok = False
160
+ missing.append(f"{name}:{key} -> {abs_path}")
161
+ sec = data.get("security", {})
162
+ perms = sec.get("permissions", {})
163
+ exists, abs_path = self._validate_file(base, perms.get("roles_file"))
164
+ if sec.get("enabled") and perms.get("enabled") and not exists:
165
+ ok = False
166
+ missing.append(f"{name}:roles_file -> {abs_path}")
167
+ except Exception as e:
168
+ ok = False
169
+ missing.append(f"{name}: cannot read {cfg_path} ({e})")
170
+ if not ok:
171
+ print("❌ CONFIG VALIDATION FAILED. Missing files:")
172
+ for m in missing:
173
+ print(" -", m)
174
+ else:
175
+ print("✅ Configuration file paths validated")
176
+ return ok
63
177
  def check_prerequisites(self) -> bool:
64
178
  """Check if all prerequisites are met."""
65
179
  print("🔍 Checking prerequisites...")
@@ -69,7 +183,7 @@ class SecurityTestRunner:
69
183
  return False
70
184
  # Check if certificates exist
71
185
  cert_files = [
72
- "certs/mcp_proxy_adapter_test_ca_ca.crt",
186
+ "certs/mcp_proxy_adapter_ca_ca.crt",
73
187
  "certs/localhost_server.crt",
74
188
  "keys/localhost_server.key"
75
189
  ]
@@ -87,11 +201,26 @@ class SecurityTestRunner:
87
201
  """Start a server in background."""
88
202
  try:
89
203
  print(f"🚀 Starting {name} server on port {port}...")
204
+ # Ensure the port is free just before starting
205
+ self.ensure_ports_free([port])
206
+ if self._port_in_use(port):
207
+ print(f"⚠️ Port {port} still busy, waiting...")
208
+ if not self.wait_for_port(port, timeout_sec=1.5):
209
+ # After wait_for_port True means busy; we invert logic here
210
+ pass
211
+ # If still busy after attempts, abort this server start
212
+ if self._port_in_use(port):
213
+ print(f"❌ Port {port} is in use, cannot start {name}")
214
+ return None
90
215
  # Start server in background
216
+ logs_dir = Path("logs"); logs_dir.mkdir(exist_ok=True)
217
+ log_path = logs_dir / f"{name}.log"
218
+ log_file = open(log_path, "wb")
219
+ self.server_logs[name] = log_file
91
220
  process = subprocess.Popen([
92
221
  sys.executable, "-m", "mcp_proxy_adapter.main",
93
222
  "--config", config_path
94
- ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
223
+ ], stdout=log_file, stderr=subprocess.STDOUT)
95
224
  # Wait a bit for server to start
96
225
  time.sleep(3)
97
226
  # Check if process is still running
@@ -99,10 +228,7 @@ class SecurityTestRunner:
99
228
  print(f"✅ {name} server started (PID: {process.pid})")
100
229
  return process
101
230
  else:
102
- stdout, stderr = process.communicate()
103
- print(f"❌ Failed to start {name} server:")
104
- print(f"STDOUT: {stdout.decode()}")
105
- print(f"STDERR: {stderr.decode()}")
231
+ print(f"❌ Failed to start {name} server (see logs/{name}.log)")
106
232
  return None
107
233
  except Exception as e:
108
234
  print(f"❌ Error starting {name} server: {e}")
@@ -122,6 +248,13 @@ class SecurityTestRunner:
122
248
  process.wait()
123
249
  except Exception as e:
124
250
  print(f"❌ Error stopping {name} server: {e}")
251
+ finally:
252
+ try:
253
+ lf = self.server_logs.pop(name, None)
254
+ if lf:
255
+ lf.close()
256
+ except Exception:
257
+ pass
125
258
 
126
259
  def start_proxy_server(self) -> bool:
127
260
  """Start the proxy server for server registration."""
@@ -134,27 +267,24 @@ class SecurityTestRunner:
134
267
  return False
135
268
 
136
269
  # Start proxy server
137
- cmd = [sys.executable, str(proxy_script), "--host", "127.0.0.1", "--port", "3004"]
270
+ cmd = [sys.executable, str(proxy_script), "--host", "127.0.0.1", "--port", str(self.proxy_port)]
271
+ logs_dir = Path("logs"); logs_dir.mkdir(exist_ok=True)
272
+ proxy_log_path = logs_dir / "proxy_server.log"
273
+ self.proxy_log = open(proxy_log_path, "wb")
138
274
  process = subprocess.Popen(
139
275
  cmd,
140
- stdout=subprocess.PIPE,
141
- stderr=subprocess.PIPE,
276
+ stdout=self.proxy_log,
277
+ stderr=subprocess.STDOUT,
142
278
  cwd=Path.cwd()
143
279
  )
144
280
 
145
- # Wait a bit for server to start
146
- time.sleep(2)
147
-
148
- # Check if process is still running
149
- if process.poll() is None:
281
+ # Check readiness
282
+ if process.poll() is None and self.wait_for_http(f"http://127.0.0.1:{self.proxy_port}"):
150
283
  self.proxy_server = process
151
284
  print("✅ Proxy server started successfully (PID: {})".format(process.pid))
152
285
  return True
153
286
  else:
154
- stdout, stderr = process.communicate()
155
- print("❌ Failed to start proxy server:")
156
- print("STDOUT:", stdout.decode())
157
- print("STDERR:", stderr.decode())
287
+ print("❌ Failed to start proxy server (see logs/proxy_server.log)")
158
288
  return False
159
289
 
160
290
  except Exception as e:
@@ -178,6 +308,12 @@ class SecurityTestRunner:
178
308
  print(f"❌ Error stopping proxy server: {e}")
179
309
  finally:
180
310
  self.proxy_server = None
311
+ try:
312
+ if self.proxy_log:
313
+ self.proxy_log.close()
314
+ self.proxy_log = None
315
+ except Exception:
316
+ pass
181
317
  async def test_server(self, name: str, config: Dict[str, Any]) -> List[TestResult]:
182
318
  """Test a specific server configuration."""
183
319
  print(f"\n🧪 Testing {name} server...")
@@ -209,16 +345,23 @@ class SecurityTestRunner:
209
345
  """Run tests against all server configurations."""
210
346
  print("🚀 Starting comprehensive security testing")
211
347
  print("=" * 60)
212
- # Start all servers
348
+ # Start all servers with verification and abort on failure
213
349
  for name, config in self.configs.items():
214
350
  process = self.start_server(name, config["config"], config["port"])
215
- if process:
216
- self.servers[name] = process
351
+ if not process:
352
+ print(f"❌ {name} failed to start. Aborting.")
353
+ return {}
354
+ url = config["url"]
355
+ ready = False
356
+ if name == "mtls":
357
+ ready = self.wait_for_port(config["port"], timeout_sec=8.0)
217
358
  else:
218
- print(f"⚠️ Skipping tests for {name} due to startup failure")
219
- # Wait for all servers to be ready
220
- print("\n⏳ Waiting for servers to be ready...")
221
- time.sleep(5)
359
+ ready = self.wait_for_http(url, timeout_sec=8.0)
360
+ if not ready:
361
+ print(f" {name} did not become ready. Aborting.")
362
+ return {}
363
+ self.servers[name] = process
364
+ print("\n✅ All servers started and verified. Proceeding to client tests...")
222
365
  # Test each server
223
366
  all_results = {}
224
367
  for name, config in self.configs.items():
@@ -320,6 +463,14 @@ class SecurityTestRunner:
320
463
  if not self.check_prerequisites():
321
464
  return False
322
465
 
466
+ # Free ports before run
467
+ print("\n🧹 Freeing ports before startup...")
468
+ self.ensure_ports_free(self.get_all_ports())
469
+
470
+ # Validate config file paths
471
+ if not self.validate_config_files():
472
+ return False
473
+
323
474
  # Start proxy server first
324
475
  print("\n🚀 Starting proxy server for server registration...")
325
476
  if not self.start_proxy_server():