Files
TSYSDevStack-SupportStack-L…/demo/scripts/demo-test.sh
reachableceo 25f7a6cd75 feat(demo): migrate 5 SelfStack services to demo stack (16→24 services)
Add Reactive Resume, Metrics, Kiwix, Resume Matcher, and Apple Health
from the earlier SelfStack project. Rewrite Apple Health collector to
use InfluxDB v2 with proper error handling. Update all tests, scripts,
Homepage config, env template, and documentation for the expanded stack.

New services:
- Reactive Resume (4016) + Postgres/Minio/Chrome companions
- Metrics (4021) - GitHub metrics visualization
- Kiwix (4022) - offline wiki reader
- Resume Matcher (4023) - AI resume screening
- Apple Health (4024) - health data collector → InfluxDB v2

Also adds git policy to AGENTS.md: always commit and push automatically.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-05-08 12:28:56 -05:00

261 lines
7.6 KiB
Bash
Executable File

#!/bin/bash
# TSYS Developer Support Stack - Demo Testing Script
# Version: 2.0
# Purpose: Comprehensive QA and validation
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
DEMO_ENV_FILE="$PROJECT_ROOT/demo.env"
COMPOSE_FILE="$PROJECT_ROOT/docker-compose.yml"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
TESTS_PASSED=0
TESTS_FAILED=0
TESTS_TOTAL=0
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[PASS]${NC} $1"; ((TESTS_PASSED++)) || true; }
log_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[FAIL]${NC} $1"; ((TESTS_FAILED++)) || true; }
log_test() { echo -e "${BLUE}[TEST]${NC} $1"; ((TESTS_TOTAL++)) || true; }
test_file_ownership() {
log_test "File ownership (no root-owned files)"
local root_files
root_files=$(find "$PROJECT_ROOT" -type f -user root 2>/dev/null || true)
if [[ -z "$root_files" ]]; then
log_success "No root-owned files"
else
log_error "Root-owned files found: $root_files"
fi
}
test_user_mapping() {
log_test "UID/GID detection"
source "$DEMO_ENV_FILE"
if [[ -z "${DEMO_UID:-}" || -z "${DEMO_GID:-}" ]]; then
log_error "DEMO_UID or DEMO_GID not set"
return
fi
local cur_uid cur_gid
cur_uid=$(id -u)
cur_gid=$(id -g)
if [[ "$DEMO_UID" -eq "$cur_uid" && "$DEMO_GID" -eq "$cur_gid" ]]; then
log_success "UID/GID correct ($DEMO_UID/$DEMO_GID)"
else
log_error "UID/GID mismatch: env=$DEMO_UID/$DEMO_GID actual=$cur_uid/$cur_gid"
fi
}
test_docker_group() {
log_test "Docker group access"
source "$DEMO_ENV_FILE"
if [[ -z "${DEMO_DOCKER_GID:-}" ]]; then
log_error "DEMO_DOCKER_GID not set"
return
fi
local actual_gid
actual_gid=$(getent group docker | cut -d: -f3)
if [[ "$DEMO_DOCKER_GID" -eq "$actual_gid" ]]; then
log_success "Docker GID correct ($DEMO_DOCKER_GID)"
else
log_error "Docker GID mismatch: env=$DEMO_DOCKER_GID actual=$actual_gid"
fi
}
test_service_health() {
log_test "Service health"
local unhealthy=0
while IFS= read -r line; do
local name status
name=$(echo "$line" | awk '{print $1}')
[[ "$name" == "NAMES" || -z "$name" ]] && continue
if echo "$line" | grep -q "(healthy)"; then
log_success "$name healthy"
elif echo "$line" | grep -q "Up"; then
log_success "$name running"
else
log_error "$name not running: $line"
((unhealthy++)) || true
fi
done < <(docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "{{.Names}} {{.Status}}" 2>/dev/null)
if [[ $unhealthy -eq 0 ]]; then
log_success "All services running"
fi
}
test_port_accessibility() {
log_test "Port accessibility"
source "$DEMO_ENV_FILE"
# These are exposed to host
local port_tests=(
"$HOMEPAGE_PORT:Homepage"
"$PIHOLE_PORT:Pi-hole"
"$DOCKHAND_PORT:Dockhand"
"$INFLUXDB_PORT:InfluxDB"
"$GRAFANA_PORT:Grafana"
"$DRAWIO_PORT:Draw.io"
"$KROKI_PORT:Kroki"
"$ATOMIC_TRACKER_PORT:AtomicTracker"
"$ARCHIVEBOX_PORT:ArchiveBox"
"$TUBE_ARCHIVIST_PORT:TubeArchivist"
"$WAKAPI_PORT:Wakapi"
"$MAILHOG_PORT:MailHog"
"$ATUIN_PORT:Atuin"
"$REACTIVE_RESUME_PORT:ReactiveResume"
"$METRICS_PORT:Metrics"
"$KIWIX_PORT:Kiwix"
"$RESUME_MATCHER_PORT:ResumeMatcher"
"$APPLEHEALTH_PORT:AppleHealth"
)
local failed=0
for pt in "${port_tests[@]}"; do
local port="${pt%:*}"
local svc="${pt#*:}"
if timeout 5 bash -c "echo > /dev/tcp/localhost/$port" 2>/dev/null; then
log_success "$svc (:$port)"
else
log_error "$svc (:$port) not accessible"
((failed++)) || true
fi
done
if [[ $failed -eq 0 ]]; then
log_success "All exposed ports accessible"
fi
}
test_network_isolation() {
log_test "Network isolation"
source "$DEMO_ENV_FILE"
if docker network ls --format '{{.Name}}' | grep -q "$COMPOSE_NETWORK_NAME"; then
log_success "Network $COMPOSE_NETWORK_NAME exists"
local driver
driver=$(docker network inspect "$COMPOSE_NETWORK_NAME" --format '{{.Driver}}' 2>/dev/null || echo "")
if [[ "$driver" == "bridge" ]]; then
log_success "Bridge driver confirmed"
else
log_warning "Driver: $driver"
fi
else
log_error "Network $COMPOSE_NETWORK_NAME not found"
fi
}
test_volume_permissions() {
log_test "Docker volumes exist"
source "$DEMO_ENV_FILE"
local vol_count
vol_count=$(docker volume ls --filter "name=${COMPOSE_PROJECT_NAME}" -q 2>/dev/null | wc -l)
if [[ $vol_count -ge 19 ]]; then
log_success "$vol_count volumes created"
else
log_error "Only $vol_count volumes found"
fi
}
test_security_compliance() {
log_test "Security compliance"
source "$DEMO_ENV_FILE"
# Docker socket proxy present
if grep -q "docker-socket-proxy" "$COMPOSE_FILE"; then
log_success "Docker socket proxy configured"
else
log_error "Docker socket proxy not found"
fi
# Count direct socket mounts - only proxy should have one
local socket_mounts
socket_mounts=$(grep -c '/var/run/docker.sock' "$COMPOSE_FILE" || echo "0")
if [[ "$socket_mounts" -le 1 ]]; then
log_success "Socket mount on proxy only ($socket_mounts)"
else
log_error "Unexpected socket mounts: $socket_mounts (expected 1, proxy only)"
fi
# Dockhand uses proxy, not direct socket
if grep -q 'DOCKER_HOST=tcp://docker-socket-proxy' "$COMPOSE_FILE"; then
log_success "Dockhand routes through socket proxy"
else
log_error "Dockhand not using socket proxy"
fi
}
run_full_tests() {
log_info "Running comprehensive test suite..."
test_file_ownership || true
test_user_mapping || true
test_docker_group || true
test_service_health || true
test_port_accessibility || true
test_network_isolation || true
test_volume_permissions || true
test_security_compliance || true
display_test_results
}
run_security_tests() {
log_info "Running security tests..."
test_file_ownership || true
test_network_isolation || true
test_security_compliance || true
display_test_results
}
run_permission_tests() {
log_info "Running permission tests..."
test_file_ownership || true
test_user_mapping || true
test_docker_group || true
test_volume_permissions || true
display_test_results
}
run_network_tests() {
log_info "Running network tests..."
test_network_isolation || true
test_port_accessibility || true
display_test_results
}
display_test_results() {
echo ""
echo "===================================="
echo "TEST RESULTS"
echo "===================================="
echo "Total: $TESTS_TOTAL"
echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Failed: ${RED}$TESTS_FAILED${NC}"
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "\n${GREEN}ALL TESTS PASSED${NC}"
return 0
else
echo -e "\n${RED}SOME TESTS FAILED${NC}"
return 1
fi
}
main() {
case "${1:-full}" in
full) run_full_tests ;;
security) run_security_tests ;;
permissions) run_permission_tests ;;
network) run_network_tests ;;
help|--help|-h)
echo "Usage: $0 {full|security|permissions|network|help}"
;;
*) log_error "Unknown: $1"; exit 1 ;;
esac
}
main "$@"