#!/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" ADMIN_API_KEY="${ADMIN_API_KEY:-admin-local-dev-key}" OPERATOR_API_KEY="${OPERATOR_API_KEY:-operator-local-dev-key}" VIEWER_API_KEY="${VIEWER_API_KEY:-viewer-local-dev-key}" wait_for_health create_fresh_scheme_from_upload "smoke-authz-admin-ops" request "scheme_current" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200" CURRENT_VERSION_ID="$(json_get "${TMP_DIR}/scheme_current.body" "scheme_version_id")" echo "CURRENT_VERSION_ID=${CURRENT_VERSION_ID}" request "ensure_draft" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/ensure?expected_current_scheme_version_id=${CURRENT_VERSION_ID}" \ "200" DRAFT_VERSION_ID="$(json_get "${TMP_DIR}/ensure_draft.body" "scheme_version_id")" echo "DRAFT_VERSION_ID=${DRAFT_VERSION_ID}" request "draft_structure" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" TARGET_SEAT_ID="$(python3 - "${TMP_DIR}/draft_structure.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 for authz admin ops smoke") print(seat["seat_id"]) PY )" echo "TARGET_SEAT_ID=${TARGET_SEAT_ID}" STAMP="$(date +%s)-$$" CLEANUP_PREFIX="AUTHZ_ADMINOPS_${STAMP}_" DELETE_CATEGORY_NAME="authz-adminops-delete-${STAMP}" DELETE_CATEGORY_CODE="${CLEANUP_PREFIX}DELETE" KEEP_CATEGORY_NAME="authz-adminops-keep-${STAMP}" KEEP_CATEGORY_CODE="${CLEANUP_PREFIX}KEEP" request "create_delete_category" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/categories?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" \ "{\"name\":\"${DELETE_CATEGORY_NAME}\",\"code\":\"${DELETE_CATEGORY_CODE}\"}" DELETE_CATEGORY_ID="$(json_get "${TMP_DIR}/create_delete_category.body" "pricing_category_id")" echo "DELETE_CATEGORY_ID=${DELETE_CATEGORY_ID}" request "create_keep_category" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/categories?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" \ "{\"name\":\"${KEEP_CATEGORY_NAME}\",\"code\":\"${KEEP_CATEGORY_CODE}\"}" KEEP_CATEGORY_ID="$(json_get "${TMP_DIR}/create_keep_category.body" "pricing_category_id")" echo "KEEP_CATEGORY_ID=${KEEP_CATEGORY_ID}" request "create_keep_category_rule" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" \ "{\"pricing_category_id\":\"${KEEP_CATEGORY_ID}\",\"target_type\":\"seat\",\"target_ref\":\"${TARGET_SEAT_ID}\",\"amount\":\"666.00\",\"currency\":\"RUB\"}" KEEP_RULE_ID="$(json_get "${TMP_DIR}/create_keep_category_rule.body" "price_rule_id")" echo "KEEP_RULE_ID=${KEEP_RULE_ID}" request "draft_pricing_snapshot" "POST" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/pricing/snapshot?expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" request "publish_preview_refresh" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-preview?refresh=true&expected_scheme_version_id=${DRAFT_VERSION_ID}" \ "200" request_with_api_key "${ADMIN_API_KEY}" "admin_publish_preview_audit" "GET" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/audit" "200" request_with_api_key "${OPERATOR_API_KEY}" "operator_publish_preview_audit" "GET" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/audit" "403" request_with_api_key "${VIEWER_API_KEY}" "viewer_publish_preview_audit" "GET" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/audit" "403" assert_file_contains "${TMP_DIR}/operator_publish_preview_audit.body" "Admin role required" assert_file_contains "${TMP_DIR}/viewer_publish_preview_audit.body" "Admin role required" request_with_api_key "${ADMIN_API_KEY}" "admin_publish_preview_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" "200" request_with_api_key "${OPERATOR_API_KEY}" "operator_publish_preview_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" "403" request_with_api_key "${VIEWER_API_KEY}" "viewer_publish_preview_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" "403" assert_file_contains "${TMP_DIR}/operator_publish_preview_cleanup_dry_run.body" "Admin role required" assert_file_contains "${TMP_DIR}/viewer_publish_preview_cleanup_dry_run.body" "Admin role required" request_with_api_key "${ADMIN_API_KEY}" "admin_pricing_cleanup_preview" "GET" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview?code_prefix=${CLEANUP_PREFIX}" "200" request_with_api_key "${OPERATOR_API_KEY}" "operator_pricing_cleanup_preview" "GET" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview?code_prefix=${CLEANUP_PREFIX}" "403" request_with_api_key "${VIEWER_API_KEY}" "viewer_pricing_cleanup_preview" "GET" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview?code_prefix=${CLEANUP_PREFIX}" "403" assert_file_contains "${TMP_DIR}/operator_pricing_cleanup_preview.body" "Admin role required" assert_file_contains "${TMP_DIR}/viewer_pricing_cleanup_preview.body" "Admin role required" request_with_api_key "${ADMIN_API_KEY}" "admin_pricing_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "200" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":true}" request_with_api_key "${OPERATOR_API_KEY}" "operator_pricing_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "403" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":true}" request_with_api_key "${VIEWER_API_KEY}" "viewer_pricing_cleanup_dry_run" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "403" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":true}" assert_file_contains "${TMP_DIR}/operator_pricing_cleanup_dry_run.body" "Admin role required" assert_file_contains "${TMP_DIR}/viewer_pricing_cleanup_dry_run.body" "Admin role required" request_with_api_key "${OPERATOR_API_KEY}" "operator_pricing_cleanup_execute" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "403" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":false}" request_with_api_key "${VIEWER_API_KEY}" "viewer_pricing_cleanup_execute" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "403" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":false}" assert_file_contains "${TMP_DIR}/operator_pricing_cleanup_execute.body" "Admin role required" assert_file_contains "${TMP_DIR}/viewer_pricing_cleanup_execute.body" "Admin role required" request_with_api_key "${ADMIN_API_KEY}" "admin_pricing_cleanup_execute" "POST" \ "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "200" \ "{\"code_prefixes\":[\"${CLEANUP_PREFIX}\"],\"name_prefixes\":[],\"pricing_category_ids\":[],\"delete_only_without_rules\":true,\"dry_run\":false}" assert_json_int_eq "${TMP_DIR}/admin_pricing_cleanup_execute.body" "deleted_count" "1" assert_json_int_eq "${TMP_DIR}/admin_pricing_cleanup_execute.body" "skipped_count" "1" request "pricing_bundle_after_admin_cleanup_execute" "GET" \ "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" "200" assert_json_len_eq "${TMP_DIR}/pricing_bundle_after_admin_cleanup_execute.body" "categories" "1" assert_json_len_eq "${TMP_DIR}/pricing_bundle_after_admin_cleanup_execute.body" "rules" "1" python3 - "${TMP_DIR}/pricing_bundle_after_admin_cleanup_execute.body" "${DELETE_CATEGORY_ID}" "${KEEP_CATEGORY_ID}" "${KEEP_RULE_ID}" <<'PY' import json import sys from pathlib import Path payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) delete_category_id = sys.argv[2] keep_category_id = sys.argv[3] keep_rule_id = sys.argv[4] category_ids = {item["pricing_category_id"] for item in payload.get("categories", [])} rule_ids = {item["price_rule_id"] for item in payload.get("rules", [])} if delete_category_id in category_ids: raise SystemExit("Authz cleanup execute left deletable category behind") if keep_category_id not in category_ids: raise SystemExit("Authz cleanup execute removed protected category") if keep_rule_id not in rule_ids: raise SystemExit("Authz cleanup execute removed protected rule") PY echo "[OK] admin cleanup execute remained destructive only for safe fixture category" echo echo "===== done =====" echo "[OK] smoke authz admin ops completed successfully" echo "FRESH_SCHEME_ID=${SCHEME_ID}"