mirror of
https://github.com/trycua/computer.git
synced 2025-12-30 18:09:55 -06:00
Format tests with uv run pre-commit run --all-files
This commit is contained in:
32
.github/workflows/python-tests.yml
vendored
32
.github/workflows/python-tests.yml
vendored
@@ -3,23 +3,23 @@ name: Python Unit Tests
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'libs/python/**'
|
||||
- '.github/workflows/python-tests.yml'
|
||||
- "libs/python/**"
|
||||
- ".github/workflows/python-tests.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'libs/python/**'
|
||||
- '.github/workflows/python-tests.yml'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
- "libs/python/**"
|
||||
- ".github/workflows/python-tests.yml"
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test ${{ matrix.package }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
strategy:
|
||||
fail-fast: false # Test all packages even if one fails
|
||||
fail-fast: false # Test all packages even if one fails
|
||||
matrix:
|
||||
package:
|
||||
- core
|
||||
@@ -29,20 +29,20 @@ jobs:
|
||||
- mcp-server
|
||||
- pylume
|
||||
- som
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
run: |
|
||||
pip install uv
|
||||
|
||||
|
||||
- name: Install package and dependencies
|
||||
run: |
|
||||
cd libs/python/${{ matrix.package }}
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
uv pip install --system pytest pytest-asyncio pytest-mock pytest-cov
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd libs/python/${{ matrix.package }}
|
||||
@@ -64,8 +64,8 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
CUA_TELEMETRY_DISABLED: "1" # Disable telemetry during tests
|
||||
|
||||
CUA_TELEMETRY_DISABLED: "1" # Disable telemetry during tests
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
if: always()
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
if: always()
|
||||
|
||||
|
||||
steps:
|
||||
- name: Check test results
|
||||
run: |
|
||||
|
||||
@@ -58,7 +58,7 @@ from unittest.mock import patch
|
||||
|
||||
class TestMyFeature:
|
||||
"""Test MyFeature class."""
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test that feature initializes."""
|
||||
from my_package import MyFeature
|
||||
@@ -78,6 +78,7 @@ def mock_api():
|
||||
## 🔄 CI/CD
|
||||
|
||||
Tests run automatically on every PR via GitHub Actions (`.github/workflows/python-tests.yml`):
|
||||
|
||||
- Matrix strategy: each package tested separately
|
||||
- Python 3.12
|
||||
- ~2 minute runtime
|
||||
@@ -91,11 +92,12 @@ Tests run automatically on every PR via GitHub Actions (`.github/workflows/pytho
|
||||
**Async tests error**: Install `pytest-asyncio` and use `@pytest.mark.asyncio`
|
||||
|
||||
**Mock not working**: Patch at usage location, not definition:
|
||||
|
||||
```python
|
||||
# ✅ Right
|
||||
@patch("my_package.module.external_function")
|
||||
|
||||
# ❌ Wrong
|
||||
# ❌ Wrong
|
||||
@patch("external_library.function")
|
||||
```
|
||||
|
||||
|
||||
@@ -4,18 +4,20 @@ This file contains shared fixtures and configuration for all agent tests.
|
||||
Following SRP: This file ONLY handles test setup/teardown.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_litellm():
|
||||
"""Mock liteLLM completion calls.
|
||||
|
||||
|
||||
Use this fixture to avoid making real LLM API calls during tests.
|
||||
Returns a mock that simulates LLM responses.
|
||||
"""
|
||||
with patch("litellm.acompletion") as mock_completion:
|
||||
|
||||
async def mock_response(*args, **kwargs):
|
||||
"""Simulate a typical LLM response."""
|
||||
return {
|
||||
@@ -39,7 +41,7 @@ def mock_litellm():
|
||||
"total_tokens": 30,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
mock_completion.side_effect = mock_response
|
||||
yield mock_completion
|
||||
|
||||
@@ -47,7 +49,7 @@ def mock_litellm():
|
||||
@pytest.fixture
|
||||
def mock_computer():
|
||||
"""Mock Computer interface for agent tests.
|
||||
|
||||
|
||||
Use this fixture to test agent logic without requiring a real Computer instance.
|
||||
"""
|
||||
computer = AsyncMock()
|
||||
@@ -56,18 +58,18 @@ def mock_computer():
|
||||
computer.interface.left_click = AsyncMock()
|
||||
computer.interface.type = AsyncMock()
|
||||
computer.interface.key = AsyncMock()
|
||||
|
||||
|
||||
# Mock context manager
|
||||
computer.__aenter__ = AsyncMock(return_value=computer)
|
||||
computer.__aexit__ = AsyncMock()
|
||||
|
||||
|
||||
return computer
|
||||
|
||||
|
||||
@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_DISABLED", "1")
|
||||
@@ -76,9 +78,7 @@ def disable_telemetry(monkeypatch):
|
||||
@pytest.fixture
|
||||
def sample_messages():
|
||||
"""Provide sample messages for testing.
|
||||
|
||||
|
||||
Returns a list of messages in the expected format.
|
||||
"""
|
||||
return [
|
||||
{"role": "user", "content": "Take a screenshot and tell me what you see"}
|
||||
]
|
||||
return [{"role": "user", "content": "Take a screenshot and tell me what you see"}]
|
||||
|
||||
@@ -5,8 +5,9 @@ Following SRP: This file tests ONE class (ComputerAgent).
|
||||
All external dependencies (liteLLM, Computer) are mocked.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
|
||||
|
||||
class TestComputerAgentInitialization:
|
||||
@@ -16,9 +17,9 @@ class TestComputerAgentInitialization:
|
||||
def test_agent_initialization_with_model(self, mock_litellm, disable_telemetry):
|
||||
"""Test that agent can be initialized with a model string."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
|
||||
assert agent is not None
|
||||
assert hasattr(agent, "model")
|
||||
assert agent.model == "anthropic/claude-3-5-sonnet-20241022"
|
||||
@@ -27,12 +28,9 @@ class TestComputerAgentInitialization:
|
||||
def test_agent_initialization_with_tools(self, mock_litellm, disable_telemetry, mock_computer):
|
||||
"""Test that agent can be initialized with tools."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
agent = ComputerAgent(
|
||||
model="anthropic/claude-3-5-sonnet-20241022",
|
||||
tools=[mock_computer]
|
||||
)
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022", tools=[mock_computer])
|
||||
|
||||
assert agent is not None
|
||||
assert hasattr(agent, "tools")
|
||||
|
||||
@@ -40,20 +38,19 @@ class TestComputerAgentInitialization:
|
||||
def test_agent_initialization_with_max_budget(self, mock_litellm, disable_telemetry):
|
||||
"""Test that agent can be initialized with max trajectory budget."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
budget = 5.0
|
||||
agent = ComputerAgent(
|
||||
model="anthropic/claude-3-5-sonnet-20241022",
|
||||
max_trajectory_budget=budget
|
||||
model="anthropic/claude-3-5-sonnet-20241022", max_trajectory_budget=budget
|
||||
)
|
||||
|
||||
|
||||
assert agent is not None
|
||||
|
||||
@patch("agent.agent.litellm")
|
||||
def test_agent_requires_model(self, mock_litellm, disable_telemetry):
|
||||
"""Test that agent requires a model parameter."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
# Should fail without model parameter - intentionally missing required argument
|
||||
ComputerAgent() # type: ignore[call-arg]
|
||||
@@ -67,41 +64,36 @@ class TestComputerAgentRun:
|
||||
async def test_agent_run_with_messages(self, mock_litellm, disable_telemetry, sample_messages):
|
||||
"""Test that agent.run() works with valid messages."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
# Mock liteLLM response
|
||||
mock_response = {
|
||||
"id": "chatcmpl-test",
|
||||
"choices": [{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Test response"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 20,
|
||||
"total_tokens": 30
|
||||
}
|
||||
"choices": [
|
||||
{
|
||||
"message": {"role": "assistant", "content": "Test response"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
|
||||
}
|
||||
|
||||
|
||||
mock_litellm.acompletion = AsyncMock(return_value=mock_response)
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
|
||||
# Run should return an async generator
|
||||
result_generator = agent.run(sample_messages)
|
||||
|
||||
|
||||
assert result_generator is not None
|
||||
# Check it's an async generator
|
||||
assert hasattr(result_generator, '__anext__')
|
||||
assert hasattr(result_generator, "__anext__")
|
||||
|
||||
def test_agent_has_run_method(self, disable_telemetry):
|
||||
"""Test that agent has run method available."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
|
||||
# Verify run method exists
|
||||
assert hasattr(agent, "run")
|
||||
assert callable(agent.run)
|
||||
@@ -109,9 +101,9 @@ class TestComputerAgentRun:
|
||||
def test_agent_has_agent_loop(self, disable_telemetry):
|
||||
"""Test that agent has agent_loop initialized."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
|
||||
# Verify agent_loop is initialized
|
||||
assert hasattr(agent, "agent_loop")
|
||||
assert agent.agent_loop is not None
|
||||
@@ -123,13 +115,13 @@ class TestComputerAgentTypes:
|
||||
def test_messages_type_exists(self):
|
||||
"""Test that Messages type is exported."""
|
||||
from agent import Messages
|
||||
|
||||
|
||||
assert Messages is not None
|
||||
|
||||
def test_agent_response_type_exists(self):
|
||||
"""Test that AgentResponse type is exported."""
|
||||
from agent import AgentResponse
|
||||
|
||||
|
||||
assert AgentResponse is not None
|
||||
|
||||
|
||||
@@ -139,12 +131,9 @@ class TestComputerAgentIntegration:
|
||||
def test_agent_accepts_computer_tool(self, disable_telemetry, mock_computer):
|
||||
"""Test that agent can be initialized with Computer tool."""
|
||||
from agent import ComputerAgent
|
||||
|
||||
agent = ComputerAgent(
|
||||
model="anthropic/claude-3-5-sonnet-20241022",
|
||||
tools=[mock_computer]
|
||||
)
|
||||
|
||||
|
||||
agent = ComputerAgent(model="anthropic/claude-3-5-sonnet-20241022", tools=[mock_computer])
|
||||
|
||||
# Verify agent accepted the tool
|
||||
assert agent is not None
|
||||
assert hasattr(agent, "tools")
|
||||
|
||||
@@ -4,28 +4,29 @@ This file contains shared fixtures and configuration for all computer-server tes
|
||||
Following SRP: This file ONLY handles test setup/teardown.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
|
||||
|
||||
@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()
|
||||
@@ -33,14 +34,14 @@ def mock_computer_interface():
|
||||
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_DISABLED", "1")
|
||||
|
||||
@@ -5,8 +5,9 @@ Following SRP: This file tests server initialization and basic operations.
|
||||
All external dependencies are mocked.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
|
||||
|
||||
class TestServerImports:
|
||||
@@ -16,6 +17,7 @@ class TestServerImports:
|
||||
"""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")
|
||||
@@ -29,6 +31,7 @@ class TestServerInitialization:
|
||||
"""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")
|
||||
|
||||
@@ -4,14 +4,15 @@ This file contains shared fixtures and configuration for all computer tests.
|
||||
Following SRP: This file ONLY handles test setup/teardown.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_interface():
|
||||
"""Mock computer interface for testing.
|
||||
|
||||
|
||||
Use this fixture to test Computer logic without real OS calls.
|
||||
"""
|
||||
interface = AsyncMock()
|
||||
@@ -25,14 +26,14 @@ def mock_interface():
|
||||
interface.move_mouse = AsyncMock()
|
||||
interface.scroll = AsyncMock()
|
||||
interface.get_screen_size = AsyncMock(return_value=(1920, 1080))
|
||||
|
||||
|
||||
return interface
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_cloud_provider():
|
||||
"""Mock cloud provider for testing.
|
||||
|
||||
|
||||
Use this fixture to test cloud provider logic without real API calls.
|
||||
"""
|
||||
provider = AsyncMock()
|
||||
@@ -40,14 +41,14 @@ def mock_cloud_provider():
|
||||
provider.stop = AsyncMock()
|
||||
provider.get_status = AsyncMock(return_value="running")
|
||||
provider.execute_command = AsyncMock(return_value="command output")
|
||||
|
||||
|
||||
return provider
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_local_provider():
|
||||
"""Mock local provider for testing.
|
||||
|
||||
|
||||
Use this fixture to test local provider logic without real VM operations.
|
||||
"""
|
||||
provider = AsyncMock()
|
||||
@@ -55,14 +56,14 @@ def mock_local_provider():
|
||||
provider.stop = AsyncMock()
|
||||
provider.get_status = AsyncMock(return_value="running")
|
||||
provider.execute_command = AsyncMock(return_value="command output")
|
||||
|
||||
|
||||
return provider
|
||||
|
||||
|
||||
@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_DISABLED", "1")
|
||||
|
||||
@@ -5,8 +5,9 @@ Following SRP: This file tests ONE class (Computer).
|
||||
All external dependencies (providers, interfaces) are mocked.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
|
||||
|
||||
class TestComputerImport:
|
||||
@@ -15,13 +16,13 @@ class TestComputerImport:
|
||||
def test_computer_class_exists(self):
|
||||
"""Test that Computer class can be imported."""
|
||||
from computer import Computer
|
||||
|
||||
|
||||
assert Computer is not None
|
||||
|
||||
def test_vm_provider_type_exists(self):
|
||||
"""Test that VMProviderType enum can be imported."""
|
||||
from computer import VMProviderType
|
||||
|
||||
|
||||
assert VMProviderType is not None
|
||||
|
||||
|
||||
@@ -31,15 +32,15 @@ class TestComputerInitialization:
|
||||
def test_computer_class_can_be_imported(self, disable_telemetry):
|
||||
"""Test that Computer class can be imported without errors."""
|
||||
from computer import Computer
|
||||
|
||||
|
||||
assert Computer is not None
|
||||
|
||||
def test_computer_has_required_methods(self, disable_telemetry):
|
||||
"""Test that Computer class has required methods."""
|
||||
from computer import Computer
|
||||
|
||||
assert hasattr(Computer, '__aenter__')
|
||||
assert hasattr(Computer, '__aexit__')
|
||||
|
||||
assert hasattr(Computer, "__aenter__")
|
||||
assert hasattr(Computer, "__aexit__")
|
||||
|
||||
|
||||
class TestComputerContextManager:
|
||||
@@ -48,11 +49,11 @@ class TestComputerContextManager:
|
||||
def test_computer_is_async_context_manager(self, disable_telemetry):
|
||||
"""Test that Computer has async context manager methods."""
|
||||
from computer import Computer
|
||||
|
||||
assert hasattr(Computer, '__aenter__')
|
||||
assert hasattr(Computer, '__aexit__')
|
||||
assert callable(getattr(Computer, '__aenter__'))
|
||||
assert callable(getattr(Computer, '__aexit__'))
|
||||
|
||||
assert hasattr(Computer, "__aenter__")
|
||||
assert hasattr(Computer, "__aexit__")
|
||||
assert callable(Computer.__aenter__)
|
||||
assert callable(Computer.__aexit__)
|
||||
|
||||
|
||||
class TestComputerInterface:
|
||||
@@ -61,6 +62,6 @@ class TestComputerInterface:
|
||||
def test_computer_class_structure(self, disable_telemetry):
|
||||
"""Test that Computer class has expected structure."""
|
||||
from computer import Computer
|
||||
|
||||
|
||||
# Verify Computer is a class
|
||||
assert isinstance(Computer, type)
|
||||
|
||||
@@ -4,14 +4,15 @@ This file contains shared fixtures and configuration for all core tests.
|
||||
Following SRP: This file ONLY handles test setup/teardown.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_httpx_client():
|
||||
"""Mock httpx.AsyncClient for API calls.
|
||||
|
||||
|
||||
Use this fixture to avoid making real HTTP requests during tests.
|
||||
"""
|
||||
with patch("httpx.AsyncClient") as mock_client:
|
||||
@@ -23,7 +24,7 @@ def mock_httpx_client():
|
||||
@pytest.fixture
|
||||
def mock_posthog():
|
||||
"""Mock PostHog client for telemetry tests.
|
||||
|
||||
|
||||
Use this fixture to avoid sending real telemetry during tests.
|
||||
"""
|
||||
with patch("posthog.Posthog") as mock_ph:
|
||||
@@ -35,7 +36,7 @@ def mock_posthog():
|
||||
@pytest.fixture
|
||||
def disable_telemetry(monkeypatch):
|
||||
"""Disable telemetry for tests that don't need it.
|
||||
|
||||
|
||||
Use this fixture to ensure telemetry is disabled during tests.
|
||||
"""
|
||||
monkeypatch.setenv("CUA_TELEMETRY_DISABLED", "1")
|
||||
|
||||
@@ -5,9 +5,10 @@ All external dependencies (PostHog, file system) are mocked.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock, mock_open
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestTelemetryEnabled:
|
||||
@@ -18,43 +19,43 @@ class TestTelemetryEnabled:
|
||||
# Remove any environment variables that might affect the test
|
||||
monkeypatch.delenv("CUA_TELEMETRY", raising=False)
|
||||
monkeypatch.delenv("CUA_TELEMETRY_ENABLED", raising=False)
|
||||
|
||||
|
||||
from core.telemetry import is_telemetry_enabled
|
||||
|
||||
|
||||
assert is_telemetry_enabled() is True
|
||||
|
||||
def test_telemetry_disabled_with_legacy_flag(self, monkeypatch):
|
||||
"""Test that telemetry can be disabled with legacy CUA_TELEMETRY=off."""
|
||||
monkeypatch.setenv("CUA_TELEMETRY", "off")
|
||||
|
||||
|
||||
from core.telemetry import is_telemetry_enabled
|
||||
|
||||
|
||||
assert is_telemetry_enabled() is False
|
||||
|
||||
def test_telemetry_disabled_with_new_flag(self, monkeypatch):
|
||||
"""Test that telemetry can be disabled with CUA_TELEMETRY_ENABLED=false."""
|
||||
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "false")
|
||||
|
||||
|
||||
from core.telemetry import is_telemetry_enabled
|
||||
|
||||
|
||||
assert is_telemetry_enabled() is False
|
||||
|
||||
@pytest.mark.parametrize("value", ["0", "false", "no", "off"])
|
||||
def test_telemetry_disabled_with_various_values(self, monkeypatch, value):
|
||||
"""Test that telemetry respects various disable values."""
|
||||
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", value)
|
||||
|
||||
|
||||
from core.telemetry import is_telemetry_enabled
|
||||
|
||||
|
||||
assert is_telemetry_enabled() is False
|
||||
|
||||
@pytest.mark.parametrize("value", ["1", "true", "yes", "on"])
|
||||
def test_telemetry_enabled_with_various_values(self, monkeypatch, value):
|
||||
"""Test that telemetry respects various enable values."""
|
||||
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", value)
|
||||
|
||||
|
||||
from core.telemetry import is_telemetry_enabled
|
||||
|
||||
|
||||
assert is_telemetry_enabled() is True
|
||||
|
||||
|
||||
@@ -66,18 +67,18 @@ class TestPostHogTelemetryClient:
|
||||
def test_client_initialization(self, mock_path, mock_posthog, disable_telemetry):
|
||||
"""Test that client initializes correctly."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
# Mock the storage directory
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.exists.return_value = False
|
||||
mock_path.return_value.parent.parent = MagicMock()
|
||||
mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client = PostHogTelemetryClient()
|
||||
|
||||
|
||||
assert client is not None
|
||||
assert hasattr(client, "installation_id")
|
||||
assert hasattr(client, "initialized")
|
||||
@@ -88,22 +89,22 @@ class TestPostHogTelemetryClient:
|
||||
def test_installation_id_generation(self, mock_path, mock_posthog, disable_telemetry):
|
||||
"""Test that installation ID is generated if not exists."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
# Mock file system
|
||||
mock_id_file = MagicMock()
|
||||
mock_id_file.exists.return_value = False
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.__truediv__.return_value = mock_id_file
|
||||
|
||||
|
||||
mock_core_dir = MagicMock()
|
||||
mock_core_dir.__truediv__.return_value = mock_storage_dir
|
||||
mock_path.return_value.parent.parent = mock_core_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client = PostHogTelemetryClient()
|
||||
|
||||
|
||||
# Should have generated a new UUID
|
||||
assert client.installation_id is not None
|
||||
assert len(client.installation_id) == 36 # UUID format
|
||||
@@ -113,26 +114,26 @@ class TestPostHogTelemetryClient:
|
||||
def test_installation_id_persistence(self, mock_path, mock_posthog, disable_telemetry):
|
||||
"""Test that installation ID is read from file if exists."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
existing_id = "test-installation-id-123"
|
||||
|
||||
|
||||
# Mock file system
|
||||
mock_id_file = MagicMock()
|
||||
mock_id_file.exists.return_value = True
|
||||
mock_id_file.read_text.return_value = existing_id
|
||||
|
||||
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.__truediv__.return_value = mock_id_file
|
||||
|
||||
|
||||
mock_core_dir = MagicMock()
|
||||
mock_core_dir.__truediv__.return_value = mock_storage_dir
|
||||
mock_path.return_value.parent.parent = mock_core_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client = PostHogTelemetryClient()
|
||||
|
||||
|
||||
assert client.installation_id == existing_id
|
||||
|
||||
@patch("core.telemetry.posthog.posthog")
|
||||
@@ -140,22 +141,22 @@ class TestPostHogTelemetryClient:
|
||||
def test_record_event_when_disabled(self, mock_path, mock_posthog, monkeypatch):
|
||||
"""Test that events are not recorded when telemetry is disabled."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
# Disable telemetry explicitly using the correct environment variable
|
||||
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "false")
|
||||
|
||||
|
||||
# Mock file system
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.exists.return_value = False
|
||||
mock_path.return_value.parent.parent = MagicMock()
|
||||
mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client = PostHogTelemetryClient()
|
||||
client.record_event("test_event", {"key": "value"})
|
||||
|
||||
|
||||
# PostHog capture should not be called at all when telemetry is disabled
|
||||
mock_posthog.capture.assert_not_called()
|
||||
|
||||
@@ -164,26 +165,26 @@ class TestPostHogTelemetryClient:
|
||||
def test_record_event_when_enabled(self, mock_path, mock_posthog, monkeypatch):
|
||||
"""Test that events are recorded when telemetry is enabled."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
# Enable telemetry
|
||||
monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "true")
|
||||
|
||||
|
||||
# Mock file system
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.exists.return_value = False
|
||||
mock_path.return_value.parent.parent = MagicMock()
|
||||
mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client = PostHogTelemetryClient()
|
||||
client.initialized = True # Pretend it's initialized
|
||||
|
||||
|
||||
event_name = "test_event"
|
||||
event_props = {"key": "value"}
|
||||
client.record_event(event_name, event_props)
|
||||
|
||||
|
||||
# PostHog capture should be called
|
||||
assert mock_posthog.capture.call_count >= 1
|
||||
|
||||
@@ -192,19 +193,19 @@ class TestPostHogTelemetryClient:
|
||||
def test_singleton_pattern(self, mock_path, mock_posthog, disable_telemetry):
|
||||
"""Test that get_client returns the same instance."""
|
||||
from core.telemetry.posthog import PostHogTelemetryClient
|
||||
|
||||
|
||||
# Mock file system
|
||||
mock_storage_dir = MagicMock()
|
||||
mock_storage_dir.exists.return_value = False
|
||||
mock_path.return_value.parent.parent = MagicMock()
|
||||
mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir
|
||||
|
||||
|
||||
# Reset singleton
|
||||
PostHogTelemetryClient.destroy_client()
|
||||
|
||||
|
||||
client1 = PostHogTelemetryClient.get_client()
|
||||
client2 = PostHogTelemetryClient.get_client()
|
||||
|
||||
|
||||
assert client1 is client2
|
||||
|
||||
|
||||
@@ -215,29 +216,29 @@ class TestRecordEvent:
|
||||
def test_record_event_calls_client(self, mock_client_class, disable_telemetry):
|
||||
"""Test that record_event delegates to the client."""
|
||||
from core.telemetry import record_event
|
||||
|
||||
|
||||
mock_client_instance = Mock()
|
||||
mock_client_class.get_client.return_value = mock_client_instance
|
||||
|
||||
|
||||
event_name = "test_event"
|
||||
event_props = {"key": "value"}
|
||||
|
||||
|
||||
record_event(event_name, event_props)
|
||||
|
||||
|
||||
mock_client_instance.record_event.assert_called_once_with(event_name, event_props)
|
||||
|
||||
@patch("core.telemetry.posthog.PostHogTelemetryClient")
|
||||
def test_record_event_without_properties(self, mock_client_class, disable_telemetry):
|
||||
"""Test that record_event works without properties."""
|
||||
from core.telemetry import record_event
|
||||
|
||||
|
||||
mock_client_instance = Mock()
|
||||
mock_client_class.get_client.return_value = mock_client_instance
|
||||
|
||||
|
||||
event_name = "test_event"
|
||||
|
||||
|
||||
record_event(event_name)
|
||||
|
||||
|
||||
mock_client_instance.record_event.assert_called_once_with(event_name, {})
|
||||
|
||||
|
||||
@@ -248,7 +249,7 @@ class TestDestroyTelemetryClient:
|
||||
def test_destroy_client_calls_class_method(self, mock_client_class):
|
||||
"""Test that destroy_telemetry_client delegates correctly."""
|
||||
from core.telemetry import destroy_telemetry_client
|
||||
|
||||
|
||||
destroy_telemetry_client()
|
||||
|
||||
|
||||
mock_client_class.destroy_client.assert_called_once()
|
||||
|
||||
@@ -4,28 +4,29 @@ This file contains shared fixtures and configuration for all mcp-server tests.
|
||||
Following SRP: This file ONLY handles test setup/teardown.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_mcp_context():
|
||||
"""Mock MCP context for testing.
|
||||
|
||||
|
||||
Use this fixture to test MCP server logic without real MCP connections.
|
||||
"""
|
||||
context = AsyncMock()
|
||||
context.request_context = AsyncMock()
|
||||
context.session = Mock()
|
||||
context.session.send_resource_updated = AsyncMock()
|
||||
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_computer():
|
||||
"""Mock Computer instance for MCP server tests.
|
||||
|
||||
|
||||
Use this fixture to test MCP logic without real Computer operations.
|
||||
"""
|
||||
computer = AsyncMock()
|
||||
@@ -33,18 +34,18 @@ def mock_computer():
|
||||
computer.interface.screenshot = AsyncMock(return_value=b"fake_screenshot")
|
||||
computer.interface.left_click = AsyncMock()
|
||||
computer.interface.type = AsyncMock()
|
||||
|
||||
|
||||
# Mock context manager
|
||||
computer.__aenter__ = AsyncMock(return_value=computer)
|
||||
computer.__aexit__ = AsyncMock()
|
||||
|
||||
|
||||
return computer
|
||||
|
||||
|
||||
@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_DISABLED", "1")
|
||||
|
||||
@@ -5,8 +5,9 @@ Following SRP: This file tests MCP server initialization.
|
||||
All external dependencies are mocked.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
|
||||
|
||||
class TestMCPServerImports:
|
||||
@@ -16,6 +17,7 @@ class TestMCPServerImports:
|
||||
"""Test that mcp_server module can be imported."""
|
||||
try:
|
||||
import mcp_server
|
||||
|
||||
assert mcp_server is not None
|
||||
except ImportError:
|
||||
pytest.skip("mcp_server module not installed")
|
||||
@@ -31,6 +33,7 @@ class TestMCPServerInitialization:
|
||||
"""Basic smoke test: verify MCP server components can be imported."""
|
||||
try:
|
||||
from mcp_server import server
|
||||
|
||||
assert server is not None
|
||||
except ImportError:
|
||||
pytest.skip("MCP server module not available")
|
||||
|
||||
@@ -4,23 +4,20 @@ This module provides test fixtures for the pylume package.
|
||||
Note: This package has macOS-specific dependencies and will skip tests
|
||||
if the required modules are not available.
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subprocess():
|
||||
with patch('subprocess.run') as mock_run:
|
||||
mock_run.return_value = Mock(
|
||||
returncode=0,
|
||||
stdout='',
|
||||
stderr=''
|
||||
)
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||
yield mock_run
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_requests():
|
||||
with patch('requests.get') as mock_get, \
|
||||
patch('requests.post') as mock_post:
|
||||
yield {'get': mock_get, 'post': mock_post}
|
||||
with patch("requests.get") as mock_get, patch("requests.post") as mock_post:
|
||||
yield {"get": mock_get, "post": mock_post}
|
||||
|
||||
@@ -15,6 +15,7 @@ class TestPylumeImports:
|
||||
"""Test that pylume module can be imported."""
|
||||
try:
|
||||
import pylume
|
||||
|
||||
assert pylume is not None
|
||||
except ImportError:
|
||||
pytest.skip("pylume module not installed")
|
||||
@@ -27,6 +28,7 @@ class TestPylumeInitialization:
|
||||
"""Basic smoke test: verify pylume components can be imported."""
|
||||
try:
|
||||
import pylume
|
||||
|
||||
# Check for basic attributes
|
||||
assert pylume is not None
|
||||
except ImportError:
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
This module provides test fixtures for the som (Set-of-Mark) package.
|
||||
The som package depends on heavy ML models and will skip tests if not available.
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_torch():
|
||||
with patch('torch.load') as mock_load:
|
||||
with patch("torch.load") as mock_load:
|
||||
mock_load.return_value = Mock()
|
||||
yield mock_load
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_icon_detector():
|
||||
with patch('omniparser.IconDetector') as mock_detector:
|
||||
with patch("omniparser.IconDetector") as mock_detector:
|
||||
instance = Mock()
|
||||
mock_detector.return_value = instance
|
||||
yield instance
|
||||
|
||||
@@ -15,6 +15,7 @@ class TestSomImports:
|
||||
"""Test that som module can be imported."""
|
||||
try:
|
||||
import som
|
||||
|
||||
assert som is not None
|
||||
except ImportError:
|
||||
pytest.skip("som module not installed")
|
||||
@@ -23,6 +24,7 @@ class TestSomImports:
|
||||
"""Test that OmniParser can be imported."""
|
||||
try:
|
||||
from som import OmniParser
|
||||
|
||||
assert OmniParser is not None
|
||||
except ImportError:
|
||||
pytest.skip("som module not available")
|
||||
@@ -32,7 +34,8 @@ class TestSomImports:
|
||||
def test_models_import(self):
|
||||
"""Test that model classes can be imported."""
|
||||
try:
|
||||
from som import BoundingBox, UIElement, ParseResult
|
||||
from som import BoundingBox, ParseResult, UIElement
|
||||
|
||||
assert BoundingBox is not None
|
||||
assert UIElement is not None
|
||||
assert ParseResult is not None
|
||||
@@ -49,8 +52,9 @@ class TestSomModels:
|
||||
"""Test BoundingBox class structure."""
|
||||
try:
|
||||
from som import BoundingBox
|
||||
|
||||
# Check the class exists and has expected structure
|
||||
assert hasattr(BoundingBox, '__init__')
|
||||
assert hasattr(BoundingBox, "__init__")
|
||||
except ImportError:
|
||||
pytest.skip("som models not available")
|
||||
except Exception as e:
|
||||
@@ -60,8 +64,9 @@ class TestSomModels:
|
||||
"""Test UIElement class structure."""
|
||||
try:
|
||||
from som import UIElement
|
||||
|
||||
# Check the class exists and has expected structure
|
||||
assert hasattr(UIElement, '__init__')
|
||||
assert hasattr(UIElement, "__init__")
|
||||
except ImportError:
|
||||
pytest.skip("som models not available")
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user