vibe/tests/core/test_config_patch_ops.py
Quentin adb1ca74ce
v2.11.0 (#717)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.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: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-05-25 16:05:26 +02:00

257 lines
7.5 KiB
Python

from __future__ import annotations
from collections.abc import Callable
from dataclasses import FrozenInstanceError
from typing import Any, get_args
import pytest
from vibe.core.config import (
AppendToList,
DeleteField,
PatchOp,
RemoveFromList,
SetField,
)
from vibe.core.config.patch import ConfigPatch
def test_patch_op_union_contains_all_operations() -> None:
assert get_args(PatchOp) == (SetField, AppendToList, RemoveFromList, DeleteField)
def test_set_field_accepts_top_level_key() -> None:
op = SetField("active_model", "devstral-small")
assert op.key == "active_model"
assert op.value == "devstral-small"
def test_set_field_accepts_nested_key() -> None:
op = SetField("models.providers", {"mistral": {"region": "eu"}})
assert op.key == "models.providers"
def test_append_to_list_accepts_nested_key() -> None:
op = AppendToList("tools.disabled_tools", ("bash", "python"))
assert op.key == "tools.disabled_tools"
assert op.items == ("bash", "python")
def test_remove_from_list_accepts_nested_key() -> None:
op = RemoveFromList("models.available_models", ("codestral-latest",))
assert op.key == "models.available_models"
assert op.values == ("codestral-latest",)
def test_delete_field_accepts_nested_key() -> None:
op = DeleteField("tools.deprecated_setting")
assert op.key == "tools.deprecated_setting"
@pytest.mark.parametrize(
"factory",
[
lambda key: SetField(key, "value"),
lambda key: AppendToList(key, ("value",)),
lambda key: RemoveFromList(key, ("value",)),
lambda key: DeleteField(key),
],
)
@pytest.mark.parametrize(
"invalid_key", ["", ".active_model", "active_model.", "tools..bash"]
)
def test_patch_operations_reject_invalid_key_paths(
factory: Callable[[str], object], invalid_key: str
) -> None:
with pytest.raises(ValueError, match="dot-separated path|must not be empty"):
factory(invalid_key)
@pytest.mark.parametrize(
"factory",
[
lambda key: SetField(key, "value"),
lambda key: AppendToList(key, ("value",)),
lambda key: RemoveFromList(key, ("value",)),
lambda key: DeleteField(key),
],
)
def test_patch_operations_reject_non_string_keys(
factory: Callable[[Any], object],
) -> None:
with pytest.raises(TypeError, match="Patch operation key must be a string"):
factory(1)
def test_append_to_list_rejects_non_tuple_items() -> None:
bad_items: Any = ["bash"]
with pytest.raises(TypeError, match="AppendToList.items must be a tuple"):
AppendToList("tools.disabled_tools", bad_items)
def test_remove_from_list_rejects_non_tuple_values() -> None:
bad_values: Any = ["bash"]
with pytest.raises(TypeError, match="RemoveFromList.values must be a tuple"):
RemoveFromList("tools.disabled_tools", bad_values)
def test_patch_operations_are_frozen() -> None:
op = SetField("active_model", "devstral-small")
with pytest.raises(FrozenInstanceError):
op.__setattr__("key", "models.active_model")
def test_scenario_mini_vibe_patch_operations() -> None:
operations: list[PatchOp] = [
SetField("active_model", "devstral-small"),
AppendToList("tools.disabled_tools", ("bash",)),
RemoveFromList("models.available_models", ("codestral-latest",)),
DeleteField("tools.deprecated_setting"),
]
assert operations == [
SetField("active_model", "devstral-small"),
AppendToList("tools.disabled_tools", ("bash",)),
RemoveFromList("models.available_models", ("codestral-latest",)),
DeleteField("tools.deprecated_setting"),
]
# --- ConfigPatch ---
def test_config_patch_stores_operations_and_metadata() -> None:
op = SetField("active_model", "devstral-small")
patch = ConfigPatch(op, fingerprint="fp-1", reason="test")
assert patch.operations == [op]
assert patch.fingerprint == "fp-1"
assert patch.reason == "test"
def test_config_patch_defaults() -> None:
patch = ConfigPatch(fingerprint="fp-1")
assert patch.reason == ""
assert patch.operations == []
def test_config_patch_accepts_multiple_operations() -> None:
ops: list[PatchOp] = [
SetField("active_model", "devstral-small"),
AppendToList("tools.disabled_tools", ("bash",)),
]
patch = ConfigPatch(*ops, fingerprint="fp-1")
assert patch.operations == ops
def test_config_patch_add_appends_operations() -> None:
patch = ConfigPatch(SetField("active_model", "devstral-small"), fingerprint="fp-1")
patch.add(DeleteField("tools.deprecated_setting"))
assert patch.operations == [
SetField("active_model", "devstral-small"),
DeleteField("tools.deprecated_setting"),
]
def test_config_patch_add_returns_self() -> None:
patch = ConfigPatch(fingerprint="fp-1")
result = patch.add(SetField("active_model", "devstral-small"))
assert result is patch
def test_config_patch_add_accepts_multiple_operations() -> None:
patch = ConfigPatch(fingerprint="fp-1")
patch.add(
SetField("active_model", "devstral-small"),
AppendToList("tools.disabled_tools", ("bash",)),
)
assert len(patch.operations) == 2
def test_config_patch_add_is_chainable() -> None:
patch = (
ConfigPatch(fingerprint="fp-1")
.add(SetField("active_model", "devstral-small"))
.add(DeleteField("tools.deprecated_setting"))
)
assert len(patch.operations) == 2
def test_config_patch_describe_set_field() -> None:
patch = ConfigPatch(SetField("active_model", "devstral-small"), fingerprint="fp-1")
assert patch.describe() == ["set 'active_model' = 'devstral-small'"]
def test_config_patch_describe_append_to_list() -> None:
patch = ConfigPatch(
AppendToList("tools.disabled_tools", ("bash", "python")), fingerprint="fp-1"
)
assert patch.describe() == ["append to 'tools.disabled_tools': ['bash', 'python']"]
def test_config_patch_describe_remove_from_list() -> None:
patch = ConfigPatch(
RemoveFromList("models.available_models", ("codestral-latest",)),
fingerprint="fp-1",
)
assert patch.describe() == [
"remove from 'models.available_models': ['codestral-latest']"
]
def test_config_patch_describe_delete_field() -> None:
patch = ConfigPatch(DeleteField("tools.deprecated_setting"), fingerprint="fp-1")
assert patch.describe() == ["delete 'tools.deprecated_setting'"]
def test_config_patch_describe_empty_returns_empty_list() -> None:
patch = ConfigPatch(fingerprint="fp-1")
assert patch.describe() == []
def test_config_patch_describe_multiple_operations() -> None:
patch = ConfigPatch(
SetField("active_model", "devstral-small"),
AppendToList("tools.disabled_tools", ("bash",)),
DeleteField("tools.deprecated_setting"),
fingerprint="fp-1",
)
assert patch.describe() == [
"set 'active_model' = 'devstral-small'",
"append to 'tools.disabled_tools': ['bash']",
"delete 'tools.deprecated_setting'",
]
def test_scenario_build_patch_incrementally() -> None:
patch = ConfigPatch(fingerprint="fp-abc", reason="/model command")
patch.add(SetField("active_model", "devstral-small"))
patch.add(AppendToList("tools.disabled_tools", ("bash",)))
assert patch.fingerprint == "fp-abc"
assert patch.reason == "/model command"
assert len(patch.operations) == 2
assert patch.describe() == [
"set 'active_model' = 'devstral-small'",
"append to 'tools.disabled_tools': ['bash']",
]