Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Gauthier Guinet <43207538+Gguinet@users.noreply.github.com>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Quentin <torroba.q@gmail.com>
Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com>
Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: angelapopopo <angele.lenglemetz@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-03-23 18:45:21 +01:00 committed by GitHub
parent 5103019b01
commit eb580209d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
180 changed files with 11136 additions and 1030 deletions

View file

@ -0,0 +1,127 @@
from __future__ import annotations
import pytest
from vibe.core.config import OtelExporterConfig, ProviderConfig, VibeConfig
from vibe.core.types import Backend
class TestOtelExporterConfig:
def test_derives_endpoint_from_mistral_provider(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("MISTRAL_API_KEY", "sk-test")
config = vibe_config.model_copy(
update={
"providers": [
ProviderConfig(
name="mistral",
api_base="https://customer.mistral.ai/v1",
backend=Backend.MISTRAL,
)
]
}
)
result = config.otel_exporter_config
assert result is not None
assert result.endpoint == "https://customer.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-test"}
def test_uses_first_mistral_provider(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("EU_KEY", "sk-eu")
config = vibe_config.model_copy(
update={
"providers": [
ProviderConfig(
name="mistral-eu",
api_base="https://eu.mistral.ai/v1",
api_key_env_var="EU_KEY",
backend=Backend.MISTRAL,
),
ProviderConfig(
name="mistral-us",
api_base="https://us.mistral.ai/v1",
api_key_env_var="US_KEY",
backend=Backend.MISTRAL,
),
]
}
)
result = config.otel_exporter_config
assert result is not None
assert result.endpoint == "https://eu.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-eu"}
def test_falls_back_to_default_when_no_mistral_provider(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("MISTRAL_API_KEY", "sk-fallback")
config = vibe_config.model_copy(
update={
"providers": [
ProviderConfig(
name="anthropic", api_base="https://api.anthropic.com/v1"
)
]
}
)
result = config.otel_exporter_config
assert result is not None
assert result.endpoint == "https://api.mistral.ai/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-fallback"}
def test_default_providers(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("MISTRAL_API_KEY", "sk-default")
result = vibe_config.otel_exporter_config
assert result is not None
assert result.endpoint == "https://api.mistral.ai/telemetry/v1/traces"
def test_returns_none_and_warns_when_api_key_missing(
self,
vibe_config: VibeConfig,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
with caplog.at_level("WARNING"):
assert vibe_config.otel_exporter_config is None
assert "OTEL tracing enabled but MISTRAL_API_KEY is not set" in caplog.text
def test_custom_api_key_env_var(
self, vibe_config: VibeConfig, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
monkeypatch.setenv("MY_CUSTOM_KEY", "sk-custom")
config = vibe_config.model_copy(
update={
"providers": [
ProviderConfig(
name="mistral-onprem",
api_base="https://onprem.corp.com/v1",
api_key_env_var="MY_CUSTOM_KEY",
backend=Backend.MISTRAL,
)
]
}
)
result = config.otel_exporter_config
assert result is not None
assert result.endpoint == "https://onprem.corp.com/telemetry/v1/traces"
assert result.headers == {"Authorization": "Bearer sk-custom"}
def test_explicit_otel_endpoint_bypasses_provider_derivation(
self, vibe_config: VibeConfig
) -> None:
config = vibe_config.model_copy(
update={"otel_endpoint": "https://my-collector:4318/v1/traces"}
)
result = config.otel_exporter_config
assert result is not None
assert result == OtelExporterConfig(
endpoint="https://my-collector:4318/v1/traces"
)
assert result.headers is None

View file

@ -0,0 +1,135 @@
from __future__ import annotations
from pathlib import Path
from vibe.core.paths._local_config_walk import (
_MAX_DIRS,
WALK_MAX_DEPTH,
has_config_dirs_nearby,
walk_local_config_dirs_all,
)
class TestBoundedWalk:
def test_finds_config_at_root(self, tmp_path: Path) -> None:
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
tools, skills, agents = walk_local_config_dirs_all(tmp_path)
assert tmp_path / ".vibe" / "tools" in tools
def test_finds_config_within_depth_limit(self, tmp_path: Path) -> None:
nested = tmp_path
for i in range(WALK_MAX_DEPTH):
nested = nested / f"level{i}"
(nested / ".vibe" / "skills").mkdir(parents=True)
_, skills, _ = walk_local_config_dirs_all(tmp_path)
assert nested / ".vibe" / "skills" in skills
def test_does_not_find_config_beyond_depth_limit(self, tmp_path: Path) -> None:
nested = tmp_path
for i in range(WALK_MAX_DEPTH + 1):
nested = nested / f"level{i}"
(nested / ".vibe" / "tools").mkdir(parents=True)
tools, skills, agents = walk_local_config_dirs_all(tmp_path)
assert not tools
assert not skills
assert not agents
def test_respects_dir_count_limit(self, tmp_path: Path) -> None:
# Create more directories than _MAX_DIRS at depth 1
for i in range(_MAX_DIRS + 10):
(tmp_path / f"dir{i:05d}").mkdir()
# Place config in a directory that would be scanned late
(tmp_path / "zzz_last" / ".vibe" / "tools").mkdir(parents=True)
tools, _, _ = walk_local_config_dirs_all(tmp_path)
# The walk should stop before visiting all dirs.
# Whether zzz_last is found depends on sort order and limit,
# but total visited dirs should be bounded.
# We just verify no crash and the function returns.
assert isinstance(tools, tuple)
def test_skips_ignored_directories(self, tmp_path: Path) -> None:
(tmp_path / "node_modules" / ".vibe" / "tools").mkdir(parents=True)
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
tools, _, _ = walk_local_config_dirs_all(tmp_path)
assert tools == (tmp_path / ".vibe" / "tools",)
def test_skips_dot_directories(self, tmp_path: Path) -> None:
(tmp_path / ".hidden" / ".vibe" / "tools").mkdir(parents=True)
tools, _, _ = walk_local_config_dirs_all(tmp_path)
assert not tools
def test_preserves_alphabetical_ordering(self, tmp_path: Path) -> None:
(tmp_path / "bbb" / ".vibe" / "tools").mkdir(parents=True)
(tmp_path / "aaa" / ".vibe" / "tools").mkdir(parents=True)
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
tools, _, _ = walk_local_config_dirs_all(tmp_path)
assert tools == (
tmp_path / ".vibe" / "tools",
tmp_path / "aaa" / ".vibe" / "tools",
tmp_path / "bbb" / ".vibe" / "tools",
)
def test_finds_agents_skills(self, tmp_path: Path) -> None:
(tmp_path / ".agents" / "skills").mkdir(parents=True)
_, skills, _ = walk_local_config_dirs_all(tmp_path)
assert tmp_path / ".agents" / "skills" in skills
def test_finds_all_config_types(self, tmp_path: Path) -> None:
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
(tmp_path / ".vibe" / "skills").mkdir(parents=True)
(tmp_path / ".vibe" / "agents").mkdir(parents=True)
(tmp_path / ".agents" / "skills").mkdir(parents=True)
tools, skills, agents = walk_local_config_dirs_all(tmp_path)
assert tmp_path / ".vibe" / "tools" in tools
assert tmp_path / ".vibe" / "skills" in skills
assert tmp_path / ".vibe" / "agents" in agents
assert tmp_path / ".agents" / "skills" in skills
class TestHasConfigDirsNearby:
def test_returns_true_when_vibe_tools_exist(self, tmp_path: Path) -> None:
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is True
def test_returns_true_when_vibe_skills_exist(self, tmp_path: Path) -> None:
(tmp_path / ".vibe" / "skills").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is True
def test_returns_true_when_agents_skills_exist(self, tmp_path: Path) -> None:
(tmp_path / ".agents" / "skills").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is True
def test_returns_false_when_empty(self, tmp_path: Path) -> None:
assert has_config_dirs_nearby(tmp_path) is False
def test_returns_false_for_vibe_dir_without_subdirs(self, tmp_path: Path) -> None:
(tmp_path / ".vibe").mkdir()
assert has_config_dirs_nearby(tmp_path) is False
def test_returns_true_for_shallow_nested(self, tmp_path: Path) -> None:
(tmp_path / "sub" / ".vibe" / "skills").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is True
def test_returns_true_at_depth_2(self, tmp_path: Path) -> None:
(tmp_path / "a" / "b" / ".agents" / "skills").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is True
def test_returns_false_beyond_default_depth(self, tmp_path: Path) -> None:
(tmp_path / "a" / "b" / "c" / "d" / "e" / ".vibe" / "tools").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is False
def test_custom_depth(self, tmp_path: Path) -> None:
(tmp_path / "a" / "b" / "c" / "d" / "e" / ".vibe" / "tools").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path, max_depth=5) is True
def test_early_exit_on_first_match(self, tmp_path: Path) -> None:
# Create many dirs but put config early; function should return quickly
(tmp_path / ".vibe" / "tools").mkdir(parents=True)
for i in range(100):
(tmp_path / f"dir{i}").mkdir()
assert has_config_dirs_nearby(tmp_path) is True
def test_skips_ignored_directories(self, tmp_path: Path) -> None:
(tmp_path / "node_modules" / ".vibe" / "skills").mkdir(parents=True)
assert has_config_dirs_nearby(tmp_path) is False

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from vibe.core.slug import _ADJECTIVES, _NOUNS, create_slug
from vibe.core.utils.slug import _ADJECTIVES, _NOUNS, create_slug
class TestCreateSlug:

View file

@ -9,10 +9,10 @@ import pytest
from tests.conftest import build_test_vibe_config
from tests.stubs.fake_tool import FakeTool, FakeToolArgs
from vibe.core.agent_loop import ToolDecision, ToolExecutionResponse
from vibe.core.config import Backend
from vibe.core.llm.format import ResolvedToolCall
from vibe.core.telemetry.send import DATALAKE_EVENTS_URL, TelemetryClient
from vibe.core.tools.base import BaseTool, ToolPermission
from vibe.core.types import Backend
from vibe.core.utils import get_user_agent
_original_send_telemetry_event = TelemetryClient.send_telemetry_event
@ -258,6 +258,8 @@ class TestTelemetryClient:
nb_mcp_servers=1,
nb_models=3,
entrypoint="cli",
client_name="vscode",
client_version="1.96.0",
terminal_emulator="vscode",
)
@ -270,6 +272,8 @@ class TestTelemetryClient:
assert properties["nb_mcp_servers"] == 1
assert properties["nb_models"] == 3
assert properties["entrypoint"] == "cli"
assert properties["client_name"] == "vscode"
assert properties["client_version"] == "1.96.0"
assert properties["terminal_emulator"] == "vscode"
assert "version" in properties
@ -372,3 +376,41 @@ class TestTelemetryClient:
assert (
calls[1].kwargs["json"]["properties"]["session_id"] == "second-session-id"
)
def test_send_user_rating_feedback_payload(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
client.send_user_rating_feedback(rating=2, model="mistral-large")
assert len(telemetry_events) == 1
assert telemetry_events[0]["event_name"] == "vibe.user_rating_feedback"
properties = telemetry_events[0]["properties"]
assert properties["rating"] == 2
assert properties["model"] == "mistral-large"
assert "version" in properties
def test_send_user_rating_feedback_includes_correlation_id(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
client.last_correlation_id = "corr-abc-123"
client.send_user_rating_feedback(rating=1, model="mistral-large")
assert len(telemetry_events) == 1
assert telemetry_events[0]["correlation_id"] == "corr-abc-123"
def test_send_user_rating_feedback_omits_correlation_id_when_none(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
client.send_user_rating_feedback(rating=1, model="mistral-large")
assert len(telemetry_events) == 1
assert "correlation_id" not in telemetry_events[0]

View file

@ -0,0 +1,107 @@
from __future__ import annotations
import pytest
from tests.conftest import build_test_vibe_config
from vibe.core.config import (
DEFAULT_TTS_MODELS,
DEFAULT_TTS_PROVIDERS,
TTSClient,
TTSModelConfig,
TTSProviderConfig,
)
class TestTTSConfigDefaults:
def test_default_tts_providers_loaded(self) -> None:
config = build_test_vibe_config()
assert len(config.tts_providers) == len(DEFAULT_TTS_PROVIDERS)
assert config.tts_providers[0].name == "mistral"
assert config.tts_providers[0].api_base == "https://api.mistral.ai"
def test_default_tts_models_loaded(self) -> None:
config = build_test_vibe_config()
assert len(config.tts_models) == len(DEFAULT_TTS_MODELS)
assert config.tts_models[0].alias == "voxtral-tts"
assert config.tts_models[0].name == "voxtral-mini-tts-latest"
def test_default_active_tts_model(self) -> None:
config = build_test_vibe_config()
assert config.active_tts_model == "voxtral-tts"
class TestGetActiveTTSModel:
def test_resolves_by_alias(self) -> None:
config = build_test_vibe_config()
model = config.get_active_tts_model()
assert model.alias == "voxtral-tts"
assert model.name == "voxtral-mini-tts-latest"
def test_raises_for_unknown_alias(self) -> None:
config = build_test_vibe_config(active_tts_model="nonexistent")
with pytest.raises(ValueError, match="not found in configuration"):
config.get_active_tts_model()
class TestGetTTSProviderForModel:
def test_resolves_by_name(self) -> None:
config = build_test_vibe_config()
model = config.get_active_tts_model()
provider = config.get_tts_provider_for_model(model)
assert provider.name == "mistral"
assert provider.api_base == "https://api.mistral.ai"
def test_raises_for_unknown_provider(self) -> None:
config = build_test_vibe_config(
tts_models=[
TTSModelConfig(name="test-model", provider="nonexistent", alias="test")
],
active_tts_model="test",
)
model = config.get_active_tts_model()
with pytest.raises(ValueError, match="not found in configuration"):
config.get_tts_provider_for_model(model)
class TestTTSModelUniqueness:
def test_duplicate_aliases_raise(self) -> None:
with pytest.raises(ValueError, match="Duplicate TTS model alias"):
build_test_vibe_config(
tts_models=[
TTSModelConfig(
name="model-a", provider="mistral", alias="same-alias"
),
TTSModelConfig(
name="model-b", provider="mistral", alias="same-alias"
),
],
active_tts_model="same-alias",
)
class TestTTSModelConfig:
def test_alias_defaults_to_name(self) -> None:
model = TTSModelConfig.model_validate({
"name": "my-model",
"provider": "mistral",
})
assert model.alias == "my-model"
def test_explicit_alias(self) -> None:
model = TTSModelConfig(
name="my-model", provider="mistral", alias="custom-alias"
)
assert model.alias == "custom-alias"
def test_default_values(self) -> None:
model = TTSModelConfig(name="my-model", provider="mistral", alias="my-model")
assert model.voice == "gb_jane_neutral"
assert model.response_format == "wav"
class TestTTSProviderConfig:
def test_default_values(self) -> None:
provider = TTSProviderConfig(name="test")
assert provider.api_base == "https://api.mistral.ai"
assert provider.api_key_env_var == ""
assert provider.client == TTSClient.MISTRAL

View file

@ -1,8 +1,11 @@
from __future__ import annotations
from pathlib import Path
import pytest
from vibe.core.utils import get_server_url_from_api_base
from vibe.core.utils.io import read_safe
@pytest.mark.parametrize(
@ -17,3 +20,45 @@ from vibe.core.utils import get_server_url_from_api_base
)
def test_get_server_url_from_api_base(api_base, expected):
assert get_server_url_from_api_base(api_base) == expected
class TestReadSafe:
def test_reads_utf8(self, tmp_path: Path) -> None:
f = tmp_path / "hello.txt"
f.write_text("café\n", encoding="utf-8")
assert read_safe(f) == "café\n"
def test_falls_back_on_non_utf8(self, tmp_path: Path) -> None:
f = tmp_path / "latin.txt"
# \x81 invalid UTF-8 and undefined in CP1252 → U+FFFD on all platforms
f.write_bytes(b"maf\x81\n")
result = read_safe(f)
assert result == "maf<EFBFBD>\n"
def test_raise_on_error_true_utf8_succeeds(self, tmp_path: Path) -> None:
f = tmp_path / "hello.txt"
f.write_text("café\n", encoding="utf-8")
assert read_safe(f, raise_on_error=True) == "café\n"
def test_raise_on_error_true_non_utf8_raises(self, tmp_path: Path) -> None:
f = tmp_path / "bad.txt"
# Invalid UTF-8; with raise_on_error=True we use default encoding (strict), so decode errors propagate
f.write_bytes(b"maf\x81\n")
assert read_safe(f, raise_on_error=False) == "maf<EFBFBD>\n"
with pytest.raises(UnicodeDecodeError):
read_safe(f, raise_on_error=True)
def test_empty_file(self, tmp_path: Path) -> None:
f = tmp_path / "empty.txt"
f.write_bytes(b"")
assert read_safe(f) == ""
def test_binary_garbage_does_not_raise(self, tmp_path: Path) -> None:
f = tmp_path / "garbage.bin"
f.write_bytes(bytes(range(256)))
result = read_safe(f)
assert isinstance(result, str)
def test_file_not_found_raises(self, tmp_path: Path) -> None:
with pytest.raises(FileNotFoundError):
read_safe(tmp_path / "nope.txt")