#!/usr/bin/env bash set -Eeuo pipefail API_URL="${API_URL:-http://127.0.0.1:9020}" API_KEY="${API_KEY:-admin-local-dev-key}" SCHEME_ID="${SCHEME_ID:-82086336d385427f9d56244f9e1dd772}" TMP_DIR="$(mktemp -d)" trap 'rm -rf "${TMP_DIR}"' EXIT log() { echo echo "===== $* =====" } fail() { echo echo "[FAIL] $*" >&2 exit 1 } request() { local name="$1" local method="$2" local url="$3" local body="${4:-}" local expected="${5:-200}" local body_file="${TMP_DIR}/${name}.body" local code_file="${TMP_DIR}/${name}.code" if [[ -n "${body}" ]]; then curl -sS \ -X "${method}" \ -H "X-API-Key: ${API_KEY}" \ -H "Content-Type: application/json" \ -o "${body_file}" \ -w "%{http_code}" \ --data "${body}" \ "${url}" > "${code_file}" else curl -sS \ -X "${method}" \ -H "X-API-Key: ${API_KEY}" \ -o "${body_file}" \ -w "%{http_code}" \ "${url}" > "${code_file}" fi local code code="$(cat "${code_file}")" echo "[${method}] ${url} -> ${code}" cat "${body_file}" echo if [[ "${code}" != "${expected}" ]]; then fail "Unexpected HTTP status for ${name}: expected ${expected}, got ${code}" fi } json_get() { local file="$1" local expr="$2" python3 - </dev/null || fail "healthz failed" echo "[OK] healthz" log "current + ensure draft" extract_current ensure_draft read_structure check_read_models STAMP="$(date +%s)" TEST_SECTOR_ID="reg-sector-${STAMP}" TEST_GROUP_ID="reg-group-${STAMP}" TEST_SECTOR_ELEMENT_ID="reg-sector-element-${STAMP}" TEST_GROUP_ELEMENT_ID="reg-group-element-${STAMP}" log "create sector" request "create_sector" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"element_id\":\"${TEST_SECTOR_ELEMENT_ID}\",\"sector_id\":\"${TEST_SECTOR_ID}\",\"name\":\"${TEST_SECTOR_ID}\"}" \ "200" CREATE_SECTOR_RECORD_ID="$(json_get "${TMP_DIR}/create_sector.body" "sector_record_id")" echo "CREATE_SECTOR_RECORD_ID=${CREATE_SECTOR_RECORD_ID}" log "create group" request "create_group" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/groups?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"element_id\":\"${TEST_GROUP_ELEMENT_ID}\",\"group_id\":\"${TEST_GROUP_ID}\",\"name\":\"${TEST_GROUP_ID}\"}" \ "200" CREATE_GROUP_RECORD_ID="$(json_get "${TMP_DIR}/create_group.body" "group_record_id")" echo "CREATE_GROUP_RECORD_ID=${CREATE_GROUP_RECORD_ID}" log "patch seat -> bind to new group" request "patch_seat_group" "PATCH" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"group_id\":\"${TEST_GROUP_ID}\"}" \ "200" log "verify seat after patch" request "seat_after_patch" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "" "200" assert_json_eq "${TMP_DIR}/seat_after_patch.body" "group_id" "${TEST_GROUP_ID}" assert_json_eq "${TMP_DIR}/seat_after_patch.body" "seat_number" "${ORIG_SEAT_NUMBER}" log "patch group name" request "patch_group" "PATCH" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/groups/records/${CREATE_GROUP_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"name\":\"${TEST_GROUP_ID}-updated\"}" \ "200" log "patch sector name" request "patch_sector" "PATCH" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors/records/${CREATE_SECTOR_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"name\":\"${TEST_SECTOR_ID}-updated\"}" \ "200" log "verify sector after patch" request "sector_after_patch" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors/records/${CREATE_SECTOR_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "" "200" assert_json_eq "${TMP_DIR}/sector_after_patch.body" "name" "${TEST_SECTOR_ID}-updated" assert_json_eq "${TMP_DIR}/sector_after_patch.body" "sector_id" "${TEST_SECTOR_ID}" log "bulk seat update validation path" request "bulk_seats" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/bulk?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"items\":[{\"seat_record_id\":\"${SEAT_RECORD_ID}\",\"row_label\":\"ZZ\",\"seat_number\":\"999\"}]}" \ "200" log "verify seat after bulk patch" request "seat_after_bulk" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "" "200" assert_json_eq "${TMP_DIR}/seat_after_bulk.body" "row_label" "ZZ" assert_json_eq "${TMP_DIR}/seat_after_bulk.body" "seat_number" "999" log "typed error: duplicate sector id" request "duplicate_sector" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"element_id\":\"dup-${TEST_SECTOR_ELEMENT_ID}\",\"sector_id\":\"${TEST_SECTOR_ID}\",\"name\":\"dup\"}" \ "422" log "typed error: duplicate group id" request "duplicate_group" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/groups?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{\"element_id\":\"dup-${TEST_GROUP_ELEMENT_ID}\",\"group_id\":\"${TEST_GROUP_ID}\",\"name\":\"dup\"}" \ "422" log "typed error: stale draft version" request "stale_patch" "PATCH" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=deadbeefdeadbeefdeadbeefdeadbeef" \ "{\"row_label\":\"STALE\"}" \ "409" log "typed error: remap preview without filters" request "remap_preview_invalid" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/remap/preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{}" \ "422" log "repair references" request "repair_refs" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/repair-references?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "{}" \ "200" log "post-mutation read models" check_read_models log "done" echo "[OK] editor mutation regression completed successfully"