Files

324 lines
19 KiB
Python

import io
import os
from pathlib import Path
import signal
import sys
import time
import subprocess
import tempfile
import unittest
from unittest import mock
from framebridge_expanded import tunnel
ID = "tunnel_0123456789abcdef0123456789abcdef"
ROOT = Path(__file__).resolve().parents[1]
class ParserTests(unittest.TestCase):
def test_id_and_documented_commands(self):
commands = [
ID,
"'" + ID + "'",
"tunnel-client run --control-plane.tunnel-id " + ID,
"/opt/client/tunnel-client run --control-plane.tunnel-id=" + ID + " --control-plane.api-key env:OLD_KEY --mcp.server-url url=http://127.0.0.1:8898/mcp",
"tunnel-client init --profile demo --sample sample_mcp_remote_no_auth --tunnel-id " + ID + " --mcp-server-url http://127.0.0.1:8898/mcp",
"tunnel-client runtimes connect --alias demo --tunnel-id " + ID + " --runtime-api-key env:OLD_KEY --json",
"framebridge-original tunnel -- run --control-plane.tunnel-id " + ID,
]
for command in commands:
with self.subTest(command=command):
self.assertEqual(tunnel.parse_tunnel_input(command).tunnel_id, ID)
def test_rejections_never_echo_hidden_input(self):
secret = "sk-test-DO_NOT_DISCLOSE-0123456789"
bad = [
"sudo tunnel-client run --control-plane.tunnel-id " + ID,
"env OPENAI_API_KEY=" + secret + " tunnel-client run --control-plane.tunnel-id " + ID,
"tunnel-client run --control-plane.tunnel-id " + ID + "; echo bad",
"tunnel-client run --control-plane.tunnel-id $(" + secret + ")",
"tunnel-client run --control-plane.tunnel-id " + ID + " | cat",
"tunnel-client run --control-plane.tunnel-id " + ID + " --config /private/profile.yaml",
"tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.api-key " + secret,
"tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.api-key file:/private/key",
"tunnel-client run --control-plane.tunnel-id " + ID + " --control-plane.base-url https://example.invalid",
"tunnel-client run --control-plane.tunnel-id " + ID + " --tunnel-id " + ID,
"tunnel-client run --control-plane.tunnel-id",
"tunnel-client run --control-plane.tunnel-id '" + secret,
]
for command in bad:
with self.subTest(command=command):
with self.assertRaises(tunnel.TunnelInputError) as error:
tunnel.parse_tunnel_input(command)
self.assertNotIn(secret, str(error.exception))
self.assertNotIn(command, str(error.exception))
def test_rebuild_uses_packaged_binary_and_profile_endpoint(self):
spec = tunnel.parse_tunnel_input("tunnel-client run --control-plane.tunnel-id " + ID + " --mcp.server-url https://mcp.example.com/mcp")
args = tunnel.build_command("/packaged/tunnel-client", "/own/state", "http://127.0.0.1:20865/mcp", spec)
self.assertEqual(args[0], "/packaged/tunnel-client")
self.assertIn("url=http://127.0.0.1:20865/mcp", args)
self.assertNotIn("https://mcp.example.com/mcp", args)
self.assertIn("env:" + tunnel.KEY_ENV, args)
def test_inherited_runtime_settings_removed_without_global_mutation(self):
original = {"PATH": "/usr/bin", "DISPLAY": ":77", "HTTPS_PROXY": "http://proxy.example", "CA_BUNDLE": "/ca.pem",
"TUNNEL_CLIENT_CONFIG": "/production/config", "CONTROL_PLANE_TUNNEL_ID": "production",
"MCP_SERVER_URL": "https://production", "MCP_COMMAND": "production-command",
"HEALTH_URL_FILE": "/production/health", "LOG_HTTP_RAW_UNSAFE": "true",
"CLOUDFLARED_TUNNEL_TOKEN": "not-used", "OPENAI_API_KEY": "not-used"}
snapshot = dict(original)
env = tunnel.child_environment("FAKE_RUNTIME_KEY", original)
self.assertEqual(original, snapshot)
for key in snapshot:
if key.startswith(tunnel._RUNTIME_PREFIXES):
self.assertNotIn(key, env)
self.assertEqual(env["HTTPS_PROXY"], original["HTTPS_PROXY"])
self.assertEqual(env[tunnel.KEY_ENV], "FAKE_RUNTIME_KEY")
def test_child_default_profile_paths_are_private_and_scoped(self):
with tempfile.TemporaryDirectory(prefix="tunnel-test-", dir=ROOT / "qa") as td:
env = tunnel.child_environment("FAKE_ONLY", {"HOME": "/old/home", "XDG_CONFIG_HOME": "/old/config"}, state=Path(td))
self.assertEqual(env["HOME"], str(Path(td) / "chatgpt-home"))
self.assertEqual(env["XDG_CONFIG_HOME"], str(Path(td) / "chatgpt-home/.config"))
self.assertEqual(Path(env["TMPDIR"]).stat().st_mode & 0o777, 0o700)
self.assertFalse(any(p.is_file() for p in Path(td).rglob("*")))
def test_saved_public_context_is_reused(self):
public = {"tunnel_id": ID, "control_plane_organization_id": "org_test", "control_plane_url_path": "/v1/tunnel"}
with mock.patch.object(tunnel, "_hidden", side_effect=["", "FAKE_ONLY"]), mock.patch("framebridge_expanded.connection_background.run_foreground", return_value=0) as run:
rc = tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", public, output=io.StringIO(), foreground=True)
self.assertEqual(rc, 0)
self.assertIn("org_test", run.call_args.args[0])
self.assertIn("/v1/tunnel", run.call_args.args[0])
def test_non_tty_fails_before_secret_prompt(self):
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=False), mock.patch.object(tunnel.getpass, "getpass") as prompt:
with self.assertRaises(tunnel.TunnelInputError):
tunnel._hidden("Hidden: ")
prompt.assert_not_called()
class ForegroundTests(unittest.TestCase):
def command(self, code):
return [sys.executable, "-u", "-c", code,
"--control-plane.tunnel-id", ID,
"--mcp.server-url", "url=http://127.0.0.1:20865/mcp"]
def test_fake_key_only_in_child_environment_and_private_log(self):
secret = "FAKE_RUNTIME_ONLY_0123456789"
seen = {}
real_popen = subprocess.Popen
def spawn(args, **kwargs):
seen["args"] = list(args)
seen["env"] = dict(kwargs["env"])
return real_popen(args, **kwargs)
code = "import os,time;print('TECHNICAL_DEBUG '+os.environ['" + tunnel.KEY_ENV + "'],flush=True);time.sleep(.3)"
with tempfile.TemporaryDirectory(prefix="tunnel-output-") as td:
output = io.StringIO()
with mock.patch("framebridge_expanded.connection_monitor.probe", return_value=("ready", "Verified synthetic test state")):
result = tunnel.run_foreground(self.command(code), secret, state=td, output=output, popen=spawn)
data = (Path(td) / "logs/chatgpt/connection.log").read_text()
self.assertEqual(result, 0)
self.assertEqual(seen["env"][tunnel.KEY_ENV], secret)
self.assertNotIn(secret, repr(seen["args"]))
self.assertNotIn(secret, output.getvalue())
self.assertNotIn("TECHNICAL_DEBUG", output.getvalue())
self.assertIn("TECHNICAL_DEBUG [REDACTED]", data)
self.assertNotIn(secret, data)
self.assertIn("[OK]", output.getvalue())
self.assertIn("successful tool call", output.getvalue())
self.assertEqual((Path(td) / "logs/chatgpt/connection.log").stat().st_mode & 0o777, 0o600)
def test_ctrl_c_stops_only_owned_child(self):
real_popen = subprocess.Popen
seen = {}
def spawn(args, **kwargs):
seen["process"] = real_popen(args, **kwargs)
return seen["process"]
with tempfile.TemporaryDirectory(prefix="tunnel-stop-") as td:
with mock.patch("framebridge_expanded.connection_monitor.probe", side_effect=KeyboardInterrupt):
rc = tunnel.run_foreground(self.command("import time;time.sleep(60)"), "FAKE_ONLY", state=td,
output=io.StringIO(), popen=spawn)
self.assertEqual(rc, 130)
self.assertIsNotNone(seen["process"].poll())
self.assertFalse(any((Path(td) / "connection-runtime").iterdir()))
def test_interactive_flow_saves_only_public_values(self):
secret = "FAKE_RUNTIME_ONLY_0123456789"
saved = []
output = io.StringIO()
with mock.patch.object(tunnel, "_hidden", side_effect=[ID, secret]), mock.patch("framebridge_expanded.connection_background.run_foreground", return_value=0) as run:
rc = tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", {"account_label": "test@example.com"}, saved.append, output=output, foreground=True)
self.assertEqual(rc, 0)
self.assertEqual(saved[0]["tunnel_id"], ID)
self.assertNotIn(secret, repr(saved))
self.assertNotIn(secret, output.getvalue())
self.assertEqual(run.call_args.args[1], secret)
self.assertNotIn(secret, repr(run.call_args.args[0]))
class ManagedWizardTests(unittest.TestCase):
@staticmethod
def ready():
return {"configured": True, "enabled": True, "service_active": True,
"service_state": "active", "runtime": {"running": True, "ready": True},
"connection": {"alive": True, "verified": True, "state": "ready"}}
def invoke(self, configure, *, public=None, output=None, wait_timeout=0):
return tunnel.connect_chatgpt("/binary", "/state", "http://127.0.0.1:20865/mcp", public,
output=output or io.StringIO(), configure_connection=configure,
wait_timeout=wait_timeout)
def test_three_fields_are_ordered_and_only_public_settings_are_committed(self):
events = []
def label(prompt):
events.append(prompt)
return "test@example.com"
answers = iter([ID, "SYNTHETIC-MCP-ACCESS-KEY"])
def hidden(prompt):
events.append(prompt)
return next(answers)
seen = []
def configure(state, public, credential):
events.append("commit")
seen.append((state, public, credential))
return self.ready()
output = io.StringIO()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", side_effect=label), mock.patch.object(tunnel, "_hidden", side_effect=hidden):
rc = self.invoke(configure, output=output)
self.assertEqual(0, rc)
self.assertTrue(events[0].startswith("ChatGPT account (email label)"))
self.assertTrue(events[1].startswith("Tunnel ID (hidden"))
self.assertEqual("MCP server access key (hidden): ", events[2])
self.assertEqual("commit", events[3])
self.assertEqual("test@example.com", seen[0][1]["account_label"])
self.assertEqual(ID, seen[0][1]["tunnel_id"])
self.assertNotIn("SYNTHETIC-MCP-ACCESS-KEY", repr(seen[0][1]))
self.assertNotIn("SYNTHETIC-MCP-ACCESS-KEY", output.getvalue())
self.assertEqual("Connecting...\n[OK] Connected.\n", output.getvalue())
def test_cancel_at_each_field_never_calls_committer(self):
for field in range(3):
with self.subTest(field=field):
configure = mock.Mock()
label = mock.Mock(return_value="test@example.com")
hidden = mock.Mock(side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"])
if field == 0:
label.side_effect = KeyboardInterrupt
elif field == 1:
hidden.side_effect = KeyboardInterrupt
else:
hidden.side_effect = [ID, KeyboardInterrupt]
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", label), mock.patch.object(tunnel, "_hidden", hidden):
rc = self.invoke(configure)
self.assertEqual(130, rc)
configure.assert_not_called()
def test_full_command_in_normal_hidden_id_field_is_rejected_without_echo(self):
configure = mock.Mock()
pasted = "tunnel-client run --tunnel-id " + ID + " --api-key SYNTHETIC-PASTED-SECRET"
output = io.StringIO()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", return_value=pasted) as hidden:
with self.assertRaises(tunnel.TunnelInputError) as error:
self.invoke(configure, output=output)
self.assertNotIn("SYNTHETIC-PASTED-SECRET", str(error.exception) + output.getvalue())
self.assertEqual(1, hidden.call_count)
configure.assert_not_called()
def test_invalid_key_does_not_commit_and_is_not_echoed(self):
configure = mock.Mock()
output = io.StringIO()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC INVALID KEY"]):
with self.assertRaises(tunnel.TunnelInputError) as error:
self.invoke(configure, output=output)
self.assertNotIn("SYNTHETIC INVALID KEY", str(error.exception) + output.getvalue())
configure.assert_not_called()
def test_saved_public_defaults_preserve_tunnel_context_but_key_is_requested(self):
public = {"account_label": "test@example.com", "tunnel_id": ID,
"control_plane_organization_id": "org_test", "control_plane_url_path": "/v1/tunnel"}
configure = mock.Mock(return_value=self.ready())
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value=""), mock.patch.object(tunnel, "_hidden", side_effect=["", "SYNTHETIC-MCP-ACCESS-KEY"]) as hidden:
self.assertEqual(0, self.invoke(configure, public=public))
self.assertEqual(2, hidden.call_count)
self.assertEqual(public, configure.call_args.args[1])
def test_pending_returns_prompt_nonzero_without_false_success_or_reboot(self):
pending = self.ready()
pending["connection"] = {"alive": True, "verified": False, "state": "connecting"}
output = io.StringIO()
self.assertEqual(1, tunnel._wait_managed_connection("/state", pending, output, wait_timeout=0))
self.assertIn("[PENDING]", output.getvalue())
self.assertNotIn("[OK]", output.getvalue())
self.assertNotIn("reboot", output.getvalue().lower())
def test_failed_service_cannot_use_stale_verified_connection_as_success(self):
for failure in ({"service_state": "failed", "service_active": False}, {"service_state": "failed", "service_active": True}, {"worker": {"state": "failed"}}):
with self.subTest(failure=failure):
state = self.ready()
state.update(failure)
output = io.StringIO()
self.assertEqual(1, tunnel._wait_managed_connection("/state", state, output, wait_timeout=0))
self.assertIn("[ERROR]", output.getvalue())
self.assertNotIn("[OK]", output.getvalue())
def test_readiness_is_polled_and_key_is_requested_only_once(self):
from framebridge_expanded import autostart
starting = self.ready()
starting["connection"] = {"alive": False, "verified": False, "state": "connecting"}
configure = mock.Mock(return_value=starting)
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]) as hidden, mock.patch.object(tunnel.time, "sleep"), mock.patch.object(autostart, "status", return_value=self.ready()) as status:
self.assertEqual(0, self.invoke(configure, wait_timeout=2))
self.assertEqual(2, hidden.call_count)
self.assertEqual("MCP server access key (hidden): ", hidden.call_args.args[0])
status.assert_called_once_with("/state")
def test_interrupt_during_wait_reports_saved_background_connection(self):
from framebridge_expanded import autostart
starting = self.ready()
starting["connection"] = {"alive": False, "verified": False, "state": "connecting"}
output = io.StringIO()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]), mock.patch.object(tunnel.time, "sleep"), mock.patch.object(autostart, "status", side_effect=KeyboardInterrupt):
self.assertEqual(130, self.invoke(mock.Mock(return_value=starting), output=output, wait_timeout=2))
self.assertIn("continues in the background", output.getvalue())
self.assertNotIn("Connection cancelled", output.getvalue())
def test_interrupt_after_commit_does_not_report_connection_cancelled(self):
marker = {"configured": True, "enabled": True, "interrupted_after_commit": True}
configure = mock.Mock(return_value=marker)
output = io.StringIO()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=True), mock.patch("builtins.input", return_value="test@example.com"), mock.patch.object(tunnel, "_hidden", side_effect=[ID, "SYNTHETIC-MCP-ACCESS-KEY"]):
self.assertEqual(130, self.invoke(configure, output=output))
self.assertIn("continues in the background", output.getvalue())
self.assertNotIn("cancelled", output.getvalue().lower())
self.assertNotIn("[OK]", output.getvalue())
def test_resume_status_interrupted_after_commit_is_not_reported_cancelled(self):
marker = {"configured": True, "enabled": True, "interrupted_after_commit": True}
output = io.StringIO()
self.assertEqual(130, tunnel.resume_managed_connection("/state", mock.Mock(return_value=marker), output=output, wait_timeout=0))
self.assertIn("continues in the background", output.getvalue())
self.assertNotIn("cancelled", output.getvalue().lower())
def test_non_tty_fails_before_any_input_or_commit(self):
configure = mock.Mock()
with mock.patch.object(tunnel.sys.stdin, "isatty", return_value=False), mock.patch("builtins.input") as label, mock.patch.object(tunnel, "_hidden") as hidden:
with self.assertRaises(tunnel.TunnelInputError):
self.invoke(configure)
label.assert_not_called()
hidden.assert_not_called()
configure.assert_not_called()
def test_resume_uses_no_setup_prompts(self):
resume = mock.Mock(return_value=self.ready())
with mock.patch("builtins.input") as label, mock.patch.object(tunnel, "_hidden") as hidden:
self.assertEqual(0, tunnel.resume_managed_connection("/state", resume, output=io.StringIO(), wait_timeout=0))
label.assert_not_called()
hidden.assert_not_called()
resume.assert_called_once_with("/state")
if __name__ == "__main__":
unittest.main()