@aiassesstech/mighty-mark 0.3.31 → 0.3.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════════════
3
+ # nole-fleet-backup — Backup Script Test Suite
4
+ #
5
+ # Tests fleet-backup.sh in a sandboxed environment using temp dirs.
6
+ # No real VPS data is touched. No real GitHub push occurs.
7
+ #
8
+ # Usage:
9
+ # ./test/test-backup.sh # run all tests
10
+ # ./test/test-backup.sh -v # verbose mode
11
+ #
12
+ # Exit code: 0 if all tests pass, 1 if any test fails.
13
+ # ═══════════════════════════════════════════════════════════════════
14
+
15
+ set -euo pipefail
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
19
+ VERBOSE="${1:-}"
20
+
21
+ PASS_COUNT=0
22
+ FAIL_COUNT=0
23
+ SKIP_COUNT=0
24
+
25
+ # Colors
26
+ RED='\033[0;31m'
27
+ GREEN='\033[0;32m'
28
+ YELLOW='\033[1;33m'
29
+ NC='\033[0m'
30
+
31
+ pass() { PASS_COUNT=$((PASS_COUNT + 1)); echo -e " ${GREEN}PASS${NC}: $1"; }
32
+ fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); echo -e " ${RED}FAIL${NC}: $1"; echo -e " $2"; }
33
+ skip() { SKIP_COUNT=$((SKIP_COUNT + 1)); echo -e " ${YELLOW}SKIP${NC}: $1 — $2"; }
34
+
35
+ # ─── Test Sandbox Setup ──────────────────────────────────────────
36
+
37
+ SANDBOX=""
38
+ setup_sandbox() {
39
+ SANDBOX=$(mktemp -d "/tmp/fleet-backup-test-XXXXXX")
40
+ mkdir -p "$SANDBOX"/{openclaw/extensions/grillo/agent,openclaw/extensions/jessie/agent}
41
+ mkdir -p "$SANDBOX"/{openclaw/extensions/noah/agent,openclaw/extensions/nole/agent}
42
+ mkdir -p "$SANDBOX"/{openclaw/extensions/mark/agent,openclaw/extensions/sam/agent}
43
+ mkdir -p "$SANDBOX"/{openclaw/agents/grillo/memory,openclaw/agents/jessie/memory}
44
+ mkdir -p "$SANDBOX"/{openclaw/agents/noah/memory,openclaw/agents/nole/memory}
45
+ mkdir -p "$SANDBOX"/{openclaw/agents/mark/memory,openclaw/agents/sam/memory}
46
+ mkdir -p "$SANDBOX"/{openclaw/agents/grillo/sessions,openclaw/agents/jessie/sessions}
47
+ mkdir -p "$SANDBOX"/{openclaw/memory,openclaw/grillo-data,openclaw/workspace}
48
+ mkdir -p "$SANDBOX"/{openclaw/.noah-data,openclaw/nole-data}
49
+ mkdir -p "$SANDBOX"/{openclaw/.fleet-data,openclaw/.sam-artifacts}
50
+ mkdir -p "$SANDBOX"/{clawdbot,clawd,mighty-mark-opt}
51
+ mkdir -p "$SANDBOX"/{jessie-data,mark-data}
52
+ mkdir -p "$SANDBOX"/{backup-dir/backups,backup-dir/logs}
53
+
54
+ echo "# Grillo SOUL" > "$SANDBOX/openclaw/extensions/grillo/agent/SOUL.md"
55
+ echo "# Grillo Memory" > "$SANDBOX/openclaw/agents/grillo/memory/context.md"
56
+ echo '{"model":"claude"}' > "$SANDBOX/openclaw/agents/grillo/memory/state.json"
57
+ echo "session data" > "$SANDBOX/openclaw/agents/grillo/sessions/001.jsonl"
58
+ echo '{"agents":[]}' > "$SANDBOX/openclaw/grillo-data/chain.json"
59
+ echo '{"apiKey":"sk-secret-key-123","model":"claude"}' > "$SANDBOX/openclaw/openclaw.json"
60
+ echo '{"apiKey":"sk-another-key","provider":"anthropic"}' > "$SANDBOX/clawdbot/openclaw.json"
61
+ echo '{"name":"grillo"}' > "$SANDBOX/openclaw/extensions/grillo/package.json"
62
+ echo "watchdog script" > "$SANDBOX/mighty-mark-opt/watchdog.sh"
63
+ echo "SECRET=value" > "$SANDBOX/mighty-mark-opt/.env"
64
+
65
+ # Create test config
66
+ cat > "$SANDBOX/config.sh" << 'CFGEOF'
67
+ BACKUP_DIR="__SANDBOX__/backup-dir/backups"
68
+ RETENTION_DAYS=35
69
+ LOG_FILE="__SANDBOX__/backup-dir/logs/backup.log"
70
+ MANIFEST_FILE="__SANDBOX__/backup-dir/latest-manifest.json"
71
+ OPENCLAW_HOME="__SANDBOX__/openclaw"
72
+ CLAWDBOT_HOME="__SANDBOX__/clawdbot"
73
+ CLAWD_WORKSPACE="__SANDBOX__/clawd"
74
+ MIGHTY_MARK_OPT="__SANDBOX__/mighty-mark-opt"
75
+ NOLE_CREDS="__SANDBOX__/nole"
76
+ FULL_BACKUP_DAY="0"
77
+ SYSTEMD_UNITS=()
78
+ CRON_FILES=()
79
+ LOGROTATE_FILES=()
80
+ EXCLUDE_PATTERNS=()
81
+ SANITIZE_OPENCLAW_JSON=true
82
+ PUSH_TO_GITHUB=false
83
+ GITHUB_REPO_DIR=""
84
+ GITHUB_SSH_HOST=""
85
+ GITHUB_BACKUP_BRANCH="backups"
86
+ GITHUB_MAX_FILE_SIZE=$((95 * 1024 * 1024))
87
+ CFGEOF
88
+ sed -i.bak "s|__SANDBOX__|${SANDBOX}|g" "$SANDBOX/config.sh" 2>/dev/null || \
89
+ sed -i '' "s|__SANDBOX__|${SANDBOX}|g" "$SANDBOX/config.sh"
90
+ rm -f "$SANDBOX/config.sh.bak"
91
+
92
+ cp "$PROJECT_DIR/fleet-backup.sh" "$SANDBOX/fleet-backup.sh"
93
+ chmod +x "$SANDBOX/fleet-backup.sh"
94
+
95
+ # Override config loading: point to sandbox config
96
+ # fleet-backup.sh looks for config.local.sh in SCRIPT_DIR, which we set to $SANDBOX
97
+ cat > "$SANDBOX/config.local.sh" << LOCALEOF
98
+ source "$SANDBOX/config.sh"
99
+ LOCALEOF
100
+ }
101
+
102
+ teardown_sandbox() {
103
+ [[ -n "$SANDBOX" ]] && rm -rf "$SANDBOX"
104
+ }
105
+
106
+ run_backup() {
107
+ local args="${1:-}"
108
+ cd "$SANDBOX"
109
+ # fleet-backup.sh resolves SCRIPT_DIR from BASH_SOURCE[0], so we
110
+ # can't override it via env. Instead we put config.local.sh next to the script.
111
+ bash "$SANDBOX/fleet-backup.sh" $args 2>&1
112
+ }
113
+
114
+ # ─── Tests ────────────────────────────────────────────────────────
115
+
116
+ echo "═══════════════════════════════════════════════════════"
117
+ echo " nole-fleet-backup — Backup Test Suite"
118
+ echo "═══════════════════════════════════════════════════════"
119
+ echo ""
120
+
121
+ # --- Test 1: Light backup creates archive ---
122
+ echo "▸ Light backup"
123
+ setup_sandbox
124
+ output=$(FULL_BACKUP_DAY=9 run_backup "--light")
125
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' 2>/dev/null | head -1)
126
+ if [[ -n "$archive" && -f "$archive" ]]; then
127
+ pass "Light backup creates archive"
128
+ else
129
+ fail "Light backup creates archive" "No archive found in $SANDBOX/backup-dir/backups"
130
+ fi
131
+
132
+ if echo "$output" | grep -q "Starting light backup"; then
133
+ pass "Light backup reports correct tier"
134
+ else
135
+ fail "Light backup reports correct tier" "Output: $output"
136
+ fi
137
+ teardown_sandbox
138
+
139
+ # --- Test 2: Full backup creates archive ---
140
+ echo "▸ Full backup"
141
+ setup_sandbox
142
+ output=$(run_backup "--full")
143
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-full-*.tar.gz' 2>/dev/null | head -1)
144
+ if [[ -n "$archive" && -f "$archive" ]]; then
145
+ pass "Full backup creates archive"
146
+ else
147
+ fail "Full backup creates archive" "No archive found"
148
+ fi
149
+
150
+ if echo "$output" | grep -q "Starting full backup"; then
151
+ pass "Full backup reports correct tier"
152
+ else
153
+ fail "Full backup reports correct tier" "Output: $output"
154
+ fi
155
+ teardown_sandbox
156
+
157
+ # --- Test 3: Full backup is larger than light ---
158
+ echo "▸ Size comparison"
159
+ setup_sandbox
160
+ run_backup "--light" > /dev/null 2>&1
161
+ light_archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | head -1)
162
+ light_size=$(stat -c%s "$light_archive" 2>/dev/null || stat -f%z "$light_archive" 2>/dev/null)
163
+
164
+ run_backup "--full" > /dev/null 2>&1
165
+ full_archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-full-*.tar.gz' | head -1)
166
+ full_size=$(stat -c%s "$full_archive" 2>/dev/null || stat -f%z "$full_archive" 2>/dev/null)
167
+
168
+ if [[ "$full_size" -gt "$light_size" ]]; then
169
+ pass "Full backup is larger than light ($full_size > $light_size bytes)"
170
+ else
171
+ fail "Full backup should be larger than light" "full=$full_size light=$light_size"
172
+ fi
173
+ teardown_sandbox
174
+
175
+ # --- Test 4: Manifest is written with correct fields ---
176
+ echo "▸ Manifest"
177
+ setup_sandbox
178
+ run_backup "--light" > /dev/null 2>&1
179
+ manifest="$SANDBOX/backup-dir/latest-manifest.json"
180
+ if [[ -f "$manifest" ]]; then
181
+ pass "Manifest file created"
182
+ else
183
+ fail "Manifest file created" "Not found at $manifest"
184
+ teardown_sandbox
185
+ # skip remaining manifest tests
186
+ echo ""
187
+ echo "═══════════════════════════════════════════════════════"
188
+ echo " Results: ${PASS_COUNT} passed, ${FAIL_COUNT} failed, ${SKIP_COUNT} skipped"
189
+ echo "═══════════════════════════════════════════════════════"
190
+ exit 1
191
+ fi
192
+
193
+ for field in date timestamp tier archive size_bytes checksum extensions agents memory_files retention_days; do
194
+ if grep -q "\"$field\"" "$manifest"; then
195
+ pass "Manifest contains field: $field"
196
+ else
197
+ fail "Manifest contains field: $field" "Missing from $manifest"
198
+ fi
199
+ done
200
+
201
+ tier_value=$(grep -o '"tier": *"[^"]*"' "$manifest" | sed 's/.*"tier": *"//;s/"//')
202
+ if [[ "$tier_value" == "light" ]]; then
203
+ pass "Manifest tier is 'light'"
204
+ else
205
+ fail "Manifest tier is 'light'" "Got: $tier_value"
206
+ fi
207
+ teardown_sandbox
208
+
209
+ # --- Test 5: Full manifest has tier=full ---
210
+ echo "▸ Full manifest tier"
211
+ setup_sandbox
212
+ run_backup "--full" > /dev/null 2>&1
213
+ manifest="$SANDBOX/backup-dir/latest-manifest.json"
214
+ tier_value=$(grep -o '"tier": *"[^"]*"' "$manifest" | sed 's/.*"tier": *"//;s/"//')
215
+ if [[ "$tier_value" == "full" ]]; then
216
+ pass "Full backup manifest has tier=full"
217
+ else
218
+ fail "Full backup manifest has tier=full" "Got: $tier_value"
219
+ fi
220
+ teardown_sandbox
221
+
222
+ # --- Test 6: API keys are redacted ---
223
+ echo "▸ Secret redaction"
224
+ setup_sandbox
225
+ run_backup "--light" > /dev/null 2>&1
226
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | head -1)
227
+ extract_dir=$(mktemp -d)
228
+ tar -xzf "$archive" -C "$extract_dir" 2>/dev/null
229
+
230
+ if grep -r "sk-secret-key-123" "$extract_dir" 2>/dev/null; then
231
+ fail "API keys are redacted from backup" "Found sk-secret-key-123 in archive"
232
+ else
233
+ pass "API keys are redacted from backup"
234
+ fi
235
+
236
+ if grep -r "REDACTED_FOR_BACKUP" "$extract_dir" 2>/dev/null >/dev/null; then
237
+ pass "Redaction placeholder present"
238
+ else
239
+ fail "Redaction placeholder present" "REDACTED_FOR_BACKUP not found"
240
+ fi
241
+
242
+ rm -rf "$extract_dir"
243
+ teardown_sandbox
244
+
245
+ # --- Test 7: .env files are excluded ---
246
+ echo "▸ Secret exclusion"
247
+ setup_sandbox
248
+ run_backup "--full" > /dev/null 2>&1
249
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-full-*.tar.gz' | head -1)
250
+
251
+ if tar -tzf "$archive" | grep -q "\.env"; then
252
+ fail ".env files excluded from backup" "Found .env in archive listing"
253
+ else
254
+ pass ".env files excluded from backup"
255
+ fi
256
+ teardown_sandbox
257
+
258
+ # --- Test 8: Light backup excludes heavy data ---
259
+ echo "▸ Light backup exclusions"
260
+ setup_sandbox
261
+ run_backup "--light" > /dev/null 2>&1
262
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | head -1)
263
+ listing=$(tar -tzf "$archive")
264
+
265
+ if echo "$listing" | grep -q "sessions/"; then
266
+ fail "Light backup excludes sessions" "Found sessions/ in archive"
267
+ else
268
+ pass "Light backup excludes sessions"
269
+ fi
270
+
271
+ if echo "$listing" | grep -q "grillo-data/"; then
272
+ fail "Light backup excludes data stores" "Found grillo-data/ in archive"
273
+ else
274
+ pass "Light backup excludes data stores"
275
+ fi
276
+ teardown_sandbox
277
+
278
+ # --- Test 9: Full backup includes heavy data ---
279
+ echo "▸ Full backup inclusions"
280
+ setup_sandbox
281
+ run_backup "--full" > /dev/null 2>&1
282
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-full-*.tar.gz' | head -1)
283
+ listing=$(tar -tzf "$archive")
284
+
285
+ if echo "$listing" | grep -q "sessions/"; then
286
+ pass "Full backup includes sessions"
287
+ else
288
+ fail "Full backup includes sessions" "sessions/ not in archive"
289
+ fi
290
+
291
+ if echo "$listing" | grep -q "grillo-data/"; then
292
+ pass "Full backup includes data stores"
293
+ else
294
+ fail "Full backup includes data stores" "grillo-data/ not in archive"
295
+ fi
296
+ teardown_sandbox
297
+
298
+ # --- Test 10: Light backup includes .md memory files ---
299
+ echo "▸ Light backup .md inclusion"
300
+ setup_sandbox
301
+ run_backup "--light" > /dev/null 2>&1
302
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | head -1)
303
+ listing=$(tar -tzf "$archive")
304
+
305
+ if echo "$listing" | grep -q "context.md"; then
306
+ pass "Light backup includes memory .md files"
307
+ else
308
+ fail "Light backup includes memory .md files" "context.md not in archive"
309
+ fi
310
+ teardown_sandbox
311
+
312
+ # --- Test 11: Checksum is valid ---
313
+ echo "▸ Checksum validation"
314
+ setup_sandbox
315
+ run_backup "--light" > /dev/null 2>&1
316
+ archive=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | head -1)
317
+ manifest="$SANDBOX/backup-dir/latest-manifest.json"
318
+
319
+ manifest_checksum=$(grep -o '"checksum": *"[^"]*"' "$manifest" | sed 's/.*sha256://;s/"//')
320
+ actual_checksum=$(sha256sum "$archive" 2>/dev/null | awk '{print $1}' || shasum -a 256 "$archive" | awk '{print $1}')
321
+
322
+ if [[ "$manifest_checksum" == "$actual_checksum" ]]; then
323
+ pass "Checksum matches archive"
324
+ else
325
+ fail "Checksum matches archive" "manifest=$manifest_checksum actual=$actual_checksum"
326
+ fi
327
+ teardown_sandbox
328
+
329
+ # --- Test 12: --help exits 0 ---
330
+ echo "▸ Help flag"
331
+ setup_sandbox
332
+ if run_backup "--help" > /dev/null 2>&1; then
333
+ pass "--help exits 0"
334
+ else
335
+ fail "--help exits 0" "Non-zero exit code"
336
+ fi
337
+ teardown_sandbox
338
+
339
+ # --- Test 13: Multiple backups on same day get unique names ---
340
+ echo "▸ Unique archive names"
341
+ setup_sandbox
342
+ run_backup "--light" > /dev/null 2>&1
343
+ sleep 1
344
+ run_backup "--light" > /dev/null 2>&1
345
+ count=$(find "$SANDBOX/backup-dir/backups" -name 'fleet-light-*.tar.gz' | wc -l | tr -d ' ')
346
+ if [[ "$count" -ge 2 ]]; then
347
+ pass "Multiple same-day backups get unique names ($count archives)"
348
+ else
349
+ fail "Multiple same-day backups get unique names" "Only $count archive(s) found"
350
+ fi
351
+ teardown_sandbox
352
+
353
+ # --- Test 14: Extension and agent counts are correct ---
354
+ echo "▸ Content counts"
355
+ setup_sandbox
356
+ run_backup "--full" > /dev/null 2>&1
357
+ manifest="$SANDBOX/backup-dir/latest-manifest.json"
358
+ ext_count=$(grep -o '"extensions": *[0-9]*' "$manifest" | grep -o '[0-9]*$')
359
+ agent_count=$(grep -o '"agents": *[0-9]*' "$manifest" | grep -o '[0-9]*$')
360
+
361
+ if [[ "$ext_count" -eq 6 ]]; then
362
+ pass "Extension count is 6"
363
+ else
364
+ fail "Extension count is 6" "Got: $ext_count"
365
+ fi
366
+
367
+ if [[ "$agent_count" -eq 6 ]]; then
368
+ pass "Agent count is 6"
369
+ else
370
+ fail "Agent count is 6" "Got: $agent_count"
371
+ fi
372
+ teardown_sandbox
373
+
374
+ # --- Test 15: GitHub push disabled doesn't error ---
375
+ echo "▸ GitHub push disabled"
376
+ setup_sandbox
377
+ output=$(run_backup "--light" 2>&1)
378
+ if echo "$output" | grep -q "Pushing backup to GitHub"; then
379
+ fail "GitHub push skipped when PUSH_TO_GITHUB=false" "Push was attempted"
380
+ else
381
+ pass "GitHub push skipped when PUSH_TO_GITHUB=false"
382
+ fi
383
+ teardown_sandbox
384
+
385
+ # ─── Summary ──────────────────────────────────────────────────────
386
+
387
+ echo ""
388
+ echo "═══════════════════════════════════════════════════════"
389
+ TOTAL=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))
390
+ echo -e " Results: ${GREEN}${PASS_COUNT} passed${NC}, ${RED}${FAIL_COUNT} failed${NC}, ${YELLOW}${SKIP_COUNT} skipped${NC} (${TOTAL} total)"
391
+ echo "═══════════════════════════════════════════════════════"
392
+
393
+ if [[ "$FAIL_COUNT" -gt 0 ]]; then
394
+ exit 1
395
+ fi