|
| 1 | +#!/usr/bin/env bash |
| 2 | +# SPDX-License-Identifier: PMPL-1.0-or-later |
| 3 | +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 4 | +# |
| 5 | +# gitbot-fleet — End-to-End Tests |
| 6 | +# |
| 7 | +# Validates the gitbot fleet coordinator scripts, bot directory structure, |
| 8 | +# shell script syntax, and required configuration files/directories. |
| 9 | +# |
| 10 | +# Usage: |
| 11 | +# bash tests/e2e.sh |
| 12 | +# just e2e |
| 13 | + |
| 14 | +set -euo pipefail |
| 15 | + |
| 16 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 17 | +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 18 | + |
| 19 | +PASS=0 |
| 20 | +FAIL=0 |
| 21 | +SKIP=0 |
| 22 | + |
| 23 | +# ─── Colour helpers ────────────────────────────────────────────────── |
| 24 | +green() { printf '\033[32m%s\033[0m\n' "$*"; } |
| 25 | +red() { printf '\033[31m%s\033[0m\n' "$*"; } |
| 26 | +yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } |
| 27 | +bold() { printf '\033[1m%s\033[0m\n' "$*"; } |
| 28 | + |
| 29 | +# ─── Assertion helpers ─────────────────────────────────────────────── |
| 30 | + |
| 31 | +# check <label> <expected-substring> <actual> |
| 32 | +check() { |
| 33 | + local name="$1" expected="$2" actual="$3" |
| 34 | + if echo "$actual" | grep -q "$expected"; then |
| 35 | + green " PASS: $name" |
| 36 | + PASS=$((PASS + 1)) |
| 37 | + else |
| 38 | + red " FAIL: $name (expected '$expected', got '${actual:0:120}')" |
| 39 | + FAIL=$((FAIL + 1)) |
| 40 | + fi |
| 41 | +} |
| 42 | + |
| 43 | +# check_exists <label> <path> |
| 44 | +check_exists() { |
| 45 | + local name="$1" path="$2" |
| 46 | + if [ -e "$path" ]; then |
| 47 | + green " PASS: $name" |
| 48 | + PASS=$((PASS + 1)) |
| 49 | + else |
| 50 | + red " FAIL: $name (path not found: $path)" |
| 51 | + FAIL=$((FAIL + 1)) |
| 52 | + fi |
| 53 | +} |
| 54 | + |
| 55 | +# check_dir <label> <path> |
| 56 | +check_dir() { |
| 57 | + local name="$1" path="$2" |
| 58 | + if [ -d "$path" ]; then |
| 59 | + green " PASS: $name" |
| 60 | + PASS=$((PASS + 1)) |
| 61 | + else |
| 62 | + red " FAIL: $name (directory not found: $path)" |
| 63 | + FAIL=$((FAIL + 1)) |
| 64 | + fi |
| 65 | +} |
| 66 | + |
| 67 | +# check_bash_syntax <label> <script> |
| 68 | +check_bash_syntax() { |
| 69 | + local name="$1" script="$2" |
| 70 | + if bash -n "$script" 2>/dev/null; then |
| 71 | + green " PASS: $name" |
| 72 | + PASS=$((PASS + 1)) |
| 73 | + else |
| 74 | + red " FAIL: $name (bash syntax error in $script)" |
| 75 | + bash -n "$script" 2>&1 | head -5 >&2 |
| 76 | + FAIL=$((FAIL + 1)) |
| 77 | + fi |
| 78 | +} |
| 79 | + |
| 80 | +# skip_test <label> <reason> |
| 81 | +skip_test() { |
| 82 | + yellow " SKIP: $1 ($2)" |
| 83 | + SKIP=$((SKIP + 1)) |
| 84 | +} |
| 85 | + |
| 86 | +echo "═══════════════════════════════════════════════════════════════" |
| 87 | +echo " gitbot-fleet — End-to-End Tests" |
| 88 | +echo "═══════════════════════════════════════════════════════════════" |
| 89 | +echo "" |
| 90 | + |
| 91 | +# ─── Section 1: Repository structure ──────────────────────────────── |
| 92 | +bold "Section 1: Repository structure" |
| 93 | + |
| 94 | +check_dir "bots/ directory exists" "$PROJECT_DIR/bots" |
| 95 | +check_dir "campaigns/ directory exists" "$PROJECT_DIR/campaigns" |
| 96 | +check_dir "deploy/ directory exists" "$PROJECT_DIR/deploy" |
| 97 | +check_dir "hooks/ directory exists" "$PROJECT_DIR/hooks" |
| 98 | +check_exists "AI manifest present" "$PROJECT_DIR/0-AI-MANIFEST.a2ml" |
| 99 | +check_exists "fleet-coordinator.sh present" "$PROJECT_DIR/fleet-coordinator.sh" |
| 100 | +check_exists "learning-monitor.sh present" "$PROJECT_DIR/learning-monitor.sh" |
| 101 | +check_exists "Justfile present" "$PROJECT_DIR/Justfile" |
| 102 | +check_exists "docker-compose.yml present" "$PROJECT_DIR/docker-compose.yml" |
| 103 | + |
| 104 | +echo "" |
| 105 | + |
| 106 | +# ─── Section 2: Bot directory structure ───────────────────────────── |
| 107 | +bold "Section 2: Bot directory structure (expected fleet members)" |
| 108 | + |
| 109 | +EXPECTED_BOTS="rhodibot echidnabot sustainabot panicbot glambot seambot finishingbot accessibilitybot cipherbot" |
| 110 | +for bot in $EXPECTED_BOTS; do |
| 111 | + check_dir "bots/$bot directory exists" "$PROJECT_DIR/bots/$bot" |
| 112 | +done |
| 113 | + |
| 114 | +echo "" |
| 115 | + |
| 116 | +# ─── Section 3: Shell script syntax validation ─────────────────────── |
| 117 | +bold "Section 3: Shell script syntax checks" |
| 118 | + |
| 119 | +for script in \ |
| 120 | + "$PROJECT_DIR/fleet-coordinator.sh" \ |
| 121 | + "$PROJECT_DIR/learning-monitor.sh" \ |
| 122 | + "$PROJECT_DIR/run-fleet.sh" \ |
| 123 | + "$PROJECT_DIR/setup.sh"; do |
| 124 | + if [ -f "$script" ]; then |
| 125 | + check_bash_syntax "$(basename "$script") syntax valid" "$script" |
| 126 | + else |
| 127 | + skip_test "$(basename "$script") syntax check" "file not present" |
| 128 | + fi |
| 129 | +done |
| 130 | + |
| 131 | +# Also check hooks directory for bash scripts |
| 132 | +if [ -d "$PROJECT_DIR/hooks" ]; then |
| 133 | + for script in "$PROJECT_DIR/hooks/"*.sh; do |
| 134 | + [ -f "$script" ] || continue |
| 135 | + check_bash_syntax "hooks/$(basename "$script") syntax valid" "$script" |
| 136 | + done |
| 137 | +fi |
| 138 | + |
| 139 | +echo "" |
| 140 | + |
| 141 | +# ─── Section 4: fleet-coordinator.sh usage output ─────────────────── |
| 142 | +bold "Section 4: fleet-coordinator.sh help/usage output" |
| 143 | + |
| 144 | +if [ -f "$PROJECT_DIR/fleet-coordinator.sh" ]; then |
| 145 | + OUTPUT=$(bash "$PROJECT_DIR/fleet-coordinator.sh" --help 2>&1 || bash "$PROJECT_DIR/fleet-coordinator.sh" help 2>&1 || true) |
| 146 | + if [ -z "$OUTPUT" ]; then |
| 147 | + # Try calling with no args — many scripts print usage on no args |
| 148 | + OUTPUT=$(bash "$PROJECT_DIR/fleet-coordinator.sh" 2>&1 || true) |
| 149 | + fi |
| 150 | + check "fleet-coordinator has usage/help text" "USAGE\|Usage\|usage\|COMMAND\|command\|fleet" "$OUTPUT" |
| 151 | + check "fleet-coordinator mentions bots/run-scan" "run-scan\|scan\|bot\|fleet\|deploy" "$OUTPUT" |
| 152 | +else |
| 153 | + skip_test "fleet-coordinator.sh help" "script not found" |
| 154 | +fi |
| 155 | + |
| 156 | +echo "" |
| 157 | + |
| 158 | +# ─── Section 5: GitHub Actions workflows ──────────────────────────── |
| 159 | +bold "Section 5: GitHub Actions workflow validity (YAML syntax)" |
| 160 | + |
| 161 | +WORKFLOWS_DIR="$PROJECT_DIR/.github/workflows" |
| 162 | +if [ -d "$WORKFLOWS_DIR" ]; then |
| 163 | + WORKFLOW_COUNT=0 |
| 164 | + WORKFLOW_FAIL=0 |
| 165 | + for wf in "$WORKFLOWS_DIR/"*.yml; do |
| 166 | + [ -f "$wf" ] || continue |
| 167 | + WORKFLOW_COUNT=$((WORKFLOW_COUNT + 1)) |
| 168 | + # Validate YAML by checking with python3 (available in CI) |
| 169 | + if command -v python3 >/dev/null 2>&1; then |
| 170 | + if python3 -c "import yaml; yaml.safe_load(open('$wf'))" 2>/dev/null; then |
| 171 | + : |
| 172 | + else |
| 173 | + red " FAIL: workflow YAML parse failed: $(basename "$wf")" |
| 174 | + FAIL=$((FAIL + 1)) |
| 175 | + WORKFLOW_FAIL=$((WORKFLOW_FAIL + 1)) |
| 176 | + fi |
| 177 | + fi |
| 178 | + # Check SPDX header |
| 179 | + if grep -q "SPDX-License-Identifier" "$wf" 2>/dev/null; then |
| 180 | + : |
| 181 | + else |
| 182 | + yellow " WARN: missing SPDX header in $(basename "$wf")" |
| 183 | + fi |
| 184 | + done |
| 185 | + if [ "$WORKFLOW_COUNT" -gt 0 ] && [ "$WORKFLOW_FAIL" -eq 0 ]; then |
| 186 | + green " PASS: all $WORKFLOW_COUNT workflow YAML files parse successfully" |
| 187 | + PASS=$((PASS + 1)) |
| 188 | + elif [ "$WORKFLOW_COUNT" -eq 0 ]; then |
| 189 | + skip_test "workflow YAML validation" "no .yml files in .github/workflows" |
| 190 | + fi |
| 191 | + check "minimum workflow count (>=5)" "[5-9]\|[0-9][0-9]" "$WORKFLOW_COUNT" |
| 192 | +else |
| 193 | + skip_test "workflow YAML validation" ".github/workflows not found" |
| 194 | +fi |
| 195 | + |
| 196 | +echo "" |
| 197 | + |
| 198 | +# ─── Section 6: bot Cargo.toml manifests ──────────────────────────── |
| 199 | +bold "Section 6: Bot Cargo manifests" |
| 200 | + |
| 201 | +for bot in rhodibot echidnabot panicbot cipherbot; do |
| 202 | + CARGO="$PROJECT_DIR/bots/$bot/Cargo.toml" |
| 203 | + if [ -f "$CARGO" ]; then |
| 204 | + OUTPUT=$(cat "$CARGO") |
| 205 | + check "$bot Cargo.toml has package name" "name.*$bot\|name.*\"$bot\"" "$OUTPUT" |
| 206 | + check "$bot Cargo.toml has PMPL license" "PMPL\|pmpl" "$OUTPUT" |
| 207 | + else |
| 208 | + skip_test "$bot Cargo.toml" "file not found" |
| 209 | + fi |
| 210 | +done |
| 211 | + |
| 212 | +echo "" |
| 213 | + |
| 214 | +# ═══════════════════════════════════════════════════════════════════════ |
| 215 | +# Summary |
| 216 | +# ═══════════════════════════════════════════════════════════════════════ |
| 217 | +echo "═══════════════════════════════════════════════════════════════" |
| 218 | +printf " Results: " |
| 219 | +green "PASS=$PASS" | tr -d '\n' |
| 220 | +echo -n " " |
| 221 | +if [ "$FAIL" -gt 0 ]; then red "FAIL=$FAIL" | tr -d '\n'; else echo -n "FAIL=0"; fi |
| 222 | +echo -n " " |
| 223 | +if [ "$SKIP" -gt 0 ]; then yellow "SKIP=$SKIP"; else echo "SKIP=0"; fi |
| 224 | +echo "═══════════════════════════════════════════════════════════════" |
| 225 | + |
| 226 | +exit "$FAIL" |
0 commit comments