Skip to content

Commit 3c88157

Browse files
committed
test: fix hook regression coverage and enforce in ci
1 parent 6a8b69a commit 3c88157

File tree

4 files changed

+185
-5
lines changed

4 files changed

+185
-5
lines changed

.github/workflows/validate.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,33 @@ jobs:
9191
fi
9292
echo "plugin.json has name='$name' and version='$version'"
9393
94+
- name: Verify all concept prerequisites reference valid concept IDs
95+
run: |
96+
concept_keys=$(jq -r '.categories | to_entries[] | .value.concepts | keys[]' data/concept-tree.json | sort)
97+
prereq_keys=$(jq -r '.categories | to_entries[] | .value.concepts | to_entries[] | .value.prerequisites[]?' data/concept-tree.json | sort -u)
98+
missing=$(comm -23 <(echo "$prereq_keys") <(echo "$concept_keys"))
99+
if [ -n "$missing" ]; then
100+
echo "ERROR: The following prerequisite IDs do not exist in concept-tree.json:"
101+
echo "$missing"
102+
exit 1
103+
fi
104+
echo "All prerequisite IDs are valid concept keys."
105+
106+
hook-tests:
107+
name: Hook Regression Tests
108+
runs-on: ubuntu-latest
109+
steps:
110+
- uses: actions/checkout@v4
111+
112+
- name: Install jq
113+
run: sudo apt-get update && sudo apt-get install -y jq
114+
115+
- name: Run hook regression tests
116+
run: bash tests/test-hooks.sh
117+
118+
- name: Run profile tools regression tests
119+
run: bash tests/test-profile-tools.sh
120+
94121
concept-coverage:
95122
name: Concept Coverage Check
96123
runs-on: ubuntu-latest

data/concept-tree.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@
101101
"js-basics": {
102102
"name": "JavaScript Basics",
103103
"xp_to_master": 100,
104-
"prerequisites": ["fundamentals.variables", "fundamentals.functions"],
104+
"prerequisites": ["variables", "functions"],
105105
"description": "JS syntax and core features",
106106
"triggers": [".js", ".mjs", "console.log"]
107107
},
108108
"async-await": {
109109
"name": "Async/Await",
110110
"xp_to_master": 150,
111-
"prerequisites": ["js-basics", "fundamentals.functions"],
111+
"prerequisites": ["js-basics", "functions"],
112112
"description": "Handling asynchronous operations",
113113
"triggers": ["async", "await", "Promise", ".then("]
114114
},
@@ -122,7 +122,7 @@
122122
"json": {
123123
"name": "JSON",
124124
"xp_to_master": 50,
125-
"prerequisites": ["fundamentals.objects"],
125+
"prerequisites": ["objects"],
126126
"description": "Data format for communication",
127127
"triggers": [".json", "JSON.parse", "JSON.stringify"]
128128
},
@@ -276,7 +276,7 @@
276276
"sql-basics": {
277277
"name": "SQL Basics",
278278
"xp_to_master": 150,
279-
"prerequisites": ["fundamentals.objects"],
279+
"prerequisites": ["objects"],
280280
"description": "Querying and managing data",
281281
"triggers": ["SELECT", "INSERT", "UPDATE", "DELETE", ".sql"]
282282
},

tests/test-hooks.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ trap cleanup EXIT
3030

3131
# --- Setup: create a minimal profile ---
3232
setup_profile() {
33+
rm -rf "$TEST_HOME/.code-sensei"
3334
mkdir -p "$TEST_HOME/.code-sensei"
3435
cat > "$TEST_HOME/.code-sensei/profile.json" <<'PROFILE'
3536
{
3637
"belt": "yellow",
3738
"xp": 100,
3839
"session_concepts": [],
3940
"concepts_seen": ["html"],
40-
"streak": {"current": 3}
41+
"streak": {"current": 3},
42+
"quizzes": {"total": 0, "correct": 0, "current_streak": 0, "longest_streak": 0}
4143
}
4244
PROFILE
4345
}

tests/test-profile-tools.sh

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/bin/bash
2+
# CodeSensei — Profile Tools Regression Tests
3+
# Covers doctor/import scripts and secret redaction in command logging.
4+
5+
set -euo pipefail
6+
7+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
8+
TEST_HOME=$(mktemp -d)
9+
export HOME="$TEST_HOME"
10+
11+
PASS=0
12+
FAIL=0
13+
14+
GREEN='\033[0;32m'
15+
RED='\033[0;31m'
16+
NC='\033[0m'
17+
18+
pass() { PASS=$((PASS + 1)); echo -e " ${GREEN}${NC} $1"; }
19+
fail() { FAIL=$((FAIL + 1)); echo -e " ${RED}${NC} $1: $2"; }
20+
21+
cleanup() {
22+
rm -rf "$TEST_HOME"
23+
}
24+
trap cleanup EXIT
25+
26+
setup_profile() {
27+
rm -rf "$TEST_HOME/.code-sensei"
28+
mkdir -p "$TEST_HOME/.code-sensei"
29+
cat > "$TEST_HOME/.code-sensei/profile.json" <<'PROFILE'
30+
{
31+
"version": "1.0.0",
32+
"belt": "yellow",
33+
"xp": 125,
34+
"session_concepts": [],
35+
"concepts_seen": ["html"],
36+
"concepts_mastered": ["html"],
37+
"streak": {"current": 3, "longest": 3, "last_session_date": "2026-03-18"},
38+
"quizzes": {"total": 4, "correct": 3, "current_streak": 2, "longest_streak": 2},
39+
"quiz_history": []
40+
}
41+
PROFILE
42+
}
43+
44+
create_wrapped_export() {
45+
local export_file="$1"
46+
cat > "$export_file" <<'EXPORT'
47+
{
48+
"schema_version": "1.0",
49+
"exported_at": "2026-03-19T12:00:00Z",
50+
"plugin_version": "1.1.0",
51+
"profile": {
52+
"version": "1.0.0",
53+
"belt": "green",
54+
"xp": 3800,
55+
"session_concepts": [],
56+
"concepts_seen": ["html", "css", "js-basics"],
57+
"concepts_mastered": ["html", "css"],
58+
"streak": {"current": 9, "longest": 9, "last_session_date": "2026-03-19"},
59+
"quizzes": {"total": 18, "correct": 14, "current_streak": 5, "longest_streak": 6},
60+
"quiz_history": []
61+
}
62+
}
63+
EXPORT
64+
}
65+
66+
echo ""
67+
echo "━━━ CodeSensei Profile Tools Tests ━━━"
68+
echo ""
69+
70+
# ============================================================
71+
# TEST GROUP 1: doctor.sh
72+
# ============================================================
73+
echo "▸ doctor.sh"
74+
75+
rm -rf "$TEST_HOME/.code-sensei"
76+
OUTPUT=$(bash "$SCRIPT_DIR/scripts/doctor.sh")
77+
78+
if echo "$OUTPUT" | jq . >/dev/null 2>&1; then
79+
pass "doctor output is valid JSON"
80+
else
81+
fail "doctor output is valid JSON" "got: $OUTPUT"
82+
fi
83+
84+
STATUS=$(echo "$OUTPUT" | jq -r '.status')
85+
PROFILE_EXISTS=$(echo "$OUTPUT" | jq -r '.profile.exists')
86+
COMMANDS_DETECTED=$(echo "$OUTPUT" | jq -r '.checks.commands_detected')
87+
if [ "$STATUS" = "warn" ] && [ "$PROFILE_EXISTS" = "false" ] && [ "$COMMANDS_DETECTED" -ge 10 ]; then
88+
pass "doctor reports missing profile but valid plugin setup"
89+
else
90+
fail "doctor reports missing profile but valid plugin setup" "status=$STATUS profile_exists=$PROFILE_EXISTS commands=$COMMANDS_DETECTED"
91+
fi
92+
93+
echo ""
94+
95+
# ============================================================
96+
# TEST GROUP 2: import-profile.sh
97+
# ============================================================
98+
echo "▸ import-profile.sh"
99+
100+
setup_profile
101+
EXPORT_FILE="$TEST_HOME/code-sensei-export.json"
102+
create_wrapped_export "$EXPORT_FILE"
103+
104+
PREVIEW=$(bash "$SCRIPT_DIR/scripts/import-profile.sh" "$EXPORT_FILE")
105+
PREVIEW_STATUS=$(echo "$PREVIEW" | jq -r '.status')
106+
TARGET_BELT=$(echo "$PREVIEW" | jq -r '.target_summary.belt')
107+
CURRENT_BELT=$(echo "$PREVIEW" | jq -r '.current_summary.belt')
108+
if [ "$PREVIEW_STATUS" = "preview" ] && [ "$TARGET_BELT" = "green" ] && [ "$CURRENT_BELT" = "yellow" ]; then
109+
pass "import preview shows target and current profile summaries"
110+
else
111+
fail "import preview shows target and current profile summaries" "status=$PREVIEW_STATUS target=$TARGET_BELT current=$CURRENT_BELT"
112+
fi
113+
114+
APPLY=$(bash "$SCRIPT_DIR/scripts/import-profile.sh" --apply "$EXPORT_FILE")
115+
APPLY_STATUS=$(echo "$APPLY" | jq -r '.status')
116+
IMPORTED_BELT=$(jq -r '.belt' "$TEST_HOME/.code-sensei/profile.json")
117+
if [ "$APPLY_STATUS" = "imported" ] && [ "$IMPORTED_BELT" = "green" ] && [ -f "$TEST_HOME/.code-sensei/profile.json.backup" ]; then
118+
pass "import apply writes profile and creates backup"
119+
else
120+
fail "import apply writes profile and creates backup" "status=$APPLY_STATUS belt=$IMPORTED_BELT backup=$( [ -f "$TEST_HOME/.code-sensei/profile.json.backup" ] && echo yes || echo no )"
121+
fi
122+
123+
echo ""
124+
125+
# ============================================================
126+
# TEST GROUP 3: track-command.sh redaction
127+
# ============================================================
128+
echo "▸ track-command.sh redaction"
129+
130+
setup_profile
131+
rm -f "$TEST_HOME/.code-sensei/session-commands.jsonl"
132+
cat <<'JSON' | bash "$SCRIPT_DIR/scripts/track-command.sh" >/dev/null 2>&1
133+
{"tool_input":{"command":"export OPENAI_API_KEY=sk-test-123 && curl -H \"Authorization: Bearer real-token\" https://user:secret@example.com --token cli-secret"}}
134+
JSON
135+
136+
LOG_CONTENT=$(cat "$TEST_HOME/.code-sensei/session-commands.jsonl")
137+
if echo "$LOG_CONTENT" | grep -q '\[REDACTED\]' && ! echo "$LOG_CONTENT" | grep -q 'sk-test-123' && ! echo "$LOG_CONTENT" | grep -q 'real-token' && ! echo "$LOG_CONTENT" | grep -q 'cli-secret' && ! echo "$LOG_CONTENT" | grep -q 'secret@example.com'; then
138+
pass "command log redacts sensitive values"
139+
else
140+
fail "command log redacts sensitive values" "$LOG_CONTENT"
141+
fi
142+
143+
echo ""
144+
echo "━━━ Summary ━━━"
145+
echo -e "Passed: ${GREEN}${PASS}${NC}"
146+
echo -e "Failed: ${RED}${FAIL}${NC}"
147+
echo ""
148+
149+
if [ "$FAIL" -ne 0 ]; then
150+
exit 1
151+
fi

0 commit comments

Comments
 (0)