Files
minerops/scripts/test-repair-timeline-migration.sh

172 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${repo_root}"
project_name="${MINEROPS_TIMELINE_MIGRATION_PROJECT:-minerops-timeline-migration-$$}"
empty_database="minerops_timeline_empty"
compose=(
docker compose
--project-name "${project_name}"
--env-file .env.dev
-f compose.dev.yaml
)
cleanup() {
status=$?
if (( status != 0 )); then
echo
echo "Repair Timeline migration test failed. Recent PostgreSQL logs:"
"${compose[@]}" logs --no-color --tail 120 postgres || true
fi
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
exit "${status}"
}
trap cleanup EXIT
if [[ ! -f .env.dev ]]; then
echo "Missing .env.dev. Run ./scripts/dev-env-init.sh first." >&2
exit 1
fi
run_alembic() {
"${compose[@]}" run --rm --no-deps backend alembic "$@"
}
echo "Starting disposable PostgreSQL..."
"${compose[@]}" up --detach postgres
for attempt in $(seq 1 60); do
if "${compose[@]}" exec -T postgres sh -ec \
'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
>/dev/null 2>&1; then
break
fi
if (( attempt == 60 )); then
echo "PostgreSQL did not become ready." >&2
exit 1
fi
sleep 1
done
echo "Building migration image..."
"${compose[@]}" build backend
echo "Upgrading the previous head to Repair Timeline..."
run_alembic upgrade 0010_begin_repair
previous_revision="$(run_alembic current 2>/dev/null)"
[[ "${previous_revision}" == *"0010_begin_repair"* ]]
run_alembic upgrade 0011_repair_timeline
current_revision="$(run_alembic current 2>/dev/null)"
[[ "${current_revision}" == *"0011_repair_timeline"* ]]
echo "Checking repairs.read permission and role assignments..."
"${compose[@]}" exec -T postgres sh -ec \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"' \
<<'SQL'
DO $$
DECLARE
v_read_roles TEXT[];
v_create_roles TEXT[];
BEGIN
IF to_regclass('public.repairs') IS NULL
OR to_regclass('public.repair_events') IS NULL THEN
RAISE EXCEPTION 'Repair tables changed during timeline migration';
END IF;
IF (SELECT count(*) FROM permissions WHERE key = 'repairs.read') <> 1 THEN
RAISE EXCEPTION 'repairs.read permission was not registered once';
END IF;
SELECT array_agg(roles.name ORDER BY roles.name)
INTO v_read_roles
FROM role_permissions
JOIN roles ON roles.id = role_permissions.role_id
JOIN permissions ON permissions.id = role_permissions.permission_id
WHERE permissions.key = 'repairs.read';
IF v_read_roles <> ARRAY[
'Admin', 'Manager', 'Read Only', 'Technician'
] THEN
RAISE EXCEPTION 'Unexpected repairs.read role matrix: %', v_read_roles;
END IF;
SELECT array_agg(roles.name ORDER BY roles.name)
INTO v_create_roles
FROM role_permissions
JOIN roles ON roles.id = role_permissions.role_id
JOIN permissions ON permissions.id = role_permissions.permission_id
WHERE permissions.key = 'repairs.create';
IF v_create_roles <> ARRAY['Admin', 'Manager', 'Technician'] THEN
RAISE EXCEPTION
'repairs.create role matrix changed: %',
v_create_roles;
END IF;
INSERT INTO permissions (key, description)
VALUES ('repairs.read', 'View repair details and timeline events')
ON CONFLICT (key) DO UPDATE
SET description = EXCLUDED.description;
INSERT INTO role_permissions (role_id, permission_id)
SELECT roles.id, permissions.id
FROM roles
JOIN permissions ON permissions.key = 'repairs.read'
WHERE roles.name IN ('Admin', 'Manager', 'Technician', 'Read Only')
ON CONFLICT DO NOTHING;
IF (SELECT count(*) FROM permissions WHERE key = 'repairs.read') <> 1
OR (
SELECT count(*)
FROM role_permissions
JOIN permissions
ON permissions.id = role_permissions.permission_id
WHERE permissions.key = 'repairs.read'
) <> 4 THEN
RAISE EXCEPTION 'repairs.read seeding is not idempotent';
END IF;
END
$$;
SQL
echo "Downgrading and re-upgrading Repair Timeline..."
run_alembic downgrade 0010_begin_repair
downgrade_state="$(
"${compose[@]}" exec -T postgres sh -ec \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At' \
<<'SQL'
SELECT
NOT EXISTS (
SELECT 1 FROM permissions WHERE key = 'repairs.read'
)
AND EXISTS (
SELECT 1 FROM permissions WHERE key = 'repairs.create'
)
AND to_regclass('public.repairs') IS NOT NULL
AND to_regclass('public.repair_events') IS NOT NULL;
SQL
)"
[[ "${downgrade_state}" == "t" ]]
downgraded_revision="$(run_alembic current 2>/dev/null)"
[[ "${downgraded_revision}" == *"0010_begin_repair"* ]]
run_alembic upgrade 0011_repair_timeline
reupgraded_revision="$(run_alembic current 2>/dev/null)"
[[ "${reupgraded_revision}" == *"0011_repair_timeline"* ]]
echo "Migrating an empty database from base through Repair Timeline..."
"${compose[@]}" exec -T postgres sh -ec \
'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d postgres \
-c "CREATE DATABASE minerops_timeline_empty"' >/dev/null
"${compose[@]}" run --rm --no-deps \
-e "POSTGRES_DB=${empty_database}" \
backend alembic upgrade 0011_repair_timeline
empty_revision="$(
"${compose[@]}" run --rm --no-deps \
-e "POSTGRES_DB=${empty_database}" \
backend alembic current 2>/dev/null
)"
[[ "${empty_revision}" == *"0011_repair_timeline"* ]]
echo "Repair Timeline migration test passed."