restrict ops endpoints to admin-only access block operator and viewer keys from admin maintenance routes cover destructive pricing cleanup in smoke execution, not only preview extend orchestration without regressing existing smoke stages
237 lines
11 KiB
Bash
237 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "${TMP_DIR}"' EXIT
|
|
|
|
# shellcheck source=backend/scripts/smoke_common.sh
|
|
source "${SCRIPT_DIR}/smoke_common.sh"
|
|
|
|
wait_for_health
|
|
|
|
create_fresh_scheme_from_upload "smoke-version-lifecycle"
|
|
|
|
request "scheme_detail_initial" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_detail_initial.body" "status" "draft"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_detail_initial.body" "current_version_number" "1"
|
|
|
|
request "scheme_current_initial" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
|
VERSION1_ID="$(json_get "${TMP_DIR}/scheme_current_initial.body" "scheme_version_id")"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_current_initial.body" "version_number" "1"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_initial.body" "status" "draft"
|
|
echo "VERSION1_ID=${VERSION1_ID}"
|
|
|
|
request "ensure_draft_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/ensure?expected_current_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
assert_json_eq "${TMP_DIR}/ensure_draft_v1.body" "scheme_version_id" "${VERSION1_ID}"
|
|
assert_json_eq "${TMP_DIR}/ensure_draft_v1.body" "created" "false"
|
|
|
|
request "draft_structure_v1" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
|
|
read -r VERSION1_SEAT_RECORD_ID VERSION1_SEAT_ID ORIGINAL_ROW_LABEL ORIGINAL_SEAT_NUMBER <<EOF
|
|
$(python3 - "${TMP_DIR}/draft_structure_v1.body" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
seat = next((item for item in payload.get("seats", []) if item.get("seat_id")), None)
|
|
if seat is None:
|
|
raise SystemExit("No seat with seat_id found in version 1 draft structure")
|
|
print(
|
|
seat["seat_record_id"],
|
|
seat["seat_id"],
|
|
seat.get("row_label") or "__EMPTY__",
|
|
seat.get("seat_number") or "__EMPTY__",
|
|
)
|
|
PY
|
|
)
|
|
EOF
|
|
|
|
echo "VERSION1_SEAT_RECORD_ID=${VERSION1_SEAT_RECORD_ID}"
|
|
echo "VERSION1_SEAT_ID=${VERSION1_SEAT_ID}"
|
|
echo "ORIGINAL_ROW_LABEL=${ORIGINAL_ROW_LABEL}"
|
|
echo "ORIGINAL_SEAT_NUMBER=${ORIGINAL_SEAT_NUMBER}"
|
|
|
|
STAMP="$(date +%s)-$$"
|
|
PRICING_CATEGORY_NAME="lifecycle-publish-${STAMP}"
|
|
PRICING_CATEGORY_CODE="LIFECYCLE_${STAMP}"
|
|
UPDATED_ROW_LABEL="LC-${STAMP}"
|
|
|
|
request "create_pricing_category_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/categories?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200" \
|
|
"{\"name\":\"${PRICING_CATEGORY_NAME}\",\"code\":\"${PRICING_CATEGORY_CODE}\"}"
|
|
PRICING_CATEGORY_ID="$(json_get "${TMP_DIR}/create_pricing_category_v1.body" "pricing_category_id")"
|
|
echo "PRICING_CATEGORY_ID=${PRICING_CATEGORY_ID}"
|
|
|
|
request "create_price_rule_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200" \
|
|
"{\"pricing_category_id\":\"${PRICING_CATEGORY_ID}\",\"target_type\":\"seat\",\"target_ref\":\"${VERSION1_SEAT_ID}\",\"amount\":\"777.00\",\"currency\":\"RUB\"}"
|
|
PRICE_RULE_ID="$(json_get "${TMP_DIR}/create_price_rule_v1.body" "price_rule_id")"
|
|
echo "PRICE_RULE_ID=${PRICE_RULE_ID}"
|
|
|
|
request "draft_pricing_snapshot_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/pricing/snapshot?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
|
|
request "publish_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/publish?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
assert_json_eq "${TMP_DIR}/publish_v1.body" "status" "published"
|
|
assert_json_int_eq "${TMP_DIR}/publish_v1.body" "current_version_number" "1"
|
|
|
|
request "scheme_detail_after_publish_v1" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_detail_after_publish_v1.body" "status" "published"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_detail_after_publish_v1.body" "current_version_number" "1"
|
|
|
|
request "scheme_current_after_publish_v1" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_publish_v1.body" "scheme_version_id" "${VERSION1_ID}"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_current_after_publish_v1.body" "version_number" "1"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_publish_v1.body" "status" "published"
|
|
|
|
request "ensure_draft_v2" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/ensure?expected_current_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
VERSION2_ID="$(json_get "${TMP_DIR}/ensure_draft_v2.body" "scheme_version_id")"
|
|
echo "VERSION2_ID=${VERSION2_ID}"
|
|
assert_json_eq "${TMP_DIR}/ensure_draft_v2.body" "created" "true"
|
|
assert_json_eq "${TMP_DIR}/ensure_draft_v2.body" "source_scheme_version_id" "${VERSION1_ID}"
|
|
assert_json_int_eq "${TMP_DIR}/ensure_draft_v2.body" "version_number" "2"
|
|
|
|
request "draft_structure_v2_before_mutation" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200"
|
|
|
|
VERSION2_SEAT_RECORD_ID="$(python3 - "${TMP_DIR}/draft_structure_v2_before_mutation.body" "${VERSION1_SEAT_ID}" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
seat_id = sys.argv[2]
|
|
seat = next((item for item in payload.get("seats", []) if item.get("seat_id") == seat_id), None)
|
|
if seat is None:
|
|
raise SystemExit("Target seat_id not found in version 2 draft structure")
|
|
print(seat["seat_record_id"])
|
|
PY
|
|
)"
|
|
echo "VERSION2_SEAT_RECORD_ID=${VERSION2_SEAT_RECORD_ID}"
|
|
|
|
request "patch_seat_v2" "PATCH" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${VERSION2_SEAT_RECORD_ID}?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200" \
|
|
"{\"row_label\":\"${UPDATED_ROW_LABEL}\"}"
|
|
assert_json_eq "${TMP_DIR}/patch_seat_v2.body" "row_label" "${UPDATED_ROW_LABEL}"
|
|
|
|
request "draft_structure_v2_after_mutation" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200"
|
|
assert_file_contains "${TMP_DIR}/draft_structure_v2_after_mutation.body" "\"row_label\":\"${UPDATED_ROW_LABEL}\""
|
|
|
|
request "draft_compare_preview_v2" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/compare-preview?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200"
|
|
assert_file_contains "${TMP_DIR}/draft_compare_preview_v2.body" "\"status\":\"changed\""
|
|
|
|
request "draft_pricing_snapshot_v2" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/pricing/snapshot?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200"
|
|
|
|
request "publish_v2" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/publish?expected_scheme_version_id=${VERSION2_ID}" \
|
|
"200"
|
|
assert_json_eq "${TMP_DIR}/publish_v2.body" "status" "published"
|
|
assert_json_int_eq "${TMP_DIR}/publish_v2.body" "current_version_number" "2"
|
|
|
|
request "scheme_detail_after_publish_v2" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_detail_after_publish_v2.body" "status" "published"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_detail_after_publish_v2.body" "current_version_number" "2"
|
|
|
|
request "scheme_current_after_publish_v2" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_publish_v2.body" "scheme_version_id" "${VERSION2_ID}"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_current_after_publish_v2.body" "version_number" "2"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_publish_v2.body" "status" "published"
|
|
|
|
request "scheme_versions_after_publish_v2" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/versions?limit=20&offset=0" "200"
|
|
assert_json_len_eq "${TMP_DIR}/scheme_versions_after_publish_v2.body" "items" "2"
|
|
|
|
request "rollback_to_v1" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/rollback" \
|
|
"200" \
|
|
"{\"target_version_number\":1}"
|
|
assert_json_eq "${TMP_DIR}/rollback_to_v1.body" "status" "draft"
|
|
assert_json_int_eq "${TMP_DIR}/rollback_to_v1.body" "current_version_number" "1"
|
|
|
|
request "scheme_detail_after_rollback" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_detail_after_rollback.body" "status" "draft"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_detail_after_rollback.body" "current_version_number" "1"
|
|
|
|
request "scheme_current_after_rollback" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_rollback.body" "scheme_version_id" "${VERSION1_ID}"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_current_after_rollback.body" "version_number" "1"
|
|
assert_json_eq "${TMP_DIR}/scheme_current_after_rollback.body" "status" "draft"
|
|
|
|
request "editor_context_after_rollback" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/editor/context" "200"
|
|
assert_json_eq "${TMP_DIR}/editor_context_after_rollback.body" "current_scheme_version_id" "${VERSION1_ID}"
|
|
assert_json_eq "${TMP_DIR}/editor_context_after_rollback.body" "current_is_draft" "true"
|
|
|
|
request "draft_structure_after_rollback" "GET" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${VERSION1_ID}" \
|
|
"200"
|
|
|
|
python3 - "${TMP_DIR}/draft_structure_after_rollback.body" "${VERSION1_SEAT_ID}" "${ORIGINAL_ROW_LABEL}" "${ORIGINAL_SEAT_NUMBER}" <<'PY'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
seat_id = sys.argv[2]
|
|
expected_row_label = sys.argv[3]
|
|
expected_seat_number = sys.argv[4]
|
|
|
|
seat = next((item for item in payload.get("seats", []) if item.get("seat_id") == seat_id), None)
|
|
if seat is None:
|
|
raise SystemExit(f"Seat {seat_id} not found after rollback")
|
|
|
|
actual_row_label = seat.get("row_label") or "__EMPTY__"
|
|
actual_seat_number = seat.get("seat_number") or "__EMPTY__"
|
|
if actual_row_label != expected_row_label:
|
|
raise SystemExit(
|
|
f"Rollback row_label mismatch: expected {expected_row_label}, got {actual_row_label}"
|
|
)
|
|
if actual_seat_number != expected_seat_number:
|
|
raise SystemExit(
|
|
f"Rollback seat_number mismatch: expected {expected_seat_number}, got {actual_seat_number}"
|
|
)
|
|
PY
|
|
echo "[OK] rollback restored version 1 seat semantics"
|
|
|
|
request "unpublish_after_rollback" "POST" \
|
|
"${API_URL}/api/v1/schemes/${SCHEME_ID}/unpublish" \
|
|
"200"
|
|
assert_json_eq "${TMP_DIR}/unpublish_after_rollback.body" "status" "draft"
|
|
assert_json_int_eq "${TMP_DIR}/unpublish_after_rollback.body" "current_version_number" "1"
|
|
|
|
request "scheme_detail_after_unpublish" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
|
assert_json_eq "${TMP_DIR}/scheme_detail_after_unpublish.body" "status" "draft"
|
|
assert_json_int_eq "${TMP_DIR}/scheme_detail_after_unpublish.body" "current_version_number" "1"
|
|
|
|
request "audit_trail" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/audit" "200"
|
|
assert_file_contains "${TMP_DIR}/audit_trail.body" "\"event_type\":\"scheme.published\""
|
|
assert_file_contains "${TMP_DIR}/audit_trail.body" "\"event_type\":\"scheme.version.created\""
|
|
assert_file_contains "${TMP_DIR}/audit_trail.body" "\"event_type\":\"scheme.rolled_back\""
|
|
assert_file_contains "${TMP_DIR}/audit_trail.body" "\"event_type\":\"scheme.unpublished\""
|
|
|
|
echo
|
|
echo "===== done ====="
|
|
echo "[OK] smoke version lifecycle completed successfully"
|
|
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
|
echo "VERSION1_ID=${VERSION1_ID}"
|
|
echo "VERSION2_ID=${VERSION2_ID}"
|