Files
TSYSDevStack-SupportStack-L…/demo/scripts/demo-test.sh
reachableceo be03c95929 fix(demo): harden deployment scripts, remove duplicate fix-and-ship.sh
demo-stack.sh:
- Add ensure_env() to create demo.env from template if missing
- Add envsubst prerequisite check
- Fix wait_healthy() to use docker inspect instead of fragile
  sed/awk parsing of docker ps output
- Fix smoke_test() to use env vars instead of hardcoded ports
- Remove fix_env() which overwrote TA_HOST with wrong value
- Add MailHog SMTP port to display_summary()
- Add service names to smoke test output

demo-test.sh:
- Fix security compliance test to expect only 1 socket mount
  (proxy only, now that Dockhand uses DOCKER_HOST)
- Add Dockhand proxy routing check
- Fix arithmetic increment operators for set -e compatibility

- Remove scripts/fix-and-ship.sh (was identical copy of demo-stack.sh)

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-05-01 09:50:40 -05:00

256 lines
7.5 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"
)
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 15 ]]; 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 "$@"