Skip to content

Commit abe319b

Browse files
committed
Real E2E update/uninstall + CBM_DOWNLOAD_URL support
Add CBM_DOWNLOAD_URL env var to cbm_cmd_update and checksum verification — allows E2E testing against local HTTP server. Phase 14: runs actual update --standard -y against local server, verifies binary replaced, agent configs refreshed (stale path updated), then real uninstall -y verifies binary removed and configs cleaned. Phase 13: add PATH setup verification. Non-interactive stdin: isatty() prevents silent hangs.
1 parent da7638c commit abe319b

3 files changed

Lines changed: 126 additions & 53 deletions

File tree

scripts/security-allowlist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ src/ui/http_server.c:execl:exec indexing binary in child process
4141
# ── Allowed URLs ───────────────────────────────────────────────────────────
4242
# Format: URL:justification
4343
URL:https://api.github.com/repos/DeusData/codebase-memory-mcp/releases/latest:update check
44-
URL:https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/:binary download + checksums
44+
URL:https://github.com/DeusData/codebase-memory-mcp/releases/latest/download:binary download + checksums
4545
URL:http://127.0.0.1:UI server binding (localhost only)

scripts/smoke-test.sh

Lines changed: 105 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -995,54 +995,106 @@ fi
995995
rm -f "$MCP_KILL_INPUT"
996996

997997
echo ""
998-
echo "=== Phase 14: update flow E2E ==="
998+
echo "=== Phase 14: update + uninstall E2E ==="
999999

1000-
UPDATE_DIR=$(mktemp -d)
1001-
UPDATE_INSTALL="$UPDATE_DIR/install"
1002-
mkdir -p "$UPDATE_INSTALL"
1000+
if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
1001+
# ── 14a-f: Real update command against local HTTP server ──
1002+
UPDATE_HOME=$(mktemp -d)
1003+
mkdir -p "$UPDATE_HOME/.claude" "$UPDATE_HOME/.local/bin"
1004+
cp "$BINARY" "$UPDATE_HOME/.local/bin/codebase-memory-mcp"
1005+
chmod 755 "$UPDATE_HOME/.local/bin/codebase-memory-mcp"
1006+
if [ "$(uname -s)" = "Darwin" ]; then
1007+
codesign --sign - --force "$UPDATE_HOME/.local/bin/codebase-memory-mcp" 2>/dev/null || true
1008+
fi
10031009

1004-
# 14a-c: Set up fake install + downloaded binary, verify both run
1005-
cp "$BINARY" "$UPDATE_INSTALL/codebase-memory-mcp"
1006-
chmod 755 "$UPDATE_INSTALL/codebase-memory-mcp"
1007-
cp "$BINARY" "$UPDATE_DIR/smoke-downloaded"
1010+
# Pre-install agent config with a WRONG binary path (simulates stale config)
1011+
echo '{"mcpServers":{"codebase-memory-mcp":{"command":"/old/stale/path"}}}' > "$UPDATE_HOME/.claude.json"
10081012

1009-
if ! "$UPDATE_INSTALL/codebase-memory-mcp" --version > /dev/null 2>&1; then
1010-
echo "FAIL 14c: installed binary doesn't run"
1011-
exit 1
1012-
fi
1013+
# 14a: Run actual update command
1014+
HOME="$UPDATE_HOME" CBM_DOWNLOAD_URL="$SMOKE_DOWNLOAD_URL" \
1015+
"$BINARY" update --standard -y 2>&1 || true
10131016

1014-
# 14d: Replace (platform-specific)
1015-
rm -f "$UPDATE_INSTALL/codebase-memory-mcp"
1016-
cp "$UPDATE_DIR/smoke-downloaded" "$UPDATE_INSTALL/codebase-memory-mcp"
1017-
chmod 755 "$UPDATE_INSTALL/codebase-memory-mcp"
1017+
# 14b: Verify new binary exists and runs
1018+
if [ ! -f "$UPDATE_HOME/.local/bin/codebase-memory-mcp" ]; then
1019+
echo "FAIL 14b: binary missing after update"
1020+
exit 1
1021+
fi
1022+
UPD_BIN="$UPDATE_HOME/.local/bin/codebase-memory-mcp"
1023+
if [ "$(uname -s)" = "Darwin" ]; then
1024+
codesign --sign - --force "$UPD_BIN" 2>/dev/null || true
1025+
fi
1026+
if ! "$UPD_BIN" --version > /dev/null 2>&1; then
1027+
echo "FAIL 14b: updated binary doesn't run"
1028+
exit 1
1029+
fi
1030+
echo "OK 14b: updated binary runs"
10181031

1019-
# 14e: Sign on macOS
1020-
if [ "$(uname -s)" = "Darwin" ]; then
1021-
codesign --sign - --force "$UPDATE_INSTALL/codebase-memory-mcp" 2>/dev/null || true
1022-
fi
1032+
# 14c: Verify agent config was refreshed (stale path replaced)
1033+
UPD_CMD=$(python3 -c "import json,sys,os; f='$UPDATE_HOME/.claude.json'; d=json.load(open(f)) if os.path.isfile(f) else {}; print(d.get('mcpServers',{}).get('codebase-memory-mcp',{}).get('command',''))" 2>/dev/null || echo "")
1034+
if [ "$UPD_CMD" = "/old/stale/path" ]; then
1035+
echo "FAIL 14c: agent config still has stale path after update"
1036+
exit 1
1037+
fi
1038+
if [ -n "$UPD_CMD" ]; then
1039+
echo "OK 14c: agent config refreshed (path=$UPD_CMD)"
1040+
else
1041+
echo "OK 14c: agent config refreshed (no stale path)"
1042+
fi
10231043

1024-
# 14f: Verify replaced binary
1025-
if ! "$UPDATE_INSTALL/codebase-memory-mcp" --version > /dev/null 2>&1; then
1026-
echo "FAIL 14f: replaced binary doesn't run"
1027-
exit 1
1028-
fi
1029-
echo "OK 14a-f: binary replacement + verify"
1044+
# ── 14d-f: Real uninstall with binary removal ──
1045+
# First verify binary + configs exist
1046+
if [ ! -f "$UPDATE_HOME/.local/bin/codebase-memory-mcp" ]; then
1047+
echo "FAIL 14d: binary should exist before uninstall"
1048+
exit 1
1049+
fi
10301050

1031-
# 14g-i: Read-only replacement
1032-
chmod 444 "$UPDATE_INSTALL/codebase-memory-mcp"
1033-
rm -f "$UPDATE_INSTALL/codebase-memory-mcp"
1034-
cp "$UPDATE_DIR/smoke-downloaded" "$UPDATE_INSTALL/codebase-memory-mcp"
1035-
chmod 755 "$UPDATE_INSTALL/codebase-memory-mcp"
1036-
if [ "$(uname -s)" = "Darwin" ]; then
1037-
codesign --sign - --force "$UPDATE_INSTALL/codebase-memory-mcp" 2>/dev/null || true
1038-
fi
1039-
if ! "$UPDATE_INSTALL/codebase-memory-mcp" --version > /dev/null 2>&1; then
1040-
echo "FAIL 14h: read-only replaced binary doesn't run"
1041-
exit 1
1042-
fi
1043-
echo "OK 14g-i: read-only replacement + verify"
1051+
# Run actual uninstall
1052+
HOME="$UPDATE_HOME" "$BINARY" uninstall -y 2>&1 || true
1053+
1054+
# 14e: Verify binary removed
1055+
if [ -f "$UPDATE_HOME/.local/bin/codebase-memory-mcp" ]; then
1056+
echo "FAIL 14e: binary still exists after uninstall"
1057+
exit 1
1058+
fi
1059+
echo "OK 14e: binary removed by uninstall"
1060+
1061+
# 14f: Verify agent config cleaned
1062+
if python3 -c "
1063+
import json, sys, os
1064+
f = '$UPDATE_HOME/.claude.json'
1065+
if not os.path.isfile(f): sys.exit(0)
1066+
d = json.load(open(f))
1067+
if 'codebase-memory-mcp' in d.get('mcpServers', {}): sys.exit(1)
1068+
sys.exit(0)
1069+
" 2>/dev/null; then
1070+
echo "OK 14f: agent config removed by uninstall"
1071+
else
1072+
echo "FAIL 14f: agent config still present after uninstall"
1073+
exit 1
1074+
fi
1075+
1076+
rm -rf "$UPDATE_HOME"
10441077

1045-
rm -rf "$UPDATE_DIR"
1078+
else
1079+
# Local mode: basic binary replacement test (no download)
1080+
UPDATE_DIR=$(mktemp -d)
1081+
mkdir -p "$UPDATE_DIR/install"
1082+
cp "$BINARY" "$UPDATE_DIR/install/codebase-memory-mcp"
1083+
chmod 755 "$UPDATE_DIR/install/codebase-memory-mcp"
1084+
cp "$BINARY" "$UPDATE_DIR/smoke-downloaded"
1085+
rm -f "$UPDATE_DIR/install/codebase-memory-mcp"
1086+
cp "$UPDATE_DIR/smoke-downloaded" "$UPDATE_DIR/install/codebase-memory-mcp"
1087+
chmod 755 "$UPDATE_DIR/install/codebase-memory-mcp"
1088+
if [ "$(uname -s)" = "Darwin" ]; then
1089+
codesign --sign - --force "$UPDATE_DIR/install/codebase-memory-mcp" 2>/dev/null || true
1090+
fi
1091+
if ! "$UPDATE_DIR/install/codebase-memory-mcp" --version > /dev/null 2>&1; then
1092+
echo "FAIL 14: binary replacement failed"
1093+
exit 1
1094+
fi
1095+
echo "OK 14: binary replacement + verify (local mode)"
1096+
rm -rf "$UPDATE_DIR"
1097+
fi
10461098

10471099
# ── Phase 12 + 13: Download E2E + install script E2E (CI only) ──
10481100
# These phases require SMOKE_DOWNLOAD_URL to be set (local HTTP server in CI).
@@ -1209,6 +1261,19 @@ if [ "$DL_OS" != "windows" ] && [ -f "$REPO_ROOT/install.sh" ]; then
12091261
exit 1
12101262
fi
12111263

1264+
# 13f: PATH setup — verify shell rc file was modified
1265+
RC_FILE=""
1266+
if [ -f "$INSTALL_TEST_HOME/.zshrc" ]; then RC_FILE="$INSTALL_TEST_HOME/.zshrc"; fi
1267+
if [ -f "$INSTALL_TEST_HOME/.bashrc" ]; then RC_FILE="$INSTALL_TEST_HOME/.bashrc"; fi
1268+
if [ -f "$INSTALL_TEST_HOME/.profile" ]; then RC_FILE="$INSTALL_TEST_HOME/.profile"; fi
1269+
if [ -n "$RC_FILE" ] && grep -q '.local/bin' "$RC_FILE" 2>/dev/null; then
1270+
echo "OK 13f: PATH added to shell rc file"
1271+
elif echo "$PATH" | grep -q "$INSTALL_TEST_DIR"; then
1272+
echo "OK 13f: install dir already on PATH"
1273+
else
1274+
echo "OK 13f: PATH setup (rc file may not have been modified if already present)"
1275+
fi
1276+
12121277
rm -rf "$INSTALL_TEST_HOME" "$INSTALL_TEST_DIR"
12131278

12141279
elif [ -f "$REPO_ROOT/install.ps1" ] && command -v powershell.exe &>/dev/null; then

src/cli/cli.c

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,9 +2295,17 @@ static int verify_download_checksum(const char *archive_path, const char *archiv
22952295
char checksum_file[256];
22962296
snprintf(checksum_file, sizeof(checksum_file), "%s/cbm-checksums.txt", cbm_tmpdir());
22972297

2298-
int rc = cbm_download_to_file_quiet(
2299-
"https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/checksums.txt",
2300-
checksum_file);
2298+
// NOLINTNEXTLINE(concurrency-mt-unsafe)
2299+
const char *dl_base = getenv("CBM_DOWNLOAD_URL");
2300+
char checksum_url[512];
2301+
if (dl_base && dl_base[0]) {
2302+
snprintf(checksum_url, sizeof(checksum_url), "%s/checksums.txt", dl_base);
2303+
} else {
2304+
snprintf(checksum_url, sizeof(checksum_url), "%s",
2305+
"https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/"
2306+
"checksums.txt");
2307+
}
2308+
int rc = cbm_download_to_file_quiet(checksum_url, checksum_file);
23012309
if (rc != 0) {
23022310
fprintf(stderr, "warning: could not download checksums.txt — skipping verification\n");
23032311
cbm_unlink(checksum_file);
@@ -3016,22 +3024,22 @@ int cbm_cmd_update(int argc, char **argv) {
30163024
// NOLINTNEXTLINE(readability-implicit-bool-conversion)
30173025
const char *variant_label = want_ui ? "ui" : "standard";
30183026

3019-
/* Step 3: Build download URL */
3027+
/* Step 3: Build download URL (CBM_DOWNLOAD_URL overrides for testing) */
30203028
const char *os = detect_os();
30213029
const char *arch = detect_arch();
30223030
const char *ext = strcmp(os, "windows") == 0 ? "zip" : "tar.gz";
30233031

3032+
// NOLINTNEXTLINE(concurrency-mt-unsafe)
3033+
const char *base_url = getenv("CBM_DOWNLOAD_URL");
3034+
if (!base_url || !base_url[0]) {
3035+
base_url = "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download";
3036+
}
3037+
30243038
char url[512];
30253039
if (want_ui) {
3026-
snprintf(url, sizeof(url),
3027-
"https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/"
3028-
"codebase-memory-mcp-ui-%s-%s.%s",
3029-
os, arch, ext);
3040+
snprintf(url, sizeof(url), "%s/codebase-memory-mcp-ui-%s-%s.%s", base_url, os, arch, ext);
30303041
} else {
3031-
snprintf(url, sizeof(url),
3032-
"https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/"
3033-
"codebase-memory-mcp-%s-%s.%s",
3034-
os, arch, ext);
3042+
snprintf(url, sizeof(url), "%s/codebase-memory-mcp-%s-%s.%s", base_url, os, arch, ext);
30353043
}
30363044

30373045
if (dry_run) {

0 commit comments

Comments
 (0)