* fix(claude-code): fix plugin installation and release workflow - Fix plugin.json author field (string → object) to pass claude plugin validate - Add hindsight-integrations/.claude-plugin/marketplace.json so users can install via: claude plugin marketplace add vectorize-io/hindsight --sparse hindsight-integrations - Update README and install.sh with correct two-command install flow - Fix release-integration.yml: add explicit package.json check for typescript type and add plugin type for integrations with neither pyproject.toml nor package.json (prevents claude-code from incorrectly falling into the typescript build path) - Add CHANGELOG.md for the claude-code integration * remove install.sh — users install via claude plugin commands directly * test(claude-code): add 116 unit tests for plugin hooks and lib modules * feat(claude-code): user settings.json at CLAUDE_PLUGIN_DATA for stable config Plugin now checks CLAUDE_PLUGIN_DATA/settings.json after the versioned plugin default, giving users a path that persists across updates: ~/.claude/plugins/data/hindsight-memory-hindsight/settings.json Loading order: defaults → plugin settings.json → user settings.json → env vars * fix(claude-code): use ~/.hindsight/claude-code.json for user config Matches the ~/.openclaw/openclaw.json convention. Removes the confusing CLAUDE_PLUGIN_DATA path whose name depends on marketplace+plugin identifiers. * docs(claude-code): add ToS hint for claude-code LLM provider option * fix(claude-code): set author to Hindsight Team in plugin.json * ci: add test-claude-code-integration job to run plugin unit tests
121 lines
5 KiB
Python
121 lines
5 KiB
Python
"""Tests for lib/config.py — configuration loading and env overrides."""
|
|
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from lib.config import _cast_env, load_config
|
|
|
|
|
|
class TestCastEnv:
|
|
def test_bool_true_values(self):
|
|
for v in ("true", "True", "TRUE", "1", "yes", "YES"):
|
|
assert _cast_env(v, bool) is True
|
|
|
|
def test_bool_false_values(self):
|
|
for v in ("false", "False", "0", "no"):
|
|
assert _cast_env(v, bool) is False
|
|
|
|
def test_int_cast(self):
|
|
assert _cast_env("42", int) == 42
|
|
|
|
def test_int_invalid_returns_none(self):
|
|
assert _cast_env("notanint", int) is None
|
|
|
|
def test_str_passthrough(self):
|
|
assert _cast_env("hello", str) == "hello"
|
|
|
|
|
|
class TestLoadConfig:
|
|
def test_defaults_applied_when_no_settings_file(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
# No settings.json in tmp_path
|
|
cfg = load_config()
|
|
assert cfg["autoRecall"] is True
|
|
assert cfg["autoRetain"] is True
|
|
assert cfg["recallBudget"] == "mid"
|
|
assert cfg["retainEveryNTurns"] == 10
|
|
|
|
def test_settings_json_overrides_defaults(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
(tmp_path / "settings.json").write_text(json.dumps({"recallBudget": "high", "bankId": "my-bank"}))
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "high"
|
|
assert cfg["bankId"] == "my-bank"
|
|
|
|
def test_env_var_overrides_settings_json(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
(tmp_path / "settings.json").write_text(json.dumps({"recallBudget": "low"}))
|
|
monkeypatch.setenv("HINDSIGHT_RECALL_BUDGET", "high")
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "high"
|
|
|
|
def test_bool_env_var_override(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
monkeypatch.setenv("HINDSIGHT_AUTO_RECALL", "false")
|
|
cfg = load_config()
|
|
assert cfg["autoRecall"] is False
|
|
|
|
def test_int_env_var_override(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
monkeypatch.setenv("HINDSIGHT_API_PORT", "9999")
|
|
cfg = load_config()
|
|
assert cfg["apiPort"] == 9999
|
|
|
|
def test_invalid_settings_json_falls_back_to_defaults(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
(tmp_path / "settings.json").write_text("not valid json{{")
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "mid" # default still applies
|
|
|
|
def test_null_values_in_settings_json_not_applied(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
(tmp_path / "settings.json").write_text(json.dumps({"bankId": None, "recallBudget": "high"}))
|
|
cfg = load_config()
|
|
# None values in file should not override defaults
|
|
assert cfg["bankId"] is None # default is None, so ok
|
|
assert cfg["recallBudget"] == "high"
|
|
|
|
def test_api_url_env_override(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
monkeypatch.setenv("HINDSIGHT_API_URL", "http://myserver:8080")
|
|
cfg = load_config()
|
|
assert cfg["hindsightApiUrl"] == "http://myserver:8080"
|
|
|
|
def test_user_config_overrides_plugin_settings(self, tmp_path, monkeypatch):
|
|
plugin_root = tmp_path / "plugin"
|
|
plugin_root.mkdir()
|
|
|
|
# Plugin default ships with "low"
|
|
(plugin_root / "settings.json").write_text(json.dumps({"recallBudget": "low"}))
|
|
# User overrides to "high" via ~/.hindsight/claude-code.json
|
|
user_cfg = tmp_path / ".hindsight" / "claude-code.json"
|
|
user_cfg.parent.mkdir()
|
|
user_cfg.write_text(json.dumps({"recallBudget": "high"}))
|
|
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(plugin_root))
|
|
import lib.config as cfg_mod
|
|
monkeypatch.setattr(cfg_mod, "USER_CONFIG_PATH", str(user_cfg))
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "high"
|
|
|
|
def test_user_config_missing_falls_back_gracefully(self, tmp_path, monkeypatch):
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(tmp_path))
|
|
import lib.config as cfg_mod
|
|
monkeypatch.setattr(cfg_mod, "USER_CONFIG_PATH", str(tmp_path / "nonexistent.json"))
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "mid" # default
|
|
|
|
def test_env_var_wins_over_user_config(self, tmp_path, monkeypatch):
|
|
plugin_root = tmp_path / "plugin"
|
|
plugin_root.mkdir()
|
|
user_cfg = tmp_path / "claude-code.json"
|
|
user_cfg.write_text(json.dumps({"recallBudget": "low"}))
|
|
|
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", str(plugin_root))
|
|
monkeypatch.setenv("HINDSIGHT_RECALL_BUDGET", "high")
|
|
import lib.config as cfg_mod
|
|
monkeypatch.setattr(cfg_mod, "USER_CONFIG_PATH", str(user_cfg))
|
|
cfg = load_config()
|
|
assert cfg["recallBudget"] == "high"
|