Private
Public Access
Merge remote-tracking branch 'tier2-clone/tier2/send_result_to_send_20260616'
# Conflicts: # manualslop_layout.ini
This commit is contained in:
@@ -45,7 +45,7 @@ def test_gemini_cache_tracking() -> None:
|
||||
mock_client.caches.list.return_value = [MagicMock(size_bytes=5000)]
|
||||
|
||||
# Act
|
||||
result = ai_client.send_result(
|
||||
result = ai_client.send(
|
||||
md_content="Some long context that triggers caching",
|
||||
user_message="Hello",
|
||||
file_items=file_items
|
||||
|
||||
@@ -20,7 +20,7 @@ def test_ai_client_send_gemini_cli() -> None:
|
||||
MockAdapterClass.return_value = mock_adapter_instance
|
||||
ai_client._gemini_cli_adapter = mock_adapter_instance
|
||||
with patch.object(ai_client.events, "emit") as mock_emit:
|
||||
result = ai_client.send_result(
|
||||
result = ai_client.send(
|
||||
md_content="<context></context>",
|
||||
user_message=test_message,
|
||||
base_dir=".",
|
||||
|
||||
@@ -4,40 +4,40 @@ from src import ai_client
|
||||
from src.result_types import Result, ErrorInfo, ErrorKind
|
||||
|
||||
|
||||
def test_send_result_public_api_returns_result() -> None:
|
||||
def test_send_public_api_returns_result() -> None:
|
||||
with patch.object(ai_client, "set_provider"):
|
||||
with patch.object(ai_client, "_send_gemini", return_value=Result(data="hello")) as mock_send:
|
||||
r = ai_client.send_result("system", "user")
|
||||
r = ai_client.send("system", "user")
|
||||
assert isinstance(r, Result)
|
||||
assert r.ok
|
||||
assert r.data == "hello"
|
||||
|
||||
|
||||
def test_send_result_does_not_emit_deprecation() -> None:
|
||||
def test_send_does_not_emit_deprecation() -> None:
|
||||
import warnings
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
with patch.object(ai_client, "set_provider"):
|
||||
with patch.object(ai_client, "_send_gemini", return_value=Result(data="hi")):
|
||||
r = ai_client.send_result("system", "user")
|
||||
r = ai_client.send("system", "user")
|
||||
assert r.ok and r.data == "hi"
|
||||
assert not any(issubclass(x.category, DeprecationWarning) for x in w)
|
||||
|
||||
|
||||
def test_send_result_preserves_errors() -> None:
|
||||
def test_send_preserves_errors() -> None:
|
||||
err = ErrorInfo(kind=ErrorKind.RATE_LIMIT, message="slow down", source="test")
|
||||
with patch.object(ai_client, "set_provider"):
|
||||
with patch.object(ai_client, "_send_gemini", return_value=Result(data="", errors=[err])):
|
||||
r = ai_client.send_result("system", "user")
|
||||
r = ai_client.send("system", "user")
|
||||
assert not r.ok
|
||||
assert r.errors == [err]
|
||||
|
||||
|
||||
def test_send_result_returns_empty_data_with_error_on_auth_failure() -> None:
|
||||
def test_send_returns_empty_data_with_error_on_auth_failure() -> None:
|
||||
err = ErrorInfo(kind=ErrorKind.AUTH, message="bad key", source="test")
|
||||
with patch.object(ai_client, "set_provider"):
|
||||
with patch.object(ai_client, "_send_gemini", return_value=Result(data="", errors=[err])):
|
||||
r = ai_client.send_result("system", "user")
|
||||
r = ai_client.send("system", "user")
|
||||
assert not r.ok
|
||||
assert r.data == ""
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ def _make_event(prompt: str = "Hello AI") -> UserRequestEvent:
|
||||
|
||||
def test_fr1_error_becomes_discussion_entry(mock_app: App, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""
|
||||
When send_result returns errors, _handle_request_event must enqueue a
|
||||
When send returns errors, _handle_request_event must enqueue a
|
||||
'response' event with status='error' and the error message in the text.
|
||||
|
||||
Currently broken: the code calls deprecated ai_client.send_result() which
|
||||
Currently broken: the code calls deprecated ai_client.send() which
|
||||
silently returns '' on error. The empty string is then routed to the
|
||||
event_queue as a 'done' response and _on_comms_entry filters it out
|
||||
via `if text_content.strip():` (src/app_controller.py:3801).
|
||||
@@ -54,7 +54,7 @@ def test_fr1_error_becomes_discussion_entry(mock_app: App, monkeypatch: pytest.M
|
||||
app = mock_app
|
||||
err = ErrorInfo(kind=ErrorKind.NETWORK, message="connection refused", source="ai_client.test")
|
||||
err_result = Result(data="", errors=[err])
|
||||
monkeypatch.setattr(ai_client, "send_result", lambda *a, **kw: err_result)
|
||||
monkeypatch.setattr(ai_client, "send", lambda *a, **kw: err_result)
|
||||
monkeypatch.setattr(ai_client, "set_custom_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_base_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_use_default_base_prompt", lambda *a, **kw: None)
|
||||
@@ -83,7 +83,7 @@ def test_fr1_success_still_works(mock_app: App, monkeypatch: pytest.MonkeyPatch)
|
||||
"""
|
||||
app = mock_app
|
||||
ok_result = Result(data="Hello back from AI")
|
||||
monkeypatch.setattr(ai_client, "send_result", lambda *a, **kw: ok_result)
|
||||
monkeypatch.setattr(ai_client, "send", lambda *a, **kw: ok_result)
|
||||
monkeypatch.setattr(ai_client, "set_custom_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_base_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_use_default_base_prompt", lambda *a, **kw: None)
|
||||
@@ -111,7 +111,7 @@ def test_fr1_ai_status_updated(mock_app: App, monkeypatch: pytest.MonkeyPatch) -
|
||||
app = mock_app
|
||||
err = ErrorInfo(kind=ErrorKind.RATE_LIMIT, message="slow down", source="ai_client.test")
|
||||
err_result = Result(data="", errors=[err])
|
||||
monkeypatch.setattr(ai_client, "send_result", lambda *a, **kw: err_result)
|
||||
monkeypatch.setattr(ai_client, "send", lambda *a, **kw: err_result)
|
||||
monkeypatch.setattr(ai_client, "set_custom_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_base_system_prompt", lambda *a, **kw: None)
|
||||
monkeypatch.setattr(ai_client, "set_use_default_base_prompt", lambda *a, **kw: None)
|
||||
@@ -154,18 +154,18 @@ def test_fr2_no_provider_error_in_source() -> None:
|
||||
assert not violations, f"Found {len(violations)} ProviderError reference(s) in {src_path}: {violations}"
|
||||
|
||||
|
||||
def test_fr2_send_result_callable_in_app_controller_namespace() -> None:
|
||||
def test_fr2_send_callable_in_app_controller_namespace() -> None:
|
||||
"""
|
||||
Sanity check: ai_client.send_result exists and returns a Result. This
|
||||
guards the FR2 fix path -- the replacement code calls send_result() and
|
||||
Sanity check: ai_client.send exists and returns a Result. This
|
||||
guards the FR2 fix path -- the replacement code calls send() and
|
||||
branches on result.ok.
|
||||
"""
|
||||
from src import result_types
|
||||
assert hasattr(ai_client, "send_result"), "ai_client.send_result is the migration target; it must exist"
|
||||
assert callable(ai_client.send_result)
|
||||
ok = ai_client.send_result("system", "user") if False else None
|
||||
assert hasattr(ai_client, "send"), "ai_client.send is the migration target; it must exist"
|
||||
assert callable(ai_client.send)
|
||||
ok = ai_client.send("system", "user") if False else None
|
||||
# Smoke test: just verify the import path and signature; the actual call
|
||||
# path is exercised in test_ai_client_result.py::test_send_result_public_api_returns_result
|
||||
# path is exercised in test_ai_client_result.py::test_send_public_api_returns_result
|
||||
|
||||
|
||||
# endregion: FR2 tests
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_send_emits_events_proper() -> None:
|
||||
ai_client.events.on("request_start", start_callback)
|
||||
ai_client.events.on("response_received", response_callback)
|
||||
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
||||
result = ai_client.send_result("context", "message", )
|
||||
result = ai_client.send("context", "message", )
|
||||
assert result.ok
|
||||
assert start_callback.called
|
||||
assert response_callback.called
|
||||
@@ -105,6 +105,6 @@ def test_send_emits_tool_events() -> None:
|
||||
tool_callback(*args, **kwargs)
|
||||
|
||||
ai_client.events.on("tool_execution", debug_tool)
|
||||
result = ai_client.send_result("context", "message", enable_tools=True)
|
||||
result = ai_client.send("context", "message", enable_tools=True)
|
||||
assert result.ok
|
||||
assert tool_callback.call_count >= 1
|
||||
|
||||
@@ -35,9 +35,9 @@ def test_conductor_engine_run_executes_tickets_in_order(monkeypatch: pytest.Monk
|
||||
vlogger.log_state("T1 Status", "todo", "todo")
|
||||
vlogger.log_state("T2 Status", "todo", "todo")
|
||||
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
# We mock run_worker_lifecycle as it is expected to be in the same module
|
||||
with patch("src.multi_agent_conductor.run_worker_lifecycle") as mock_lifecycle:
|
||||
# Mocking lifecycle to mark ticket as complete so dependencies can be resolved
|
||||
@@ -76,15 +76,15 @@ def test_run_worker_lifecycle_calls_ai_client_send(monkeypatch: pytest.MonkeyPat
|
||||
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
mock_send.return_value = Result(data="Task complete. I have updated the file.")
|
||||
result = run_worker_lifecycle(ticket, context)
|
||||
assert result == "Task complete. I have updated the file."
|
||||
assert ticket.status == "completed"
|
||||
mock_send.assert_called_once()
|
||||
# Check if description was passed to send_result()
|
||||
# Check if description was passed to send()
|
||||
args, kwargs = mock_send.call_args
|
||||
# user_message is passed as a keyword argument
|
||||
assert ticket.description in kwargs["user_message"]
|
||||
@@ -99,9 +99,9 @@ def test_run_worker_lifecycle_context_injection(monkeypatch: pytest.MonkeyPatch)
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
context_files = ["primary.py", "secondary.py"]
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
# We mock ASTParser which is expected to be imported in multi_agent_conductor
|
||||
with patch("src.multi_agent_conductor.ASTParser") as mock_ast_parser_class, \
|
||||
patch("builtins.open", new_callable=MagicMock) as mock_open:
|
||||
@@ -145,9 +145,9 @@ def test_run_worker_lifecycle_handles_blocked_response(monkeypatch: pytest.Monke
|
||||
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1")
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
# Simulate a response indicating a block
|
||||
mock_send.return_value = Result(data="I am BLOCKED because I don't have enough information.")
|
||||
run_worker_lifecycle(ticket, context)
|
||||
@@ -158,16 +158,16 @@ def test_run_worker_lifecycle_step_mode_confirmation(monkeypatch: pytest.MonkeyP
|
||||
"""
|
||||
|
||||
|
||||
Test that run_worker_lifecycle passes confirm_execution to ai_client.send_result when step_mode is True.
|
||||
Verify that if confirm_execution is called (simulated by mocking ai_client.send_result to call its callback),
|
||||
Test that run_worker_lifecycle passes confirm_execution to ai_client.send when step_mode is True.
|
||||
Verify that if confirm_execution is called (simulated by mocking ai_client.send to call its callback),
|
||||
the flow works as expected.
|
||||
"""
|
||||
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1", step_mode=True)
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
|
||||
# Important: confirm_spawn is called first if event_queue is present!
|
||||
with patch("src.multi_agent_conductor.confirm_spawn") as mock_spawn, \
|
||||
@@ -202,9 +202,9 @@ def test_run_worker_lifecycle_step_mode_rejection(monkeypatch: pytest.MonkeyPatc
|
||||
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker1", step_mode=True)
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
with patch("src.multi_agent_conductor.confirm_spawn") as mock_spawn, \
|
||||
patch("src.multi_agent_conductor.confirm_execution") as mock_confirm:
|
||||
mock_spawn.return_value = (True, "mock prompt", "mock context")
|
||||
@@ -214,7 +214,7 @@ def test_run_worker_lifecycle_step_mode_rejection(monkeypatch: pytest.MonkeyPatc
|
||||
mock_event_queue = MagicMock()
|
||||
run_worker_lifecycle(ticket, context, event_queue=mock_event_queue)
|
||||
|
||||
# Verify it was passed to send_result
|
||||
# Verify it was passed to send
|
||||
args, kwargs = mock_send.call_args
|
||||
assert kwargs["pre_tool_callback"] is not None
|
||||
|
||||
@@ -258,9 +258,9 @@ def test_conductor_engine_dynamic_parsing_and_execution(monkeypatch: pytest.Monk
|
||||
assert engine.track.tickets[0].id == "T1"
|
||||
assert engine.track.tickets[1].id == "T2"
|
||||
assert engine.track.tickets[2].id == "T3"
|
||||
# Mock ai_client.send_result using monkeypatch
|
||||
# Mock ai_client.send using monkeypatch
|
||||
mock_send = MagicMock()
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
# Mock run_worker_lifecycle to mark tickets as complete
|
||||
with patch("src.multi_agent_conductor.run_worker_lifecycle") as mock_lifecycle:
|
||||
def side_effect(ticket, context, *args, **kwargs):
|
||||
@@ -298,7 +298,7 @@ def test_run_worker_lifecycle_pushes_response_via_queue(monkeypatch: pytest.Monk
|
||||
context = WorkerContext(ticket_id="T1", model_name="test-model", messages=[])
|
||||
mock_event_queue = MagicMock()
|
||||
mock_send = MagicMock(return_value=Result(data="Task complete."))
|
||||
monkeypatch.setattr(ai_client, 'send_result', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'send', mock_send)
|
||||
monkeypatch.setattr(ai_client, 'reset_session', MagicMock())
|
||||
from src.multi_agent_conductor import run_worker_lifecycle
|
||||
with patch("src.multi_agent_conductor.confirm_spawn") as mock_spawn, \
|
||||
@@ -327,11 +327,11 @@ def test_run_worker_lifecycle_token_usage_from_comms_log(monkeypatch: pytest.Mon
|
||||
{"direction": "OUT", "kind": "request", "payload": {"message": "hello"}},
|
||||
{"direction": "IN", "kind": "response", "payload": {"usage": {"input_tokens": 120, "output_tokens": 45}}},
|
||||
]
|
||||
monkeypatch.setattr(ai_client, 'send_result', MagicMock(return_value=Result(data="Done.")))
|
||||
monkeypatch.setattr(ai_client, 'send', MagicMock(return_value=Result(data="Done.")))
|
||||
monkeypatch.setattr(ai_client, 'reset_session', MagicMock())
|
||||
monkeypatch.setattr(ai_client, 'get_comms_log', MagicMock(side_effect=[
|
||||
[], # baseline call (before send_result)
|
||||
fake_comms, # after-send_result call
|
||||
[], # baseline call (before send)
|
||||
fake_comms, # after-send call
|
||||
]))
|
||||
from src.multi_agent_conductor import run_worker_lifecycle, ConductorEngine
|
||||
track = Track(id="test_track", description="Test")
|
||||
|
||||
@@ -6,23 +6,23 @@ import pytest
|
||||
|
||||
class TestConductorTechLead(unittest.TestCase):
|
||||
def test_generate_tickets_retry_failure(self) -> None:
|
||||
with patch('src.ai_client.send_result') as mock_send_result:
|
||||
mock_send_result.return_value = Result(data="invalid json")
|
||||
with patch('src.ai_client.send') as mock_send:
|
||||
mock_send.return_value = Result(data="invalid json")
|
||||
# conductor_tech_lead.generate_tickets now raises RuntimeError on error after 3 attempts
|
||||
with pytest.raises(RuntimeError):
|
||||
conductor_tech_lead.generate_tickets("brief", "skeletons")
|
||||
assert mock_send_result.call_count == 3
|
||||
assert mock_send.call_count == 3
|
||||
|
||||
def test_generate_tickets_retry_success(self) -> None:
|
||||
with patch('src.ai_client.send_result') as mock_send_result:
|
||||
mock_send_result.side_effect = [Result(data="invalid json"), Result(data='[{"Task": "Test"}]')]
|
||||
with patch('src.ai_client.send') as mock_send:
|
||||
mock_send.side_effect = [Result(data="invalid json"), Result(data='[{"Task": "Test"}]')]
|
||||
tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
|
||||
assert tickets == [{"Task": "Test"}]
|
||||
assert mock_send_result.call_count == 2
|
||||
assert mock_send.call_count == 2
|
||||
|
||||
def test_generate_tickets_success(self) -> None:
|
||||
with patch('src.ai_client.send_result') as mock_send_result:
|
||||
mock_send_result.return_value = Result(data='[{"id": "T1", "description": "desc", "depends_on": []}]')
|
||||
with patch('src.ai_client.send') as mock_send:
|
||||
mock_send.return_value = Result(data='[{"id": "T1", "description": "desc", "depends_on": []}]')
|
||||
tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
|
||||
self.assertEqual(len(tickets), 1)
|
||||
self.assertEqual(tickets[0]['id'], "T1")
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_token_reduction_logging(capsys):
|
||||
with pytest.MonkeyPatch().context() as m:
|
||||
m.setattr("builtins.open", lambda f, *args, **kwargs: type('obj', (object,), {'read': lambda s: code, '__enter__': lambda s: s, '__exit__': lambda s, *a: None})())
|
||||
m.setattr("pathlib.Path.exists", lambda s: True)
|
||||
m.setattr("src.ai_client.send_result", lambda **kwargs: Result(data="DONE"))
|
||||
m.setattr("src.ai_client.send", lambda **kwargs: Result(data="DONE"))
|
||||
|
||||
run_worker_lifecycle(ticket, context, context_files=["test.py"])
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_deepseek_completion_logic(mock_post: MagicMock) -> None:
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Hi", base_dir=".")
|
||||
result = ai_client.send(md_content="Context", user_message="Hi", base_dir=".")
|
||||
assert result.ok
|
||||
assert result.data == "Hello World"
|
||||
assert mock_post.called
|
||||
@@ -53,7 +53,7 @@ def test_deepseek_reasoning_logic(mock_post: MagicMock) -> None:
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Hi", base_dir=".")
|
||||
result = ai_client.send(md_content="Context", user_message="Hi", base_dir=".")
|
||||
assert result.ok
|
||||
assert "<thinking>\nChain of thought\n</thinking>" in result.data
|
||||
assert "Final answer" in result.data
|
||||
@@ -96,7 +96,7 @@ def test_deepseek_tool_calling(mock_post: MagicMock) -> None:
|
||||
mock_post.side_effect = [mock_resp1, mock_resp2]
|
||||
mock_dispatch.return_value = "Hello World"
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Read test.txt", base_dir=".")
|
||||
result = ai_client.send(md_content="Context", user_message="Read test.txt", base_dir=".")
|
||||
assert result.ok
|
||||
assert "File content is: Hello World" in result.data
|
||||
assert mock_dispatch.called
|
||||
@@ -123,7 +123,7 @@ def test_deepseek_streaming(mock_post: MagicMock) -> None:
|
||||
mock_response.iter_lines.return_value = [c.encode('utf-8') for c in chunks]
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Stream test", base_dir=".", stream=True)
|
||||
result = ai_client.send(md_content="Context", user_message="Stream test", base_dir=".", stream=True)
|
||||
assert result.ok
|
||||
assert result.data == "Hello World"
|
||||
|
||||
@@ -144,7 +144,7 @@ def test_deepseek_payload_verification(mock_post: MagicMock) -> None:
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Message 1", base_dir=".", discussion_history="History")
|
||||
result = ai_client.send(md_content="Context", user_message="Message 1", base_dir=".", discussion_history="History")
|
||||
assert result.ok
|
||||
|
||||
args, kwargs = mock_post.call_args
|
||||
@@ -174,7 +174,7 @@ def test_deepseek_reasoner_payload_verification(mock_post: MagicMock) -> None:
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = ai_client.send_result(md_content="Context", user_message="Message 1", base_dir=".")
|
||||
result = ai_client.send(md_content="Context", user_message="Message 1", base_dir=".")
|
||||
assert result.ok
|
||||
|
||||
args, kwargs = mock_post.call_args
|
||||
|
||||
@@ -36,6 +36,6 @@ def test_gemini_cli_loop_termination() -> None:
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
ai_client.set_provider("gemini_cli", "gemini-2.0-flash")
|
||||
result = ai_client.send_result("context", "prompt")
|
||||
result = ai_client.send("context", "prompt")
|
||||
assert result.ok
|
||||
assert result.data == "Final answer"
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_gemini_cli_full_integration() -> None:
|
||||
}
|
||||
mock_adapter.last_usage = {"total_tokens": 10}
|
||||
ai_client._gemini_cli_adapter = mock_adapter
|
||||
result = ai_client.send_result("context", "integrated test")
|
||||
result = ai_client.send("context", "integrated test")
|
||||
assert result.ok
|
||||
assert "Final integrated answer" in result.data
|
||||
|
||||
@@ -28,5 +28,5 @@ def test_gemini_cli_rejection_and_history() -> None:
|
||||
}
|
||||
mock_adapter.last_usage = {}
|
||||
ai_client._gemini_cli_adapter = mock_adapter
|
||||
result = ai_client.send_result("ctx", "msg", pre_tool_callback=lambda *a, **kw: None)
|
||||
result = ai_client.send("ctx", "msg", pre_tool_callback=lambda *a, **kw: None)
|
||||
assert result is not None
|
||||
|
||||
@@ -10,6 +10,6 @@ def test_send_invokes_adapter_send() -> None:
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
ai_client.set_provider("gemini_cli", "gemini-2.0-flash")
|
||||
res = ai_client.send_result("context", "msg")
|
||||
res = ai_client.send("context", "msg")
|
||||
assert res.ok
|
||||
assert res.data == "Hello from mock adapter"
|
||||
|
||||
@@ -45,7 +45,7 @@ def test_mcp_tool_call_is_dispatched(app_instance: App) -> None:
|
||||
mock_chat.send_message.side_effect = [mock_response_with_tool, mock_response_final]
|
||||
ai_client.set_provider("gemini", "mock-model")
|
||||
# 5. Call the send function
|
||||
result = ai_client.send_result(
|
||||
result = ai_client.send(
|
||||
md_content="some context",
|
||||
user_message="read the file",
|
||||
base_dir=".",
|
||||
|
||||
@@ -56,7 +56,7 @@ class TestHeadlessAPI(unittest.TestCase):
|
||||
self.assertIn("not configured", response.json()["detail"])
|
||||
|
||||
def test_generate_endpoint(self) -> None:
|
||||
with patch('src.ai_client.send_result', return_value=Result(data="AI Response")), \
|
||||
with patch('src.ai_client.send', return_value=Result(data="AI Response")), \
|
||||
patch('src.app_controller.AppController._do_generate', return_value=("md", "path", [], "stable", "disc")):
|
||||
payload = {"prompt": "test prompt", "auto_add_history": False}
|
||||
response = self.client.post("/api/v1/generate", json=payload, headers=self.headers)
|
||||
|
||||
@@ -28,7 +28,7 @@ async def test_headless_verification_full_run(vlogger) -> None:
|
||||
vlogger.log_state("T2 Status Initial", "todo", t2.status)
|
||||
|
||||
# We must patch where it is USED: multi_agent_conductor
|
||||
with patch("src.multi_agent_conductor.ai_client.send_result") as mock_send, \
|
||||
with patch("src.multi_agent_conductor.ai_client.send") as mock_send, \
|
||||
patch("src.multi_agent_conductor.ai_client.reset_session") as mock_reset, \
|
||||
patch("src.multi_agent_conductor.confirm_spawn", return_value=(True, "mock_prompt", "mock_ctx")):
|
||||
# We need mock_send to return something that doesn't contain "BLOCKED"
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_user_request_integration_flow(mock_app: App) -> None:
|
||||
# Mock all ai_client methods called during _handle_request_event
|
||||
mock_response = "This is a test AI response"
|
||||
with (
|
||||
patch('src.ai_client.send_result', return_value=Result(data=mock_response)) as mock_send,
|
||||
patch('src.ai_client.send', return_value=Result(data=mock_response)) as mock_send,
|
||||
patch('src.ai_client.set_custom_system_prompt'),
|
||||
patch('src.ai_client.set_model_params'),
|
||||
patch('src.ai_client.set_agent_tools'),
|
||||
@@ -52,8 +52,8 @@ def test_user_request_integration_flow(mock_app: App) -> None:
|
||||
# Let's call the handler
|
||||
app.controller._handle_request_event(event)
|
||||
|
||||
# 3. Verify ai_client.send_result was called
|
||||
assert mock_send.called, "ai_client.send_result was not called"
|
||||
# 3. Verify ai_client.send was called
|
||||
assert mock_send.called, "ai_client.send was not called"
|
||||
|
||||
# 4. First event should be 'comms' (request logging)
|
||||
event_name, payload = app.controller.event_queue.get()
|
||||
@@ -85,7 +85,7 @@ def test_user_request_error_handling(mock_app: App) -> None:
|
||||
app = mock_app
|
||||
err = ErrorInfo(kind=ErrorKind.NETWORK, message="API Failure", source="ai_client.test")
|
||||
with (
|
||||
patch('src.ai_client.send_result', return_value=Result(data="", errors=[err])),
|
||||
patch('src.ai_client.send', return_value=Result(data="", errors=[err])),
|
||||
patch('src.ai_client.set_custom_system_prompt'),
|
||||
patch('src.ai_client.set_model_params'),
|
||||
patch('src.ai_client.set_agent_tools'),
|
||||
|
||||
@@ -13,7 +13,7 @@ def test_generate_tracks() -> None:
|
||||
{"id": "track_2", "title": "Refactor", "goal": "decouple modules", "type": "refactor"}
|
||||
]
|
||||
"""
|
||||
with patch("src.ai_client.send_result", return_value=Result(data=mock_response)):
|
||||
with patch("src.ai_client.send", return_value=Result(data=mock_response)):
|
||||
tracks = orchestrator_pm.generate_tracks("Develop feature X", {}, [])
|
||||
assert len(tracks) == 2
|
||||
assert tracks[0]["id"] == "track_1"
|
||||
@@ -26,7 +26,7 @@ def test_generate_tickets() -> None:
|
||||
{"id": "T2", "description": "task 2", "depends_on": ["T1"]}
|
||||
]
|
||||
"""
|
||||
with patch("src.ai_client.send_result", return_value=Result(data=mock_response)):
|
||||
with patch("src.ai_client.send", return_value=Result(data=mock_response)):
|
||||
tickets = conductor_tech_lead.generate_tickets("Track goal", "code skeletons")
|
||||
assert len(tickets) == 2
|
||||
assert tickets[0]["id"] == "T1"
|
||||
@@ -105,7 +105,7 @@ def test_conductor_engine_parse_json_tickets() -> None:
|
||||
def test_run_worker_lifecycle_blocked() -> None:
|
||||
ticket = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1")
|
||||
context = WorkerContext(ticket_id="T1", model_name="model", messages=[])
|
||||
with patch("src.ai_client.send_result") as mock_ai_client, \
|
||||
with patch("src.ai_client.send") as mock_ai_client, \
|
||||
patch("src.ai_client.reset_session"), \
|
||||
patch("src.ai_client.set_provider"), \
|
||||
patch("src.multi_agent_conductor.confirm_spawn", return_value=(True, "p", "c")):
|
||||
|
||||
@@ -9,8 +9,8 @@ from src.result_types import Result
|
||||
class TestOrchestratorPM(unittest.TestCase):
|
||||
|
||||
@patch('src.summarize.build_summary_markdown')
|
||||
@patch('src.ai_client.send_result')
|
||||
def test_generate_tracks_success(self, mock_send_result: Any, mock_summarize: Any) -> None:
|
||||
@patch('src.ai_client.send')
|
||||
def test_generate_tracks_success(self, mock_send: Any, mock_summarize: Any) -> None:
|
||||
# Setup mocks
|
||||
mock_summarize.return_value = "REPO_MAP_CONTENT"
|
||||
mock_response_data = [
|
||||
@@ -24,7 +24,7 @@ class TestOrchestratorPM(unittest.TestCase):
|
||||
"acceptance_criteria": ["criteria 1"]
|
||||
}
|
||||
]
|
||||
mock_send_result.return_value = Result(data=json.dumps(mock_response_data))
|
||||
mock_send.return_value = Result(data=json.dumps(mock_response_data))
|
||||
user_request = "Implement unit tests"
|
||||
project_config = {"files": {"paths": ["src"]}}
|
||||
file_items = [{"path": "src/main.py", "content": "print('hello')"}]
|
||||
@@ -32,12 +32,12 @@ class TestOrchestratorPM(unittest.TestCase):
|
||||
result = orchestrator_pm.generate_tracks(user_request, project_config, file_items)
|
||||
# Verify summarize call
|
||||
mock_summarize.assert_called_once_with(file_items)
|
||||
# Verify ai_client.send_result call
|
||||
# Verify ai_client.send call
|
||||
mma_prompts.PROMPTS['tier1_epic_init']
|
||||
mock_send_result.assert_called_once()
|
||||
args, kwargs = mock_send_result.call_args
|
||||
mock_send.assert_called_once()
|
||||
args, kwargs = mock_send.call_args
|
||||
self.assertEqual(kwargs['md_content'], "")
|
||||
# Cannot check system_prompt via mock_send_result kwargs anymore as it's set globally
|
||||
# Cannot check system_prompt via mock_send kwargs anymore as it's set globally
|
||||
# But we can verify user_message was passed
|
||||
self.assertIn(user_request, kwargs['user_message'])
|
||||
self.assertIn("REPO_MAP_CONTENT", kwargs['user_message'])
|
||||
@@ -45,25 +45,25 @@ class TestOrchestratorPM(unittest.TestCase):
|
||||
self.assertEqual(result[0]['id'], mock_response_data[0]['id'])
|
||||
|
||||
@patch('src.summarize.build_summary_markdown')
|
||||
@patch('src.ai_client.send_result')
|
||||
def test_generate_tracks_markdown_wrapped(self, mock_send_result: Any, mock_summarize: Any) -> None:
|
||||
@patch('src.ai_client.send')
|
||||
def test_generate_tracks_markdown_wrapped(self, mock_send: Any, mock_summarize: Any) -> None:
|
||||
mock_summarize.return_value = "REPO_MAP"
|
||||
mock_response_data = [{"id": "track_1"}]
|
||||
expected_result = [{"id": "track_1", "title": "Untitled Track"}]
|
||||
# Wrapped in ```json ... ```
|
||||
mock_send_result.return_value = Result(data=f"Here is the plan:\n```json\n{json.dumps(mock_response_data)}\n```\nHope this helps.")
|
||||
mock_send.return_value = Result(data=f"Here is the plan:\n```json\n{json.dumps(mock_response_data)}\n```\nHope this helps.")
|
||||
result = orchestrator_pm.generate_tracks("req", {}, [])
|
||||
self.assertEqual(result, expected_result)
|
||||
# Wrapped in ``` ... ```
|
||||
mock_send_result.return_value = Result(data=f"```\n{json.dumps(mock_response_data)}\n```")
|
||||
mock_send.return_value = Result(data=f"```\n{json.dumps(mock_response_data)}\n```")
|
||||
result = orchestrator_pm.generate_tracks("req", {}, [])
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
@patch('src.summarize.build_summary_markdown')
|
||||
@patch('src.ai_client.send_result')
|
||||
def test_generate_tracks_malformed_json(self, mock_send_result: Any, mock_summarize: Any) -> None:
|
||||
@patch('src.ai_client.send')
|
||||
def test_generate_tracks_malformed_json(self, mock_send: Any, mock_summarize: Any) -> None:
|
||||
mock_summarize.return_value = "REPO_MAP"
|
||||
mock_send_result.return_value = Result(data="NOT A JSON")
|
||||
mock_send.return_value = Result(data="NOT A JSON")
|
||||
# Should return empty list and print error (we can mock print if we want to be thorough)
|
||||
with patch('builtins.print') as mock_print:
|
||||
result = orchestrator_pm.generate_tracks("req", {}, [])
|
||||
|
||||
@@ -59,13 +59,13 @@ class TestOrchestratorPMHistory(unittest.TestCase):
|
||||
self.assertIn("No overview available", summary)
|
||||
|
||||
@patch('src.orchestrator_pm.summarize.build_summary_markdown')
|
||||
@patch('src.ai_client.send_result')
|
||||
def test_generate_tracks_with_history(self, mock_send_result: MagicMock, mock_summarize: MagicMock) -> None:
|
||||
@patch('src.ai_client.send')
|
||||
def test_generate_tracks_with_history(self, mock_send: MagicMock, mock_summarize: MagicMock) -> None:
|
||||
mock_summarize.return_value = "REPO_MAP"
|
||||
mock_send_result.return_value = Result(data="[]")
|
||||
mock_send.return_value = Result(data="[]")
|
||||
history_summary = "PAST_HISTORY_SUMMARY"
|
||||
orchestrator_pm.generate_tracks("req", {}, [], history_summary=history_summary)
|
||||
args, kwargs = mock_send_result.call_args
|
||||
args, kwargs = mock_send.call_args
|
||||
self.assertIn(history_summary, kwargs['user_message'])
|
||||
self.assertIn("### TRACK HISTORY:", kwargs['user_message'])
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ def test_worker_streaming_intermediate():
|
||||
event_queue = MagicMock()
|
||||
|
||||
with (
|
||||
patch("src.ai_client.send_result") as mock_send_result,
|
||||
patch("src.ai_client.send") as mock_send,
|
||||
patch("src.multi_agent_conductor._queue_put") as mock_q_put,
|
||||
patch("src.multi_agent_conductor.confirm_spawn", return_value=(True, "p", "c")),
|
||||
patch("src.ai_client.reset_session"),
|
||||
@@ -26,7 +26,7 @@ def test_worker_streaming_intermediate():
|
||||
cb({"kind": "tool_result", "payload": {"name": "test_tool", "output": "hello"}})
|
||||
return Result(data="DONE")
|
||||
|
||||
mock_send_result.side_effect = side_effect
|
||||
mock_send.side_effect = side_effect
|
||||
run_worker_lifecycle(ticket, context, event_queue=event_queue)
|
||||
|
||||
# _queue_put(event_queue, event_name, payload)
|
||||
|
||||
@@ -73,7 +73,7 @@ def test_rag_integration(mock_project):
|
||||
# message sent to the provider. We use 'wraps' to let the real logic run
|
||||
# while still having a mock we can inspect. We also mock the internal
|
||||
# _send_gemini which is what actually "sends to the provider".
|
||||
with patch('src.ai_client.send_result', wraps=ai_client.send_result) as mock_send:
|
||||
with patch('src.ai_client.send', wraps=ai_client.send) as mock_send:
|
||||
with patch('src.ai_client._send_gemini') as mock_provider:
|
||||
mock_provider.return_value = Result(data="Mock AI Response")
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ class TestRunWorkerLifecycleAbort(unittest.TestCase):
|
||||
Test that run_worker_lifecycle returns early and marks ticket as 'killed'
|
||||
if the abort event is set for the ticket.
|
||||
"""
|
||||
# Mock ai_client.send_result
|
||||
with patch('src.ai_client.send_result') as mock_send_result:
|
||||
# Mock ai_client.send
|
||||
with patch('src.ai_client.send') as mock_send:
|
||||
# Mock ticket and context
|
||||
ticket = Ticket(id="T-001", description="Test task")
|
||||
ticket = Ticket(id="T-001", description="Test task")
|
||||
@@ -34,8 +34,8 @@ class TestRunWorkerLifecycleAbort(unittest.TestCase):
|
||||
# Assert ticket status is 'killed'
|
||||
self.assertEqual(ticket.status, "killed")
|
||||
|
||||
# Also assert ai_client.send_result was NOT called (abort fires before the call)
|
||||
mock_send_result.assert_not_called()
|
||||
# Also assert ai_client.send was NOT called (abort fires before the call)
|
||||
mock_send.assert_not_called()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -20,9 +20,9 @@ class MockDialog:
|
||||
|
||||
@pytest.fixture
|
||||
def mock_ai_client() -> Generator[MagicMock, None, None]:
|
||||
with patch("src.ai_client.send_result") as mock_send_result:
|
||||
mock_send_result.return_value = Result(data="Task completed")
|
||||
yield mock_send_result
|
||||
with patch("src.ai_client.send") as mock_send:
|
||||
mock_send.return_value = Result(data="Task completed")
|
||||
yield mock_send
|
||||
|
||||
def test_confirm_spawn_pushed_to_queue() -> None:
|
||||
event_queue = events.SyncEventQueue()
|
||||
|
||||
@@ -43,7 +43,7 @@ def test_handle_request_event_appends_definitions(controller):
|
||||
with (
|
||||
patch('src.app_controller.parse_symbols', return_value=["Track"]) as mock_parse,
|
||||
patch('src.app_controller.get_symbol_definition', return_value=("src/models.py", "class Track: pass", 42)) as mock_get_def,
|
||||
patch('src.ai_client.send_result', return_value=Result(data="mocked response")) as mock_send_result
|
||||
patch('src.ai_client.send', return_value=Result(data="mocked response")) as mock_send
|
||||
):
|
||||
# Execute
|
||||
controller._handle_request_event(event)
|
||||
@@ -54,8 +54,8 @@ def test_handle_request_event_appends_definitions(controller):
|
||||
|
||||
# Check if enriched prompt was sent to AI
|
||||
expected_suffix = "\n\n[Definition: Track from src/models.py (line 42)]\n```python\nclass Track: pass\n```"
|
||||
mock_send_result.assert_called_once()
|
||||
args, kwargs = mock_send_result.call_args
|
||||
mock_send.assert_called_once()
|
||||
args, kwargs = mock_send.call_args
|
||||
sent_prompt = args[1]
|
||||
assert sent_prompt == "Explain @Track object" + expected_suffix
|
||||
|
||||
@@ -72,13 +72,13 @@ def test_handle_request_event_no_symbols(controller):
|
||||
|
||||
with (
|
||||
patch('src.app_controller.parse_symbols', return_value=[]) as mock_parse,
|
||||
patch('src.ai_client.send_result', return_value=Result(data="mocked response")) as mock_send_result
|
||||
patch('src.ai_client.send', return_value=Result(data="mocked response")) as mock_send
|
||||
):
|
||||
# Execute
|
||||
controller._handle_request_event(event)
|
||||
|
||||
# Verify
|
||||
mock_send_result.assert_called_once()
|
||||
args, kwargs = mock_send_result.call_args
|
||||
mock_send.assert_called_once()
|
||||
args, kwargs = mock_send.call_args
|
||||
sent_prompt = args[1]
|
||||
assert sent_prompt == "Just a normal prompt"
|
||||
|
||||
@@ -88,7 +88,7 @@ class TestThemeNervFx(unittest.TestCase):
|
||||
pulse.render(800.0, 600.0)
|
||||
|
||||
mock_imgui.get_foreground_draw_list.assert_called()
|
||||
mock_draw_list.add_rect.assert_called_with((0.0, 0.0), (800.0, 600.0), 0xFF0000FF, 0.0, 0, 10.0)
|
||||
mock_draw_list.add_rect.assert_called_with((0.0, 0.0), (800.0, 600.0), 0xFF0000FF, rounding=0.0, thickness=10.0, flags=0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -76,17 +76,17 @@ def test_end_to_end_tier4_integration(vlogger) -> None:
|
||||
vlogger.finalize("E2E Tier 4 Integration", "PASS", "ai_client.run_tier4_analysis correctly called and results merged.")
|
||||
|
||||
def test_ai_client_passes_qa_callback() -> None:
|
||||
"""Verifies that ai_client.send_result passes the qa_callback down to the provider function."""
|
||||
"""Verifies that ai_client.send passes the qa_callback down to the provider function."""
|
||||
qa_callback = lambda x: "analysis"
|
||||
|
||||
with patch("src.ai_client._send_gemini", return_value=Result(data="ok")) as mock_send:
|
||||
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
||||
result = ai_client.send_result("ctx", "msg", qa_callback=qa_callback)
|
||||
result = ai_client.send("ctx", "msg", qa_callback=qa_callback)
|
||||
assert result.ok
|
||||
args, kwargs = mock_send.call_args
|
||||
# It might be passed as positional or keyword depending on how 'send_result' calls it
|
||||
# send_result() calls _send_gemini(md_content, user_message, base_dir, ..., qa_callback, ...)
|
||||
# In current impl of send_result(), it is the 7th argument after md_content, user_msg, base_dir, file_items, disc_hist, pre_tool
|
||||
# It might be passed as positional or keyword depending on how 'send' calls it
|
||||
# send() calls _send_gemini(md_content, user_message, base_dir, ..., qa_callback, ...)
|
||||
# In current impl of send(), it is the 7th argument after md_content, user_msg, base_dir, file_items, disc_hist, pre_tool
|
||||
assert args[6] == qa_callback or kwargs.get("qa_callback") == qa_callback
|
||||
|
||||
def test_gemini_provider_passes_qa_callback_to_run_script() -> None:
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_app_controller_do_generate_uses_persona_strategy(mock_build):
|
||||
assert call_kwargs.get("aggregation_strategy") == "full"
|
||||
|
||||
@patch("src.summarize.summarise_file")
|
||||
@patch("src.multi_agent_conductor.ai_client.send_result")
|
||||
@patch("src.multi_agent_conductor.ai_client.send")
|
||||
def test_run_worker_lifecycle_uses_strategy(mock_send, mock_summarise, tmp_path):
|
||||
mock_send.return_value = Result(data="fake response")
|
||||
mock_summarise.return_value = "fake summary"
|
||||
|
||||
@@ -32,7 +32,7 @@ def test_token_usage_tracking() -> None:
|
||||
mock_response.text = "Mock Response"
|
||||
mock_chat.send_message.return_value = mock_response
|
||||
ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
|
||||
result = ai_client.send_result("Context", "Hello")
|
||||
result = ai_client.send("Context", "Hello")
|
||||
assert result.ok
|
||||
comms = ai_client.get_comms_log()
|
||||
response_entries = [e for e in comms if e.get("direction") == "IN" and e["kind"] == "response"]
|
||||
|
||||
Reference in New Issue
Block a user