142 lines
3.6 KiB
Python
142 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from core.models import Bounds, Scene, SceneElement
|
|
from semantic.config import SemanticConfig, load_config
|
|
from semantic.enricher import enrich_scene
|
|
from semantic.llm_client import EnrichmentUnavailable
|
|
|
|
|
|
class FakeClient:
|
|
def __init__(
|
|
self,
|
|
*,
|
|
response: dict[str, Any] | None = None,
|
|
error: Exception | None = None,
|
|
) -> None:
|
|
self.response = response
|
|
self.error = error
|
|
self.calls: list[tuple[dict[str, Any], float]] = []
|
|
|
|
def enrich(self, scene_json: dict[str, Any], *, timeout: float) -> dict[str, Any]:
|
|
self.calls.append((scene_json, timeout))
|
|
if self.error:
|
|
raise self.error
|
|
assert self.response is not None
|
|
return self.response
|
|
|
|
|
|
def _scene() -> Scene:
|
|
return Scene(
|
|
width=10,
|
|
height=20,
|
|
elements=[
|
|
SceneElement(
|
|
id="input",
|
|
type="input",
|
|
bounds=Bounds(1, 2, 3, 4),
|
|
text="Message",
|
|
),
|
|
SceneElement(
|
|
id="send",
|
|
type="button",
|
|
bounds=Bounds(5, 2, 3, 4),
|
|
text="Send",
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
def _semantic_payload() -> dict[str, Any]:
|
|
return {
|
|
"page": "Chat",
|
|
"intents": ["send a message"],
|
|
"widgets": [
|
|
{"element_id": "input", "purpose": "message text input"},
|
|
{"element_id": "send", "purpose": "send message"},
|
|
],
|
|
}
|
|
|
|
|
|
def test_enrichment_config_is_disabled_by_default() -> None:
|
|
assert load_config({}).enabled is False
|
|
|
|
|
|
def test_enrich_scene_returns_semantic_scene_when_enabled() -> None:
|
|
client = FakeClient(response=_semantic_payload())
|
|
|
|
result = enrich_scene(
|
|
_scene(),
|
|
client=client,
|
|
config=SemanticConfig(enabled=True, timeout=2.5),
|
|
)
|
|
|
|
assert result is not None
|
|
assert result.to_dict() == _semantic_payload()
|
|
assert len(client.calls) == 1
|
|
assert client.calls[0][1] == 2.5
|
|
|
|
|
|
def test_enrich_scene_disabled_short_circuits_without_client_call() -> None:
|
|
client = FakeClient(response=_semantic_payload())
|
|
|
|
result = enrich_scene(
|
|
_scene(),
|
|
client=client,
|
|
config=SemanticConfig(enabled=False),
|
|
)
|
|
|
|
assert result is None
|
|
assert client.calls == []
|
|
|
|
|
|
def test_enrich_scene_degrades_to_none_on_client_failure() -> None:
|
|
client = FakeClient(error=EnrichmentUnavailable("rate limited"))
|
|
|
|
result = enrich_scene(
|
|
_scene(),
|
|
client=client,
|
|
config=SemanticConfig(enabled=True),
|
|
)
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_enrich_scene_filters_dangling_element_ids() -> None:
|
|
payload = _semantic_payload()
|
|
payload["widgets"] = [
|
|
{"element_id": "send", "purpose": "send message"},
|
|
{"element_id": "missing", "purpose": "unknown control"},
|
|
]
|
|
client = FakeClient(response=payload)
|
|
|
|
result = enrich_scene(
|
|
_scene(),
|
|
client=client,
|
|
config=SemanticConfig(enabled=True),
|
|
)
|
|
|
|
assert result is not None
|
|
assert result.to_dict()["widgets"] == [
|
|
{"element_id": "send", "purpose": "send message"}
|
|
]
|
|
|
|
|
|
def test_enrich_scene_returns_none_when_all_widgets_are_dangling() -> None:
|
|
client = FakeClient(
|
|
response={
|
|
"page": "Chat",
|
|
"intents": ["send a message"],
|
|
"widgets": [{"element_id": "missing", "purpose": "unknown control"}],
|
|
}
|
|
)
|
|
|
|
result = enrich_scene(
|
|
_scene(),
|
|
client=client,
|
|
config=SemanticConfig(enabled=True),
|
|
)
|
|
|
|
assert result is None
|