vibe/tests/core/test_config_patch_ops.py
Clément Drouin 6bedf271ce
v2.17.0 (#822)
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Hdandria <henri.dandria@mistral.ai>
Co-authored-by: Ivana Dunisijevic <ivana.dunisijevic@mistral.ai>
Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Mert Unsal <mert.unsal@mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@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: renovate-mistral[bot] <253709520+renovate-mistral[bot]@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-06-19 11:01:24 +02:00

221 lines
6.9 KiB
Python

from __future__ import annotations
from typing import Any, get_args
from pydantic import ValidationError
import pytest
from vibe.core.config import (
AddOperationPatch,
RemoveOperationPatch,
ReplaceOperationPatch,
)
from vibe.core.config.patch import ConfigPatch, PatchOp
@pytest.mark.parametrize(
("operation", "expected"),
[
(
AddOperationPatch(path="/tools/disabled_tools/0", value="bash"),
{"op": "add", "path": "/tools/disabled_tools/0", "value": "bash"},
),
(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
{"op": "replace", "path": "/active_model", "value": "devstral-small"},
),
(
RemoveOperationPatch(path="/tools/deprecated_setting"),
{"op": "remove", "path": "/tools/deprecated_setting"},
),
],
)
def test_json_patch_operations_convert_to_json_patch_payload(
operation: AddOperationPatch | ReplaceOperationPatch | RemoveOperationPatch,
expected: dict[str, Any],
) -> None:
assert operation.to_json_patch() == expected
def test_patch_op_union_contains_all_operations() -> None:
assert set(get_args(PatchOp.__value__)) == {
AddOperationPatch,
ReplaceOperationPatch,
RemoveOperationPatch,
}
@pytest.mark.parametrize(
"factory",
[
lambda path: AddOperationPatch(path=path, value="value"),
lambda path: ReplaceOperationPatch(path=path, value="value"),
lambda path: RemoveOperationPatch(path=path),
],
)
def test_json_patch_operations_reject_non_pointer_paths(factory: Any) -> None:
with pytest.raises(ValidationError, match="valid JSON Pointer"):
factory("tools.disabled_tools")
@pytest.mark.parametrize(
"factory",
[
lambda path: AddOperationPatch(path=path, value="value"),
lambda path: ReplaceOperationPatch(path=path, value="value"),
lambda path: RemoveOperationPatch(path=path),
],
)
def test_json_patch_operations_reject_invalid_escapes(factory: Any) -> None:
with pytest.raises(ValidationError, match="valid JSON Pointer"):
factory("/tools/~2")
def test_json_patch_operations_accept_slash_prefixed_paths() -> None:
op = ReplaceOperationPatch(path="/", value={"active_model": "devstral-small"})
assert op.path == "/"
def test_config_patch_stores_operations_and_metadata() -> None:
op = ReplaceOperationPatch(path="/active_model", value="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 = [
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
AddOperationPatch(path="/tools/disabled_tools/-", value="bash"),
]
patch = ConfigPatch(*ops, fingerprint="fp-1")
assert patch.operations == ops
def test_config_patch_add_appends_operations() -> None:
patch = ConfigPatch(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
fingerprint="fp-1",
)
patch.add(RemoveOperationPatch(path="/tools/deprecated_setting"))
assert patch.operations == [
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
RemoveOperationPatch(path="/tools/deprecated_setting"),
]
def test_config_patch_add_returns_self() -> None:
patch = ConfigPatch(fingerprint="fp-1")
result = patch.add(
ReplaceOperationPatch(path="/active_model", value="devstral-small")
)
assert result is patch
def test_config_patch_add_accepts_multiple_operations() -> None:
patch = ConfigPatch(fingerprint="fp-1")
patch.add(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
AddOperationPatch(path="/tools/disabled_tools/-", value="bash"),
)
assert len(patch.operations) == 2
def test_config_patch_add_is_chainable() -> None:
patch = (
ConfigPatch(fingerprint="fp-1")
.add(ReplaceOperationPatch(path="/active_model", value="devstral-small"))
.add(RemoveOperationPatch(path="/tools/deprecated_setting"))
)
assert len(patch.operations) == 2
def test_config_patch_to_json_patch_from_wrappers() -> None:
patch = ConfigPatch(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
AddOperationPatch(path="/tools/disabled_tools/-", value="bash"),
RemoveOperationPatch(path="/tools/deprecated_setting"),
fingerprint="fp-1",
)
assert patch.to_json_patch() == [
{"op": "replace", "path": "/active_model", "value": "devstral-small"},
{"op": "add", "path": "/tools/disabled_tools/-", "value": "bash"},
{"op": "remove", "path": "/tools/deprecated_setting"},
]
def test_config_patch_describe_add_operation() -> None:
patch = ConfigPatch(
AddOperationPatch(path="/tools/disabled_tools/-", value="bash"),
fingerprint="fp-1",
)
assert patch.describe() == ["add '/tools/disabled_tools/-' = 'bash'"]
def test_config_patch_describe_replace_operation() -> None:
patch = ConfigPatch(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
fingerprint="fp-1",
)
assert patch.describe() == ["replace '/active_model' = 'devstral-small'"]
def test_config_patch_describe_remove_operation() -> None:
patch = ConfigPatch(
RemoveOperationPatch(path="/tools/deprecated_setting"), fingerprint="fp-1"
)
assert patch.describe() == ["remove '/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(
ReplaceOperationPatch(path="/active_model", value="devstral-small"),
AddOperationPatch(path="/tools/disabled_tools/-", value="bash"),
RemoveOperationPatch(path="/tools/deprecated_setting"),
fingerprint="fp-1",
)
assert patch.describe() == [
"replace '/active_model' = 'devstral-small'",
"add '/tools/disabled_tools/-' = 'bash'",
"remove '/tools/deprecated_setting'",
]
def test_scenario_build_patch_incrementally() -> None:
patch = ConfigPatch(fingerprint="fp-abc", reason="/model command")
patch.add(ReplaceOperationPatch(path="/active_model", value="devstral-small"))
patch.add(AddOperationPatch(path="/tools/disabled_tools/-", value="bash"))
assert patch.fingerprint == "fp-abc"
assert patch.reason == "/model command"
assert len(patch.operations) == 2
assert patch.describe() == [
"replace '/active_model' = 'devstral-small'",
"add '/tools/disabled_tools/-' = 'bash'",
]