v2.8.0 (#616)
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
95336528f6
commit
04305bd77c
68 changed files with 3473 additions and 1764 deletions
|
|
@ -9,6 +9,7 @@ class TestCommandRegistry:
|
|||
assert registry.get_command_name("/help") == "help"
|
||||
assert registry.get_command_name("/config") == "config"
|
||||
assert registry.get_command_name("/model") == "model"
|
||||
assert registry.get_command_name("/connectors") == "mcp"
|
||||
assert registry.get_command_name("/clear") == "clear"
|
||||
assert registry.get_command_name("/exit") == "exit"
|
||||
assert registry.get_command_name("/data-retention") == "data-retention"
|
||||
|
|
@ -77,6 +78,11 @@ class TestCommandRegistry:
|
|||
result = registry.parse_command("/mcp filesystem")
|
||||
assert result == ("mcp", registry.commands["mcp"], "filesystem")
|
||||
|
||||
def test_parse_command_maps_connector_alias_to_mcp(self) -> None:
|
||||
registry = CommandRegistry()
|
||||
result = registry.parse_command("/connectors filesystem")
|
||||
assert result == ("mcp", registry.commands["mcp"], "filesystem")
|
||||
|
||||
def test_data_retention_command_registration(self) -> None:
|
||||
registry = CommandRegistry()
|
||||
result = registry.parse_command("/data-retention")
|
||||
|
|
|
|||
|
|
@ -2,11 +2,19 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from vibe.cli.textual_ui.widgets.mcp_app import MCPApp, collect_mcp_tool_index
|
||||
import pytest
|
||||
|
||||
from tests.stubs.fake_connector_registry import FakeConnectorRegistry
|
||||
from vibe.cli.textual_ui.widgets.mcp_app import (
|
||||
MCPApp,
|
||||
_sort_connector_names_for_menu,
|
||||
collect_mcp_tool_index,
|
||||
)
|
||||
from vibe.core.config import MCPStdio
|
||||
from vibe.core.tools.base import InvokeContext
|
||||
from vibe.core.tools.connectors.connector_registry import RemoteTool
|
||||
from vibe.core.tools.mcp.tools import MCPTool, MCPToolResult, _OpenArgs
|
||||
from vibe.core.types import ToolStreamEvent
|
||||
|
||||
|
|
@ -158,3 +166,103 @@ class TestMCPAppInit:
|
|||
)
|
||||
app.action_back()
|
||||
assert render_calls == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_action_refresh_dispatches_worker(self) -> None:
|
||||
servers = [MCPStdio(name="srv", transport="stdio", command="cmd")]
|
||||
mgr = _make_tool_manager({})
|
||||
refresh_callback = AsyncMock(return_value="Refreshed.")
|
||||
app = MCPApp(
|
||||
mcp_servers=servers, tool_manager=mgr, refresh_callback=refresh_callback
|
||||
)
|
||||
app._viewing_server = "srv"
|
||||
render_calls: list[tuple[str | None, str | None]] = []
|
||||
app._refresh_view = lambda server_name, *, kind=None: render_calls.append((
|
||||
server_name,
|
||||
kind,
|
||||
))
|
||||
app.run_worker = MagicMock()
|
||||
|
||||
await app.action_refresh()
|
||||
|
||||
assert app._status_message == "Refreshing..."
|
||||
assert render_calls == [("srv", None)]
|
||||
app.run_worker.assert_called_once()
|
||||
|
||||
def test_on_worker_state_changed_updates_after_refresh(self) -> None:
|
||||
from textual.worker import Worker
|
||||
|
||||
servers = [MCPStdio(name="srv", transport="stdio", command="cmd")]
|
||||
mgr = _make_tool_manager({})
|
||||
app = MCPApp(mcp_servers=servers, tool_manager=mgr)
|
||||
app.refresh_index = MagicMock()
|
||||
|
||||
worker = MagicMock(spec=Worker)
|
||||
worker.group = "refresh"
|
||||
worker.is_finished = True
|
||||
worker.result = "Refreshed."
|
||||
event = MagicMock(spec=Worker.StateChanged)
|
||||
event.worker = worker
|
||||
|
||||
app.on_worker_state_changed(event)
|
||||
|
||||
assert app._status_message == "Refreshed."
|
||||
assert app._refreshing is False
|
||||
app.refresh_index.assert_called_once()
|
||||
|
||||
def test_close_blocked_while_refreshing(self) -> None:
|
||||
mgr = _make_tool_manager({})
|
||||
app = MCPApp(mcp_servers=[], tool_manager=mgr)
|
||||
app._refreshing = True
|
||||
app.post_message = MagicMock()
|
||||
|
||||
app.action_close()
|
||||
|
||||
app.post_message.assert_not_called()
|
||||
|
||||
def test_back_blocked_while_refreshing(self) -> None:
|
||||
servers = [MCPStdio(name="srv", transport="stdio", command="cmd")]
|
||||
mgr = _make_tool_manager({})
|
||||
app = MCPApp(mcp_servers=servers, tool_manager=mgr)
|
||||
app._viewing_server = "srv"
|
||||
app._refreshing = True
|
||||
render_calls: list[str | None] = []
|
||||
app._refresh_view = lambda server_name, *, kind=None: render_calls.append(
|
||||
server_name
|
||||
)
|
||||
|
||||
app.action_back()
|
||||
|
||||
assert render_calls == []
|
||||
|
||||
|
||||
class TestConnectorMenuOrdering:
|
||||
def test_connectors_are_sorted_by_connected_state_then_name(self) -> None:
|
||||
registry = FakeConnectorRegistry(
|
||||
connectors={
|
||||
"zeta": [],
|
||||
"alpha": [RemoteTool(name="lookup", description="Lookup")],
|
||||
"beta": [],
|
||||
}
|
||||
)
|
||||
|
||||
ordered = _sort_connector_names_for_menu(
|
||||
registry.get_connector_names(), registry
|
||||
)
|
||||
|
||||
assert ordered == ["alpha", "beta", "zeta"]
|
||||
|
||||
def test_sorting_is_case_insensitive(self) -> None:
|
||||
registry = FakeConnectorRegistry(
|
||||
connectors={
|
||||
"Zeta": [],
|
||||
"alpha": [RemoteTool(name="lookup", description="Lookup")],
|
||||
"Beta": [],
|
||||
}
|
||||
)
|
||||
|
||||
ordered = _sort_connector_names_for_menu(
|
||||
registry.get_connector_names(), registry
|
||||
)
|
||||
|
||||
assert ordered == ["alpha", "Beta", "Zeta"]
|
||||
|
|
|
|||
|
|
@ -129,24 +129,3 @@ async def test_skill_without_args_does_not_prepend_invocation_line(
|
|||
vibe_app_with_skills, pilot, "Do the thing."
|
||||
)
|
||||
assert "/my-skill" not in message._content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_skill_with_missing_file_shows_error(tmp_path: Path) -> None:
|
||||
skills_dir = tmp_path / "skills"
|
||||
skills_dir.mkdir()
|
||||
skill_dir = create_skill(skills_dir, "my-skill", body=SKILL_BODY)
|
||||
vibe_app = build_test_vibe_app(
|
||||
config=build_test_vibe_config(skill_paths=[skills_dir])
|
||||
)
|
||||
(skill_dir / "SKILL.md").unlink()
|
||||
|
||||
async with vibe_app.run_test() as pilot:
|
||||
await pilot.pause(0.1)
|
||||
chat_input = vibe_app.query_one(ChatInputContainer)
|
||||
chat_input.post_message(ChatInputContainer.Submitted("/my-skill"))
|
||||
|
||||
error = await _wait_for_error_message_containing(
|
||||
vibe_app, pilot, "Failed to read skill file"
|
||||
)
|
||||
assert "Failed to read skill file" in error._error
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue