v2.9.2 (#645)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
1fd7eea289
commit
3b8f65b306
16 changed files with 300 additions and 52 deletions
|
|
@ -34,7 +34,7 @@ class TestACPInitialize:
|
|||
),
|
||||
)
|
||||
assert response.agent_info == Implementation(
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.1"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.2"
|
||||
)
|
||||
|
||||
assert response.auth_methods == []
|
||||
|
|
@ -62,7 +62,7 @@ class TestACPInitialize:
|
|||
),
|
||||
)
|
||||
assert response.agent_info == Implementation(
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.1"
|
||||
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.2"
|
||||
)
|
||||
|
||||
assert response.auth_methods is not None
|
||||
|
|
|
|||
128
tests/core/test_config_schema.py
Normal file
128
tests/core/test_config_schema.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
import pytest
|
||||
|
||||
from vibe.core.config import (
|
||||
ConfigDefinitionError,
|
||||
ConfigFragment,
|
||||
ConfigSchema,
|
||||
WithConcatMerge,
|
||||
WithReplaceMerge,
|
||||
WithShallowMerge,
|
||||
WithUnionMerge,
|
||||
)
|
||||
|
||||
|
||||
class ToolSettings(BaseModel):
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ModelDefinition(BaseModel):
|
||||
alias: str
|
||||
provider: str
|
||||
|
||||
|
||||
class ToolsFragment(ConfigFragment):
|
||||
disabled_tools: Annotated[list[str], WithConcatMerge()] = Field(
|
||||
default_factory=list
|
||||
)
|
||||
tool_settings: Annotated[dict[str, ToolSettings], WithShallowMerge()] = Field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
|
||||
class ModelsFragment(ConfigFragment):
|
||||
default_model: Annotated[str, WithReplaceMerge()] = "devstral-small"
|
||||
available_models: Annotated[
|
||||
list[ModelDefinition], WithUnionMerge(merge_key="alias")
|
||||
] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MiniVibeConfigSchema(ConfigSchema):
|
||||
active_model: Annotated[str, WithReplaceMerge()] = "devstral-2"
|
||||
models: ModelsFragment = Field(default_factory=ModelsFragment)
|
||||
tools: ToolsFragment = Field(default_factory=ToolsFragment)
|
||||
|
||||
|
||||
def test_config_fragment_accepts_merge_aware_fields() -> None:
|
||||
fragment = ToolsFragment(
|
||||
disabled_tools=["bash"], tool_settings={"search": ToolSettings(enabled=False)}
|
||||
)
|
||||
|
||||
assert fragment.disabled_tools == ["bash"]
|
||||
assert fragment.tool_settings["search"].enabled is False
|
||||
|
||||
|
||||
def test_config_fragment_rejects_plain_fields() -> None:
|
||||
with pytest.raises(
|
||||
ConfigDefinitionError, match="BrokenFragment.name must declare merge metadata"
|
||||
):
|
||||
|
||||
class BrokenFragment(ConfigFragment):
|
||||
name: str = "bash"
|
||||
|
||||
|
||||
def test_scenario_mini_vibe_config_schema() -> None:
|
||||
config = MiniVibeConfigSchema(
|
||||
active_model="codestral-latest",
|
||||
models=ModelsFragment(
|
||||
available_models=[
|
||||
ModelDefinition(alias="devstral-small", provider="mistral"),
|
||||
ModelDefinition(alias="codestral-latest", provider="mistral"),
|
||||
]
|
||||
),
|
||||
tools=ToolsFragment(disabled_tools=["bash"]),
|
||||
)
|
||||
|
||||
assert config.active_model == "codestral-latest"
|
||||
assert config.models.default_model == "devstral-small"
|
||||
assert [model.alias for model in config.models.available_models] == [
|
||||
"devstral-small",
|
||||
"codestral-latest",
|
||||
]
|
||||
assert config.tools.disabled_tools == ["bash"]
|
||||
|
||||
|
||||
def test_config_schema_rejects_plain_top_level_fields() -> None:
|
||||
with pytest.raises(
|
||||
ConfigDefinitionError,
|
||||
match="BrokenSchema.active_model must declare merge metadata",
|
||||
):
|
||||
|
||||
class BrokenSchema(ConfigSchema):
|
||||
active_model: str = "devstral-2"
|
||||
|
||||
|
||||
def test_config_schema_rejects_plain_nested_models() -> None:
|
||||
with pytest.raises(
|
||||
ConfigDefinitionError,
|
||||
match="BrokenSchema.tools must declare merge metadata or use a ConfigFragment subclass",
|
||||
):
|
||||
|
||||
class BrokenSchema(ConfigSchema):
|
||||
tools: ToolSettings = Field(default_factory=ToolSettings)
|
||||
|
||||
|
||||
def test_config_schema_rejects_merge_metadata_on_fragments() -> None:
|
||||
with pytest.raises(
|
||||
ConfigDefinitionError,
|
||||
match="BrokenSchema.tools is a ConfigFragment field and must not declare merge metadata",
|
||||
):
|
||||
|
||||
class BrokenSchema(ConfigSchema):
|
||||
tools: Annotated[ToolsFragment, WithReplaceMerge()] = Field(
|
||||
default_factory=ToolsFragment
|
||||
)
|
||||
|
||||
|
||||
def test_config_schema_rejects_optional_fragments() -> None:
|
||||
with pytest.raises(
|
||||
ConfigDefinitionError,
|
||||
match="BrokenSchema.tools must declare merge metadata or use a ConfigFragment subclass",
|
||||
):
|
||||
|
||||
class BrokenSchema(ConfigSchema):
|
||||
tools: ToolsFragment | None = None
|
||||
|
|
@ -23,9 +23,10 @@ class TestWaitForGithubConnection:
|
|||
connected = GitHubPublicData(status=GitHubStatus.CONNECTED)
|
||||
client.get_github_integration = AsyncMock(return_value=connected)
|
||||
|
||||
result = await client.wait_for_github_connection("exec-1")
|
||||
results = [data async for data in client.wait_for_github_connection("exec-1")]
|
||||
|
||||
assert result.connected is True
|
||||
assert len(results) == 1
|
||||
assert results[0].connected is True
|
||||
client.get_github_integration.assert_called_once_with("exec-1")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -40,9 +41,12 @@ class TestWaitForGithubConnection:
|
|||
)
|
||||
|
||||
with patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock):
|
||||
result = await client.wait_for_github_connection("exec-1")
|
||||
results = [
|
||||
data async for data in client.wait_for_github_connection("exec-1")
|
||||
]
|
||||
|
||||
assert result.connected is True
|
||||
assert len(results) == 3
|
||||
assert results[-1].connected is True
|
||||
assert client.get_github_integration.call_count == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -53,7 +57,8 @@ class TestWaitForGithubConnection:
|
|||
client.get_github_integration = AsyncMock(return_value=error_data)
|
||||
|
||||
with pytest.raises(ServiceTeleportError, match="App not installed"):
|
||||
await client.wait_for_github_connection("exec-1")
|
||||
async for _ in client.wait_for_github_connection("exec-1"):
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raises_on_oauth_timeout(self, client: NuageClient) -> None:
|
||||
|
|
@ -61,7 +66,8 @@ class TestWaitForGithubConnection:
|
|||
client.get_github_integration = AsyncMock(return_value=timeout_data)
|
||||
|
||||
with pytest.raises(ServiceTeleportError, match="oauth_timeout"):
|
||||
await client.wait_for_github_connection("exec-1")
|
||||
async for _ in client.wait_for_github_connection("exec-1"):
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raises_on_timeout(self, client: NuageClient) -> None:
|
||||
|
|
@ -77,7 +83,8 @@ class TestWaitForGithubConnection:
|
|||
patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock),
|
||||
pytest.raises(ServiceTeleportError, match="timed out"),
|
||||
):
|
||||
await client.wait_for_github_connection("exec-1", timeout=600.0)
|
||||
async for _ in client.wait_for_github_connection("exec-1", timeout=600.0):
|
||||
pass
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sleeps_with_correct_interval(self, client: NuageClient) -> None:
|
||||
|
|
@ -88,8 +95,31 @@ class TestWaitForGithubConnection:
|
|||
with patch(
|
||||
"vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock
|
||||
) as mock_sleep:
|
||||
await client.wait_for_github_connection("exec-1", interval=5.0)
|
||||
async for _ in client.wait_for_github_connection("exec-1", interval=5.0):
|
||||
pass
|
||||
|
||||
mock_sleep.assert_called_once()
|
||||
sleep_duration = mock_sleep.call_args[0][0]
|
||||
assert sleep_duration <= 5.0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_yields_each_poll_result(self, client: NuageClient) -> None:
|
||||
pending = GitHubPublicData(
|
||||
status=GitHubStatus.WAITING_FOR_OAUTH,
|
||||
error="Please connect GitHub",
|
||||
oauth_url="https://github.com/auth",
|
||||
)
|
||||
connected = GitHubPublicData(status=GitHubStatus.CONNECTED)
|
||||
client.get_github_integration = AsyncMock(
|
||||
side_effect=[pending, pending, connected]
|
||||
)
|
||||
|
||||
with patch("vibe.core.teleport.nuage.asyncio.sleep", new_callable=AsyncMock):
|
||||
results = [
|
||||
data async for data in client.wait_for_github_connection("exec-1")
|
||||
]
|
||||
|
||||
assert len(results) == 3
|
||||
assert results[0].error == "Please connect GitHub"
|
||||
assert results[1].error == "Please connect GitHub"
|
||||
assert results[2].connected is True
|
||||
|
|
|
|||
|
|
@ -260,11 +260,12 @@ class TestTeleportServiceExecute:
|
|||
service._git.get_info = AsyncMock(return_value=git_info)
|
||||
service._git.is_commit_pushed = AsyncMock(return_value=True)
|
||||
|
||||
async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def]
|
||||
yield mock_github_connected
|
||||
|
||||
mock_nuage = MagicMock()
|
||||
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
|
||||
mock_nuage.get_github_integration = AsyncMock(
|
||||
return_value=mock_github_connected
|
||||
)
|
||||
mock_nuage.wait_for_github_connection = _connected_gen
|
||||
mock_nuage.get_chat_assistant_url = AsyncMock(
|
||||
return_value="https://chat.example.com/123"
|
||||
)
|
||||
|
|
@ -279,9 +280,10 @@ class TestTeleportServiceExecute:
|
|||
assert isinstance(events[0], TeleportCheckingGitEvent)
|
||||
assert isinstance(events[1], TeleportStartingWorkflowEvent)
|
||||
assert isinstance(events[2], TeleportWaitingForGitHubEvent)
|
||||
assert isinstance(events[3], TeleportFetchingUrlEvent)
|
||||
assert isinstance(events[4], TeleportCompleteEvent)
|
||||
assert events[4].url == "https://chat.example.com/123"
|
||||
assert isinstance(events[3], TeleportAuthCompleteEvent)
|
||||
assert isinstance(events[4], TeleportFetchingUrlEvent)
|
||||
assert isinstance(events[5], TeleportCompleteEvent)
|
||||
assert events[5].url == "https://chat.example.com/123"
|
||||
workflow_params = mock_nuage.start_workflow.call_args.args[0]
|
||||
assert workflow_params.integrations.chat_assistant is not None
|
||||
assert workflow_params.integrations.chat_assistant.project_name is None
|
||||
|
|
@ -298,11 +300,12 @@ class TestTeleportServiceExecute:
|
|||
service._git.get_unpushed_commit_count = AsyncMock(return_value=3)
|
||||
service._git.push_current_branch = AsyncMock(return_value=True)
|
||||
|
||||
async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def]
|
||||
yield mock_github_connected
|
||||
|
||||
mock_nuage = MagicMock()
|
||||
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
|
||||
mock_nuage.get_github_integration = AsyncMock(
|
||||
return_value=mock_github_connected
|
||||
)
|
||||
mock_nuage.wait_for_github_connection = _connected_gen
|
||||
mock_nuage.get_chat_assistant_url = AsyncMock(
|
||||
return_value="https://chat.example.com/123"
|
||||
)
|
||||
|
|
@ -357,18 +360,22 @@ class TestTeleportServiceExecute:
|
|||
github_pending = MagicMock()
|
||||
github_pending.connected = False
|
||||
github_pending.oauth_url = "https://github.com/login/oauth"
|
||||
github_pending.error = None
|
||||
github_pending.error = "Please connect GitHub"
|
||||
github_pending.status = GitHubStatus.WAITING_FOR_OAUTH
|
||||
|
||||
github_connected = MagicMock()
|
||||
github_connected.connected = True
|
||||
github_connected.oauth_url = None
|
||||
github_connected.error = None
|
||||
github_connected.status = GitHubStatus.CONNECTED
|
||||
|
||||
async def _oauth_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def]
|
||||
yield github_pending
|
||||
yield github_connected
|
||||
|
||||
mock_nuage = MagicMock()
|
||||
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
|
||||
mock_nuage.get_github_integration = AsyncMock(return_value=github_pending)
|
||||
mock_nuage.wait_for_github_connection = AsyncMock(return_value=github_connected)
|
||||
mock_nuage.wait_for_github_connection = _oauth_gen
|
||||
mock_nuage.get_chat_assistant_url = AsyncMock(
|
||||
return_value="https://chat.example.com/123"
|
||||
)
|
||||
|
|
@ -383,9 +390,12 @@ class TestTeleportServiceExecute:
|
|||
assert isinstance(events[0], TeleportCheckingGitEvent)
|
||||
assert isinstance(events[1], TeleportStartingWorkflowEvent)
|
||||
assert isinstance(events[2], TeleportWaitingForGitHubEvent)
|
||||
assert events[2].message is None
|
||||
assert isinstance(events[3], TeleportAuthRequiredEvent)
|
||||
assert events[3].oauth_url == "https://github.com/login/oauth"
|
||||
assert isinstance(events[4], TeleportAuthCompleteEvent)
|
||||
assert isinstance(events[4], TeleportWaitingForGitHubEvent)
|
||||
assert events[4].message == "Please connect GitHub"
|
||||
assert isinstance(events[5], TeleportAuthCompleteEvent)
|
||||
assert isinstance(events[-1], TeleportCompleteEvent)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -398,11 +408,12 @@ class TestTeleportServiceExecute:
|
|||
service._git.get_info = AsyncMock(return_value=git_info)
|
||||
service._git.is_commit_pushed = AsyncMock(return_value=True)
|
||||
|
||||
async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def]
|
||||
yield mock_github_connected
|
||||
|
||||
mock_nuage = MagicMock()
|
||||
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
|
||||
mock_nuage.get_github_integration = AsyncMock(
|
||||
return_value=mock_github_connected
|
||||
)
|
||||
mock_nuage.wait_for_github_connection = _connected_gen
|
||||
mock_nuage.get_chat_assistant_url = AsyncMock(return_value=None)
|
||||
service._nuage_client_instance = mock_nuage
|
||||
|
||||
|
|
@ -423,11 +434,12 @@ class TestTeleportServiceExecute:
|
|||
service._git.get_info = AsyncMock(return_value=git_info)
|
||||
service._git.is_commit_pushed = AsyncMock(return_value=True)
|
||||
|
||||
async def _connected_gen(*_a: object, **_kw: object): # type: ignore[no-untyped-def]
|
||||
yield mock_github_connected
|
||||
|
||||
mock_nuage = MagicMock()
|
||||
mock_nuage.start_workflow = AsyncMock(return_value="exec-123")
|
||||
mock_nuage.get_github_integration = AsyncMock(
|
||||
return_value=mock_github_connected
|
||||
)
|
||||
mock_nuage.wait_for_github_connection = _connected_gen
|
||||
mock_nuage.get_chat_assistant_url = AsyncMock(
|
||||
return_value="https://chat.example.com/123"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue