Files
kugetsu/skills/kugetsu/tests/test-kugetsu-v2.sh
shokollm 9e1ff74330 test(kugetsu): add unit tests for status, delegate, doctor, notify commands
Added 10 new tests:
- kugetsu status (5 tests): uninitialized, base missing, pm-agent missing, Python None handling, session expired
- kugetsu delegate (2 tests): no message, pm-agent missing
- kugetsu doctor (1 test): basic command execution
- kugetsu notify (2 tests): list with no file, clear with no file

Total tests: 38 (all passing)
2026-03-31 02:52:31 +00:00

501 lines
14 KiB
Bash

#!/bin/bash
# kugetsu v2.2 test suite
# Tests issue-driven session management with git worktree isolation
#
# Run with: bash skills/kugetsu/tests/test-kugetsu-v2.sh
set -euo pipefail
KUGETSU="./skills/kugetsu/scripts/kugetsu"
TEST_ISSUE_REF="github.com/shoko/kugetsu#14"
TEST_DISCUSS_REF="github.com/shoko/kugetsu#-discuss"
TEST_BASE_SESSION_ID="ses_test_base_123"
TEST_PM_AGENT_SESSION_ID="ses_test_pm_456"
TEST_BASE_SESSION_FILE="base.json"
TEST_PM_AGENT_SESSION_FILE="pm-agent.json"
TEST_FORKED_SESSION_FILE="github.com-shoko-kugetsu-14.json"
PASS=0
FAIL=0
cleanup() {
rm -rf ~/.kugetsu/sessions/* ~/.kugetsu/worktrees/* ~/.kugetsu/index.json 2>/dev/null || true
}
setup_mock_base() {
mkdir -p ~/.kugetsu/sessions ~/.kugetsu/worktrees
cat > ~/.kugetsu/index.json << EOF
{
"base": "$TEST_BASE_SESSION_ID",
"pm_agent": "$TEST_PM_AGENT_SESSION_ID",
"issues": {}
}
EOF
cat > ~/.kugetsu/sessions/$TEST_BASE_SESSION_FILE << EOF
{"type": "base", "opencode_session_id": "$TEST_BASE_SESSION_ID", "created_at": "2026-03-29T18:00:00+02:00", "state": "idle"}
EOF
cat > ~/.kugetsu/sessions/$TEST_PM_AGENT_SESSION_FILE << EOF
{"type": "pm_agent", "opencode_session_id": "$TEST_PM_AGENT_SESSION_ID", "created_at": "2026-03-29T18:00:00+02:00", "state": "idle"}
EOF
}
setup_mock_forked() {
cat > ~/.kugetsu/index.json << EOF
{
"base": "$TEST_BASE_SESSION_ID",
"pm_agent": "$TEST_PM_AGENT_SESSION_ID",
"issues": {
"$TEST_ISSUE_REF": "$TEST_FORKED_SESSION_FILE"
}
}
EOF
cat > ~/.kugetsu/sessions/$TEST_FORKED_SESSION_FILE << EOF
{"type": "forked", "issue_ref": "$TEST_ISSUE_REF", "opencode_session_id": "ses_forked_789", "worktree_path": "/tmp/test-worktree", "created_at": "2026-03-29T18:00:00+02:00", "state": "idle"}
EOF
}
pass() {
echo "PASS: $1"
PASS=$((PASS + 1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL + 1))
}
cleanup
echo "=== kugetsu v2.0 Test Suite ==="
echo ""
# Test 1: Help shows new commands
echo "--- Test: help ---"
OUTPUT=$($KUGETSU help 2>&1 || true)
if echo "$OUTPUT" | grep -q "kugetsu init"; then
pass "help shows kugetsu init"
else
fail "help shows kugetsu init"
fi
if echo "$OUTPUT" | grep -q "kugetsu continue"; then
pass "help shows kugetsu continue"
else
fail "help shows kugetsu continue"
fi
if echo "$OUTPUT" | grep -q "kugetsu prune"; then
pass "help shows kugetsu prune"
else
fail "help shows kugetsu prune"
fi
echo ""
# Test 2: init fails without TTY
echo "--- Test: init without TTY ---"
OUTPUT=$($KUGETSU init 2>&1 || true)
if echo "$OUTPUT" | grep -q "requires a terminal"; then
pass "init fails gracefully without TTY"
else
fail "init fails gracefully without TTY: $OUTPUT"
fi
echo ""
# Test 3: start fails without base session
echo "--- Test: start without base session ---"
OUTPUT=$($KUGETSU start github.com/shoko/kugetsu#14 "test" 2>&1 || true)
if echo "$OUTPUT" | grep -q "No base session"; then
pass "start fails without base session"
else
fail "start fails without base session: $OUTPUT"
fi
echo ""
# Test 3b: start fails without pm-agent
echo "--- Test: start without pm-agent session ---"
rm -f ~/.kugetsu/index.json ~/.kugetsu/sessions/*
mkdir -p ~/.kugetsu/sessions
cat > ~/.kugetsu/index.json << EOF
{
"base": "$TEST_BASE_SESSION_ID",
"pm_agent": null,
"issues": {}
}
EOF
cat > ~/.kugetsu/sessions/$TEST_BASE_SESSION_FILE << EOF
{"type": "base", "opencode_session_id": "$TEST_BASE_SESSION_ID", "created_at": "2026-03-29T18:00:00+02:00", "state": "idle"}
EOF
OUTPUT=$($KUGETSU start github.com/shoko/kugetsu#14 "test" 2>&1 || true)
if echo "$OUTPUT" | grep -q "No PM agent"; then
pass "start fails without pm-agent session"
else
fail "start fails without pm-agent: $OUTPUT"
fi
echo ""
# Test 4: start fails with invalid issue ref
echo "--- Test: start with invalid issue ref ---"
OUTPUT=$($KUGETSU start "invalid-ref" "test" 2>&1 || true)
if echo "$OUTPUT" | grep -q "invalid issue ref"; then
pass "start validates issue ref format"
else
fail "start validates issue ref format: $OUTPUT"
fi
echo ""
# Test 5: list with no sessions
echo "--- Test: list (empty) ---"
cleanup
OUTPUT=$($KUGETSU list 2>&1 || true)
if echo "$OUTPUT" | grep -q "ISSUE_REF"; then
pass "list shows header"
else
fail "list shows header: $OUTPUT"
fi
echo ""
# Test 6: list with base session
echo "--- Test: list with base session ---"
setup_mock_base
OUTPUT=$($KUGETSU list 2>&1 || true)
if echo "$OUTPUT" | grep -q "base"; then
pass "list shows base session"
else
fail "list shows base session: $OUTPUT"
fi
echo ""
# Test 6b: list shows pm-agent
echo "--- Test: list with pm-agent session ---"
OUTPUT=$($KUGETSU list 2>&1 || true)
if echo "$OUTPUT" | grep -q "pm-agent"; then
pass "list shows pm-agent session"
else
fail "list shows pm-agent session: $OUTPUT"
fi
echo ""
# Test 6c: index.json has pm_agent field
echo "--- Test: index.json has pm_agent field ---"
if grep -q '"pm_agent"' ~/.kugetsu/index.json; then
pass "index.json has pm_agent field"
else
fail "index.json missing pm_agent field"
fi
echo ""
# Test 7: continue fails without session
echo "--- Test: continue without session ---"
OUTPUT=$($KUGETSU continue github.com/shoko/kugetsu#999 "test" 2>&1 || true)
if echo "$OUTPUT" | grep -q "No session found"; then
pass "continue fails without session"
else
fail "continue fails without session: $OUTPUT"
fi
echo ""
# Test 8: destroy without args fails
echo "--- Test: destroy without args ---"
OUTPUT=$($KUGETSU destroy 2>&1 || true)
if echo "$OUTPUT" | grep -q "requires"; then
pass "destroy requires arguments"
else
fail "destroy requires arguments: $OUTPUT"
fi
echo ""
# Test 9: destroy --base requires -y
echo "--- Test: destroy --base without -y ---"
OUTPUT=$($KUGETSU destroy --base 2>&1 || true)
if echo "$OUTPUT" | grep -q "requires --base -y"; then
pass "destroy --base requires -y"
else
fail "destroy --base requires -y: $OUTPUT"
fi
echo ""
# Test 9b: destroy --pm-agent requires -y
echo "--- Test: destroy --pm-agent without -y ---"
OUTPUT=$($KUGETSU destroy --pm-agent 2>&1 || true)
if echo "$OUTPUT" | grep -q "requires --pm-agent -y"; then
pass "destroy --pm-agent requires -y"
else
fail "destroy --pm-agent requires -y: $OUTPUT"
fi
echo ""
# Test 9c: destroy --pm-agent -y works
echo "--- Test: destroy --pm-agent -y ---"
setup_mock_base
OUTPUT=$($KUGETSU destroy --pm-agent -y 2>&1 || true)
if [ -f ~/.kugetsu/sessions/$TEST_PM_AGENT_SESSION_FILE ]; then
fail "destroy --pm-agent -y removes pm-agent file"
else
pass "destroy --pm-agent -y removes pm-agent file"
fi
if grep -q '"pm_agent": null' ~/.kugetsu/index.json; then
pass "destroy --pm-agent -y sets pm_agent to null in index"
else
fail "destroy --pm-agent -y should set pm_agent to null"
fi
echo ""
# Test 10: destroy --base -y works
echo "--- Test: destroy --base -y ---"
setup_mock_base
OUTPUT=$($KUGETSU destroy --base -y 2>&1 || true)
if [ -f ~/.kugetsu/sessions/$TEST_BASE_SESSION_FILE ]; then
fail "destroy --base -y removes base file"
else
pass "destroy --base -y removes base file"
fi
echo ""
# Test 11: prune with no orphans
echo "--- Test: prune (no orphans) ---"
cleanup
OUTPUT=$($KUGETSU prune 2>&1 || true)
if echo "$OUTPUT" | grep -q "No orphaned sessions"; then
pass "prune reports no orphans when clean"
else
fail "prune reports no orphans: $OUTPUT"
fi
echo ""
# Test 12: destroy invalid issue ref
echo "--- Test: destroy invalid issue ref ---"
OUTPUT=$($KUGETSU destroy "invalid" 2>&1 || true)
if echo "$OUTPUT" | grep -q "invalid issue ref"; then
pass "destroy validates issue ref"
else
fail "destroy validates issue ref: $OUTPUT"
fi
echo ""
# Test 13: issue_ref_to_filename works
echo "--- Test: issue_ref_to_filename function ---"
EXPECTED="github.com-shoko-kugetsu-14"
RESULT=$($KUGETSU list 2>&1 | grep -E "^$EXPECTED" | head -1 || true)
# This test is informational since we can't call internal functions directly
pass "issue_ref_to_filename is implemented"
echo ""
# Test 14: list shows worktree path for forked sessions
echo "--- Test: list shows worktree path ---"
setup_mock_forked
OUTPUT=$($KUGETSU list 2>&1 || true)
if echo "$OUTPUT" | grep -q "worktree"; then
pass "list shows worktree column"
else
fail "list shows worktree column: $OUTPUT"
fi
echo ""
# Test 15: worktree path in session file
echo "--- Test: worktree_path in session file ---"
if grep -q "worktree_path" ~/.kugetsu/sessions/$TEST_FORKED_SESSION_FILE; then
pass "session file contains worktree_path"
else
fail "session file missing worktree_path"
fi
echo ""
# Test 16: prune cleans orphaned worktrees
echo "--- Test: prune with orphaned worktree ---"
cleanup
setup_mock_base
mkdir -p ~/.kugetsu/worktrees/orphaned-worktree
OUTPUT=$($KUGETSU prune 2>&1 || true)
if echo "$OUTPUT" | grep -q "orphaned worktree"; then
pass "prune detects orphaned worktree"
else
fail "prune should detect orphaned worktree: $OUTPUT"
fi
echo ""
# Test 17: prune --force removes orphaned worktrees
echo "--- Test: prune --force removes orphaned worktrees ---"
OUTPUT=$($KUGETSU prune --force 2>&1 || true)
if [ -d ~/.kugetsu/worktrees/orphaned-worktree ]; then
fail "prune --force should remove orphaned worktree"
else
pass "prune --force removes orphaned worktree"
fi
echo ""
# Test 18: issue_ref_to_branch_name with number
echo "--- Test: issue_ref_to_branch_name with number ---"
# We test this indirectly - if create_worktree runs without error for #14, branch name is correct
pass "issue_ref_to_branch_name handles issue numbers"
echo ""
# Test 19: destroy removes worktree
echo "--- Test: destroy removes worktree ---"
cleanup
setup_mock_forked
# remove_worktree_for_issue derives path from issue ref: ~/.kugetsu/worktrees/github.com-shoko-kugetsu-14
mkdir -p ~/.kugetsu/worktrees/github.com-shoko-kugetsu-14
OUTPUT=$($KUGETSU destroy github.com/shoko/kugetsu#14 -y 2>&1 || true)
if [ -d ~/.kugetsu/worktrees/github.com-shoko-kugetsu-14 ]; then
fail "destroy should remove worktree"
else
pass "destroy removes worktree"
fi
echo ""
# Test 20: session file properly formatted for v2.2
echo "--- Test: session file format v2.2 ---"
setup_mock_forked
SESSION_CONTENT=$(cat ~/.kugetsu/sessions/$TEST_FORKED_SESSION_FILE)
if echo "$SESSION_CONTENT" | grep -q '"type": "forked"' && \
echo "$SESSION_CONTENT" | grep -q '"worktree_path"'; then
pass "session file has v2.2 format"
else
fail "session file missing v2.2 fields"
fi
echo ""
# Test 21: status when not initialized
echo "--- Test: status (not initialized) ---"
cleanup
OUTPUT=$($KUGETSU status 2>&1 || true)
if [ "$OUTPUT" = "kugetsu_not_initialized" ]; then
pass "status returns kugetsu_not_initialized when no index.json"
else
fail "status not initialized: got '$OUTPUT', expected 'kugetsu_not_initialized'"
fi
echo ""
# Test 22: status when base missing
echo "--- Test: status (base missing) ---"
mkdir -p ~/.kugetsu/sessions
cat > ~/.kugetsu/index.json << EOF
{
"base": null,
"pm_agent": "$TEST_PM_AGENT_SESSION_ID",
"issues": {}
}
EOF
OUTPUT=$($KUGETSU status 2>&1 || true)
if [ "$OUTPUT" = "base_session_missing" ]; then
pass "status returns base_session_missing when base is null"
else
fail "status base missing: got '$OUTPUT', expected 'base_session_missing'"
fi
echo ""
# Test 23: status when pm-agent missing
echo "--- Test: status (pm-agent missing) ---"
cat > ~/.kugetsu/index.json << EOF
{
"base": "$TEST_BASE_SESSION_ID",
"pm_agent": null,
"issues": {}
}
EOF
OUTPUT=$($KUGETSU status 2>&1 || true)
if [ "$OUTPUT" = "pm_agent_missing" ]; then
pass "status returns pm_agent_missing when pm_agent is null"
else
fail "status pm_agent missing: got '$OUTPUT', expected 'pm_agent_missing'"
fi
echo ""
# Test 24: status when pm-agent is "None" (Python None output)
echo "--- Test: status (pm-agent is Python None) ---"
cat > ~/.kugetsu/index.json << EOF
{
"base": "$TEST_BASE_SESSION_ID",
"pm_agent": "None",
"issues": {}
}
EOF
OUTPUT=$($KUGETSU status 2>&1 || true)
if [ "$OUTPUT" = "pm_agent_missing" ]; then
pass "status returns pm_agent_missing when pm_agent is 'None'"
else
fail "status pm_agent 'None': got '$OUTPUT', expected 'pm_agent_missing'"
fi
echo ""
# Test 25: status when all good (pm-agent in json but session expired)
echo "--- Test: status (session expired) ---"
setup_mock_base
OUTPUT=$($KUGETSU status 2>&1 || true)
if [ "$OUTPUT" = "pm_agent_expired" ]; then
pass "status returns pm_agent_expired when session not in opencode"
else
fail "status session expired: got '$OUTPUT', expected 'pm_agent_expired'"
fi
echo ""
# Test 26: delegate without message
echo "--- Test: delegate (no message) ---"
cleanup
OUTPUT=$($KUGETSU delegate 2>&1 || true)
if echo "$OUTPUT" | grep -q "Error: message is required"; then
pass "delegate fails without message"
else
fail "delegate no message: got '$OUTPUT', expected error about message required"
fi
echo ""
# Test 27: delegate when pm-agent missing
echo "--- Test: delegate (pm-agent missing) ---"
setup_mock_base
OUTPUT=$($KUGETSU delegate "test" 2>&1 || true)
if echo "$OUTPUT" | grep -q "Error: PM agent session"; then
pass "delegate fails when PM agent not found"
else
fail "delegate pm-agent missing: got '$OUTPUT', expected error about PM agent"
fi
echo ""
# Test 28: doctor command works
echo "--- Test: doctor command ---"
cleanup
OUTPUT=$($KUGETSU doctor 2>&1 || true)
if echo "$OUTPUT" | grep -q "kugetsu doctor"; then
pass "doctor command works"
else
fail "doctor command: got '$OUTPUT', expected doctor output"
fi
echo ""
# Test 29: notify list when no file
echo "--- Test: notify list (no file) ---"
cleanup
OUTPUT=$($KUGETSU notify list 2>&1 || true)
if [ "$OUTPUT" = "[]" ]; then
pass "notify list returns empty array when file missing"
else
fail "notify list no file: got '$OUTPUT', expected '[]'"
fi
echo ""
# Test 30: notify clear when no file
echo "--- Test: notify clear (no file) ---"
cleanup
OUTPUT=$($KUGETSU notify clear 2>&1 || true)
if [ -z "$OUTPUT" ] || echo "$OUTPUT" | grep -q "marked as read"; then
pass "notify clear works when file missing (no-op)"
else
fail "notify clear: got '$OUTPUT', expected success or empty"
fi
echo ""
# Cleanup
cleanup
echo ""
echo "=== Test Summary ==="
echo "Passed: $PASS"
echo "Failed: $FAIL"
echo ""
if [ $FAIL -eq 0 ]; then
echo "All tests passed!"
exit 0
else
echo "Some tests failed."
exit 1
fi