From 4f9eb3d33a8481578bce656928a51dd16ec8c9d5 Mon Sep 17 00:00:00 2001 From: wellorbetter <92317982+wellorbetter@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:27:28 +0800 Subject: [PATCH 1/3] fix(tests): isolate Flash model construction from credentials --- tests/unit/agents/test_flash_runner.py | 12 ++++++++++++ tests/unit/agents/test_flash_runner_ledger.py | 12 ++++++++++++ tests/unit/agents/test_flash_scrub_edge.py | 14 +++++++++++++- tests/unit/agents/test_flash_step_summarizer.py | 15 ++++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/unit/agents/test_flash_runner.py b/tests/unit/agents/test_flash_runner.py index f188b5e1..8bf8ac18 100644 --- a/tests/unit/agents/test_flash_runner.py +++ b/tests/unit/agents/test_flash_runner.py @@ -29,6 +29,18 @@ from artemis.context import ArtemisContext +@pytest.fixture(autouse=True) +def isolate_summarizer_model(monkeypatch): + """Keep real summarizer behavior without constructing a provider client.""" + model = Mock() + model.ainvoke = AsyncMock( + side_effect=AssertionError("Configure a model response before invoking the summarizer") + ) + factory = Mock(return_value=model) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_llm", factory) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_google_llm", factory) + + @pytest.fixture def mock_context(): ctx = Mock(spec=ArtemisContext) diff --git a/tests/unit/agents/test_flash_runner_ledger.py b/tests/unit/agents/test_flash_runner_ledger.py index 57ff1ef8..8e164625 100644 --- a/tests/unit/agents/test_flash_runner_ledger.py +++ b/tests/unit/agents/test_flash_runner_ledger.py @@ -56,6 +56,18 @@ ) +@pytest.fixture(autouse=True) +def isolate_summarizer_model(monkeypatch): + """Keep real summarizer behavior without constructing a provider client.""" + model = Mock() + model.ainvoke = AsyncMock( + side_effect=AssertionError("Configure a model response before invoking the summarizer") + ) + factory = Mock(return_value=model) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_llm", factory) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_google_llm", factory) + + @pytest.fixture def mock_context(): ctx = Mock(spec=ArtemisContext) diff --git a/tests/unit/agents/test_flash_scrub_edge.py b/tests/unit/agents/test_flash_scrub_edge.py index 1524c3b9..1d0779d2 100644 --- a/tests/unit/agents/test_flash_scrub_edge.py +++ b/tests/unit/agents/test_flash_scrub_edge.py @@ -20,7 +20,7 @@ """ import json -from unittest.mock import Mock +from unittest.mock import AsyncMock, Mock import pytest from langchain_core.messages import AIMessage, HumanMessage, ToolMessage @@ -30,6 +30,18 @@ from artemis.context import ArtemisContext +@pytest.fixture(autouse=True) +def isolate_summarizer_model(monkeypatch): + """Keep real summarizer behavior without constructing a provider client.""" + model = Mock() + model.ainvoke = AsyncMock( + side_effect=AssertionError("Configure a model response before invoking the summarizer") + ) + factory = Mock(return_value=model) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_llm", factory) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_google_llm", factory) + + @pytest.fixture def mock_context(): ctx = Mock(spec=ArtemisContext) diff --git a/tests/unit/agents/test_flash_step_summarizer.py b/tests/unit/agents/test_flash_step_summarizer.py index a26bc57b..b179fc8a 100644 --- a/tests/unit/agents/test_flash_step_summarizer.py +++ b/tests/unit/agents/test_flash_step_summarizer.py @@ -31,6 +31,18 @@ from artemis.sdk.builders import Builders +@pytest.fixture(autouse=True) +def isolate_summarizer_model(monkeypatch): + """Keep real summarizer behavior without constructing a provider client.""" + model = Mock() + model.ainvoke = AsyncMock( + side_effect=AssertionError("Configure a model response before invoking the summarizer") + ) + factory = Mock(return_value=model) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_llm", factory) + monkeypatch.setattr("artemis.agents.flash.summarizer.get_google_llm", factory) + + @pytest.fixture def mock_context(): ctx = Mock(spec=ArtemisContext) @@ -834,13 +846,14 @@ def test_build_focus_context_drops_blank_fields_and_caps_reasoning_tail_intact() def test_flash_config_and_builder(): """Verify Flash profile configuration model and SDK builder fluent API.""" + # This checks configuration values, not external provider credentials. cfg = Builders.AgentConfig.with_flash_config( max_turns=25, explorer_mode="flash", step_summarizer=True, step_summarizer_model="gemini-2.5-flash-lite", prune_history_xml=True, - ).build() + ).build(validate_profiles=False) assert cfg.flash.max_turns == 25 assert cfg.flash.explorer_mode == "flash" From 75c34ca67035148d49df5f38eba9be50ce64867a Mon Sep 17 00:00:00 2001 From: wellorbetter <92317982+wellorbetter@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:38:01 +0800 Subject: [PATCH 2/3] fix(tests): prevent mocked MCP spawns from leaking watchdog threads --- tests/unit/mcp/test_mcp_tools.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/unit/mcp/test_mcp_tools.py b/tests/unit/mcp/test_mcp_tools.py index 0064ed9a..1a1d015d 100644 --- a/tests/unit/mcp/test_mcp_tools.py +++ b/tests/unit/mcp/test_mcp_tools.py @@ -34,6 +34,14 @@ from artemis.runtime import trace_store +@pytest.fixture(autouse=True) +def mock_spawn_watchdog(monkeypatch): + """Fake runner processes must not leave live watchdogs after fixture teardown.""" + watchdog = MagicMock() + monkeypatch.setattr("mcp_server.tools.task_runner._start_spawn_watchdog", watchdog) + return watchdog + + @pytest.fixture def temp_trace_env(monkeypatch): temp_dir = tempfile.mkdtemp() @@ -88,7 +96,9 @@ def test_mobile_run_task_invalid_model(): mobile_run_task(task_desc="test", conversation_id="conv-1", model="invalid_model") -def test_mobile_run_task_reserves_and_passes_global_queue_ticket(temp_trace_env): +def test_mobile_run_task_reserves_and_passes_global_queue_ticket( + temp_trace_env, mock_spawn_watchdog +): process = MagicMock(pid=43210) with ( patch( @@ -117,11 +127,14 @@ def test_mobile_run_task_reserves_and_passes_global_queue_ticket(temp_trace_env) assert popen.call_args.kwargs["env"]["ARTEMIS_DEVICE_QUEUE_TICKET"] == "queue-ticket-1" assert popen.call_args.kwargs["env"]["ARTEMIS_TASK_INGRESS"] == "mcp" status = trace_store.read_status(result["trace_id"]) + mock_spawn_watchdog.assert_called_once_with( + result["trace_id"], 43210, "queue-ticket-1", "conv-1" + ) assert status["queue_ticket"] == "queue-ticket-1" assert status["device_serial"] is None -def test_mobile_run_task_with_device_serial(temp_trace_env): +def test_mobile_run_task_with_device_serial(temp_trace_env, mock_spawn_watchdog): process = MagicMock(pid=54321) with ( patch( @@ -158,6 +171,9 @@ def test_mobile_run_task_with_device_serial(temp_trace_env): ingress="mcp", device_id="pixel-11-pro-001", ) + mock_spawn_watchdog.assert_called_once_with( + result["trace_id"], 54321, "queue-ticket-dev", "conv-2" + ) assert result["device_serial"] == "pixel-11-pro-001" cmd = popen.call_args.args[0] assert "--device-serial" in cmd From cfcfc8d0578d0f67501c74ef8c1d199de79bc778 Mon Sep 17 00:00:00 2001 From: wellorbetter <92317982+wellorbetter@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:49:03 +0800 Subject: [PATCH 3/3] test(mcp): create an isolated database for invalid-action coverage --- tests/unit/mcp/test_mcp_tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/mcp/test_mcp_tools.py b/tests/unit/mcp/test_mcp_tools.py index 1a1d015d..3032f54e 100644 --- a/tests/unit/mcp/test_mcp_tools.py +++ b/tests/unit/mcp/test_mcp_tools.py @@ -18,6 +18,7 @@ import json import os import shutil +import sqlite3 import tempfile from types import SimpleNamespace import uuid @@ -386,7 +387,9 @@ async def test_mobile_get_device_state_hierarchy_without_ocr(): @pytest.mark.asyncio -async def test_mobile_inspect_trace_invalid_action(): +async def test_mobile_inspect_trace_invalid_action(temp_trace_env): + # The database precondition must not depend on a previous local task run. + sqlite3.connect(os.path.join(temp_trace_env, "data_engine.db")).close() res = await mobile_inspect_trace(action="invalid_action", trace_id="trace-123") assert "error" in res assert "not supported" in res["message"]