275 lines
9.2 KiB
Bash
Executable File
275 lines
9.2 KiB
Bash
Executable File
#!/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 - <<PY
|
|
import json
|
|
from pathlib import Path
|
|
|
|
data = json.loads(Path("${file}").read_text())
|
|
expr = "${expr}"
|
|
|
|
value = data
|
|
for part in expr.split("."):
|
|
if not part:
|
|
continue
|
|
if part.startswith("[") and part.endswith("]"):
|
|
cond = part[1:-1]
|
|
try:
|
|
if cond.endswith("!=null"):
|
|
k = cond[:-6]
|
|
value = next(item for item in value if item.get(k) is not None)
|
|
elif cond.endswith("==null"):
|
|
k = cond[:-6]
|
|
value = next(item for item in value if item.get(k) is None)
|
|
elif cond == "LAST":
|
|
value = value[-1]
|
|
else:
|
|
value = value[0]
|
|
except StopIteration:
|
|
value = None
|
|
elif part.isdigit():
|
|
value = value[int(part)]
|
|
else:
|
|
value = value[part] if value else None
|
|
|
|
if value is None:
|
|
print("")
|
|
elif isinstance(value, bool):
|
|
print("true" if value else "false")
|
|
else:
|
|
print(value)
|
|
PY
|
|
}
|
|
|
|
assert_json_eq() {
|
|
local file="$1"
|
|
local expr="$2"
|
|
local expected="$3"
|
|
local actual
|
|
actual="$(json_get "${file}" "${expr}")"
|
|
if [[ "${actual}" != "${expected}" ]]; then
|
|
fail "Assertion failed: ${expr} expected '${expected}', got '${actual}'"
|
|
fi
|
|
echo "[OK] ${expr}=${actual}"
|
|
}
|
|
|
|
extract_current() {
|
|
request "current" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "" "200"
|
|
CURRENT_VERSION_ID="$(json_get "${TMP_DIR}/current.body" "scheme_version_id")"
|
|
CURRENT_STATUS="$(json_get "${TMP_DIR}/current.body" "status")"
|
|
echo "CURRENT_VERSION_ID=${CURRENT_VERSION_ID}"
|
|
echo "CURRENT_STATUS=${CURRENT_STATUS}"
|
|
}
|
|
|
|
ensure_draft() {
|
|
request "ensure_draft" "POST" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/ensure" "" "200"
|
|
DRAFT_VERSION_ID="$(json_get "${TMP_DIR}/ensure_draft.body" "scheme_version_id")"
|
|
DRAFT_CREATED="$(json_get "${TMP_DIR}/ensure_draft.body" "created")"
|
|
echo "DRAFT_VERSION_ID=${DRAFT_VERSION_ID}"
|
|
echo "DRAFT_CREATED=${DRAFT_CREATED}"
|
|
}
|
|
|
|
read_structure() {
|
|
request "draft_structure" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
|
"" "200"
|
|
|
|
SEAT_RECORD_ID="$(json_get "${TMP_DIR}/draft_structure.body" "seats.[seat_id!=null].seat_record_id")"
|
|
SEAT_ID="$(json_get "${TMP_DIR}/draft_structure.body" "seats.[seat_id!=null].seat_id")"
|
|
ORIG_SEAT_NUMBER="$(json_get "${TMP_DIR}/draft_structure.body" "seats.[seat_id!=null].seat_number")"
|
|
|
|
echo "SEAT_RECORD_ID=${SEAT_RECORD_ID}"
|
|
echo "SEAT_ID=${SEAT_ID}"
|
|
echo "ORIG_SEAT_NUMBER=${ORIG_SEAT_NUMBER}"
|
|
}
|
|
|
|
check_read_models() {
|
|
request "draft_summary" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/summary?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
|
"" "200"
|
|
request "draft_validation" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/validation?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
|
"" "200"
|
|
request "draft_compare_preview" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/compare-preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
|
"" "200"
|
|
|
|
assert_json_eq "${TMP_DIR}/draft_summary.body" "scheme_version_id" "${DRAFT_VERSION_ID}"
|
|
assert_json_eq "${TMP_DIR}/draft_validation.body" "scheme_version_id" "${DRAFT_VERSION_ID}"
|
|
assert_json_eq "${TMP_DIR}/draft_compare_preview.body" "draft_scheme_version_id" "${DRAFT_VERSION_ID}"
|
|
}
|
|
|
|
log "health"
|
|
curl -fsS "${API_URL}/healthz" >/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"
|