65 lines
2.6 KiB
Python
65 lines
2.6 KiB
Python
"""Hopper failures must not become successful negative lookups."""
|
|
|
|
import asyncio
|
|
from importlib import import_module
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
|
|
from artemis.agents.hopper.hopper import HopperError, HopperOutput
|
|
|
|
module = import_module("artemis.agents.hopper.hopper")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("invalid", [None, {}, {"found": True, "output": " ", "reason": "Empty"}])
|
|
async def test_invalid_extraction_switches_to_fallback(monkeypatch, invalid):
|
|
good = HopperOutput(found=True, output="com.example.app", reason="Matched")
|
|
primary = SimpleNamespace(ainvoke=AsyncMock(return_value=invalid))
|
|
secondary = SimpleNamespace(ainvoke=AsyncMock(return_value=good))
|
|
factory = Mock(
|
|
side_effect=[
|
|
SimpleNamespace(with_structured_output=lambda _: primary),
|
|
SimpleNamespace(with_structured_output=lambda _: secondary),
|
|
]
|
|
)
|
|
monkeypatch.setattr(module, "get_llm", factory)
|
|
assert (
|
|
await module.hopper(SimpleNamespace(data_engine=None), "Find app", "com.example.app")
|
|
== good
|
|
)
|
|
assert primary.ainvoke.await_count == secondary.ainvoke.await_count == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("use_fallback", [False, True])
|
|
async def test_exhausted_extraction_raises_a_distinct_error(monkeypatch, use_fallback):
|
|
model = SimpleNamespace(ainvoke=AsyncMock(return_value=None))
|
|
monkeypatch.setattr(
|
|
module, "get_llm", lambda **_: SimpleNamespace(with_structured_output=lambda _: model)
|
|
)
|
|
with pytest.raises(HopperError, match="模型未能完成数据提取"):
|
|
await module.hopper(
|
|
SimpleNamespace(data_engine=None), "Find app", "packages", use_fallback=use_fallback
|
|
)
|
|
assert model.ainvoke.await_count == (2 if use_fallback else 1)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cancelled_extraction_never_uses_fallback(monkeypatch):
|
|
model = SimpleNamespace(ainvoke=AsyncMock(side_effect=asyncio.CancelledError))
|
|
monkeypatch.setattr(
|
|
module, "get_llm", lambda **_: SimpleNamespace(with_structured_output=lambda _: model)
|
|
)
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await module.hopper(SimpleNamespace(data_engine=None), "Find app", "packages")
|
|
assert model.ainvoke.await_count == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_model_setup_failure_is_not_reported_as_missing_data(monkeypatch):
|
|
monkeypatch.setattr(module, "get_llm", Mock(side_effect=ValueError("invalid model settings")))
|
|
with pytest.raises(HopperError, match="模型未能完成数据提取"):
|
|
await module.hopper(SimpleNamespace(data_engine=None), "Find app", "packages")
|