chore: import upstream snapshot with attribution
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:03:19 +08:00
commit 91e75e620b
3227 changed files with 1307078 additions and 0 deletions
@@ -0,0 +1,47 @@
"""Pytest configuration and shared fixtures for computer-server package tests.
This file contains shared fixtures and configuration for all computer-server tests.
Following SRP: This file ONLY handles test setup/teardown.
"""
from unittest.mock import AsyncMock, Mock, patch
import pytest
@pytest.fixture
def mock_websocket():
"""Mock WebSocket connection for testing.
Use this fixture to test WebSocket logic without real connections.
"""
websocket = AsyncMock()
websocket.send = AsyncMock()
websocket.recv = AsyncMock()
websocket.close = AsyncMock()
return websocket
@pytest.fixture
def mock_computer_interface():
"""Mock computer interface for server tests.
Use this fixture to test server logic without real computer operations.
"""
interface = AsyncMock()
interface.screenshot = AsyncMock(return_value=b"fake_screenshot")
interface.left_click = AsyncMock()
interface.type = AsyncMock()
interface.key = AsyncMock()
return interface
@pytest.fixture
def disable_telemetry(monkeypatch):
"""Disable telemetry for tests.
Use this fixture to ensure no telemetry is sent during tests.
"""
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "false")
@@ -0,0 +1,188 @@
"""Integration tests for UNAVAILABLE_WITHOUT_CONTAINER_NAME behavior.
These tests verify two things:
1. **Backwards compat** — when neither ``CONTAINER_NAME`` nor
``UNAVAILABLE_WITHOUT_CONTAINER_NAME`` is set, the server continues to
operate in local development mode (no auth required, requests succeed).
2. **New behavior** — when ``UNAVAILABLE_WITHOUT_CONTAINER_NAME`` is truthy
and ``CONTAINER_NAME`` is unset, requests are rejected with the status
code configured by
``UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE`` (default 503)
rather than being allowed through.
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
try:
from computer_server.main import _unavailable_status_code, app
except Exception as import_error: # pragma: no cover - environment-dependent
pytest.skip(
f"computer_server.main unavailable in this environment: {import_error}",
allow_module_level=True,
)
@pytest.fixture
def clean_env(monkeypatch):
"""Remove all env vars that influence auth availability for a clean baseline."""
for var in (
"CONTAINER_NAME",
"UNAVAILABLE_WITHOUT_CONTAINER_NAME",
"UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE",
):
monkeypatch.delenv(var, raising=False)
return monkeypatch
@pytest.fixture
def client():
return TestClient(app)
class TestUnavailableStatusCode:
"""Unit tests for the `_unavailable_status_code` helper."""
def test_returns_none_when_both_unset(self, clean_env):
assert _unavailable_status_code() is None
def test_returns_none_when_container_name_set(self, clean_env):
clean_env.setenv("CONTAINER_NAME", "vm-abc")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
# CONTAINER_NAME being set overrides the unavailable flag.
assert _unavailable_status_code() is None
def test_returns_default_503_when_flag_truthy_and_container_missing(self, clean_env):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
assert _unavailable_status_code() == 503
@pytest.mark.parametrize("value", ["1", "true", "True", "YES", "y", "on"])
def test_accepts_various_truthy_values(self, clean_env, value):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", value)
assert _unavailable_status_code() == 503
@pytest.mark.parametrize("value", ["0", "false", "no", "", "random"])
def test_rejects_falsy_values(self, clean_env, value):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", value)
assert _unavailable_status_code() is None
def test_custom_status_code(self, clean_env):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "1")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE", "418")
assert _unavailable_status_code() == 418
def test_invalid_status_code_falls_back_to_503(self, clean_env):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "1")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE", "not-a-number")
assert _unavailable_status_code() == 503
class TestCmdEndpoint:
"""Integration tests for the POST /cmd endpoint."""
def test_backwards_compat_local_dev_allows_requests(self, clean_env, client):
# No CONTAINER_NAME, no availability flag — old "local dev" behavior.
resp = client.post("/cmd", json={"command": "version", "params": {}})
assert resp.status_code == 200, resp.text
assert "success" in resp.text
def test_unavailable_flag_rejects_with_default_503(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.post("/cmd", json={"command": "version", "params": {}})
assert resp.status_code == 503
assert "CONTAINER_NAME" in resp.json()["detail"]
def test_unavailable_flag_with_custom_status_code(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "1")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE", "418")
resp = client.post("/cmd", json={"command": "version", "params": {}})
assert resp.status_code == 418
def test_container_name_set_bypasses_unavailable_flag(self, clean_env, client):
# CONTAINER_NAME being set means auth is required — but the unavailable
# flag should NOT apply. Without valid creds, this should 401, not 503.
clean_env.setenv("CONTAINER_NAME", "vm-xyz")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.post("/cmd", json={"command": "version", "params": {}})
assert resp.status_code == 401
class TestPtyEndpointAuthGate:
"""Integration tests for PTY endpoints (via `_require_auth`)."""
def test_backwards_compat_local_dev_allows_access(self, clean_env, client):
# Use a non-existent PID — we just want to verify we get past the auth gate.
# If auth passes, we get 404 (PTY not found); if not, we get 401/503.
resp = client.get("/pty/999999")
assert resp.status_code == 404, resp.text
def test_unavailable_flag_rejects_with_default_503(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.get("/pty/999999")
assert resp.status_code == 503
def test_unavailable_flag_with_custom_status_code(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE", "599")
resp = client.get("/pty/999999")
assert resp.status_code == 599
class TestPlaywrightExecEndpoint:
def test_backwards_compat_local_dev_accepts_auth(self, clean_env, client):
# Browser manager may fail for other reasons, but it should NOT be 503/401.
resp = client.post("/playwright_exec", json={"command": "noop", "params": {}})
assert resp.status_code not in (401, 503)
def test_unavailable_flag_rejects(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.post("/playwright_exec", json={"command": "noop", "params": {}})
assert resp.status_code == 503
class TestStatusEndpointMiddlewareGating:
"""The middleware applies uniformly — /status is reachable when the flag is off."""
def test_status_accessible_in_local_dev_mode(self, clean_env, client):
resp = client.get("/status")
assert resp.status_code == 200
assert resp.json()["status"] == "ok"
def test_status_rejected_by_middleware_when_flag_set(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.get("/status")
assert resp.status_code == 503
def test_status_accessible_when_container_name_set(self, clean_env, client):
clean_env.setenv("CONTAINER_NAME", "vm-abc")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
resp = client.get("/status")
assert resp.status_code == 200
class TestWebSocketEndpoint:
def test_backwards_compat_local_dev_allows_commands(self, clean_env, client):
with client.websocket_connect("/ws") as ws:
ws.send_json({"command": "version", "params": {}})
data = ws.receive_json()
assert data["success"] is True
def test_unavailable_flag_closes_with_error(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
with client.websocket_connect("/ws") as ws:
data = ws.receive_json()
assert data["success"] is False
assert data["status_code"] == 503
assert "CONTAINER_NAME" in data["error"]
def test_unavailable_flag_reports_custom_status_code(self, clean_env, client):
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME", "true")
clean_env.setenv("UNAVAILABLE_WITHOUT_CONTAINER_NAME_RESPONSE_STATUS_CODE", "599")
with client.websocket_connect("/ws") as ws:
data = ws.receive_json()
assert data["success"] is False
assert data["status_code"] == 599
@@ -0,0 +1,113 @@
"""Tests for keypress layout-independence fix (issue #1605).
Verifies that single printable characters are routed through type_text
(layout-independent) rather than press_key (layout-dependent).
"""
import unicodedata
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
def _is_printable_char(key: str) -> bool:
"""Mirror of the routing logic in DirectComputer.keypress()."""
return len(key) == 1 and unicodedata.category(key) not in ("Cc", "Cs", "Cn")
class TestPrintableCharDetection:
"""Unit tests for the printable-character detection logic."""
def test_ascii_letter_is_printable(self):
assert _is_printable_char("a") is True
assert _is_printable_char("Z") is True
def test_ascii_digit_is_printable(self):
assert _is_printable_char("0") is True
assert _is_printable_char("9") is True
def test_ascii_punctuation_is_printable(self):
assert _is_printable_char("/") is True
assert _is_printable_char(".") is True
assert _is_printable_char("_") is True
def test_space_is_printable(self):
# Space (U+0020) has category Zs — printable, should use type_text.
assert _is_printable_char(" ") is True
def test_special_key_names_are_not_printable(self):
# Multi-char strings are never single printable chars.
assert _is_printable_char("return") is False
assert _is_printable_char("tab") is False
assert _is_printable_char("escape") is False
assert _is_printable_char("f1") is False
assert _is_printable_char("backspace") is False
assert _is_printable_char("up") is False
def test_control_characters_are_not_printable(self):
# Cc category — should go through press_key, not type_text.
assert _is_printable_char("\x00") is False
assert _is_printable_char("\x1b") is False # ESC
assert _is_printable_char("\n") is False # newline
def test_empty_string_is_not_printable(self):
assert _is_printable_char("") is False
@pytest.mark.asyncio
async def test_keypress_single_printable_uses_type_text():
"""Single printable char must call type_text, not press_key."""
auto = MagicMock()
auto.type_text = AsyncMock()
auto.press_key = AsyncMock()
auto.hotkey = AsyncMock()
# Simulate the routing logic directly.
key = "a"
if _is_printable_char(key):
await auto.type_text(key)
else:
await auto.press_key(key)
auto.type_text.assert_awaited_once_with("a")
auto.press_key.assert_not_awaited()
@pytest.mark.asyncio
async def test_keypress_special_key_uses_press_key():
"""Special key names must still call press_key."""
auto = MagicMock()
auto.type_text = AsyncMock()
auto.press_key = AsyncMock()
key = "return"
if _is_printable_char(key):
await auto.type_text(key)
else:
await auto.press_key(key)
auto.press_key.assert_awaited_once_with("return")
auto.type_text.assert_not_awaited()
@pytest.mark.asyncio
async def test_keypress_combo_uses_hotkey():
"""Multi-key combos must still call hotkey."""
auto = MagicMock()
auto.hotkey = AsyncMock()
auto.type_text = AsyncMock()
auto.press_key = AsyncMock()
parts = ["cmd", "shift", "g"]
if len(parts) == 1:
key = parts[0]
if _is_printable_char(key):
await auto.type_text(key)
else:
await auto.press_key(key)
else:
await auto.hotkey(parts)
auto.hotkey.assert_awaited_once_with(["cmd", "shift", "g"])
auto.type_text.assert_not_awaited()
auto.press_key.assert_not_awaited()
@@ -0,0 +1,80 @@
"""Tests for SDK-configurable ``run_command`` timeout propagation.
The SDK's ``cua_sandbox.interfaces.shell.Shell.run(cmd, timeout=...)`` sends
the timeout as a ``/cmd`` param; ``computer_server.main``'s dispatcher
filters kwargs by the handler signature before forwarding. These tests
cover the handler-side contract:
* handler accepts ``timeout`` as a float keyword
* ``timeout=None`` → waits indefinitely
* ``timeout=<float>`` → times out at expiry with a ``success=False`` result
containing the standardised ``Command timed out after <t>s`` stderr and
``return_code=-1``
The Android / Windows variants share the same shape; the base handler is
exercised here because it exposes a POSIX subprocess without adb/emulator
state being required for a focused unit test.
"""
import asyncio
import pytest
from computer_server.handlers.base import BaseAutomationHandler
class _MinimalHandler(BaseAutomationHandler):
"""Concrete BaseAutomationHandler for exercising ``run_command`` only.
We clear ``__abstractmethods__`` AFTER class creation (below) instead
of stubbing every abstract method — the set changes as the interface
grows and keeping a mirror of the full abstract surface here is pure
maintenance overhead for a test that only touches ``run_command``.
Note: setting ``__abstractmethods__ = frozenset()`` inside the class
body does NOT work — Python's ``ABCMeta`` overwrites it during class
creation. The override must happen after the class statement.
"""
pass
# Bypass ABC enforcement — ABCMeta.__call__ checks this at instantiation time.
_MinimalHandler.__abstractmethods__ = frozenset()
class TestRunCommandTimeout:
@pytest.mark.asyncio
async def test_no_timeout_defaults_to_indefinite_wait(self, monkeypatch):
# Ensure we hit the subprocess_shell branch (not the android adb branch).
monkeypatch.delenv("IS_CUA_ANDROID", raising=False)
h = _MinimalHandler()
result = await h.run_command("echo hello")
assert result["success"] is True
assert result["stdout"].strip() == "hello"
assert result["return_code"] == 0
@pytest.mark.asyncio
async def test_timeout_honoured_when_passed(self, monkeypatch):
monkeypatch.delenv("IS_CUA_ANDROID", raising=False)
h = _MinimalHandler()
# sleep 5, cap at 0.2 — should time out
start = asyncio.get_event_loop().time()
result = await h.run_command("sleep 5", timeout=0.2)
elapsed = asyncio.get_event_loop().time() - start
assert result["success"] is False
assert "timed out" in result["stderr"].lower()
assert result["return_code"] == -1
# Must have returned within a small factor of the requested timeout.
assert elapsed < 2.0, f"expected quick timeout, took {elapsed:.2f}s"
@pytest.mark.asyncio
async def test_fast_command_under_timeout_succeeds(self, monkeypatch):
monkeypatch.delenv("IS_CUA_ANDROID", raising=False)
h = _MinimalHandler()
result = await h.run_command("echo done", timeout=10.0)
assert result["success"] is True
assert result["stdout"].strip() == "done"
@@ -0,0 +1,222 @@
"""Unit tests for computer-server package.
This file tests ONLY basic server functionality.
Following SRP: This file tests server initialization and basic operations.
All external dependencies are mocked.
"""
import importlib.util
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
import pytest
class TestServerImports:
"""Test server module imports (SRP: Only tests imports)."""
def test_server_module_exists(self):
"""Test that server module can be imported."""
try:
import computer_server
assert computer_server is not None
except ImportError:
pytest.skip("computer_server module not installed")
class TestServerInitialization:
"""Test server initialization (SRP: Only tests initialization)."""
@pytest.mark.asyncio
async def test_server_can_be_imported(self):
"""Basic smoke test: verify server components can be imported."""
try:
from computer_server import server
assert server is not None
except ImportError:
pytest.skip("Server module not available")
except Exception as e:
# Some initialization errors are acceptable in unit tests
pytest.skip(f"Server initialization requires specific setup: {e}")
class TestMcpBarePathRewrite:
"""Test the ASGI shim that serves MCP at both /mcp and /mcp/ (SRP:
Only tests the path rewrite). The class is pure ASGI with no
dependencies, so it is tested standalone against a recording stub."""
@pytest.mark.asyncio
async def test_bare_mcp_path_is_rewritten(self):
try:
from computer_server.main import McpBarePathRewrite
except ImportError:
pytest.skip("computer_server module not installed")
except Exception as e:
pytest.skip(f"Server initialization requires specific setup: {e}")
seen = {}
async def inner(scope, receive, send):
seen.update(scope)
shim = McpBarePathRewrite(inner)
await shim({"type": "http", "path": "/mcp"}, None, None)
assert seen["path"] == "/mcp/"
@pytest.mark.asyncio
async def test_other_paths_untouched(self):
try:
from computer_server.main import McpBarePathRewrite
except ImportError:
pytest.skip("computer_server module not installed")
except Exception as e:
pytest.skip(f"Server initialization requires specific setup: {e}")
seen = {}
async def inner(scope, receive, send):
seen.update(scope)
shim = McpBarePathRewrite(inner)
for path in ("/mcp/", "/mcpx", "/status", "/"):
await shim({"type": "http", "path": path}, None, None)
assert seen["path"] == path
class TestMcpServerSmoke:
"""Smoke-test the real FastMCP integration so dependency major bumps cannot
silently remove the /mcp endpoint while unit tests stay green."""
@staticmethod
def _load_mcp_server_module():
# Import the module file directly. Importing through
# `computer_server.mcp_server` first executes computer_server/__init__.py,
# which imports platform input backends and fails on headless Linux CI
# with no DISPLAY. The MCP module itself has no runtime dependency on
# those input backends until a tool is actually invoked.
module_path = Path(__file__).parents[1] / "computer_server" / "mcp_server.py"
spec = importlib.util.spec_from_file_location("_computer_server_mcp_smoke", module_path)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_create_mcp_server_and_http_app(self):
mcp_module = self._load_mcp_server_module()
mcp_server = mcp_module.create_mcp_server()
mcp_app = mcp_server.http_app(path="/")
assert mcp_server is not None
assert mcp_app is not None
def test_mcp_http_app_accepts_initialize_post(self):
from starlette.testclient import TestClient
mcp_module = self._load_mcp_server_module()
mcp_server = mcp_module.create_mcp_server()
mcp_app = mcp_server.http_app(path="/")
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "computer-server-smoke", "version": "0"},
},
}
with TestClient(mcp_app) as client:
response = client.post(
"/",
json=payload,
headers={"accept": "application/json, text/event-stream"},
)
assert response.status_code == 200
assert "cua-computer-server" in response.text
assert '"id":1' in response.text
class TestMcpAcceptHeaderShim:
"""Test the ASGI shim that normalizes the Accept header for /mcp so the
MCP SDK's strict Accept check passes for any client (SRP: Only tests the
Accept-header rewrite). Pure ASGI with no deps; tested standalone."""
@staticmethod
def _accept(headers):
for k, v in headers:
if k == b"accept":
return v
return None
@pytest.mark.asyncio
async def test_mcp_accept_header_is_normalized(self):
try:
from computer_server.main import McpAcceptHeaderShim
except ImportError:
pytest.skip("computer_server module not installed")
except Exception as e:
pytest.skip(f"Server initialization requires specific setup: {e}")
seen = {}
async def inner(scope, receive, send):
seen.update(scope)
shim = McpAcceptHeaderShim(inner)
# claude.ai-style request that only advertises application/json:
for path in ("/mcp", "/mcp/", "/mcp/anything"):
await shim(
{"type": "http", "path": path, "headers": [(b"accept", b"application/json")]},
None,
None,
)
assert self._accept(seen["headers"]) == b"application/json, text/event-stream"
@pytest.mark.asyncio
async def test_missing_accept_header_is_added(self):
try:
from computer_server.main import McpAcceptHeaderShim
except ImportError:
pytest.skip("computer_server module not installed")
except Exception as e:
pytest.skip(f"Server initialization requires specific setup: {e}")
seen = {}
async def inner(scope, receive, send):
seen.update(scope)
shim = McpAcceptHeaderShim(inner)
await shim({"type": "http", "path": "/mcp", "headers": []}, None, None)
assert self._accept(seen["headers"]) == b"application/json, text/event-stream"
@pytest.mark.asyncio
async def test_non_mcp_paths_untouched(self):
try:
from computer_server.main import McpAcceptHeaderShim
except ImportError:
pytest.skip("computer_server module not installed")
except Exception as e:
pytest.skip(f"Server initialization requires specific setup: {e}")
seen = {}
async def inner(scope, receive, send):
seen.update(scope)
shim = McpAcceptHeaderShim(inner)
for path in ("/status", "/", "/mcpx", "/ws"):
await shim(
{"type": "http", "path": path, "headers": [(b"accept", b"application/json")]},
None,
None,
)
# Untouched: still exactly what the client sent.
assert self._accept(seen["headers"]) == b"application/json"