first commit
This commit is contained in:
242
文档润色流和知识库构建流/claude-scholar/scripts/test_install_uninstall.sh
Normal file
242
文档润色流和知识库构建流/claude-scholar/scripts/test_install_uninstall.sh
Normal file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SETUP_SH="$REPO_ROOT/scripts/setup.sh"
|
||||
UNINSTALL_SH="$REPO_ROOT/scripts/uninstall.sh"
|
||||
|
||||
pass() {
|
||||
echo "[PASS] $1"
|
||||
}
|
||||
|
||||
make_home() {
|
||||
mktemp -d /tmp/claude-scholar-test.XXXXXX
|
||||
}
|
||||
|
||||
run_setup() {
|
||||
HOME="$1" bash "$SETUP_SH" >/dev/null
|
||||
}
|
||||
|
||||
run_uninstall() {
|
||||
HOME="$1" bash "$UNINSTALL_SH" >/dev/null
|
||||
}
|
||||
|
||||
test_roundtrip_empty_home() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
run_setup "$home"
|
||||
|
||||
test -f "$home/.claude/.claude-scholar-manifest.txt"
|
||||
test -f "$home/.claude/.claude-scholar-install-state"
|
||||
test -f "$home/.claude/CLAUDE.md"
|
||||
|
||||
run_uninstall "$home"
|
||||
|
||||
test ! -f "$home/.claude/.claude-scholar-manifest.txt"
|
||||
test ! -f "$home/.claude/.claude-scholar-install-state"
|
||||
test ! -f "$home/.claude/settings.json"
|
||||
if [ -d "$home/.claude" ]; then
|
||||
! find "$home/.claude"/{skills,commands,agents,rules,hooks,scripts} -type f 2>/dev/null | grep -q .
|
||||
fi
|
||||
pass "roundtrip on empty home"
|
||||
}
|
||||
|
||||
test_preserve_preexisting_settings_keys() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
mkdir -p "$home/.claude"
|
||||
python3 - "$home/.claude/settings.json" <<'PY'
|
||||
import json, sys
|
||||
path = sys.argv[1]
|
||||
data = {
|
||||
"mcpServers": {
|
||||
"zotero": {
|
||||
"command": "custom-zotero"
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"superpowers@claude-plugins-official": False
|
||||
},
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo user-hook",
|
||||
"timeout": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
|
||||
run_setup "$home"
|
||||
run_uninstall "$home"
|
||||
|
||||
python3 - "$home/.claude/settings.json" <<'PY'
|
||||
import json, sys
|
||||
data = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
assert data["mcpServers"]["zotero"]["command"] == "custom-zotero"
|
||||
assert "args" not in data["mcpServers"]["zotero"]
|
||||
assert "env" not in data["mcpServers"]["zotero"]
|
||||
assert data["enabledPlugins"]["superpowers@claude-plugins-official"] is False
|
||||
hooks = data["hooks"]["Stop"][0]["hooks"]
|
||||
assert len(hooks) == 1 and hooks[0]["command"] == "echo user-hook"
|
||||
PY
|
||||
pass "preserve pre-existing settings entries with overlapping keys"
|
||||
}
|
||||
|
||||
test_manifest_missing_fails_safe() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
run_setup "$home"
|
||||
|
||||
rm -f "$home/.claude/.claude-scholar-manifest.txt"
|
||||
if HOME="$home" bash "$UNINSTALL_SH" >/tmp/claude-scholar-uninstall-fail.log 2>&1; then
|
||||
echo "[FAIL] manifest missing should fail"
|
||||
cat /tmp/claude-scholar-uninstall-fail.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test -f "$home/.claude/CLAUDE.md"
|
||||
test -f "$home/.claude/.claude-scholar-install-state"
|
||||
pass "manifest missing fails safely"
|
||||
}
|
||||
|
||||
test_state_records_direct_claude_targets() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
run_setup "$home"
|
||||
|
||||
python3 - "$home/.claude/.claude-scholar-install-state" <<'PY'
|
||||
import json, sys
|
||||
state = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
targets = set(state["claudeTargets"])
|
||||
assert "CLAUDE.md" in targets, targets
|
||||
assert "CLAUDE.zh-CN.md" in targets, targets
|
||||
PY
|
||||
|
||||
run_uninstall "$home"
|
||||
test ! -f "$home/.claude/CLAUDE.md"
|
||||
test ! -f "$home/.claude/CLAUDE.zh-CN.md"
|
||||
pass "state records direct CLAUDE targets"
|
||||
}
|
||||
|
||||
test_identical_preexisting_file_is_not_owned() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
mkdir -p "$home/.claude/hooks"
|
||||
cp "$REPO_ROOT/hooks/security-guard.js" "$home/.claude/hooks/security-guard.js"
|
||||
|
||||
run_setup "$home"
|
||||
run_uninstall "$home"
|
||||
|
||||
test -f "$home/.claude/hooks/security-guard.js"
|
||||
pass "identical pre-existing file is not treated as owned"
|
||||
}
|
||||
|
||||
test_reinstall_keeps_owned_files_owned() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
run_setup "$home"
|
||||
run_setup "$home"
|
||||
run_uninstall "$home"
|
||||
|
||||
test ! -f "$home/.claude/hooks/security-guard.js"
|
||||
test ! -f "$home/.claude/CLAUDE.md"
|
||||
pass "reinstall preserves ownership of previously installed files"
|
||||
}
|
||||
|
||||
test_legacy_install_upgrade_adopts_existing_files() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
mkdir -p "$home/.claude/hooks"
|
||||
cp "$REPO_ROOT/CLAUDE.md" "$home/.claude/CLAUDE.md"
|
||||
cp "$REPO_ROOT/hooks/security-guard.js" "$home/.claude/hooks/security-guard.js"
|
||||
python3 - "$home/.claude/settings.json" <<'PY'
|
||||
import json, sys
|
||||
data = {
|
||||
"mcpServers": {
|
||||
"streamable-mcp-server": {
|
||||
"type": "streamable-http",
|
||||
"url": "http://127.0.0.1:12306/mcp"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash|Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"const p=require('path'),h=require('os').homedir();require('child_process').execSync('node '+p.join(h,'.claude/hooks/security-guard.js'),{stdio:'inherit'})\"",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
with open(sys.argv[1], "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
|
||||
run_setup "$home"
|
||||
run_uninstall "$home"
|
||||
|
||||
test ! -f "$home/.claude/CLAUDE.md"
|
||||
test ! -f "$home/.claude/hooks/security-guard.js"
|
||||
pass "legacy install upgrade adopts existing managed files"
|
||||
}
|
||||
|
||||
test_overlapping_mcp_keys_do_not_trigger_legacy_adoption() {
|
||||
local home
|
||||
home="$(make_home)"
|
||||
mkdir -p "$home/.claude/hooks"
|
||||
cp "$REPO_ROOT/hooks/security-guard.js" "$home/.claude/hooks/security-guard.js"
|
||||
python3 - "$home/.claude/settings.json" <<'PY'
|
||||
import json, sys
|
||||
data = {
|
||||
"mcpServers": {
|
||||
"zotero": {
|
||||
"command": "custom-zotero"
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"superpowers@claude-plugins-official": False
|
||||
}
|
||||
}
|
||||
with open(sys.argv[1], "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
PY
|
||||
|
||||
run_setup "$home"
|
||||
run_uninstall "$home"
|
||||
|
||||
test -f "$home/.claude/hooks/security-guard.js"
|
||||
pass "overlapping MCP keys alone do not trigger legacy adoption"
|
||||
}
|
||||
|
||||
main() {
|
||||
bash -n "$SETUP_SH"
|
||||
bash -n "$UNINSTALL_SH"
|
||||
test_roundtrip_empty_home
|
||||
test_preserve_preexisting_settings_keys
|
||||
test_manifest_missing_fails_safe
|
||||
test_state_records_direct_claude_targets
|
||||
test_identical_preexisting_file_is_not_owned
|
||||
test_reinstall_keeps_owned_files_owned
|
||||
test_legacy_install_upgrade_adopts_existing_files
|
||||
test_overlapping_mcp_keys_do_not_trigger_legacy_adoption
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user