Files
minerops/scripts/test-dev-baseline.sh
T

191 lines
7.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
project_name="${MINEROPS_SMOKE_PROJECT:-minerops-smoke-$$}"
dev_port="${MINEROPS_SMOKE_PORT:-18001}"
keep_environment="${MINEROPS_SMOKE_KEEP:-false}"
base_url="http://127.0.0.1:${dev_port}"
compose=(
docker compose
--project-name "${project_name}"
--env-file .env.dev
-f compose.dev.yaml
)
tmp_prefix="/tmp/${project_name}"
admin_cookie="${tmp_prefix}-admin.cookies"
manager_cookie="${tmp_prefix}-manager.cookies"
technician_cookie="${tmp_prefix}-technician.cookies"
readonly_cookie="${tmp_prefix}-readonly.cookies"
cleanup() {
status=$?
if (( status != 0 )); then
echo
echo "Smoke test failed. Recent service logs:"
"${compose[@]}" logs --no-color --tail 160 || true
fi
rm -f "${tmp_prefix}"-*.json "${tmp_prefix}"-*.cookies "${tmp_prefix}"-*.headers
if [[ "${keep_environment}" != "true" ]]; then
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
else
echo "Keeping Compose project ${project_name} for inspection."
fi
exit "${status}"
}
trap cleanup EXIT
status_for() {
local cookie_jar="$1"
local method="$2"
local path="$3"
local body="${4:-}"
local args=(--silent --output /dev/null --write-out '%{http_code}' -X "${method}")
if [[ -n "${cookie_jar}" ]]; then
# Read and update the jar so logout and refreshed cookies behave like a browser.
args+=(-b "${cookie_jar}" -c "${cookie_jar}")
fi
if [[ -n "${body}" ]]; then
args+=(-H 'Content-Type: application/json' --data "${body}")
fi
curl "${args[@]}" "${base_url}${path}"
}
if [[ ! -f .env.dev ]]; then
echo "Missing .env.dev. Run ./scripts/dev-env-init.sh first." >&2
exit 1
fi
echo "Validating Compose configuration..."
MINEROPS_DEV_PORT="${dev_port}" "${compose[@]}" config --quiet
echo "Building and starting disposable development stack..."
MINEROPS_DEV_PORT="${dev_port}" "${compose[@]}" up --detach --build
echo "Waiting for the gateway health endpoint..."
health_file="${tmp_prefix}-health.json"
for attempt in $(seq 1 90); do
if curl --fail --silent "${base_url}/api/health" >"${health_file}"; then
break
fi
if (( attempt == 90 )); then
echo "Gateway did not become healthy within 90 seconds." >&2
exit 1
fi
sleep 1
done
health_json="$(cat "${health_file}")"
[[ "${health_json}" == *'"status":"ok"'* ]]
[[ "${health_json}" == *'"environment":"development"'* ]]
[[ "${health_json}" == *'"database":"ok"'* ]]
[[ "${health_json}" == *'"redis":"ok"'* ]]
echo "Checking migrations and required schema..."
current_revision="$("${compose[@]}" exec -T backend alembic current 2>/dev/null)"
[[ "${current_revision}" == *"0008_manual_miner_inventory"* ]]
missing_tables="$(
"${compose[@]}" exec -T postgres sh -c \
'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At' \
< sql/verify-development-schema.sql
)"
if [[ -n "${missing_tables}" ]]; then
echo "Missing required tables:" >&2
echo "${missing_tables}" >&2
exit 1
fi
echo "Checking the unauthenticated login gate..."
setup_status="$(curl --fail --silent "${base_url}/api/auth/setup-status")"
[[ "${setup_status}" == *'"setup_required":true'* ]]
[[ "$(status_for "" GET /login)" == "200" ]]
for path in dashboard repairs locations system-health accounts; do
[[ "$(status_for "" GET "/${path}")" == "303" ]]
done
[[ "$(status_for "" GET /api/scanner/status)" == "401" ]]
echo "Creating the first administrator and role test accounts..."
setup_response="$(curl --fail --silent -c "${admin_cookie}" \
-H 'Content-Type: application/json' \
--data '{"username":"admin","display_name":"Test Administrator","email":"admin@example.com","password":"MinerOpsTest!123"}' \
"${base_url}/api/auth/setup-admin")"
[[ "${setup_response}" == *'"role":"Admin"'* ]]
[[ "$(status_for "${admin_cookie}" GET /dashboard)" == "200" ]]
[[ "$(status_for "${admin_cookie}" GET /accounts)" == "200" ]]
for account in \
'manager|Operations Manager|Manager|manager@example.com' \
'technician|Repair Technician|Technician|technician@example.com' \
'readonly|Read Only User|Read Only|readonly@example.com'; do
IFS='|' read -r username display_name role email <<<"${account}"
response="$(curl --fail --silent -b "${admin_cookie}" \
-H 'Content-Type: application/json' \
--data "{\"username\":\"${username}\",\"display_name\":\"${display_name}\",\"email\":\"${email}\",\"role\":\"${role}\",\"password\":\"MinerOpsTest!123\"}" \
"${base_url}/api/admin/users")"
[[ "${response}" == *"\"role\":\"${role}\""* ]]
done
roles_response="$(curl --fail --silent -b "${admin_cookie}" "${base_url}/api/admin/roles")"
for role in Admin Manager Technician 'Read Only'; do
[[ "${roles_response}" == *"\"name\":\"${role}\""* ]]
done
users_response="$(curl --fail --silent -b "${admin_cookie}" "${base_url}/api/admin/users")"
for username in admin manager technician readonly; do
[[ "${users_response}" == *"\"username\":\"${username}\""* ]]
done
echo "Logging in each role and checking authorization boundaries..."
for login in \
"manager|${manager_cookie}|Manager" \
"technician|${technician_cookie}|Technician" \
"readonly|${readonly_cookie}|Read Only"; do
IFS='|' read -r username cookie_jar role <<<"${login}"
login_response="$(curl --fail --silent -c "${cookie_jar}" \
-H 'Content-Type: application/json' \
--data "{\"username\":\"${username}\",\"password\":\"MinerOpsTest!123\"}" \
"${base_url}/api/auth/login")"
[[ "${login_response}" == *"\"role\":\"${role}\""* ]]
[[ "$(status_for "${cookie_jar}" GET /dashboard)" == "200" ]]
done
[[ "$(status_for "${manager_cookie}" GET /system-health)" == "200" ]]
[[ "$(status_for "${manager_cookie}" GET /accounts)" == "403" ]]
[[ "$(status_for "${manager_cookie}" GET /api/admin/users)" == "403" ]]
[[ "$(status_for "${technician_cookie}" GET /repairs)" == "200" ]]
[[ "$(status_for "${technician_cookie}" GET /system-health)" == "403" ]]
[[ "$(status_for "${readonly_cookie}" GET /locations)" == "200" ]]
[[ "$(status_for "${readonly_cookie}" POST /api/rooms '{"name":"Must Not Be Created"}')" == "403" ]]
room_status="$(status_for "${technician_cookie}" POST /api/rooms '{"name":"Role Matrix Test Room"}')"
[[ "${room_status}" == "200" || "${room_status}" == "201" ]]
me_response="$(curl --fail --silent -b "${readonly_cookie}" "${base_url}/api/auth/me")"
[[ "${me_response}" == *'"role":"Read Only"'* ]]
[[ "$(status_for "${readonly_cookie}" POST /api/auth/logout)" == "200" ]]
[[ "$(status_for "${readonly_cookie}" GET /dashboard)" == "303" ]]
echo "Checking development safety interlocks..."
"${compose[@]}" exec -T backend sh -ec '
test "$SCHEDULED_SCAN_ENABLED" = "false"
test "$MINEROPS_HUB_MODE" = "true"
test "$MINEROPS_DISABLE_LOCAL_SCANNER" = "true"
test "$MINEROPS_ENABLE_REAL_REBOOT" = "false"
test "$MINEROPS_ENABLE_REAL_POWER_CONTROL" = "false"
'
echo "Running retention rollups against the disposable baseline..."
"${compose[@]}" exec -T postgres sh -c \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< sql/minerops-retention-hourly.sql >/dev/null
"${compose[@]}" exec -T postgres sh -c \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
< sql/minerops-retention-daily.sql >/dev/null
echo "Development identity baseline passed."