feat: add repair timeline read APIs

This commit is contained in:
lussierj committed 2026-07-29 16:28:44 +00:00
1 parent f6a2512e23
commit 36a0caecd8
16 files changed
+1562 -9

No files matched your search

+24 -1
View File
@@ -88,7 +88,7 @@ health_json="$(cat "${health_file}")"
echo "Checking migrations and required schema..."
current_revision="$("${compose[@]}" exec -T backend alembic current 2>/dev/null)"
[[ "${current_revision}" == *"0010_begin_repair"* ]]
[[ "${current_revision}" == *"0011_repair_timeline"* ]]
missing_tables="$(
"${compose[@]}" exec -T postgres sh -c \
@@ -197,6 +197,29 @@ begin_repair_response="$(curl --fail --silent -b "${technician_cookie}" \
'{"miner_id":"00000000-0000-0000-0000-000000000981","reason":"CONTROL_BOARD"}'
)" == "409" ]]
smoke_repair_id="$(
printf '%s' "${begin_repair_response}" \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["data"]["repair_id"])'
)"
[[ -n "${smoke_repair_id}" ]]
readonly_repair_detail="$(
curl --fail --silent -b "${readonly_cookie}" \
"${base_url}/api/repairs/${smoke_repair_id}"
)"
[[ "${readonly_repair_detail}" == *'"miner_asset_id":"SMOKE-REPAIR-001"'* ]]
[[ "${readonly_repair_detail}" == *'"miner_lifecycle":"in_repair"'* ]]
[[ "${readonly_repair_detail}" == *'"latest_event_sequence":2'* ]]
readonly_repair_events="$(
curl --fail --silent -b "${readonly_cookie}" \
"${base_url}/api/repairs/${smoke_repair_id}/events"
)"
[[ "${readonly_repair_events}" == *'"sequence":1'* ]]
[[ "${readonly_repair_events}" == *'"sequence":2'* ]]
[[ "${readonly_repair_events}" == *'"event_type":"REPAIR_STARTED"'* ]]
[[ "${readonly_repair_events}" == *'"event_type":"INITIAL_NOTE"'* ]]
[[ "$(status_for "${technician_cookie}" GET /api/repairs/queue)" == "200" ]]
[[ "$(status_for "${technician_cookie}" GET /api/repairs/parts-summary)" == "200" ]]
room_status="$(status_for "${technician_cookie}" POST /api/rooms '{"name":"Role Matrix Test Room"}')"
[[ "${room_status}" == "200" || "${room_status}" == "201" ]]
+171
View File
@@ -0,0 +1,171 @@
#!/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 head
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 head
reupgraded_revision="$(run_alembic current 2>/dev/null)"
[[ "${reupgraded_revision}" == *"0011_repair_timeline"* ]]
echo "Migrating an empty database from base through head..."
"${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 head
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."
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${repo_root}"
project_name="${MINEROPS_TIMELINE_TEST_PROJECT:-minerops-repair-timeline-$$}"
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 tests 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
echo "Starting disposable Repair Timeline database..."
"${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 Repair Timeline test image..."
"${compose[@]}" build backend
echo "Migrating disposable database..."
"${compose[@]}" run --rm --no-deps backend alembic upgrade head
echo "Running Repair Timeline repository, service, and router tests..."
"${compose[@]}" run --rm --no-deps \
-v "${repo_root}/backend/tests:/app/tests:ro" \
backend python -m unittest -v tests.test_repair_timeline
echo "Repair Timeline tests passed."