Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions tests/unit/agents/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Shared fixtures for agent unit tests."""

from typing import Any

import pytest


class StubChatModel:
"""Stand-in for a resolved provider client.

Mirrors the only attribute ``VisualStepSummarizer.__init__`` reads back
(``model``), so the constructor's model-name resolution is still
exercised, and refuses to be invoked: a test that needs generation must
install its own fake on ``summarizer._llm``.
"""

def __init__(self, model_name: str) -> None:
self.model = model_name

async def ainvoke(self, *_args: Any, **_kwargs: Any) -> Any:
raise AssertionError(
"The stubbed summarizer model was invoked. A unit test that needs "
"generation must assign its own fake to `summarizer._llm`."
)

def __repr__(self) -> str: # pragma: no cover - debugging aid
return f"<StubChatModel model={self.model!r}>"


@pytest.fixture
def stub_summarizer_model_factories(monkeypatch):
"""Resolve the summarizer's model without provider credentials.

``VisualStepSummarizer.__init__`` asks ``get_llm`` for the utility model
and falls back to ``get_google_llm`` on *any* exception, so with no
``GOOGLE_API_KEY`` present both paths end in a real
``ChatGoogleGenerativeAI`` construction that raises. Replacing
``summarizer._llm`` after the fact is too late -- the object never gets
built. Both factories are therefore stubbed at the summarizer module
boundary, which leaves the real summarizer, its retry loop, the
compressor, the ledger and the runner under test.
"""

def _fake_get_google_llm(model_name: str | None = None, **_kwargs: Any) -> StubChatModel:
return StubChatModel(model_name or "gemini-2.5-flash-lite")

def _fake_get_llm(_ctx: Any, *, name: str = "", is_utils: bool = False, **_kwargs: Any):
return StubChatModel("gemini-2.5-flash-lite")

monkeypatch.setattr(
"artemis.agents.flash.summarizer.get_google_llm",
_fake_get_google_llm,
)
monkeypatch.setattr("artemis.agents.flash.summarizer.get_llm", _fake_get_llm)
5 changes: 5 additions & 0 deletions tests/unit/agents/test_flash_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
from artemis.context import ArtemisContext


@pytest.fixture(autouse=True)
def _isolate_summarizer_model_factories(stub_summarizer_model_factories):
"""Keep this module runnable without provider credentials (see conftest)."""


@pytest.fixture
def mock_context():
ctx = Mock(spec=ArtemisContext)
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/agents/test_flash_runner_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
)


@pytest.fixture(autouse=True)
def _isolate_summarizer_model_factories(stub_summarizer_model_factories):
"""Keep this module runnable without provider credentials (see conftest)."""


@pytest.fixture
def mock_context():
ctx = Mock(spec=ArtemisContext)
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/agents/test_flash_scrub_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
from artemis.context import ArtemisContext


@pytest.fixture(autouse=True)
def _isolate_summarizer_model_factories(stub_summarizer_model_factories):
"""Keep this module runnable without provider credentials (see conftest)."""


@pytest.fixture
def mock_context():
ctx = Mock(spec=ArtemisContext)
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/agents/test_flash_step_summarizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
from artemis.sdk.builders import Builders


@pytest.fixture(autouse=True)
def _isolate_summarizer_model_factories(stub_summarizer_model_factories):
"""Keep this module runnable without provider credentials (see conftest)."""


@pytest.fixture
def mock_context():
ctx = Mock(spec=ArtemisContext)
Expand Down Expand Up @@ -840,7 +845,7 @@ def test_flash_config_and_builder():
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"
Expand Down