v2.16.1 (#808)
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
c2cb612ac1
commit
564a14365e
22 changed files with 952 additions and 252 deletions
|
|
@ -72,7 +72,7 @@ class TestACPInitialize:
|
|||
),
|
||||
)
|
||||
assert response.agent_info == Implementation(
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.16.0"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.16.1"
|
||||
)
|
||||
|
||||
assert response.auth_methods is not None
|
||||
|
|
@ -172,7 +172,7 @@ class TestACPInitialize:
|
|||
),
|
||||
)
|
||||
assert response.agent_info == Implementation(
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.16.0"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.16.1"
|
||||
)
|
||||
|
||||
assert response.auth_methods is not None
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from acp import RequestError
|
||||
from acp.schema import (
|
||||
AgentMessageChunk,
|
||||
AgentThoughtChunk,
|
||||
ClientCapabilities,
|
||||
ToolCallProgress,
|
||||
ToolCallStart,
|
||||
UserMessageChunk,
|
||||
|
|
@ -15,10 +17,11 @@ import pytest
|
|||
from tests.conftest import build_test_vibe_config
|
||||
from tests.stubs.fake_backend import FakeBackend
|
||||
from tests.stubs.fake_client import FakeClient
|
||||
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
||||
from vibe.acp.acp_agent_loop import WORKSPACE_TRUST_CAPABILITY, VibeAcpAgentLoop
|
||||
from vibe.core.agent_loop import AgentLoop
|
||||
from vibe.core.agents.models import BuiltinAgentName
|
||||
from vibe.core.config import ModelConfig, SessionLoggingConfig
|
||||
from vibe.core.trusted_folders import trusted_folders_manager
|
||||
from vibe.core.types import Role
|
||||
|
||||
|
||||
|
|
@ -122,6 +125,37 @@ class TestLoadSession:
|
|||
assert response.config_options[2].category == "thinking"
|
||||
assert response.config_options[2].current_value == "off"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_session_resolves_workspace_trust_before_loading_config(
|
||||
self,
|
||||
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
|
||||
temp_session_dir: Path,
|
||||
create_test_session,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
acp_agent, client = acp_agent_with_session_config
|
||||
acp_agent.client_capabilities = ClientCapabilities(
|
||||
field_meta={WORKSPACE_TRUST_CAPABILITY: True}
|
||||
)
|
||||
request_trust = AsyncMock(return_value={"decision": "trust_cwd"})
|
||||
monkeypatch.setattr(client, "ext_method", request_trust)
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Loaded session project instructions", encoding="utf-8"
|
||||
)
|
||||
session_id = "test-sess-trust"
|
||||
create_test_session(temp_session_dir, session_id, str(tmp_working_directory))
|
||||
|
||||
await acp_agent.load_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[], session_id=session_id
|
||||
)
|
||||
|
||||
request_trust.assert_awaited_once()
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
|
||||
system_prompt = acp_agent.sessions[session_id].agent_loop.messages[0].content
|
||||
assert system_prompt is not None
|
||||
assert "Loaded session project instructions" in system_prompt
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_session_registers_session_with_original_id(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,31 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from acp import RequestError
|
||||
from acp.schema import ClientCapabilities
|
||||
import pytest
|
||||
|
||||
from tests.acp.conftest import _create_acp_agent
|
||||
from tests.conftest import build_test_vibe_config
|
||||
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
||||
from vibe.acp.acp_agent_loop import WORKSPACE_TRUST_CAPABILITY, VibeAcpAgentLoop
|
||||
from vibe.acp.exceptions import InvalidRequestError
|
||||
from vibe.core.agent_loop import AgentLoop
|
||||
from vibe.core.agents.models import BuiltinAgentName
|
||||
from vibe.core.config import ModelConfig
|
||||
from vibe.core.trusted_folders import trusted_folders_manager
|
||||
|
||||
|
||||
def _system_prompt(acp_agent_loop: VibeAcpAgentLoop, session_id: str) -> str:
|
||||
session = acp_agent_loop.sessions[session_id]
|
||||
return session.agent_loop.messages[0].content or ""
|
||||
|
||||
|
||||
def _enable_workspace_trust(acp_agent_loop: VibeAcpAgentLoop) -> None:
|
||||
acp_agent_loop.client_capabilities = ClientCapabilities(
|
||||
field_meta={WORKSPACE_TRUST_CAPABILITY: True}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -137,6 +152,231 @@ class TestACPNewSession:
|
|||
assert thinking_config.current_value == "off"
|
||||
assert len(thinking_config.options) == 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_loads_root_agents_md_from_workspace_cwd(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Root project instructions", encoding="utf-8"
|
||||
)
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
request_trust = AsyncMock(return_value={"decision": "trust_cwd"})
|
||||
monkeypatch.setattr(acp_agent_loop.client, "ext_method", request_trust)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
request_trust.assert_awaited_once()
|
||||
await_args = request_trust.await_args
|
||||
assert await_args is not None
|
||||
method, params = await_args.args
|
||||
assert method == "trust/request"
|
||||
assert params["cwd"] == str(tmp_working_directory.resolve())
|
||||
assert params["detectedFiles"] == ["AGENTS.md"]
|
||||
assert params["repoDetectedFiles"] == []
|
||||
assert params["availableDecisions"] == ["trust_cwd", "trust_session", "decline"]
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
|
||||
assert "Root project instructions" in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_can_trust_full_repo_from_subdirectory(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
repo = tmp_path / "repo"
|
||||
cwd = repo / "src" / "pkg"
|
||||
(repo / ".git").mkdir(parents=True)
|
||||
(repo / ".git" / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8")
|
||||
cwd.mkdir(parents=True)
|
||||
(repo / "AGENTS.md").write_text("Repo instructions", encoding="utf-8")
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
request_trust = AsyncMock(return_value={"decision": "trust_repo"})
|
||||
monkeypatch.setattr(acp_agent_loop.client, "ext_method", request_trust)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(cwd), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
request_trust.assert_awaited_once()
|
||||
await_args = request_trust.await_args
|
||||
assert await_args is not None
|
||||
method, params = await_args.args
|
||||
assert method == "trust/request"
|
||||
assert params["cwd"] == str(cwd.resolve())
|
||||
assert params["repoRoot"] == str(repo.resolve())
|
||||
assert params["detectedFiles"] == []
|
||||
assert params["repoDetectedFiles"] == ["AGENTS.md"]
|
||||
assert params["availableDecisions"] == [
|
||||
"trust_repo",
|
||||
"trust_cwd",
|
||||
"trust_session",
|
||||
"decline",
|
||||
]
|
||||
assert trusted_folders_manager.is_trusted(repo) is True
|
||||
assert trusted_folders_manager.is_trusted(cwd) is True
|
||||
assert "Repo instructions" in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_decline_skips_project_docs(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Do not load this", encoding="utf-8"
|
||||
)
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
monkeypatch.setattr(
|
||||
acp_agent_loop.client,
|
||||
"ext_method",
|
||||
AsyncMock(return_value={"decision": "decline"}),
|
||||
)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is False
|
||||
assert "Do not load this" not in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_session_trust_loads_docs_without_persisting(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Session-only instructions", encoding="utf-8"
|
||||
)
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
monkeypatch.setattr(
|
||||
acp_agent_loop.client,
|
||||
"ext_method",
|
||||
AsyncMock(return_value={"decision": "trust_session"}),
|
||||
)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
normalized = str(tmp_working_directory.resolve())
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
|
||||
assert normalized in trusted_folders_manager._session_trusted
|
||||
assert normalized not in trusted_folders_manager._trusted
|
||||
assert "Session-only instructions" in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_skips_trust_prompt_without_trustable_files(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
request_trust = AsyncMock(return_value={"decision": "trust_cwd"})
|
||||
monkeypatch.setattr(acp_agent_loop.client, "ext_method", request_trust)
|
||||
|
||||
await acp_agent_loop.new_session(cwd=str(tmp_working_directory), mcp_servers=[])
|
||||
|
||||
request_trust.assert_not_awaited()
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_direct_client_fallback_skips_project_docs(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Direct client should skip this", encoding="utf-8"
|
||||
)
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
monkeypatch.setattr(
|
||||
acp_agent_loop.client,
|
||||
"ext_method",
|
||||
AsyncMock(side_effect=RequestError.method_not_found("trust/request")),
|
||||
)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|
||||
assert "Direct client should skip this" not in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_without_workspace_trust_capability_skips_prompt(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Unsupported client should skip this", encoding="utf-8"
|
||||
)
|
||||
request_trust = AsyncMock(return_value={"decision": "trust_cwd"})
|
||||
monkeypatch.setattr(acp_agent_loop.client, "ext_method", request_trust)
|
||||
|
||||
session_response = await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
assert session_response.session_id is not None
|
||||
|
||||
request_trust.assert_not_awaited()
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|
||||
assert "Unsupported client should skip this" not in _system_prompt(
|
||||
acp_agent_loop, session_response.session_id
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_cancelled_trust_prompt_cancels_session_creation(
|
||||
self,
|
||||
acp_agent_loop: VibeAcpAgentLoop,
|
||||
tmp_working_directory: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
(tmp_working_directory / "AGENTS.md").write_text(
|
||||
"Cancelled prompt", encoding="utf-8"
|
||||
)
|
||||
_enable_workspace_trust(acp_agent_loop)
|
||||
monkeypatch.setattr(
|
||||
acp_agent_loop.client,
|
||||
"ext_method",
|
||||
AsyncMock(return_value={"decision": "cancelled"}),
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidRequestError):
|
||||
await acp_agent_loop.new_session(
|
||||
cwd=str(tmp_working_directory), mcp_servers=[]
|
||||
)
|
||||
|
||||
assert trusted_folders_manager.is_trusted(tmp_working_directory) is None
|
||||
assert acp_agent_loop.sessions == {}
|
||||
|
||||
@pytest.mark.skip(reason="TODO: Fix this test")
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_session_preserves_model_after_set_model(
|
||||
|
|
|
|||
|
|
@ -194,7 +194,9 @@ def test_failed_update_prints_error_message(
|
|||
):
|
||||
_maybe_run_startup_update_prompt(config, repository)
|
||||
|
||||
assert "could not be updated automatically" in capsys.readouterr().out
|
||||
out = capsys.readouterr().out
|
||||
assert "could not update automatically" in out
|
||||
assert "package manager" in out
|
||||
|
||||
|
||||
def test_failed_update_does_not_dismiss_so_user_is_reprompted_on_next_launch(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,82 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe.core.nuage.workflow import (
|
||||
WorkflowExecutionListResponse,
|
||||
WorkflowExecutionStatus,
|
||||
WorkflowExecutionWithoutResultResponse,
|
||||
)
|
||||
from vibe.core.session.resume_sessions import (
|
||||
RemoteResumeResult,
|
||||
RemoteResumeSessions,
|
||||
ResumeSessionInfo,
|
||||
can_delete_resume_session_source,
|
||||
list_remote_resume_sessions,
|
||||
session_latest_messages,
|
||||
short_session_id,
|
||||
)
|
||||
from vibe.core.session.session_id import shorten_session_id
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RemoteResumeRequest:
|
||||
workflow_identifier: str | None
|
||||
page_size: int
|
||||
status: Sequence[WorkflowExecutionStatus] | None
|
||||
|
||||
|
||||
class FakeRemoteResumeClient:
|
||||
def __init__(
|
||||
self,
|
||||
response: WorkflowExecutionListResponse | None = None,
|
||||
*,
|
||||
delay: float = 0.0,
|
||||
) -> None:
|
||||
self.response = response or WorkflowExecutionListResponse(executions=[])
|
||||
self.delay = delay
|
||||
self.requests: list[RemoteResumeRequest] = []
|
||||
self.closed = False
|
||||
|
||||
async def get_workflow_runs(
|
||||
self,
|
||||
workflow_identifier: str | None = None,
|
||||
page_size: int = 50,
|
||||
next_page_token: str | None = None,
|
||||
status: Sequence[WorkflowExecutionStatus] | None = None,
|
||||
user_id: str = "current",
|
||||
) -> WorkflowExecutionListResponse:
|
||||
self.requests.append(
|
||||
RemoteResumeRequest(
|
||||
workflow_identifier=workflow_identifier,
|
||||
page_size=page_size,
|
||||
status=status,
|
||||
)
|
||||
)
|
||||
if self.delay:
|
||||
await asyncio.sleep(self.delay)
|
||||
return self.response
|
||||
|
||||
async def aclose(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def enabled_vibe_code_config() -> MagicMock:
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = True
|
||||
config.vibe_code_api_key = "test-key"
|
||||
config.vibe_code_base_url = "https://test.example.com"
|
||||
config.api_timeout = 30
|
||||
config.vibe_code_workflow_id = "workflow-1"
|
||||
return config
|
||||
|
||||
|
||||
class TestShortenSessionId:
|
||||
def test_shortens_to_first_8_chars(self) -> None:
|
||||
sid = "abcdef1234567890"
|
||||
|
|
@ -67,40 +131,8 @@ class TestCanDeleteResumeSession:
|
|||
|
||||
|
||||
class TestListRemoteResumeSessions:
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_when_vibe_code_disabled(self) -> None:
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = False
|
||||
config.vibe_code_api_key = "key"
|
||||
result = await list_remote_resume_sessions(config)
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_when_no_api_key(self) -> None:
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = True
|
||||
config.vibe_code_api_key = None
|
||||
result = await list_remote_resume_sessions(config)
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_when_both_missing(self) -> None:
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = False
|
||||
config.vibe_code_api_key = None
|
||||
result = await list_remote_resume_sessions(config)
|
||||
assert result == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_passes_active_statuses_to_api(self) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
from vibe.core.nuage.workflow import (
|
||||
WorkflowExecutionListResponse,
|
||||
WorkflowExecutionStatus,
|
||||
WorkflowExecutionWithoutResultResponse,
|
||||
)
|
||||
|
||||
running = WorkflowExecutionWithoutResultResponse(
|
||||
workflow_name="vibe",
|
||||
execution_id="exec-running",
|
||||
|
|
@ -117,47 +149,28 @@ class TestListRemoteResumeSessions:
|
|||
)
|
||||
|
||||
mock_response = WorkflowExecutionListResponse(executions=[running, continued])
|
||||
client = FakeRemoteResumeClient(mock_response)
|
||||
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = True
|
||||
config.vibe_code_api_key = "test-key"
|
||||
config.vibe_code_base_url = "https://test.example.com"
|
||||
config.api_timeout = 30
|
||||
config.vibe_code_workflow_id = "workflow-1"
|
||||
|
||||
with patch("vibe.core.session.resume_sessions.WorkflowsClient") as MockClient:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get_workflow_runs.return_value = mock_response
|
||||
MockClient.return_value.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
MockClient.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
result = await list_remote_resume_sessions(config)
|
||||
result = await list_remote_resume_sessions(client, "workflow-1")
|
||||
|
||||
assert len(result) == 2
|
||||
session_ids = {s.session_id for s in result}
|
||||
assert "exec-running" in session_ids
|
||||
assert "exec-continued" in session_ids
|
||||
assert all(s.source == "remote" for s in result)
|
||||
|
||||
mock_client.get_workflow_runs.assert_called_once_with(
|
||||
workflow_identifier="workflow-1",
|
||||
page_size=50,
|
||||
status=[
|
||||
WorkflowExecutionStatus.RUNNING,
|
||||
WorkflowExecutionStatus.CONTINUED_AS_NEW,
|
||||
],
|
||||
)
|
||||
assert client.requests == [
|
||||
RemoteResumeRequest(
|
||||
workflow_identifier="workflow-1",
|
||||
page_size=50,
|
||||
status=[
|
||||
WorkflowExecutionStatus.RUNNING,
|
||||
WorkflowExecutionStatus.CONTINUED_AS_NEW,
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deduplicates_execution_ids_keeps_latest(self) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
from vibe.core.nuage.workflow import (
|
||||
WorkflowExecutionListResponse,
|
||||
WorkflowExecutionStatus,
|
||||
WorkflowExecutionWithoutResultResponse,
|
||||
)
|
||||
|
||||
older = WorkflowExecutionWithoutResultResponse(
|
||||
workflow_name="vibe",
|
||||
execution_id="exec-1",
|
||||
|
|
@ -181,21 +194,9 @@ class TestListRemoteResumeSessions:
|
|||
)
|
||||
|
||||
mock_response = WorkflowExecutionListResponse(executions=[older, newer, other])
|
||||
client = FakeRemoteResumeClient(mock_response)
|
||||
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = True
|
||||
config.vibe_code_api_key = "test-key"
|
||||
config.vibe_code_base_url = "https://test.example.com"
|
||||
config.api_timeout = 30
|
||||
config.vibe_code_workflow_id = "workflow-1"
|
||||
|
||||
with patch("vibe.core.session.resume_sessions.WorkflowsClient") as MockClient:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get_workflow_runs.return_value = mock_response
|
||||
MockClient.return_value.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
MockClient.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
result = await list_remote_resume_sessions(config)
|
||||
result = await list_remote_resume_sessions(client, "workflow-1")
|
||||
|
||||
assert len(result) == 2
|
||||
by_id = {s.session_id: s for s in result}
|
||||
|
|
@ -206,14 +207,6 @@ class TestListRemoteResumeSessions:
|
|||
async def test_dedup_keeps_latest_start_time_when_previous_has_end_time(
|
||||
self,
|
||||
) -> None:
|
||||
from datetime import datetime
|
||||
|
||||
from vibe.core.nuage.workflow import (
|
||||
WorkflowExecutionListResponse,
|
||||
WorkflowExecutionStatus,
|
||||
WorkflowExecutionWithoutResultResponse,
|
||||
)
|
||||
|
||||
previous = WorkflowExecutionWithoutResultResponse(
|
||||
workflow_name="vibe",
|
||||
execution_id="exec-1",
|
||||
|
|
@ -230,22 +223,85 @@ class TestListRemoteResumeSessions:
|
|||
)
|
||||
|
||||
mock_response = WorkflowExecutionListResponse(executions=[previous, newer])
|
||||
client = FakeRemoteResumeClient(mock_response)
|
||||
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = True
|
||||
config.vibe_code_api_key = "test-key"
|
||||
config.vibe_code_base_url = "https://test.example.com"
|
||||
config.api_timeout = 30
|
||||
config.vibe_code_workflow_id = "workflow-1"
|
||||
|
||||
with patch("vibe.core.session.resume_sessions.WorkflowsClient") as MockClient:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.get_workflow_runs.return_value = mock_response
|
||||
MockClient.return_value.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
MockClient.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
result = await list_remote_resume_sessions(config)
|
||||
result = await list_remote_resume_sessions(client, "workflow-1")
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].session_id == "exec-1"
|
||||
assert result[0].status == WorkflowExecutionStatus.RUNNING
|
||||
|
||||
|
||||
class TestSessionLatestMessages:
|
||||
def test_remote_session_formats_title_and_status(self) -> None:
|
||||
session = ResumeSessionInfo(
|
||||
session_id="exec-1",
|
||||
source="remote",
|
||||
cwd="",
|
||||
title="My run",
|
||||
end_time=None,
|
||||
status="RUNNING",
|
||||
)
|
||||
messages = session_latest_messages([session], MagicMock())
|
||||
assert messages[session.option_id] == "My run (running)"
|
||||
|
||||
|
||||
class TestRemoteResumeSessions:
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_skips_when_vibe_code_disabled(self) -> None:
|
||||
config = MagicMock()
|
||||
config.vibe_code_enabled = False
|
||||
config.vibe_code_api_key = "key"
|
||||
created_clients: list[FakeRemoteResumeClient] = []
|
||||
|
||||
def client_factory(_config: object) -> FakeRemoteResumeClient:
|
||||
client = FakeRemoteResumeClient()
|
||||
created_clients.append(client)
|
||||
return client
|
||||
|
||||
remote = RemoteResumeSessions(lambda: config, client_factory)
|
||||
|
||||
result = await remote.fetch(10.0)
|
||||
|
||||
assert result == RemoteResumeResult([], None)
|
||||
assert created_clients == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_cancels_previous_fetch(self) -> None:
|
||||
config = enabled_vibe_code_config()
|
||||
client = FakeRemoteResumeClient(delay=10.0)
|
||||
remote = RemoteResumeSessions(lambda: config, lambda _config: client)
|
||||
|
||||
first = remote.start(100.0)
|
||||
await asyncio.sleep(0)
|
||||
second = remote.start(100.0)
|
||||
await asyncio.sleep(0)
|
||||
await remote.aclose()
|
||||
|
||||
assert first.cancelled()
|
||||
assert first is not second
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_returns_error_tuple_on_timeout(self) -> None:
|
||||
config = enabled_vibe_code_config()
|
||||
client = FakeRemoteResumeClient(delay=10.0)
|
||||
remote = RemoteResumeSessions(lambda: config, lambda _config: client)
|
||||
|
||||
sessions, error = await remote.fetch(0.01)
|
||||
await remote.aclose()
|
||||
|
||||
assert sessions == []
|
||||
assert error is not None and "Timed out" in error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aclose_cancels_inflight_fetch_before_closing_client(self) -> None:
|
||||
config = enabled_vibe_code_config()
|
||||
client = FakeRemoteResumeClient(delay=10.0)
|
||||
remote = RemoteResumeSessions(lambda: config, lambda _config: client)
|
||||
|
||||
task = remote.start(100.0)
|
||||
await asyncio.sleep(0)
|
||||
await remote.aclose()
|
||||
|
||||
assert task.cancelled()
|
||||
assert client.closed is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue