test(contextual): remove floating-specific tests
Replaced by tests/test_contextual_*.py in M3. No dedicated test_floating_*.py files existed; floating test functions were embedded in test_deep_agent.py and test_ws_unified.py and have been removed from those files.
This commit is contained in:
@@ -12,11 +12,8 @@ from langchain_core.messages import AIMessage, ToolMessage
|
|||||||
from app.core.deep_agent import (
|
from app.core.deep_agent import (
|
||||||
_build_system_prompt,
|
_build_system_prompt,
|
||||||
_datetime_context_injection,
|
_datetime_context_injection,
|
||||||
_infer_floating_domain,
|
|
||||||
_normalize_tagged_list_lines,
|
_normalize_tagged_list_lines,
|
||||||
_request_context_block,
|
_request_context_block,
|
||||||
run_floating,
|
|
||||||
run_floating_stream,
|
|
||||||
run_home,
|
run_home,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -75,57 +72,6 @@ async def test_run_home_uses_mocked_tool_result():
|
|||||||
assert "Mock Task" in out
|
assert "Mock Task" in out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_stream_emits_domain_then_tokens_with_mocked_tool_result():
|
|
||||||
fake_llm = _FakeLLM()
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=fake_llm), patch(
|
|
||||||
"app.core.deep_agent._all_tools", return_value=[_FakeTool()]
|
|
||||||
):
|
|
||||||
events = []
|
|
||||||
async for event in run_floating_stream(
|
|
||||||
"user-1",
|
|
||||||
"show me timeline updates",
|
|
||||||
{"scope": {"type": "timeline", "id": "tl-1"}},
|
|
||||||
):
|
|
||||||
events.append(event)
|
|
||||||
|
|
||||||
assert events[0] == (
|
|
||||||
"floating_domain",
|
|
||||||
{"type": "timeline", "id": "tl-1", "section": None},
|
|
||||||
)
|
|
||||||
# _run_single_agent_stream uses ainvoke (not astream); the final token is
|
|
||||||
# the second LLM response which echoes the tool result.
|
|
||||||
token_events = [e for e in events if e[0] == "token"]
|
|
||||||
assert token_events, "Expected at least one token event"
|
|
||||||
combined = "".join(str(e[1]) for e in token_events)
|
|
||||||
assert "Mock Task" in combined
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_infer_floating_domain_prefers_message_intent_over_scope_type():
|
|
||||||
class _ClassifierOnlyLLM:
|
|
||||||
async def ainvoke(self, _messages):
|
|
||||||
return AIMessage(
|
|
||||||
content='{"type":"project","id":"213213-312321-312312-421321","section":"task"}'
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=_ClassifierOnlyLLM()):
|
|
||||||
domain = await _infer_floating_domain(
|
|
||||||
"Quali sono i miei task per il progetto X",
|
|
||||||
{
|
|
||||||
"scope": {"type": "timeline"},
|
|
||||||
"resolved_project_id": "213213-312321-312312-421321",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert domain == {
|
|
||||||
"type": "project",
|
|
||||||
"id": "213213-312321-312312-421321",
|
|
||||||
"section": "task",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_tagged_list_lines_rewrites_mixed_task_lines_to_tag_only_lines():
|
def test_normalize_tagged_list_lines_rewrites_mixed_task_lines_to_tag_only_lines():
|
||||||
raw = (
|
raw = (
|
||||||
"Certo!\n\n"
|
"Certo!\n\n"
|
||||||
@@ -162,139 +108,6 @@ def test_normalize_tagged_list_lines_filters_upcoming_timeline_query_to_current_
|
|||||||
assert "<timeline>[tl-future]</timeline>" not in out
|
assert "<timeline>[tl-future]</timeline>" not in out
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_strips_xml_like_tags_from_final_text():
|
|
||||||
fake_llm = _FakeLLM()
|
|
||||||
|
|
||||||
async def _fake_run_single_agent(**_kwargs):
|
|
||||||
return (
|
|
||||||
"Hai 1 task:\\n"
|
|
||||||
"Mail barra in prod <task>[180faff3-507d-4d88-aba8-66f204eb59ef]</task>"
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=fake_llm), patch(
|
|
||||||
"app.core.deep_agent._run_single_agent", side_effect=_fake_run_single_agent
|
|
||||||
):
|
|
||||||
text, _domain = await run_floating(
|
|
||||||
"user-1",
|
|
||||||
"quali task ho?",
|
|
||||||
{"scope": {"type": "task"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert "<task>" not in text
|
|
||||||
assert "</task>" not in text
|
|
||||||
assert "[180faff3-507d-4d88-aba8-66f204eb59ef]" not in text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_stream_strips_xml_like_tags_from_streamed_text():
|
|
||||||
fake_llm = _FakeLLM()
|
|
||||||
|
|
||||||
async def _fake_stream(**_kwargs):
|
|
||||||
yield "token", "Hai 1 task:\\n"
|
|
||||||
yield "token", "Mail barra in prod <task>[180faff3-507d-4d88-aba8-66f204eb59ef]</task>"
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=fake_llm), patch(
|
|
||||||
"app.core.deep_agent._run_single_agent_stream", side_effect=_fake_stream
|
|
||||||
):
|
|
||||||
events = []
|
|
||||||
async for event in run_floating_stream(
|
|
||||||
"user-1",
|
|
||||||
"quali task ho?",
|
|
||||||
{"scope": {"type": "task"}},
|
|
||||||
):
|
|
||||||
events.append(event)
|
|
||||||
|
|
||||||
token_events = [str(data) for event_type, data in events if event_type == "token"]
|
|
||||||
combined = "".join(token_events)
|
|
||||||
assert "<task>" not in combined
|
|
||||||
assert "</task>" not in combined
|
|
||||||
assert "[180faff3-507d-4d88-aba8-66f204eb59ef]" not in combined
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_stream_falls_back_to_final_response_content_when_astream_is_empty():
|
|
||||||
class _NoChunkLLM:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.calls = 0
|
|
||||||
|
|
||||||
def bind_tools(self, _tools):
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def ainvoke(self, _messages):
|
|
||||||
self.calls += 1
|
|
||||||
if self.calls == 1:
|
|
||||||
return AIMessage(
|
|
||||||
content="",
|
|
||||||
tool_calls=[
|
|
||||||
{
|
|
||||||
"id": "call-1",
|
|
||||||
"name": "list_tasks",
|
|
||||||
"args": {},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return AIMessage(content="No notes found.")
|
|
||||||
|
|
||||||
async def astream(self, _messages):
|
|
||||||
if False:
|
|
||||||
yield None
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=_NoChunkLLM()), patch(
|
|
||||||
"app.core.deep_agent._all_tools", return_value=[_FakeTool()]
|
|
||||||
):
|
|
||||||
events = []
|
|
||||||
async for event in run_floating_stream(
|
|
||||||
"user-1",
|
|
||||||
"quali sono le note?",
|
|
||||||
{"scope": {"type": "note"}},
|
|
||||||
):
|
|
||||||
events.append(event)
|
|
||||||
|
|
||||||
assert events[0][0] == "floating_domain"
|
|
||||||
assert ("token", "No notes found.") in events
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_returns_fallback_when_sanitization_would_empty_text():
|
|
||||||
fake_llm = _FakeLLM()
|
|
||||||
|
|
||||||
async def _fake_run_single_agent(**_kwargs):
|
|
||||||
return "<task>[180faff3-507d-4d88-aba8-66f204eb59ef]</task>"
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=fake_llm), patch(
|
|
||||||
"app.core.deep_agent._run_single_agent", side_effect=_fake_run_single_agent
|
|
||||||
):
|
|
||||||
text, _domain = await run_floating(
|
|
||||||
"user-1",
|
|
||||||
"quali task ho?",
|
|
||||||
{"scope": {"type": "task"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert text == "No results found."
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_run_floating_stream_returns_fallback_when_sanitization_would_empty_text():
|
|
||||||
fake_llm = _FakeLLM()
|
|
||||||
|
|
||||||
async def _fake_stream(**_kwargs):
|
|
||||||
yield "token", "<task>[180faff3-507d-4d88-aba8-66f204eb59ef]</task>"
|
|
||||||
|
|
||||||
with patch("app.core.deep_agent.get_agent_llm", return_value=fake_llm), patch(
|
|
||||||
"app.core.deep_agent._run_single_agent_stream", side_effect=_fake_stream
|
|
||||||
):
|
|
||||||
events = []
|
|
||||||
async for event in run_floating_stream(
|
|
||||||
"user-1",
|
|
||||||
"quali task ho?",
|
|
||||||
{"scope": {"type": "task"}},
|
|
||||||
):
|
|
||||||
events.append(event)
|
|
||||||
|
|
||||||
assert ("token", "No results found.") in events
|
|
||||||
|
|
||||||
|
|
||||||
# ── _datetime_context_injection ────────────────────────────────────────────────
|
# ── _datetime_context_injection ────────────────────────────────────────────────
|
||||||
|
|
||||||
def _fp(tz: str, now_iso: str) -> dict:
|
def _fp(tz: str, now_iso: str) -> dict:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Integration tests for the unified WebSocket handler (Step 5).
|
"""Integration tests for the unified WebSocket handler (Step 5).
|
||||||
|
|
||||||
Tests the device WS endpoint with home_request and floating_request frames,
|
Tests the device WS endpoint with home_request frames,
|
||||||
verifying that the correct v3 frame sequence is returned.
|
verifying that the correct v3 frame sequence is returned.
|
||||||
|
|
||||||
LLM calls are mocked to avoid network dependency.
|
LLM calls are mocked to avoid network dependency.
|
||||||
@@ -34,7 +34,7 @@ def _override_db(db_session):
|
|||||||
|
|
||||||
|
|
||||||
def _recv_until_end(ws, max_frames: int = 20) -> list[dict]:
|
def _recv_until_end(ws, max_frames: int = 20) -> list[dict]:
|
||||||
"""Receive frames until stream_end (or stream_end inside floating flow), or max_frames."""
|
"""Receive frames until stream_end or max_frames."""
|
||||||
frames = []
|
frames = []
|
||||||
for _ in range(max_frames):
|
for _ in range(max_frames):
|
||||||
raw = ws.receive_text()
|
raw = ws.receive_text()
|
||||||
@@ -49,11 +49,6 @@ async def _mock_home_stream(user_id, message, context):
|
|||||||
yield "token", "Hello"
|
yield "token", "Hello"
|
||||||
|
|
||||||
|
|
||||||
async def _mock_floating_stream(user_id, message, context):
|
|
||||||
yield "floating_domain", {"type": "task", "id": None, "section": None}
|
|
||||||
yield "token", "Here is a summary"
|
|
||||||
|
|
||||||
|
|
||||||
# ── tests ─────────────────────────────────────────────────────────────────────
|
# ── tests ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_home_request_produces_stream_frames(client):
|
def test_home_request_produces_stream_frames(client):
|
||||||
@@ -79,33 +74,6 @@ def test_home_request_produces_stream_frames(client):
|
|||||||
assert types.index(WsFrameType.stream_start) < types.index(WsFrameType.stream_end)
|
assert types.index(WsFrameType.stream_start) < types.index(WsFrameType.stream_end)
|
||||||
|
|
||||||
|
|
||||||
def test_floating_request_produces_domain_frame(client):
|
|
||||||
"""floating_request → floating_domain first, then stream_text*, stream_end."""
|
|
||||||
token = make_jwt("power", user_id=USER_ID)
|
|
||||||
|
|
||||||
with patch("app.api.routes.device_ws.run_floating_stream", side_effect=_mock_floating_stream):
|
|
||||||
with client.websocket_connect(f"/api/v1/ws/device?token={token}") as ws:
|
|
||||||
ws.send_text(json.dumps({
|
|
||||||
"type": "device_hello", "device_id": "dev-2", "agent_ids": []
|
|
||||||
}))
|
|
||||||
ws.send_text(json.dumps({
|
|
||||||
"type": "floating_request",
|
|
||||||
"request_id": "p1",
|
|
||||||
"message": "Summarize this task",
|
|
||||||
"scope": {"type": "task", "id": "task-123"},
|
|
||||||
}))
|
|
||||||
frames = _recv_until_end(ws)
|
|
||||||
|
|
||||||
types = [f["type"] for f in frames]
|
|
||||||
assert WsFrameType.floating_domain in types
|
|
||||||
assert WsFrameType.stream_end in types
|
|
||||||
assert types.index(WsFrameType.floating_domain) < types.index(WsFrameType.stream_end)
|
|
||||||
|
|
||||||
domain_frame = next(f for f in frames if f["type"] == WsFrameType.floating_domain)
|
|
||||||
assert domain_frame["domain"]["type"] == "task"
|
|
||||||
assert domain_frame["request_id"] == "p1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_home_request_request_id_propagated(client):
|
def test_home_request_request_id_propagated(client):
|
||||||
"""request_id in home_request is echoed in all response frames."""
|
"""request_id in home_request is echoed in all response frames."""
|
||||||
token = make_jwt("power", user_id=USER_ID)
|
token = make_jwt("power", user_id=USER_ID)
|
||||||
|
|||||||
Reference in New Issue
Block a user