"""Server entry tests: outbound-only config and run() wiring.""" from __future__ import annotations import unittest from importlib import import_module from unittest.mock import Mock, patch gateway_module = import_module(f"{__package__}.server.http") load_config = gateway_module.load_config class OutboundConfigTests(unittest.TestCase): def test_platform_url_and_key_are_required(self) -> None: config = load_config({ "CREATOR_PLATFORM_URL": "http://platform.example:8080", "CREATOR_GATEWAY_KEY": "0123456789abcdef", "BROWSER_PATH": "/bin/true", }) self.assertEqual(config["platform_url"], "http://platform.example:8080") self.assertEqual(config["access_key"], "0123456789abcdef") self.assertNotIn("listen", config) self.assertNotIn("token", config) def test_platform_url_must_be_http(self) -> None: with self.assertRaises(ValueError): load_config({ "CREATOR_PLATFORM_URL": "ftp://platform.example", "CREATOR_GATEWAY_KEY": "0123456789abcdef", "BROWSER_PATH": "/bin/true", }) def test_platform_url_must_not_embed_agent_path(self) -> None: with self.assertRaises(ValueError): load_config({ "CREATOR_PLATFORM_URL": "http://platform.example:8080/v1/agent", "CREATOR_GATEWAY_KEY": "0123456789abcdef", "BROWSER_PATH": "/bin/true", }) def test_access_key_minimum_length(self) -> None: with self.assertRaises(ValueError): load_config({ "CREATOR_PLATFORM_URL": "http://platform.example:8080", "CREATOR_GATEWAY_KEY": "short", "BROWSER_PATH": "/bin/true", }) def test_trailing_slash_platform_url_is_normalized(self) -> None: config = load_config({ "CREATOR_PLATFORM_URL": "http://platform.example:8080/", "CREATOR_GATEWAY_KEY": "0123456789abcdef", "BROWSER_PATH": "/bin/true", }) self.assertEqual(config["platform_url"], "http://platform.example:8080") class RunWiringTests(unittest.TestCase): def test_run_builds_agent_from_config_and_stops_on_signal(self) -> None: config = load_config({ "CREATOR_PLATFORM_URL": "http://platform.example:8080", "CREATOR_GATEWAY_KEY": "0123456789abcdef", "BROWSER_PATH": "/bin/true", }) agent_instances = [] class FakeAgent: def __init__(self, gateway, agent_config) -> None: self.gateway = gateway self.config = agent_config self.ran = False self.stopped = False agent_instances.append(self) def run(self) -> None: self.ran = True def stop(self) -> None: self.stopped = True registered = {} def record_signal(signum, handler): registered[signum] = handler with patch.object(gateway_module, "OutboundAgent", FakeAgent), \ patch.object(gateway_module, "NativeRuntimeManager", Mock()), \ patch.object(gateway_module.signal, "signal", record_signal): exit_code = gateway_module.run(config) self.assertEqual(exit_code, 0) self.assertEqual(len(agent_instances), 1) self.assertEqual(agent_instances[0].config.platform_url, config["platform_url"]) self.assertEqual(agent_instances[0].config.access_key, config["access_key"]) self.assertTrue(agent_instances[0].ran) # SIGINT/SIGTERM 都注册了停止回调,触发后 agent 收到 stop self.assertIn(gateway_module.signal.SIGINT, registered) self.assertIn(gateway_module.signal.SIGTERM, registered) registered[gateway_module.signal.SIGTERM](15, None) registered[gateway_module.signal.SIGINT](2, None) self.assertTrue(agent_instances[0].stopped) # Gateway 的业务方法必须仍挂在 agent 上(agent.gateway) self.assertIsNotNone(agent_instances[0].gateway.subscriptions) if __name__ == "__main__": unittest.main()