Add operational schema and identity roles
This commit is contained in:
1 parent
f5af75f305
commit
f181482463
11 files changed
+2068
-284
No files matched your search
Executable
+189
@@ -0,0 +1,189 @@
|
||||
#!/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
|
||||
args+=(-b "${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}" == *"0007_identity_and_roles"* ]]
|
||||
|
||||
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."
|
||||
Reference in new issue
Block a user