from __future__ import annotations import base64 import io import json import socket import threading import unittest from collections import deque from contextlib import contextmanager from importlib import import_module from typing import Any, cast from unittest.mock import Mock, patch import websocket from .docker_client import ( BINDING_VERSION_LABEL, BROWSER_NETWORK_ROLE, GATEWAY_MEMBER_LABEL, MANAGED_LABEL, NETWORK_EXIT_LABEL, NETWORK_ID_LABEL, NETWORK_ROLE_LABEL, RESERVATION_LABEL, RESERVATION_OWNER_LABEL, RUNTIME_ID_LABEL, AliasReservationManager, DockerClient, DockerError, DockerResponse, GenerationConflict, NetworkSetupError, TenantNetworkGeneration, UnmanagedContainer, split_image_ref, tenant_network_name, ) from .douyin import ( BrowserResponse, CDPConnection, DouyinBrowser, DouyinError, DouyinSubscription, SubscriptionManager, ack_expression, action_expression, detect_challenge, im_expression, install_expression, is_douyin_url, normalize_notice, notice_ids, wait_expression, ) from .proxy import ( MemoryProxy, ProxyExit, ProxyRegistry, _copy_until_close, _dial_http_proxy, _dial_socks4, _dial_socks5, _is_ipv4, _parse_request, _read_request, _read_status, _recv_exact, ) gateway_module = import_module(f"{__package__}.gateway") douyin_module = import_module(f"{__package__}.douyin") proxy_module = import_module(f"{__package__}.proxy") docker_client_module = import_module(f"{__package__}.docker_client") Gateway = gateway_module.Gateway RequestError = gateway_module.RequestError browser_tmpfs = gateway_module.browser_tmpfs decode_generation = gateway_module.decode_generation json_bytes = gateway_module.json_bytes load_config = gateway_module.load_config split_listen_address = gateway_module.split_listen_address valid_douyin_url = gateway_module.valid_douyin_url validate_create = gateway_module.validate_create parse_proxy_exit = gateway_module.parse_proxy_exit validate_proxy_exit = gateway_module.validate_proxy_exit validate_proxy_restore = gateway_module.validate_proxy_restore valid_douyin_generation = gateway_module.valid_douyin_generation valid_account_key_query = gateway_module.valid_account_key_query numeric_cursor = gateway_module.numeric_cursor proxy_port = gateway_module.proxy_port has_control = gateway_module.has_control class FakeSocket: def __init__(self, messages: list[object]) -> None: self.messages = list(messages) self.sent: list[bytes] = [] self.timeout = 0.0 def send(self, data: bytes) -> None: self.sent.append(data) def recv(self) -> str: if not self.messages: raise TimeoutError("no more messages") return json.dumps(self.messages.pop(0)) def settimeout(self, value: float) -> None: self.timeout = value def close(self) -> None: return None class FakeConnection: def __init__(self, values: list[object]) -> None: self.values = list(values) def evaluate(self, expression: str) -> object: del expression if not self.values: raise DouyinError("no fake response") return self.values.pop(0) class FakeDocker: def __init__(self, containers: list[dict] | None = None) -> None: self.containers = containers or [] def container_network_address(self, container_id: str, network_id: str) -> str: del container_id, network_id return "192.0.2.10" def request( self, method: str, path: str, body: object | None = None ) -> DockerResponse: del body if method == "GET" and path.startswith("/containers/json"): return DockerResponse(200, "OK", json.dumps(self.containers).encode()) return DockerResponse(404, "Not Found", b"") class GatewayValidationTests(unittest.TestCase): def test_create_and_generation_validation(self) -> None: value = { "alias": "safe-account", "name": "Safe account", "image": "creatorhub/browser:latest", "cmd": ["about:blank"], "volume": "creatorhub-safe-account", "binding_version": 1, "stopped": True, } validate_create(value) self.assertEqual(value["network_exit"], ProxyExit("", "", 0)) self.assertEqual(value["network_exit_id"], "") self.assertEqual( decode_generation( { "binding_version": 1, "runtime_id": "runtime-not-found", "network_id": "network-id", }, False, False, )["runtime_id"], "runtime-not-found", ) with self.assertRaises(RequestError): validate_create( {**value, "cmd": ["--proxy-server=http://x", "about:blank"]} ) with self.assertRaises(RequestError): decode_generation( {"binding_version": 1, "runtime_id": 4, "network_id": "n"}, True, True ) def test_config_and_urls(self) -> None: self.assertEqual(split_listen_address(":8081"), ("", 8081)) self.assertEqual(split_listen_address("[::1]:8081"), ("::1", 8081)) with self.assertRaises(ValueError): split_listen_address("missing-port") config = load_config( { "LISTEN_ADDR": ":8081", "DOCKER_SOCKET": "/var/run/docker.sock", "BROWSER_NETWORK": "creatorhub_browser", "GATEWAY_TOKEN": "0123456789abcdef", } ) self.assertEqual(config["listen"], ("", 8081)) tmp_mount = next(path for path in browser_tmpfs() if path.endswith("tmp")) self.assertTrue(browser_tmpfs()[tmp_mount].startswith("rw,")) self.assertTrue( valid_douyin_url( "https://www.douyin.com/aweme/v1/web/user/profile/self/?aid=6383&device_platform=webapp" ) ) self.assertFalse(valid_douyin_url("https://www.douyin.com.evil/")) self.assertTrue(is_douyin_url("https://www.douyin.com/video/123")) self.assertFalse(is_douyin_url("https://www.douyin.com.evil/video/123")) def test_http_routes_and_body_validation(self) -> None: handler = gateway_module.GatewayHandler.__new__(gateway_module.GatewayHandler) gateway = Mock() gateway.list_browsers.return_value = [] gateway.douyin_identity.return_value = {"uid": "123"} gateway.douyin_action.return_value = {"status": "succeeded"} gateway.poll_douyin_events.return_value = [] server = Mock() server.gateway = gateway cast(Any, handler).server = server cast(Any, handler).server_as_gateway = lambda: server self.assertEqual(handler._route("GET", "/v1/browsers", {}, {}), []) self.assertEqual( handler._route("POST", "/v1/browsers", {}, {}), (201, gateway.create.return_value), ) handler._route( "DELETE", "/v1/browsers/safe", {}, {"binding_version": 1, "runtime_id": "a" * 64, "network_id": ""}, ) gateway.remove.assert_called_once_with( "safe", {"binding_version": 1, "runtime_id": "a" * 64, "network_id": ""} ) handler._route("POST", "/v1/browsers/safe/start", {}, {}) handler._route("POST", "/v1/browsers/safe/stop", {}, {}) handler._route("POST", "/v1/browsers/safe/proxy", {}, {}) with self.assertRaises(RequestError): handler._route("POST", "/v1/browsers/safe/douyin/cookies", {}, {}) handler._route("POST", "/v1/browsers/safe/douyin/get", {}, {}) handler._route("POST", "/v1/browsers/safe/douyin/identity", {}, {}) handler._route("POST", "/v1/browsers/safe/douyin/action", {}, {}) self.assertEqual( handler._route("GET", "/v1/browsers/safe/douyin/events", {}, {}), [] ) handler._route("POST", "/v1/browsers/safe/douyin/events", {}, {}) handler._route("DELETE", "/v1/browsers/safe/douyin/events", {}, {}) with self.assertRaises(RequestError): handler._route("GET", "/v1/unknown", {}, {}) cast(Any, handler).headers = {"Content-Length": "7"} cast(Any, handler).rfile = io.BytesIO(b'{"x":1}') self.assertEqual(handler._body(), {"x": 1}) cast(Any, handler).headers = {} with self.assertRaises(RequestError): handler._body() test_token = "x" * 16 cast(Any, handler).headers = {"Authorization": f"Bearer {test_token}"} server.gateway.token = test_token self.assertTrue(handler._authorized()) handler._respond = Mock() handler._handle_exception("/v1", RequestError("bad", 400)) handler._handle_exception("/v1", ValueError("bad")) self.assertEqual(handler._respond.call_count, 2) def test_get_event_route_reads_generation_body(self) -> None: handler = gateway_module.GatewayHandler.__new__(gateway_module.GatewayHandler) cast(Any, handler).path = "/v1/browsers/safe/douyin/events?wait=1" cast(Any, handler).headers = {"Content-Length": "67"} cast(Any, handler).rfile = io.BytesIO( b'{"binding_version":1,"runtime_id":"runtime","network_id":"network"}' ) cast(Any, handler)._authorized = lambda: True cast(Any, handler)._route = Mock(return_value=[]) cast(Any, handler)._respond = Mock() handler._dispatch("GET") cast(Any, handler)._route.assert_called_once_with( "GET", "/v1/browsers/safe/douyin/events", {"wait": ["1"]}, {"binding_version": 1, "runtime_id": "runtime", "network_id": "network"}, ) def test_validation_boundaries(self) -> None: self.assertEqual(proxy_port("http://docker-gateway:1234"), 1234) self.assertTrue(has_control("bad\nvalue")) exit_value = parse_proxy_exit( {"protocol": "http", "host": "proxy", "port": 8080} ) validate_proxy_exit(exit_value) with self.assertRaises(RequestError): validate_proxy_exit( parse_proxy_exit({"protocol": "ftp", "host": "proxy", "port": 21}) ) with self.assertRaises(RequestError): validate_proxy_exit(ProxyExit("http", "proxy", 0)) generation = { "binding_version": 1, "runtime_id": "a" * 64, "network_id": "b" * 64, "network_exit_id": "exit", } self.assertTrue(valid_douyin_generation(generation)) self.assertFalse( valid_douyin_generation({**generation, "binding_version": True}) ) self.assertTrue(valid_account_key_query({"account": ["account"]}, "account")) self.assertFalse(valid_account_key_query({"account": ["bad key"]}, "account")) self.assertTrue(numeric_cursor(["0"])) self.assertFalse(numeric_cursor(["-1"])) restore = { "binding_version": 1, "runtime_id": "a" * 64, "network_id": "b" * 64, "network_exit_id": "exit", "network_exit": {"protocol": "http", "host": "proxy", "port": 8080}, } validate_proxy_restore(restore, "safe") with self.assertRaises(RequestError): validate_proxy_restore({**restore, "network_exit_id": ""}, "safe") with self.assertRaises(RequestError): validate_proxy_restore({"network_exit_id": ""}, "safe") with self.assertRaises(RequestError): validate_create({"alias": "safe", "unknown": True}) with self.assertRaises(RequestError): validate_create( { "alias": "safe", "name": "Safe", "image": "bad image", "volume": "safe", "binding_version": 1, "cmd": ["about:blank"], } ) with self.assertRaises(ValueError): load_config({"GATEWAY_TOKEN": "short"}) self.assertFalse(valid_douyin_url("http://www.douyin.com/video/1")) self.assertFalse(valid_douyin_url("https://www.douyin.com/unknown")) with self.assertRaises(RequestError): decode_generation( {"binding_version": 1, "runtime_id": "a" * 64}, True, True ) def test_list_keeps_other_environments_when_one_network_is_missing(self) -> None: class PartialDocker(FakeDocker): def container_network_address( self, container_id: str, network_id: str ) -> str: if container_id == "broken": raise GenerationConflict("network attachment disappeared") return "192.0.2.10" containers = [ { "Id": "broken", "Labels": { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "broken-account", BINDING_VERSION_LABEL: "1", NETWORK_EXIT_LABEL: "", NETWORK_ID_LABEL: "b" * 64, }, "State": "running", "Status": "Up", }, { "Id": "healthy", "Labels": { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "healthy-account", BINDING_VERSION_LABEL: "1", NETWORK_EXIT_LABEL: "", NETWORK_ID_LABEL: "c" * 64, }, "State": "running", "Status": "Up", }, ] result = Gateway( cast(DockerClient, PartialDocker(containers)), "creatorhub_browser", "0123456789abcdef", "gateway", ).list_browsers() self.assertEqual( [item["alias"] for item in result], ["broken-account", "healthy-account"] ) self.assertEqual(result[0]["endpoint"], "") self.assertIn("error", result[0]) self.assertEqual(result[1]["endpoint"], "http://192.0.2.10:9222") def test_container_network_address(self) -> None: docker = DockerClient("/var/run/docker.sock") cast(Any, docker).request = lambda method, path: DockerResponse( 200, "OK", json.dumps( { "NetworkSettings": { "Networks": { "tenant": { "NetworkID": "network", "IPAddress": "192.0.2.20", } } } } ).encode(), ) self.assertEqual( docker.container_network_address("container", "network"), "192.0.2.20" ) cast(Any, docker).request = lambda method, path: DockerResponse( 200, "OK", b'{"NetworkSettings":{"Networks":{}}}' ) with self.assertRaises(GenerationConflict): docker.container_network_address("container", "network") def test_list_browsers_and_json(self) -> None: docker = FakeDocker( [ { "Id": "container-id", "Labels": { "io.creatorhub.managed": "true", "io.creatorhub.runtime-id": "safe-account", "io.creatorhub.binding-version": "2", "io.creatorhub.network-exit-id": "", "io.creatorhub.network-id": "b" * 64, }, "State": "running", "Status": "Up 1 second", } ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub_browser", "0123456789abcdef", "gateway", ) self.assertEqual(gateway.list_browsers()[0]["id"], "container-id") self.assertEqual( gateway.list_browsers()[0]["endpoint"], "http://192.0.2.10:9222" ) self.assertEqual( json_bytes({"text": "中文"}), b'{"text":"\xe4\xb8\xad\xe6\x96\x87"}' ) class CDPTests(unittest.TestCase): def test_connect_response_headers_are_drained(self) -> None: connection = ChunkSocket( [b"HTTP/1.1 200 Connection Established\r\n", b"X-Proxy: value\r\n\r\nTLS"] ) self.assertEqual(_read_status(cast(socket.socket, connection)), 200) def test_command_queues_events_for_wait_event(self) -> None: socket_ = FakeSocket( [ { "method": "Page.frameNavigated", "params": {"frame": {"id": "frame-1"}}, }, {"id": 1, "result": {}}, ] ) connection = CDPConnection(cast(websocket.WebSocket, socket_)) self.assertEqual(connection.command("Page.enable"), {}) event = connection.wait_event( "Page.frameNavigated", lambda params: params["frame"]["id"] == "frame-1" ) self.assertEqual(event["method"], "Page.frameNavigated") self.assertTrue(socket_.sent) def test_eof_status_is_a_failure(self) -> None: class Closed: def recv(self, size: int) -> bytes: del size return b"" with self.assertRaises(OSError): _read_status(cast(socket.socket, Closed())) def test_expression_markers_and_challenge(self) -> None: expression = im_expression({"text": "hello EXPECTED_UID_VALUE"}, "123") self.assertIn("hello EXPECTED_UID_VALUE", expression) self.assertIn( "https://www.douyin.com", action_expression({"action": "like_work"}) ) self.assertEqual(detect_challenge(429, "captcha"), "") self.assertEqual(detect_challenge(412, ""), "captcha") def test_notification_details_normalize_safe_targets(self) -> None: notice = { "nid_str": "9007199254740993", "user_id": "99491952055", "create_time": 1700000000, "aweme_id": "123456", "comment": { "from_user": [{"uid": "7654321"}], "comment": {"cid_str": "987654", "user": {"uid": "7654321"}}, }, } self.assertEqual( normalize_notice(notice), { "event_key": "9007199254740993", "event_type": "comment", "interactor_uid": "7654321", "comment_id": "987654", "work_id": "123456", "platform_event_at": "2023-11-14T22:13:20+00:00", }, ) favorite = {"nid_str": "7", "favorite": {"from_user": [{"uid": "1"}]}} self.assertIsNone(normalize_notice(favorite)) def test_notification_detail_retries_partial_response(self) -> None: details = [ {"nid_str": "1", "user_id": "123"}, {"nid_str": "2", "user_id": "123"}, ] payload = json.dumps({"status_code": 0, "notice_list_v2": details}) subscription = DouyinSubscription.__new__(DouyinSubscription) subscription.uid = "123" subscription.connection = cast( CDPConnection, FakeConnection( [ json.dumps( { "status": 200, "body": json.dumps( {"status_code": 0, "notice_list_v2": details[:1]} ), } ), json.dumps({"status": 200, "body": payload}), ] ), ) subscription._get_connection = lambda: subscription.connection with patch.object(douyin_module.time, "sleep"): self.assertEqual(len(subscription._details(["1", "2"])), 2) def test_notification_detail_rejects_unexpected_id(self) -> None: subscription = DouyinSubscription.__new__(DouyinSubscription) subscription.uid = "123" subscription.connection = cast( CDPConnection, FakeConnection( [ json.dumps( { "status": 200, "body": json.dumps( { "status_code": 0, "notice_list_v2": [ {"nid_str": "9", "user_id": "123"} ], } ), } ) ] ), ) subscription._get_connection = lambda: subscription.connection with self.assertRaises(DouyinError): subscription._details(["1"]) class BrowserCDP: def __init__(self, values: list[object]) -> None: self.values = list(values) self.commands: list[tuple[str, dict | None]] = [] self.events: list[str] = [] self.closed = False def command(self, method: str, params: dict | None = None) -> dict: self.commands.append((method, params)) if method == "Page.navigate": return {"frameId": "frame-1"} return {} def wait_event(self, method: str, predicate: object, timeout: float = 15.0) -> dict: del predicate, timeout self.events.append(method) return {"method": method} def evaluate(self, expression: str) -> object: del expression if not self.values: raise DouyinError("fake CDP value exhausted") return self.values.pop(0) def close(self) -> None: self.closed = True class FakeHTTPResponse: def __init__(self, status: int, body: bytes) -> None: self.status = status self.body = body def read(self, limit: int = -1) -> bytes: del limit return self.body class FakeHTTPConnection: def __init__(self, response: FakeHTTPResponse) -> None: self.response = response self.requested: list[tuple[str, str]] = [] self.closed = False def request( self, method: str, path: str, body: bytes | None = None, headers: dict[str, str] | None = None, ) -> None: del body, headers self.requested.append((method, path)) def getresponse(self) -> FakeHTTPResponse: return self.response def close(self) -> None: self.closed = True class BrowserTests(unittest.TestCase): def _with_connection(self, browser: DouyinBrowser, connection: BrowserCDP) -> None: @contextmanager def bound(alias: str): del alias yield connection cast(Any, browser).connection = bound def test_browser_fetch_uses_manually_logged_session(self) -> None: cdp = BrowserCDP( [ "https://www.douyin.com", {"status": 200, "body": "{}", "too_large": False}, ] ) browser = DouyinBrowser() self._with_connection(browser, cdp) response = browser.get( "safe", "https://www.douyin.com/aweme/v1/web/user/profile/self/?aid=6383" ) self.assertEqual(response.status, 200) self.assertNotIn("Network.setCookies", [method for method, _ in cdp.commands]) def test_media_download_is_browser_mediated_and_bounded(self) -> None: payload = base64.b64encode(b"video-bytes").decode("ascii") cdp = BrowserCDP( [ "complete", {"status": 200, "content_type": "video/mp4", "body": payload}, ] ) browser = DouyinBrowser() self._with_connection(browser, cdp) response = browser.get_media("safe", "https://www.douyin.com/video/123") self.assertEqual(response.status, 200) self.assertEqual(response.content_type, "video/mp4") self.assertEqual(base64.b64decode(response.body_base64), b"video-bytes") self.assertIn("Page.navigate", [method for method, _ in cdp.commands]) def test_connect_identity_and_actions(self) -> None: target = [ { "type": "page", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/1", } ] http = FakeHTTPConnection(FakeHTTPResponse(200, json.dumps(target).encode())) with ( patch.object( douyin_module.http.client, "HTTPConnection", return_value=http ), patch.object( douyin_module.websocket, "create_connection", return_value=FakeSocket([]), ), ): connection = DouyinBrowser(lambda alias: "http://127.0.0.1:9222")._connect( "safe" ) self.assertIsInstance(connection, CDPConnection) self.assertEqual(http.requested[0], ("GET", "/json/list")) expression = install_expression("safe", "123") self.assertIn("safe", expression) self.assertIn("old?.dispose?.()", expression) self.assertEqual( notice_ids( { "service": 20313, "payload": json.dumps( {"notices": [{"notice_id_str": "1", "effect_groups": [960]}]} ), } ), ["1"], ) self.assertEqual( notice_ids( { "service": 20003, "payload": json.dumps({"notice_type": 45, "notice_id_str": "2"}), } ), ["2"], ) browser = DouyinBrowser() cast(Any, browser).identity = lambda alias, expected: {"uid": expected} cast(Any, browser)._evaluate = lambda *args: { "status": "succeeded", "action": "followed", } preview = browser.action( "safe", "123", "dm", "456", text="hello", confirm=False ) self.assertEqual(preview["action"], "preview") self.assertEqual( browser.action("safe", "123", "like_work", work_id="789", confirm=False)[ "action" ], "preview", ) with self.assertRaises(DouyinError): browser.action("safe", "123", "dm", "456", text=" ", confirm=False) with self.assertRaises(DouyinError): browser.action("safe", "123", "follow", "bad", confirm=False) def test_browser_queue_is_retained_until_ack(self) -> None: wait = wait_expression("alpha") self.assertIn("delivered", wait) self.assertNotIn("splice(0)", wait) ack = ack_expression("__creatorhub_notice_sub_alpha", ["browser-1"]) self.assertIn("browser-1", ack) self.assertIn("__creatorhub_notice_sub_alpha", ack) def test_subscription_receipts_are_replayed_until_ack(self) -> None: subscription = DouyinSubscription.__new__(DouyinSubscription) subscription.uid = "123" subscription.queue = deque() subscription.condition = threading.Condition() subscription.stopped = threading.Event() subscription._put({"kind": "notice", "notice": {"event_key": "1"}}) first = subscription.poll(10, 0) second = subscription.poll(10, 0) self.assertEqual(first, second) self.assertTrue(first[0]["notice"]["gateway_received_at"].endswith("+00:00")) subscription.ack([first[0]["delivery_id"]]) self.assertEqual(subscription.poll(10, 0), []) def test_subscription_detail_failure_does_not_discard_siblings(self) -> None: bad = json.dumps( { "status": 200, "body": json.dumps( { "status_code": 0, "notice_list_v2": [{"nid_str": "9", "user_id": "123"}], } ), } ) good = json.dumps( { "status": 200, "body": json.dumps( { "status_code": 0, "notice_list_v2": [ { "nid_str": "2", "user_id": "123", "follow": {"from_user": [{"uid": "7"}]}, } ], } ), } ) subscription = DouyinSubscription.__new__(DouyinSubscription) subscription.uid = "123" subscription.queue = deque() subscription.condition = threading.Condition() subscription.stopped = threading.Event() subscription.connection = cast(CDPConnection, FakeConnection([bad, bad, good])) subscription._get_connection = lambda: subscription.connection subscription._handle( { "kind": "push", "service": 20313, "payload": json.dumps( { "notices": [ {"notice_id_str": "1", "effect_groups": [960]}, {"notice_id_str": "2", "effect_groups": [960]}, ] } ), } ) events = subscription.poll(10, 0) self.assertEqual(events[0]["kind"], "error") self.assertEqual(events[0]["event_key"], "1") self.assertEqual(events[1]["notice"]["event_key"], "2") def test_subscription_manager_and_queue(self) -> None: event = {"kind": "open"} subscription = DouyinSubscription.__new__(DouyinSubscription) subscription.uid = "123" subscription.queue = deque() subscription.condition = __import__("threading").Condition() subscription.stopped = __import__("threading").Event() subscription._handle(event) first = subscription.poll(10, 0) self.assertEqual(first[0]["kind"], "open") self.assertTrue(first[0]["delivery_id"]) subscription.queue = deque([{} for _ in range(1000)]) subscription._put({"kind": "new"}) self.assertEqual(subscription.queue[0]["kind"], "error") subscription.stopped.set() overflow = subscription.poll(10, 0) self.assertEqual(overflow[0]["kind"], "error") self.assertEqual(overflow[0]["reason"], "notification queue overflow") self.assertTrue(overflow[0]["delivery_id"]) self.assertEqual(len(overflow), 10) self.assertEqual(len(subscription.queue), 1000) subscription.ack([overflow[0]["delivery_id"]]) self.assertEqual(len(subscription.queue), 999) self.assertEqual(subscription.queue[0], {}) browser = DouyinBrowser() fake = Mock() fake.poll.return_value = [{"kind": "notice"}] with patch.object(douyin_module, "DouyinSubscription", return_value=fake): manager = SubscriptionManager(browser) self.assertTrue(manager.start("safe", "123")["connected"]) self.assertEqual(manager.poll("safe", 1, 0), [{"kind": "notice"}]) manager.stop("safe") manager.close() with self.assertRaises(DouyinError): manager.poll("safe", 1, 0) class ProxyTests(unittest.TestCase): def test_chunked_request_body_is_decoded_and_forwarded_with_length(self) -> None: request = ChunkSocket( [ ( b"POST http://example.test/a HTTP/1.1\r\nHost: example.test\r\n" b"Transfer-Encoding: chunked\r\n\r\n2\r\nab\r\n3;part=x\r\ncde\r\n0\r\nX-Trailer: yes\r\n\r\n" ) ] ) head, body = _read_request(cast(socket.socket, request)) self.assertEqual(body, b"abcde") proxy = MemoryProxy.__new__(MemoryProxy) proxy.exit = ProxyExit("socks5", "proxy", 1080) upstream = ChunkSocket([b""]) proxy.dial = lambda target, timeout=20.0: cast(socket.socket, upstream) with patch.object(proxy_module, "_copy_until_close"): proxy.forward_http( cast(socket.socket, ChunkSocket([])), "POST", "http://example.test/a", _parse_request(head)[2], body, ) sent = upstream.sent[0].decode("iso-8859-1") self.assertIn("Content-Length: 5", sent) self.assertNotIn("Transfer-Encoding:", sent) def test_registry_generation_and_shutdown(self) -> None: registry = ProxyRegistry() url, undo = registry.configure( "safe", 1, "127.0.0.1", 0, ProxyExit("http", "127.0.0.1", 8080), "network-1" ) port = int(url.rsplit(":", 1)[1]) self.assertFalse(registry.ready("safe", port, "container-1", "network-1")) self.assertTrue(registry.bind("safe", 1, url, "container-1", "network-1")) self.assertTrue(registry.ready("safe", port, "container-1", "network-1")) self.assertFalse(registry.remove("safe", 1, "container-2", "network-1")) undo() registry.close() def test_memory_proxy_can_close(self) -> None: proxy = MemoryProxy( "safe", 1, "127.0.0.1", 0, ProxyExit("http", "127.0.0.1", 8080), "network" ) self.assertGreater(proxy.listener.getsockname()[1], 0) proxy.close() def test_proxy_request_parser_and_copy_helpers(self) -> None: request = ChunkSocket( [ b"POST http://example.test/a HTTP/1.1\r\nContent-Length: 3\r\nHost: example.test\r\n\r\nabc" ] ) head, body = _read_request(cast(socket.socket, request)) self.assertEqual(body, b"abc") method, target, headers = _parse_request(head) self.assertEqual((method, target), ("POST", "http://example.test/a")) self.assertEqual(headers[0], ("Content-Length", "3")) source = ChunkSocket([b"one", b"", b"ignored"]) destination = ChunkSocket([]) _copy_until_close(cast(socket.socket, source), cast(socket.socket, destination)) self.assertEqual(b"".join(destination.sent), b"one") self.assertEqual( _recv_exact(cast(socket.socket, ChunkSocket([b"ab", b"cd"])), 4), b"abcd" ) self.assertTrue(_is_ipv4("127.0.0.1")) self.assertFalse(_is_ipv4("host.example")) with self.assertRaises(ValueError): _parse_request(b"BROKEN\r\n\r\n") def test_connect_payload_in_same_read_is_preserved(self) -> None: class GreedySocket(ChunkSocket): def recv(self, size: int) -> bytes: del size return self.chunks.pop(0) if self.chunks else b"" upstream = GreedySocket([b"HTTP/1.1 200 OK\r\nX-Test: yes\r\n\r\nTLS"]) with patch.object(proxy_module, "_open_host", return_value=upstream): result = _dial_http_proxy( ProxyExit("http", "proxy", 8080), "target:443", 1.0 ) self.assertEqual(_recv_exact(cast(socket.socket, result), 3), b"TLS") result.close() def test_http_and_socks_handshakes(self) -> None: http_socket = ChunkSocket([b"HTTP/1.1 200 Connection Established\r\n\r\n"]) with patch.object(proxy_module, "_open_host", return_value=http_socket): result = _dial_http_proxy( ProxyExit("http", "proxy", 8080, "u", "c"), "target:443", 1.0 ) self.assertIs(result, http_socket) self.assertIn(b"Proxy-Authorization: Basic dTpj", http_socket.sent[0]) socks4_socket = ChunkSocket([b"\x00\x5a\x00\x00\x00\x00\x00\x00"]) with patch.object(proxy_module, "_open_host", return_value=socks4_socket): self.assertIs( _dial_socks4(ProxyExit("socks4", "proxy", 1080), "127.0.0.1:80", 1.0), socks4_socket, ) socks5_socket = ChunkSocket( [b"\x05\x00", b"\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x50"] ) with patch.object(proxy_module, "_open_host", return_value=socks5_socket): self.assertIs( _dial_socks5(ProxyExit("socks5", "proxy", 1080), "127.0.0.1:80", 1.0), socks5_socket, ) with self.assertRaises(OSError): _dial_socks5(ProxyExit("socks5", "proxy", 1080), "host:0", 1.0) def test_proxy_forward_and_tunnel_paths(self) -> None: proxy = MemoryProxy.__new__(MemoryProxy) proxy.exit = ProxyExit("socks5", "proxy", 1080) upstream = ChunkSocket([b"reply", b""]) client = ChunkSocket([]) cast(Any, proxy).dial = lambda target, timeout=20.0: cast( socket.socket, upstream ) with patch.object(proxy_module, "_copy_until_close") as copy: proxy.forward_http( cast(socket.socket, client), "GET", "http://example.test/path?q=1", [("Host", "example.test")], b"", ) copy.assert_called_once() self.assertIn(b"GET /path?q=1 HTTP/1.1", upstream.sent[0]) proxy.exit = ProxyExit("http", "proxy", 8080) upstream = ChunkSocket([b""]) cast(Any, proxy).dial = lambda target, timeout=20.0: cast( socket.socket, upstream ) with ( patch.object(proxy_module, "_open_host", return_value=upstream), patch.object(proxy_module, "_copy_until_close"), ): proxy.forward_http( cast(socket.socket, client), "GET", "http://example.test/", [], b"" ) self.assertIn(b"GET http://example.test/ HTTP/1.1", upstream.sent[0]) class ChunkSocket: def __init__(self, chunks: list[bytes]) -> None: self.chunks = list(chunks) self.sent: list[bytes] = [] def recv(self, size: int) -> bytes: if not self.chunks: return b"" chunk = self.chunks.pop(0) if len(chunk) <= size: return chunk self.chunks.insert(0, chunk[size:]) return chunk[:size] def sendall(self, data: bytes) -> None: self.sent.append(data) def send(self, data: bytes) -> None: self.sent.append(data) def settimeout(self, value: float | None) -> None: del value def close(self) -> None: return None class DockerClientTests(unittest.TestCase): def test_digest_pull_preserves_digest_in_docker_query(self) -> None: client = ScriptedDocker( [ DockerResponse(404, "Not Found", b""), DockerResponse(200, "OK", b""), ] ) client.pull_if_missing("registry.example/repo@sha256:" + "a" * 64) self.assertIn( "fromImage=registry.example%2Frepo%40sha256%3A" + "a" * 64, client.calls[1][1], ) def test_image_ref_and_expected_statuses(self) -> None: self.assertEqual( split_image_ref("registry.example/repo:tag"), ("registry.example/repo", "tag"), ) self.assertEqual(split_image_ref("repo@sha256:abc"), ("repo", "sha256:abc")) client = ScriptedDocker( [ DockerResponse(204, "No Content", b""), DockerResponse(404, "Not Found", b""), DockerResponse(500, "Error", b"failure"), ] ) client.expect("POST", "/ok") with self.assertRaises(FileNotFoundError): client.expect("POST", "/missing") with self.assertRaises(DockerError): client.expect("POST", "/error") self.assertEqual(tenant_network_name("creatorhub", "safe"), "creatorhub-safe") def test_pull_and_managed_container_response_validation(self) -> None: client = ScriptedDocker( [ DockerResponse(404, "Not Found", b""), DockerResponse(200, "OK", b"{}"), ] ) client.pull_if_missing("repo:tag") self.assertIn("/images/repo%3Atag/json", client.calls[0][1]) managed = { "Id": "container-id", "Config": {"Labels": {MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "safe"}}, "NetworkSettings": {"Networks": {"tenant": {"NetworkID": "network-id"}}}, } client = ScriptedDocker( [DockerResponse(200, "OK", json.dumps(managed).encode())] ) self.assertEqual(client.managed_container_state("safe")[0], "container-id") unmanaged = { **managed, "Config": {"Labels": {MANAGED_LABEL: "false", RUNTIME_ID_LABEL: "safe"}}, } client = ScriptedDocker( [DockerResponse(200, "OK", json.dumps(unmanaged).encode())] ) with self.assertRaises(UnmanagedContainer): client.managed_container("safe") def test_existing_network_generation_and_disconnect(self) -> None: network = { "Id": "network-id", "Name": "creatorhub-safe", "Driver": "bridge", "Internal": False, "Attachable": False, "Ingress": False, "Labels": { MANAGED_LABEL: "true", NETWORK_ROLE_LABEL: BROWSER_NETWORK_ROLE, RUNTIME_ID_LABEL: "safe", BINDING_VERSION_LABEL: "1", }, "Containers": { "gateway-id": {"Name": "gateway", "IPv4Address": "10.0.0.2/24"}, "runtime-id": {"Name": "runtime", "IPv4Address": "10.0.0.3/24"}, }, } gateway = { "Id": "gateway-id", "Config": {"Labels": {GATEWAY_MEMBER_LABEL: "true"}}, } client = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(network).encode()), DockerResponse(200, "OK", json.dumps(gateway).encode()), ] ) generation, addresses, exists = client.inspect_tenant_network( "creatorhub", "safe", 1, "runtime-id", "gateway", "network-id" ) self.assertTrue(exists) self.assertTrue(generation.runtime_attached) self.assertEqual(addresses["gateway-id"], "10.0.0.2/24") self.assertEqual(generation.self_member, "gateway-id") after = { **network, "Containers": {"gateway-id": network["Containers"]["gateway-id"]}, } client = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(network).encode()), DockerResponse(200, "OK", json.dumps(gateway).encode()), DockerResponse(200, "OK", json.dumps(network).encode()), DockerResponse(200, "OK", json.dumps(gateway).encode()), DockerResponse(200, "OK", b""), DockerResponse(200, "OK", json.dumps(after).encode()), DockerResponse(200, "OK", json.dumps(gateway).encode()), ] ) current, _, _ = client.inspect_tenant_network( "creatorhub", "safe", 1, "runtime-id", "gateway", "network-id" ) updated = client.disconnect_member( "creatorhub", "safe", 1, "runtime-id", current, "runtime-id", "gateway" ) self.assertFalse(updated.runtime_attached) def test_new_network_is_generation_fenced(self) -> None: empty = { "Id": "network-id", "Name": "creatorhub-safe", "Driver": "bridge", "Internal": False, "Attachable": False, "Ingress": False, "Labels": { MANAGED_LABEL: "true", NETWORK_ROLE_LABEL: BROWSER_NETWORK_ROLE, RUNTIME_ID_LABEL: "safe", BINDING_VERSION_LABEL: "1", }, "Containers": {}, } connected = { **empty, "Containers": { "runtime-id": {"Name": "runtime", "IPv4Address": "10.0.0.3/24"}, "gateway-id": {"Name": "gateway", "IPv4Address": "10.0.0.2/24"}, }, } gateway = { "Id": "gateway-id", "Config": {"Labels": {GATEWAY_MEMBER_LABEL: "true"}}, } client = ScriptedDocker( [ DockerResponse(404, "Not Found", b""), DockerResponse(201, "Created", b'{"Id":"network-id"}'), DockerResponse(200, "OK", json.dumps(empty).encode()), DockerResponse(200, "OK", b""), DockerResponse(200, "OK", b""), DockerResponse(200, "OK", json.dumps(connected).encode()), DockerResponse(200, "OK", json.dumps(gateway).encode()), ] ) generation, bind_host = client.ensure_tenant_network( "creatorhub", "safe", "gateway", 1, "runtime-id" ) self.assertTrue(generation.created) self.assertEqual(bind_host, "10.0.0.2") def test_alias_reservation_releases_only_its_generation(self) -> None: gateway = { "Id": "gateway-id", "Image": "creatorhub/gateway:latest", "Config": {"Labels": {MANAGED_LABEL: "true", GATEWAY_MEMBER_LABEL: "true"}}, } reservation = { "Id": "reservation-id", "Config": { "Labels": { "io.creatorhub.alias-reservation": "true", "io.creatorhub.reservation-generation": "reservation-generation", RUNTIME_ID_LABEL: "safe", } }, } client = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(gateway).encode()), DockerResponse(201, "Created", b'{"Id":"reservation-id"}'), DockerResponse(200, "OK", json.dumps(reservation).encode()), DockerResponse(200, "OK", json.dumps(reservation).encode()), DockerResponse(204, "No Content", b""), DockerResponse(404, "Not Found", b""), ] ) with patch.object( docker_client_module, "random_reservation_generation", return_value="reservation-generation", ): release = AliasReservationManager(client, "gateway").acquire("safe") release() self.assertTrue(any(call[0] == "DELETE" for call in client.calls)) def test_stale_alias_reservation_is_reclaimed(self) -> None: gateway = { "Id": "gateway-id", "Image": "creatorhub/gateway:latest", "Config": {"Labels": {MANAGED_LABEL: "true", GATEWAY_MEMBER_LABEL: "true"}}, } stale = { "Id": "stale-reservation-id", "Config": { "Labels": { RESERVATION_LABEL: "true", RUNTIME_ID_LABEL: "safe", RESERVATION_OWNER_LABEL: "old-gateway", } }, } current = { "Id": "current-reservation-id", "Config": { "Labels": { RESERVATION_LABEL: "true", RUNTIME_ID_LABEL: "safe", RESERVATION_OWNER_LABEL: "gateway", "io.creatorhub.reservation-generation": "current-generation", } }, } client = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(gateway).encode()), DockerResponse(409, "Conflict", b""), DockerResponse(200, "OK", json.dumps(stale).encode()), DockerResponse(404, "Not Found", b""), DockerResponse(204, "No Content", b""), DockerResponse(404, "Not Found", b""), DockerResponse(201, "Created", b'{"Id":"current-reservation-id"}'), DockerResponse(200, "OK", json.dumps(current).encode()), DockerResponse(200, "OK", json.dumps(current).encode()), DockerResponse(204, "No Content", b""), DockerResponse(404, "Not Found", b""), ] ) with patch.object( docker_client_module, "random_reservation_generation", return_value="current-generation", ): release = AliasReservationManager(client, "gateway").acquire("safe") release() self.assertEqual( client.calls[4][1], "/containers/stale-reservation-id?force=1&v=0", ) self.assertEqual(client.calls[6][0], "POST") class ScriptedDocker(DockerClient): def __init__(self, responses: list[DockerResponse]) -> None: super().__init__("/dev/null") self.responses = list(responses) self.calls: list[tuple[str, str, object | None]] = [] def request( self, method: str, path: str, payload: object | None = None, timeout: float = 30.0, body_limit: int = 16 * 1024 * 1024, ) -> DockerResponse: del timeout, body_limit self.calls.append((method, path, payload)) if not self.responses: raise AssertionError(f"unexpected Docker call: {method} {path}") return self.responses.pop(0) class ReservationStub: def __init__(self) -> None: self.released = 0 def acquire(self, alias: str): del alias def release() -> None: self.released += 1 return release class LockingReservationStub(ReservationStub): def __init__(self) -> None: super().__init__() self.lock = threading.Lock() def acquire(self, alias: str): del alias self.lock.acquire() def release() -> None: self.released += 1 self.lock.release() return release class LifecycleDocker(ScriptedDocker): def ensure_tenant_network( self, *args: object, **kwargs: object ) -> tuple[TenantNetworkGeneration, str]: del args, kwargs return TenantNetworkGeneration( id="network-id", self_member="gateway-id" ), "10.0.0.2" def inspect_tenant_network( self, *args: object, **kwargs: object ) -> tuple[TenantNetworkGeneration, dict[str, str], bool]: del args, kwargs return ( TenantNetworkGeneration( id="network-id", name="creatorhub-safe", self_member="gateway-id", gateway_members=["gateway-id"], runtime_attached=True, ), {"gateway-id": "10.0.0.2/24"}, True, ) class GatewayLifecycleTests(unittest.TestCase): def test_server_tracks_daemon_request_threads_and_timeout(self) -> None: server = gateway_module.GatewayHTTPServer(("127.0.0.1", 0), Mock()) try: self.assertTrue(server.daemon_threads) self.assertEqual(gateway_module.GatewayHandler.protocol_version, "HTTP/1.1") finally: server.server_close() def test_network_setup_error_keeps_created_generation(self) -> None: client = ScriptedDocker( [ DockerResponse(404, "Not Found", b""), DockerResponse(201, "Created", b'{"Id":"network-id"}'), DockerResponse(500, "Error", b"inspect failed"), ] ) with self.assertRaises(docker_client_module.NetworkSetupError) as caught: client.ensure_tenant_network("creatorhub", "safe", "gateway", 1, "runtime") self.assertEqual(caught.exception.generation.id, "network-id") self.assertTrue(caught.exception.generation.created) def test_network_cleanup_failure_returns_pending_contract(self) -> None: docker = ScriptedDocker( [ DockerResponse(404, "Not Found", b""), DockerResponse(500, "Error", b"temporary"), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) with self.assertRaises(RequestError) as caught: gateway.remove( "safe", { "binding_version": 1, "runtime_id": "runtime-not-found", "network_id": "network-id", }, ) self.assertEqual(caught.exception.status, 202) self.assertEqual(caught.exception.network_id, "network-id") self.assertEqual(str(caught.exception), "runtime_cleanup_pending") def test_timed_out_action_retains_alias_ownership(self) -> None: gateway = Gateway( cast(DockerClient, Mock()), "creatorhub", "0123456789abcdef", "gateway" ) gateway._claim_action("safe") gateway._retain_action_ownership("safe") with self.assertRaises(RequestError): gateway._claim_action("safe") def _input(self, stopped: bool = True) -> dict: return { "alias": "safe", "name": "Safe", "image": "creatorhub/browser:latest", "cmd": ["about:blank"], "volume": "creatorhub-safe", "binding_version": 1, "network_exit_id": "", "stopped": stopped, } def test_concurrent_create_cannot_delete_the_winner(self) -> None: class ConcurrentDocker(DockerClient): def __init__(self) -> None: super().__init__("/dev/null") self.created = False self.calls: list[tuple[str, str]] = [] def managed_container(self, alias: str) -> tuple[str, dict[str, str]]: if not self.created: raise FileNotFoundError(alias) return "a" * 64, { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: alias, BINDING_VERSION_LABEL: "1", NETWORK_ID_LABEL: "", } def request( self, method: str, path: str, payload: object | None = None, timeout: float = 30.0, body_limit: int = 16 * 1024 * 1024, ) -> DockerResponse: del payload, timeout, body_limit self.calls.append((method, path)) if method == "GET" and path.startswith("/images/"): return DockerResponse(200, "OK", b"{}") if method == "POST" and path.startswith("/containers/create"): if self.created: return DockerResponse(409, "Conflict", b"") self.created = True return DockerResponse( 201, "Created", b'{"Id":"' + b"b" * 64 + b'"}' ) raise AssertionError(f"unexpected Docker call: {method} {path}") docker = ConcurrentDocker() gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, LockingReservationStub()) results: list[object] = [] def create() -> None: try: results.append(gateway.create(self._input())) except ( AssertionError, DockerError, OSError, RequestError, ValueError, ) as exc: results.append(exc) threads = [threading.Thread(target=create) for _ in range(2)] for thread in threads: thread.start() for thread in threads: thread.join() self.assertEqual(sum(isinstance(item, dict) for item in results), 1) self.assertEqual(sum(isinstance(item, RequestError) for item in results), 1) self.assertFalse(any(method == "DELETE" for method, _ in docker.calls)) def test_create_stopped_pulls_image_and_keeps_container(self) -> None: docker = ScriptedDocker( [ DockerResponse(200, "OK", b"{}"), DockerResponse(404, "Not Found", b""), DockerResponse(201, "Created", b'{"Id":"container-id"}'), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) reservations = ReservationStub() gateway.reservations = cast(AliasReservationManager, reservations) result = gateway.create(self._input()) self.assertEqual(result["id"], "container-id") self.assertEqual(reservations.released, 1) self.assertEqual(docker.calls[2][0], "POST") def test_running_create_starts_container_and_network(self) -> None: docker = LifecycleDocker( [ DockerResponse(200, "OK", b"{}"), DockerResponse(404, "Not Found", b""), DockerResponse(201, "Created", b'{"Id":"container-id"}'), DockerResponse(204, "No Content", b""), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) result = gateway.create(self._input(False)) self.assertEqual(result["network_id"], "network-id") self.assertEqual(docker.calls[-1][0], "POST") def test_change_state_and_remove_are_generation_fenced(self) -> None: labels = { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "safe", BINDING_VERSION_LABEL: "1", NETWORK_ID_LABEL: "network-id", } inspected = { "Id": "a" * 64, "Config": {"Labels": labels}, "NetworkSettings": {"Networks": {}}, } docker = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(inspected).encode()), DockerResponse(204, "No Content", b""), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) gateway.change_state( "safe", "stop", { "binding_version": 1, "runtime_id": "a" * 64, "network_id": "network-id", }, ) self.assertEqual(docker.calls[-1][0], "POST") docker = LifecycleDocker( [ DockerResponse(200, "OK", json.dumps(inspected).encode()), DockerResponse(204, "No Content", b""), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) gateway._remove_network = lambda *args, **kwargs: None gateway.remove( "safe", { "binding_version": 1, "runtime_id": "a" * 64, "network_id": "network-id", }, ) self.assertEqual(docker.calls[-1][0], "DELETE") def test_remove_stopped_direct_container_without_network(self) -> None: labels = { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "safe", BINDING_VERSION_LABEL: "1", NETWORK_ID_LABEL: "", } inspected = { "Id": "a" * 64, "Config": {"Labels": labels}, "NetworkSettings": {"Networks": {}}, } docker = ScriptedDocker( [ DockerResponse(200, "OK", json.dumps(inspected).encode()), DockerResponse(404, "Not Found", b""), DockerResponse(204, "No Content", b""), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) gateway.remove( "safe", {"binding_version": 1, "runtime_id": "a" * 64, "network_id": ""}, ) self.assertEqual(docker.calls[-1][0], "DELETE") def test_remove_rejects_runtime_replacement(self) -> None: labels = { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "safe", BINDING_VERSION_LABEL: "1", NETWORK_ID_LABEL: "", } inspected = { "Id": "a" * 64, "Config": {"Labels": labels}, "NetworkSettings": {"Networks": {}}, } docker = ScriptedDocker( [DockerResponse(200, "OK", json.dumps(inspected).encode())] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) with self.assertRaises(RequestError): gateway.remove( "safe", {"binding_version": 1, "runtime_id": "b" * 64, "network_id": ""}, ) def test_create_failure_reconciles_unknown_container(self) -> None: docker = ScriptedDocker( [ DockerResponse(200, "OK", b"{}"), DockerResponse(404, "Not Found", b""), DockerResponse(500, "Error", b"failed"), DockerResponse(404, "Not Found", b""), ] ) gateway = Gateway( cast(DockerClient, docker), "creatorhub", "0123456789abcdef", "gateway" ) gateway.reservations = cast(AliasReservationManager, ReservationStub()) with self.assertRaises(RequestError): gateway.create(self._input()) class AdditionalGatewayCoverageTests(unittest.TestCase): def test_douyin_connect_rejects_bad_discovery(self) -> None: browser = DouyinBrowser(lambda _: "https://browser:9222") with self.assertRaises(DouyinError): browser._connect("safe") cases = [ (500, b"{}"), (200, b"{}"), (200, json.dumps([{"type": "service"}]).encode()), ( 200, json.dumps( [{"type": "page", "url": "https://www.douyin.com/1"}] ).encode(), ), ( 200, json.dumps( [ { "type": "page", "url": "https://www.douyin.com/1", "webSocketDebuggerUrl": "http://browser/devtools/page/1", } ] ).encode(), ), ] for status, body in cases: with self.subTest(status=status, body=body): http = FakeHTTPConnection(FakeHTTPResponse(status, body)) with ( patch.object( douyin_module.http.client, "HTTPConnection", return_value=http ), self.assertRaises(DouyinError), ): browser._connect("safe") targets = [ { "type": "page", "url": "https://www.douyin.com/1", "webSocketDebuggerUrl": "ws://browser:9222/devtools/page/1", }, { "type": "page", "url": "https://www.douyin.com/2", "webSocketDebuggerUrl": "ws://browser:9222/devtools/page/2", }, ] http = FakeHTTPConnection(FakeHTTPResponse(200, json.dumps(targets).encode())) with ( patch.object( douyin_module.http.client, "HTTPConnection", return_value=http ), self.assertRaises(DouyinError), ): browser._connect("safe") def test_douyin_fetch_identity_and_confirmed_actions(self) -> None: browser = DouyinBrowser() def bind(connection: BrowserCDP) -> None: BrowserTests()._with_connection(browser, connection) for result in ( {"too_large": True}, {"status": 302, "body": ""}, {"status": 200, "body": 1}, ): bind(BrowserCDP(["https://www.douyin.com", result])) with self.assertRaises(DouyinError): browser.get("safe", "https://www.douyin.com/a") cast(Any, browser).get = lambda alias, target: BrowserResponse(200, "not-json") with self.assertRaises(DouyinError): browser.identity("safe") cast(Any, browser).get = lambda alias, target: BrowserResponse( 200, json.dumps({"status_code": 0, "user": {"uid": "1", "sec_uid": "sec"}}) ) self.assertEqual(browser.identity("safe")["uid"], "1") cast(Any, browser).get = lambda alias, target: BrowserResponse( 403, json.dumps({"status_code": 0, "user": {"uid": "1", "sec_uid": "sec"}}) ) with self.assertRaises(DouyinError): browser.identity("safe") cast(Any, browser).identity = lambda alias, expected_uid=None: { "uid": expected_uid or "1" } cast(Any, browser)._evaluate = lambda alias, expression: { "status": 200, "action": "sent", } bind(BrowserCDP(["https://www.douyin.com", {}])) self.assertEqual( browser.action("safe", "1", "follow", "2", confirm=True)["action"], "sent" ) bind(BrowserCDP(["https://www.douyin.com", {}])) self.assertEqual( browser.action("safe", "1", "dm", "2", text="hello", confirm=True)[ "status" ], 200, ) cast(Any, browser)._evaluate = lambda alias, expression: "bad" bind(BrowserCDP(["https://www.douyin.com", "bad"])) with self.assertRaises(DouyinError): browser.action("safe", "1", "follow", "2", confirm=True) def test_gateway_and_proxy_validation_edges(self) -> None: self.assertEqual( parse_proxy_exit({"protocol": "http", "host": "", "port": 80}).host, "" ) with self.assertRaises(RequestError): validate_proxy_exit( parse_proxy_exit({"protocol": "http", "host": "", "port": 80}) ) with self.assertRaises(RequestError): validate_proxy_exit( parse_proxy_exit({"protocol": "http", "host": "proxy", "port": 0}) ) with self.assertRaises(RequestError): validate_proxy_restore( { "binding_version": 1, "runtime_id": "a" * 64, "network_id": "n", "network_exit_id": "x", }, "bad alias", ) self.assertFalse(valid_account_key_query({"key": ["bad key"]}, "key")) def test_proxy_rejected_handshakes_and_docker_errors(self) -> None: bad_http = ChunkSocket([b"HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"]) with ( patch.object(proxy_module, "_open_host", return_value=bad_http), self.assertRaises(OSError), ): _dial_http_proxy(ProxyExit("http", "proxy", 8080), "target:443", 1.0) bad_socks5 = ChunkSocket([b"\x05\x02"]) with ( patch.object(proxy_module, "_open_host", return_value=bad_socks5), self.assertRaises(OSError), ): _dial_socks5( ProxyExit("socks5", "proxy", 1080, "u", "p"), "127.0.0.1:80", 1.0 ) client = DockerClient("/not/a/socket") with ( patch.object( docker_client_module, "UnixHTTPConnection", side_effect=OSError("down") ), self.assertRaises(DockerError), ): client.request("GET", "/version", timeout=0.01) with ( patch.object( client, "request", return_value=DockerResponse(500, "bad", b"x") ), self.assertRaises(DockerError), ): client.expect("POST", "/x") with ( patch.object( client, "request", return_value=DockerResponse(404, "missing", b"") ), self.assertRaises(FileNotFoundError), ): client.expect("DELETE", "/gone") def test_docker_client_transport_and_metadata_edges(self) -> None: client = DockerClient("/unused") response = FakeHTTPResponse(200, b"{}") cast(Any, response).reason = "OK" connection = FakeHTTPConnection(response) with patch.object( docker_client_module, "UnixHTTPConnection", return_value=connection ): result = client.request("POST", "/version", {"ok": True}) self.assertEqual(result.status, 200) self.assertEqual(connection.requested, [("POST", "/v1.43/version")]) huge = FakeHTTPResponse(200, b"12345") cast(Any, huge).reason = "OK" with ( patch.object( docker_client_module, "UnixHTTPConnection", return_value=FakeHTTPConnection(huge), ), self.assertRaises(DockerError), ): client.request("GET", "/version", body_limit=4) labels = { MANAGED_LABEL: "true", RUNTIME_ID_LABEL: "safe", GATEWAY_MEMBER_LABEL: "true", } inspected = { "Id": "a" * 64, "Config": {"Labels": labels}, "NetworkSettings": { "Networks": {"n": {"NetworkID": "network", "IPAddress": "198.51.100.5"}} }, } with patch.object( client, "request", return_value=DockerResponse(200, "OK", json.dumps(inspected).encode()), ): self.assertTrue(client.trusted_gateway_member("gateway")) self.assertEqual( client.container_network_address("a" * 64, "network"), "198.51.100.5" ) bad = DockerResponse(200, "OK", b"[]") with patch.object(client, "request", return_value=bad): self.assertFalse(client.trusted_gateway_member("gateway")) with self.assertRaises(DockerError): client.managed_container_state("safe") def test_client_connection_pull_and_inspect_edges(self) -> None: class ConnectedSocket: def __init__(self) -> None: self.timeout = None self.path = "" def settimeout(self, value: float) -> None: self.timeout = value def connect(self, path: str) -> None: self.path = path connected = ConnectedSocket() with patch.object( docker_client_module.socket, "socket", return_value=connected ): connection = docker_client_module.UnixHTTPConnection( "/run/docker.sock", 1.0 ) connection.connect() self.assertEqual(connected.path, "/run/docker.sock") client = DockerClient("/unused") with patch.object( client, "request", side_effect=[ DockerResponse(404, "missing", b""), DockerResponse(200, "OK", b"{}"), ], ) as request: client.pull_if_missing("registry.example/repo:tag") self.assertIn("tag=tag", request.call_args_list[1].args[1]) with ( patch.object( client, "request", return_value=DockerResponse(500, "bad", b"no") ), self.assertRaises(DockerError), ): client.pull_if_missing("registry.example/repo:tag") invalid = DockerResponse( 200, "OK", json.dumps( { "Id": "a" * 64, "Config": {"Labels": []}, "NetworkSettings": {"Networks": {}}, } ).encode(), ) with ( patch.object(client, "request", return_value=invalid), self.assertRaises(DockerError), ): client.managed_container_state("safe") def test_cdp_error_and_proxy_auth_paths(self) -> None: socket_ = FakeSocket([{"id": 1, "result": {"result": {"value": {"ok": True}}}}]) connection = CDPConnection(cast(websocket.WebSocket, socket_)) self.assertEqual(connection.evaluate("1"), {"ok": True}) socket_ = FakeSocket([{"id": 1, "result": {"result": {}}}]) with self.assertRaises(DouyinError): CDPConnection(cast(websocket.WebSocket, socket_)).evaluate("1") with self.assertRaises(DouyinError): CDPConnection( cast(websocket.WebSocket, FakeSocket([{"id": 1, "error": {}}])) ).command("Page.enable") with self.assertRaises(DouyinError): CDPConnection( cast(websocket.WebSocket, FakeSocket([{"id": 1, "result": []}])) ).command("Page.enable") with self.assertRaises(DouyinError): CDPConnection(cast(websocket.WebSocket, FakeSocket([]))).wait_event( "Page.loadEventFired", lambda _: True, timeout=0.01 ) socket_ = FakeSocket([{"id": 1, "result": {"exceptionDetails": {}}}]) with self.assertRaises(DouyinError): CDPConnection(cast(websocket.WebSocket, socket_)).evaluate("1") target = [ { "type": "page", "url": "https://www.douyin.com/1", "webSocketDebuggerUrl": "ws://browser:9222/devtools/page/1", } ] http = FakeHTTPConnection(FakeHTTPResponse(200, json.dumps(target).encode())) with ( patch.object( douyin_module.http.client, "HTTPConnection", return_value=http ), patch.object( douyin_module.websocket, "create_connection", side_effect=OSError("down"), ), self.assertRaises(DouyinError), ): DouyinBrowser(lambda _: "http://browser:9222")._connect("safe") socks5 = ChunkSocket( [b"\x05\x02", b"\x01\x00", b"\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x50"] ) with patch.object(proxy_module, "_open_host", return_value=socks5): self.assertIs( _dial_socks5( ProxyExit("socks5", "proxy", 1080, "u", "p"), "127.0.0.1:80", 1.0 ), socks5, ) def test_direct_message_notice_and_uncertain_post_contract(self) -> None: notice = normalize_notice( { "dm": { "message_id": "123456", "from_user": {"uid": "456789"}, "text": "hello", }, "create_time": 1700000000, } ) self.assertIsNotNone(notice) assert notice is not None self.assertEqual(notice["event_type"], "dm") self.assertEqual(notice["interactor_uid"], "456789") self.assertEqual(notice["message_type"], "text") self.assertEqual(notice["message_text"], "hello") non_text = normalize_notice( { "dm": { "message_id": "123457", "from_user": {"uid": "456789"}, "message_type": "image", "image_url": "https://example.invalid/image", } } ) assert non_text is not None self.assertEqual(non_text["message_type"], "image") self.assertEqual(non_text["message_text"], "") script = action_expression({"alias": "safe", "action": "follow", "target": "2"}) self.assertIn("POST_UNCERTAIN", script) self.assertIn("BUSINESS_REJECTED", script) def test_cdp_timeout_terminates_page_evaluation(self) -> None: class TimeoutSocket(FakeSocket): def __init__(self) -> None: super().__init__([]) self.receives = 0 def recv(self) -> str: self.receives += 1 if self.receives == 1: raise TimeoutError("deadline") return json.dumps({"id": 2, "result": {}}) socket_ = TimeoutSocket() connection = CDPConnection(cast(websocket.WebSocket, socket_)) with ( patch.object(douyin_module, "CONTROL_TIMEOUT", 0.001), self.assertRaises(DouyinError), ): connection.command("Runtime.evaluate") sent = [json.loads(item) for item in socket_.sent] self.assertEqual(sent[1]["method"], "Runtime.terminateExecution") def test_network_create_reconciliation_preserves_observed_generation(self) -> None: client = DockerClient("/unused") observed = TenantNetworkGeneration(name="creatorhub-safe", id="observed") generation = TenantNetworkGeneration(name="creatorhub-safe") with ( patch.object( client, "inspect_tenant_network", return_value=(observed, [], True) ), self.assertRaises(NetworkSetupError) as caught, ): client._finish_network_create( "creatorhub", "safe", "gateway", 1, "runtime", DockerResponse(201, "Created", b"{}"), generation, ) self.assertEqual(caught.exception.generation.id, "observed") with patch.object( client, "inspect_tenant_network", return_value=(observed, [], True) ): result = client._finish_network_create( "creatorhub", "safe", "gateway", 1, "runtime", DockerResponse(201, "Created", b'{"Id":"created"}'), TenantNetworkGeneration(name="creatorhub-safe"), ) self.assertEqual(result.id, "observed") if __name__ == "__main__": unittest.main()