vibe/tests/core/test_project_config_layer.py
Mathias Gesbert 3f8487f761
v2.14.0 (#743)
Co-authored-by: Alexis Tacnet <alexis@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: p.vezia <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Hiba Chaabnia <Hiba-Chaabnia@users.noreply.github.com>
Co-authored-by: Nikhil Bhima <nikhilbhima@users.noreply.github.com>
Co-authored-by: Nkipohcs <Nkipohcs@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-06-04 18:26:35 +02:00

282 lines
9.7 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from vibe.core.config.layer import UntrustedLayerError
from vibe.core.config.layers.project import ProjectConfigLayer
from vibe.core.config.patch import ConfigPatch
from vibe.core.paths._vibe_home import GlobalPath
from vibe.core.trusted_folders import trusted_folders_manager
@pytest.mark.asyncio
async def test_reads_toml_when_trusted(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "project-model"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load()
assert data.model_extra == {"active_model": "project-model"}
@pytest.mark.asyncio
async def test_untrusted_raises(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_untrusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "project-model"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(UntrustedLayerError):
await layer.load()
@pytest.mark.asyncio
async def test_unresolved_trust_defaults_to_untrusted(
tmp_working_directory: Path,
) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(UntrustedLayerError):
await layer.load()
@pytest.mark.asyncio
async def test_missing_file_returns_empty(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load()
assert data.model_extra == {}
@pytest.mark.asyncio
async def test_default_name(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
assert layer.name == "project-toml"
@pytest.mark.asyncio
async def test_apply_raises_not_implemented(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
with pytest.raises(NotImplementedError, match="M2"):
await layer.apply(ConfigPatch(fingerprint="fp-1"))
@pytest.mark.asyncio
async def test_trust_uses_path_parent_for_resolution(
tmp_working_directory: Path,
) -> None:
project_dir = tmp_working_directory / "my-project"
project_dir.mkdir()
config_path = project_dir / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('value = "trusted"\n')
trusted_folders_manager.add_trusted(project_dir / ".vibe")
layer = ProjectConfigLayer(path=project_dir)
data = await layer.load()
assert data.model_extra == {"value": "trusted"}
@pytest.mark.asyncio
async def test_grant_trust_marks_folder_trusted(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_untrusted(tmp_working_directory / ".vibe")
await layer.resolve_trust()
await layer.grant_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory / ".vibe") is True
@pytest.mark.asyncio
async def test_revoke_trust_marks_folder_untrusted(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_trusted(tmp_working_directory / ".vibe")
await layer.resolve_trust()
await layer.revoke_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory / ".vibe") is False
@pytest.mark.asyncio
async def test_grant_trust_without_config_file_is_noop(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_untrusted(tmp_working_directory)
await layer.grant_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory) is False
@pytest.mark.asyncio
async def test_revoke_trust_without_config_file_is_noop(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
trusted_folders_manager.add_trusted(tmp_working_directory)
await layer.revoke_trust()
assert trusted_folders_manager.is_trusted(tmp_working_directory) is True
@pytest.mark.asyncio
async def test_trust_stored_at_vibe_subdir(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.resolve_trust()
await layer.grant_trust()
assert (
str((tmp_working_directory / ".vibe").resolve())
in trusted_folders_manager._trusted
)
assert str(tmp_working_directory.resolve()) not in trusted_folders_manager._trusted
@pytest.mark.asyncio
async def test_find_file_prefers_closest_config(tmp_working_directory: Path) -> None:
parent_config = tmp_working_directory / ".vibe" / "config.toml"
parent_config.parent.mkdir(parents=True, exist_ok=True)
parent_config.write_text('level = "parent"\n')
child = tmp_working_directory / "child"
child.mkdir()
child_config = child / ".vibe" / "config.toml"
child_config.parent.mkdir(parents=True, exist_ok=True)
child_config.write_text('level = "child"\n')
trusted_folders_manager.add_trusted(child)
layer = ProjectConfigLayer(path=child)
data = await layer.load()
assert data.model_extra == {"level": "child"}
@pytest.mark.asyncio
async def test_find_file_result_is_cached(tmp_working_directory: Path) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text("x = 1\n")
trusted_folders_manager.add_trusted(tmp_working_directory)
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.load()
assert layer._is_set is True
cached_path = layer._config_file_path
await layer.resolve_trust()
assert layer._config_file_path is cached_path
@pytest.mark.asyncio
async def test_config_file_added_after_first_search_is_not_detected(
tmp_working_directory: Path,
) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer._find_config_file()
assert layer._is_set is True
assert layer._config_file_path is None
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "new-model"\n')
await layer._find_config_file()
assert layer._config_file_path is None
@pytest.mark.asyncio
async def test_finds_config_in_parent_directory(tmp_working_directory: Path) -> None:
trusted_folders_manager.add_trusted(tmp_working_directory)
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('active_model = "parent-model"\n')
subdir = tmp_working_directory / "sub" / "project"
subdir.mkdir(parents=True)
layer = ProjectConfigLayer(path=subdir)
data = await layer.load()
assert data.model_extra == {"active_model": "parent-model"}
@pytest.mark.asyncio
async def test_trusted_ancestor_satisfies_trust_check(
tmp_working_directory: Path,
) -> None:
parent = tmp_working_directory / "workspace"
parent.mkdir()
child = parent / "project"
child.mkdir()
trusted_folders_manager.add_trusted(parent)
config_path = child / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True)
config_path.write_text('active_model = "child-model"\n')
layer = ProjectConfigLayer(path=child)
data = await layer.load()
assert data.model_extra == {"active_model": "child-model"}
@pytest.mark.asyncio
async def test_grant_trust_then_load_reads_config_without_prior_resolve(
tmp_working_directory: Path,
) -> None:
config_path = tmp_working_directory / ".vibe" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text('key = "value"\n')
layer = ProjectConfigLayer(path=tmp_working_directory)
await layer.resolve_trust()
await layer.grant_trust()
data = await layer.load()
assert data.model_extra == {"key": "value"}
@pytest.mark.asyncio
async def test_walk_stops_at_vibe_home_parent(
tmp_path_factory: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
) -> None:
# Create an isolated tmp root and point VIBE_HOME into it
tmp_root = tmp_path_factory.mktemp("walk_stop")
fake_vibe_home = tmp_root / ".vibe"
fake_vibe_home.mkdir()
monkeypatch.setattr(
"vibe.core.config.layers.project.VIBE_HOME", GlobalPath(lambda: fake_vibe_home)
)
# Place a config at tmp_root/.vibe/config.toml — should NOT be picked up
home_config = fake_vibe_home / "config.toml"
home_config.write_text('active_model = "home-model"\n')
# subdir lives inside tmp_root so the walk would reach the config without the stop guard
subdir = tmp_root / "vibe-test-project"
subdir.mkdir()
trusted_folders_manager.add_trusted(subdir)
layer = ProjectConfigLayer(path=subdir)
data = await layer.load()
assert data.model_extra == {}