diff --git a/skills/review-gate/tests/test-review-gate.sh b/skills/review-gate/tests/test-review-gate.sh new file mode 100755 index 0000000..3e6ca75 --- /dev/null +++ b/skills/review-gate/tests/test-review-gate.sh @@ -0,0 +1,321 @@ +#!/usr/bin/env bash +# Tests for review-gate CLI +# Run: bash skills/review-gate/tests/test-review-gate.sh + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REVIEW_GATE="$SCRIPT_DIR/../scripts/review-gate" +PASSED=0 +FAILED=0 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' + +# Test state directory (isolated per test run) +TEST_STATE_DIR=$(mktemp -d) +export REVIEW_STATE_DIR="$TEST_STATE_DIR" + +cleanup() { + rm -rf "$TEST_STATE_DIR" +} +trap cleanup EXIT + +# Test helpers +assert_exit_code() { + local description="$1" + local expected="$2" + local actual="$3" + + if [[ "$actual" == "$expected" ]]; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " Expected exit code: $expected, Actual: $actual" + ((FAILED++)) + fi +} + +assert_output_contains() { + local description="$1" + local pattern="$2" + local output="$3" + + if echo "$output" | grep -q "$pattern"; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " Pattern not found: '$pattern'" + echo " Output was:" + echo "$output" | sed 's/^/ /' + ((FAILED++)) + fi +} + +assert_output_not_contains() { + local description="$1" + local pattern="$2" + local output="$3" + + if ! echo "$output" | grep -q "$pattern"; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " Pattern found but should not be: '$pattern'" + ((FAILED++)) + fi +} + +assert_file_exists() { + local description="$1" + local file="$2" + + if [[ -f "$file" ]]; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " File does not exist: $file" + ((FAILED++)) + fi +} + +assert_file_not_exists() { + local description="$1" + local file="$2" + + if [[ ! -f "$file" ]]; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " File exists but should not: $file" + ((FAILED++)) + fi +} + +assert_json_field() { + local description="$1" + local expected="$2" + local field="$3" + local file="$4" + + local actual + actual=$(jq -r "$field" "$file" 2>/dev/null) + + if [[ "$actual" == "$expected" ]]; then + echo -e "${GREEN}PASS${NC}: $description" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}: $description" + echo " Expected $field: '$expected', Actual: '$actual'" + ((FAILED++)) + fi +} + +run_gate() { + "$REVIEW_GATE" "$@" 2>&1 +} + +run_gate_status() { + "$REVIEW_GATE" "$@" 2>&1 + echo $? +} + +echo "=== Review Gate CLI Tests ===" +echo "State dir: $TEST_STATE_DIR" +echo "" + +# --- Help Command --- +echo "## Help Command" + +output=$(run_gate help) +assert_output_contains "Help shows check command" "check \[session\]" "$output" +assert_output_contains "Help shows enable command" "enable \[session\]" "$output" +assert_output_contains "Help shows approve command" "approve \[session\]" "$output" +assert_output_contains "Help shows reject command" "reject \[session\]" "$output" + +# --- Check Without State --- +echo "" +echo "## Check Without State (No Review Required)" + +output=$(run_gate check no-state-session) +exit_code=$? +assert_exit_code "Check returns 0 when no state exists" "0" "$exit_code" +assert_output_contains "Check indicates no review required" "No review required" "$output" + +# --- Enable Command --- +echo "" +echo "## Enable Command" + +output=$(run_gate enable test-enable) +assert_output_contains "Enable confirms session" "Review enabled" "$output" +assert_file_exists "State file created" "$TEST_STATE_DIR/test-enable.json" +assert_json_field "Status is pending" "pending" ".status" "$TEST_STATE_DIR/test-enable.json" +assert_json_field "Session ID stored" "test-enable" ".session_id" "$TEST_STATE_DIR/test-enable.json" + +# --- Check When Pending --- +echo "" +echo "## Check When Pending (Should Block)" + +run_gate enable test-pending > /dev/null +output=$(run_gate check test-pending) +exit_code=$? +assert_exit_code "Check returns 1 when pending" "1" "$exit_code" +assert_output_contains "Check shows BLOCKED" "BLOCKED" "$output" +assert_output_contains "Check shows pending status" "pending" "$output" + +# --- Approve Command --- +echo "" +echo "## Approve Command" + +run_gate enable test-approve > /dev/null +output=$(run_gate approve test-approve) +assert_output_contains "Approve confirms" "Review approved" "$output" +assert_json_field "Status changed to approved" "approved" ".status" "$TEST_STATE_DIR/test-approve.json" + +# --- Check When Approved --- +echo "" +echo "## Check When Approved (Should Allow)" + +output=$(run_gate check test-approve) +exit_code=$? +assert_exit_code "Check returns 0 when approved" "0" "$exit_code" +assert_output_contains "Check shows approved" "approved" "$output" + +# --- Reject Command --- +echo "" +echo "## Reject Command" + +run_gate enable test-reject > /dev/null +output=$(run_gate reject test-reject "Missing tests") +assert_output_contains "Reject confirms" "Review rejected" "$output" +assert_output_contains "Reject shows reason" "Missing tests" "$output" +assert_json_field "Status changed to rejected" "rejected" ".status" "$TEST_STATE_DIR/test-reject.json" +assert_json_field "Reason stored" "Missing tests" ".reason" "$TEST_STATE_DIR/test-reject.json" + +# --- Check When Rejected --- +echo "" +echo "## Check When Rejected (Should Block)" + +output=$(run_gate check test-reject) +exit_code=$? +assert_exit_code "Check returns 1 when rejected" "1" "$exit_code" +assert_output_contains "Check shows BLOCKED" "BLOCKED" "$output" +assert_output_contains "Check shows reason" "Missing tests" "$output" + +# --- Reject With Issues --- +echo "" +echo "## Reject With Multiple Issues" + +run_gate enable test-issues > /dev/null +output=$(run_gate reject test-issues "Quality issues" "No unit tests" "Missing docs" "Bad naming") + +issues=$(jq -r '.issues | length' "$TEST_STATE_DIR/test-issues.json") +if [[ "$issues" == "3" ]]; then + echo -e "${GREEN}PASS${NC}: Three issues stored" + ((PASSED++)) +else + echo -e "${RED}FAIL${NC}: Three issues stored (got $issues)" + ((FAILED++)) +fi + +assert_json_field "First issue stored" "No unit tests" ".issues[0]" "$TEST_STATE_DIR/test-issues.json" + +# Check displays issues +output=$(run_gate check test-issues) +assert_output_contains "Check displays issues" "No unit tests" "$output" + +# --- Status Command --- +echo "" +echo "## Status Command" + +output=$(run_gate status test-issues) +assert_output_contains "Status shows session" "test-issues" "$output" +assert_output_contains "Status shows rejected" "rejected" "$output" + +# Status for non-existent session +output=$(run_gate status nonexistent) +assert_output_contains "Status handles missing session" "No review state" "$output" + +# --- List Command --- +echo "" +echo "## List Command" + +output=$(run_gate list) +assert_output_contains "List shows test-enable" "test-enable" "$output" +assert_output_contains "List shows test-approve" "test-approve" "$output" +assert_output_contains "List shows statuses" "approved" "$output" +assert_output_contains "List shows rejected" "rejected" "$output" + +# --- Session ID Auto-Detection --- +echo "" +echo "## Session ID Auto-Detection" + +# From REVIEW_SESSION_ID +export REVIEW_SESSION_ID="env-review-id" +output=$(run_gate enable) +assert_file_exists "Uses REVIEW_SESSION_ID" "$TEST_STATE_DIR/env-review-id.json" +unset REVIEW_SESSION_ID + +# From CLAUDE_SESSION_ID +export CLAUDE_SESSION_ID="env-claude-id" +output=$(run_gate enable) +assert_file_exists "Uses CLAUDE_SESSION_ID" "$TEST_STATE_DIR/env-claude-id.json" +unset CLAUDE_SESSION_ID + +# --- Approve Without Prior Enable --- +echo "" +echo "## Approve Without Prior Enable" + +output=$(run_gate approve new-approve) +assert_output_contains "Approve works without enable" "approved" "$output" +assert_file_exists "State file created on approve" "$TEST_STATE_DIR/new-approve.json" + +# --- Re-enable After Approve --- +echo "" +echo "## Re-enable After Approve (Workflow Reset)" + +run_gate approve reset-session > /dev/null +run_gate enable reset-session > /dev/null +assert_json_field "Status reset to pending" "pending" ".status" "$TEST_STATE_DIR/reset-session.json" + +output=$(run_gate check reset-session) +exit_code=$? +assert_exit_code "Check blocks after re-enable" "1" "$exit_code" + +# --- Clean Command --- +echo "" +echo "## Clean Command" + +# Create an old file (touch with old timestamp not reliable in all envs, so just test the command runs) +output=$(run_gate clean 0d) +assert_output_contains "Clean runs" "Cleaning" "$output" + +# --- Unknown Command --- +echo "" +echo "## Error Handling" + +output=$(run_gate unknown-cmd 2>&1) +exit_code=$? +assert_exit_code "Unknown command returns 1" "1" "$exit_code" +assert_output_contains "Unknown command shows error" "Unknown command" "$output" + +# --- Summary --- +echo "" +echo "=== Summary ===" +echo -e "Passed: ${GREEN}$PASSED${NC}" +echo -e "Failed: ${RED}$FAILED${NC}" + +if [[ $FAILED -gt 0 ]]; then + exit 1 +fi + +echo "" +echo -e "${GREEN}All tests passed!${NC}"