Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion raven/config/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,15 @@ def _apply(_text: str | None) -> tuple[str | None, tuple[list[str], bool]]:
sf_key = "skillForge"
elif isinstance(data.get("skill_forge"), dict):
sf_key = "skill_forge"
elif "skill_forge" in data and "skillForge" not in data:
sf_key = "skill_forge"
else:
sf_key = "skillForge"
section = data.setdefault(sf_key, {})
section = data.get(sf_key)
if section is None:
section = data[sf_key] = {}
elif not isinstance(section, dict):
raise ValueError(f"{sf_key} must be an object or null")
current = [str(x) for x in (section.get("blocklist") or [])]
lowered = {x.casefold() for x in current}
if blocked:
Expand Down
26 changes: 26 additions & 0 deletions tests/test_cli_skill_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,35 @@ def test_skill_unblock_removes_from_config(tmp_config: Path) -> None:
runner.invoke(app, ["skill", "block", "other"])
r = runner.invoke(app, ["skill", "unblock", "Tag-Memory"])
assert r.exit_code == 0
assert "skillForge" not in r.stdout
assert _read_blocklist(tmp_config) == ["other"]


@pytest.mark.parametrize("spelling", ["skillForge", "skill_forge"])
def test_skill_block_and_unblock_accept_a_null_section(tmp_config: Path, spelling: str) -> None:
import json

from raven.config.loader import load_config
from raven.config.raven import load_raven_config

tmp_config.write_text(json.dumps({spelling: None}), encoding="utf-8")
load_config(tmp_config)
empty = tmp_config.read_bytes()
assert runner.invoke(app, ["skill", "unblock", "absent"]).exit_code == 0
assert tmp_config.read_bytes() == empty
result = runner.invoke(app, ["skill", "block", "tag-memory"])
assert result.exit_code == 0, result.output
assert json.loads(tmp_config.read_text(encoding="utf-8")) == {spelling: {"blocklist": ["tag-memory"]}}
assert load_raven_config(config_path=tmp_config).skill_forge.blocklist == ["tag-memory"]
before = tmp_config.read_bytes()
assert runner.invoke(app, ["skill", "block", "TAG-MEMORY"]).exit_code == 0
assert tmp_config.read_bytes() == before
result = runner.invoke(app, ["skill", "unblock", "TAG-MEMORY"])
assert result.exit_code == 0
assert "Unblocked" in result.stdout and "skillForge" not in result.stdout
assert json.loads(tmp_config.read_text(encoding="utf-8")) == {spelling: {"blocklist": []}}


def test_skill_block_reports_the_block_it_wrote_on_a_snake_config(tmp_config: Path) -> None:
"""The result line must not name a key this write did not use.

Expand Down
10 changes: 10 additions & 0 deletions tests/test_config_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,16 @@ def test_skill_block_writes_camel_into_empty_config(cfg_path: Path) -> None:
assert _read(cfg_path)["skillForge"] == {"blocklist": ["alpha"]}


@pytest.mark.parametrize("spelling", ["skillForge", "skill_forge"])
@pytest.mark.parametrize("invalid", [[], 1, "invalid"])
def test_skill_block_preserves_an_invalid_section(cfg_path, spelling, invalid):
cfg_path.write_text(json.dumps({spelling: invalid}), encoding="utf-8")
before = cfg_path.read_bytes()
with pytest.raises(ValueError, match="object or null"):
set_skill_blocked("alpha", True, config_path=cfg_path)
assert cfg_path.read_bytes() == before


def test_skill_block_respects_existing_snake_casing(cfg_path: Path) -> None:
cfg_path.write_text(
json.dumps({"skill_forge": {"auto_install": "off"}}),
Expand Down
Loading