diff --git a/.env.example b/.env.example index 8b23d0a53..f14f68a5a 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,7 @@ OCR_API_KEY= # Android ADB Connectivity (Optional - defaults to 127.0.0.1:5037) ADB_HOST=127.0.0.1 +# If ADB_PORT is unset, ANDROID_ADB_SERVER_PORT is also supported. ADB_PORT=5037 # ADB_DEVICE_SERIAL= # Keep the display awake, falling back to a wake lock or safe heartbeat when needed. diff --git a/artemis/config/settings.py b/artemis/config/settings.py index 182d7ec77..75bde3d47 100644 --- a/artemis/config/settings.py +++ b/artemis/config/settings.py @@ -18,7 +18,7 @@ from pathlib import Path from dotenv import load_dotenv -from pydantic import Field, SecretStr, model_validator +from pydantic import AliasChoices, Field, SecretStr, model_validator from pydantic_settings import BaseSettings from artemis.config.constants import ( @@ -111,7 +111,10 @@ class Settings(BaseSettings): # Android ADB Connectivity ADB_HOST: str | None = Field(default=DEFAULT_ADB_HOST) - ADB_PORT: int | None = Field(default=DEFAULT_ADB_PORT) + ADB_PORT: int | None = Field( + default=DEFAULT_ADB_PORT, + validation_alias=AliasChoices("ADB_PORT", "ANDROID_ADB_SERVER_PORT"), + ) ADB_DEVICE_SERIAL: str | None = None # UI hierarchy backend: "auto" = Accessibility Helper with UIAutomator2 # fallback, "helper" = helper only, "uiautomator" = UIAutomator2 only. diff --git a/tests/unit/core/test_adb_server_connection.py b/tests/unit/core/test_adb_server_connection.py index e49a1cf83..a8862100f 100644 --- a/tests/unit/core/test_adb_server_connection.py +++ b/tests/unit/core/test_adb_server_connection.py @@ -19,7 +19,7 @@ import pytest -from artemis.config import settings +from artemis.config import Settings, settings from artemis.core.diagnostics.adb_server_connection import ( AdbServerConnectionManager, AdbServerEndpoint, @@ -70,6 +70,44 @@ def test_adb_session_builds_explicit_endpoint_command_and_environment(): assert environment[ADB_ENDPOINT_ID_ENV] == endpoint.identity +@pytest.mark.parametrize( + ("overrides", "expected_port"), + [ + ({}, 8848), + ({"ADB_PORT": "5038"}, 5038), + ({"ADB_SERVER_SOCKET": "tcp:127.0.0.1:5039"}, 5039), + ], +) +def test_synchronize_android_adb_server_port(monkeypatch, overrides, expected_port): + # Activation updates both settings and os.environ; isolate those mutations. + monkeypatch.setattr(os, "environ", os.environ.copy()) + for name in ("ADB_HOST", "ADB_PORT", "ADB_SERVER_SOCKET", ADB_ENDPOINT_ID_ENV): + monkeypatch.delenv(name, raising=False) + monkeypatch.setenv("ANDROID_ADB_SERVER_PORT", "8848") + for name, value in overrides.items(): + monkeypatch.setenv(name, value) + configured = Settings(_env_file=None) + monkeypatch.setattr(settings, "ADB_HOST", configured.ADB_HOST) + monkeypatch.setattr(settings, "ADB_PORT", configured.ADB_PORT) + manager = AdbServerConnectionManager(adb_resolver=lambda: "adb", env_files=[]) + + endpoint = manager.synchronize_environment() + + assert endpoint.port == expected_port + assert settings.ADB_PORT == expected_port + assert os.environ["ADB_PORT"] == str(expected_port) + assert os.environ["ADB_SERVER_SOCKET"] == f"tcp:127.0.0.1:{expected_port}" + session = AdbSession(endpoint, adb_path="adb") + assert session.command(["devices"]) == [ + "adb", + "-H", + "127.0.0.1", + "-P", + str(expected_port), + "devices", + ] + + @pytest.mark.asyncio async def test_connect_activates_forwarded_server_after_success(monkeypatch): captured: dict[str, object] = {} diff --git a/tests/unit/test_config_package.py b/tests/unit/test_config_package.py index 5b8c56ad8..99c2dcb85 100644 --- a/tests/unit/test_config_package.py +++ b/tests/unit/test_config_package.py @@ -107,6 +107,26 @@ def test_settings_and_api_key_fallbacks(monkeypatch): assert custom_settings.get_api_key("xai").get_secret_value() == "xai_test_key" +@pytest.mark.parametrize( + ("adb_port", "android_port", "expected"), + [(None, None, 5037), (None, "8848", 8848), ("5038", "8848", 5038)], +) +def test_settings_adb_port_environment(monkeypatch, adb_port, android_port, expected): + for name, value in (("ADB_PORT", adb_port), ("ANDROID_ADB_SERVER_PORT", android_port)): + monkeypatch.delenv(name, raising=False) + if value is not None: + monkeypatch.setenv(name, value) + + assert Settings(_env_file=None).ADB_PORT == expected + + +def test_settings_explicit_adb_port_overrides_android_environment(monkeypatch): + monkeypatch.delenv("ADB_PORT", raising=False) + monkeypatch.setenv("ANDROID_ADB_SERVER_PORT", "8848") + + assert Settings(_env_file=None, ADB_PORT=5040).ADB_PORT == 5040 + + def test_placeholder_api_key_filtering(monkeypatch): """Test that default template placeholders are filtered out and treated as unconfigured.""" from artemis.config.settings import is_placeholder_key