FrameBridge 0.3.1 community source release
This commit is contained in:
commit
b23819fff1
1354 files changed
+343716
No files matched your search
@@ -0,0 +1,323 @@
|
||||
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()
|
||||
Reference in new issue
Block a user