chore(backend): finalize backend baseline and frontend handoff contract
freeze the current backend contract for frontend integration document the stabilized backend surface and handoff expectations mark the current state as the baseline for further frontend work
This commit is contained in:
173
backend/scripts/smoke_artifact_corruption.sh
Normal file
173
backend/scripts/smoke_artifact_corruption.sh
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/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"
|
||||
|
||||
set -a
|
||||
source "${REPO_ROOT}/.env"
|
||||
set +a
|
||||
|
||||
wait_for_health
|
||||
|
||||
create_fresh_scheme_from_upload "smoke-artifact-corruption"
|
||||
|
||||
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 "initial_publish_preview_audit" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/initial_publish_preview_audit.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/initial_publish_preview_audit.body" "missing_files_for_db_rows_count" "0"
|
||||
|
||||
request "publish_preview_refresh_case_a" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-preview?refresh=true&expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
||||
"200"
|
||||
request "admin_current_artifacts_case_a" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" \
|
||||
"200"
|
||||
|
||||
read -r CASE_A_ARTIFACT_ID CASE_A_STORAGE_PATH <<EOF
|
||||
$(python3 - "${TMP_DIR}/admin_current_artifacts_case_a.body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
items = [item for item in payload.get("items", []) if item.get("artifact_type") == "publish_preview"]
|
||||
if not items:
|
||||
raise SystemExit("No publish_preview artifact found for case A")
|
||||
item = items[-1]
|
||||
print(item["artifact_id"], item["storage_path"])
|
||||
PY
|
||||
)
|
||||
EOF
|
||||
echo "CASE_A_ARTIFACT_ID=${CASE_A_ARTIFACT_ID}"
|
||||
echo "CASE_A_STORAGE_PATH=${CASE_A_STORAGE_PATH}"
|
||||
|
||||
docker compose exec -T svg-service python - "${CASE_A_STORAGE_PATH}" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
if not path.exists():
|
||||
raise SystemExit(f"Case A preview file missing before manual removal: {path}")
|
||||
path.unlink()
|
||||
PY
|
||||
echo "[OK] case A manually removed preview file while DB row remains"
|
||||
|
||||
request "audit_case_a_broken" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_a_broken.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_a_broken.body" "missing_files_for_db_rows_count" "1"
|
||||
assert_file_contains "${TMP_DIR}/audit_case_a_broken.body" "\"artifact_id\":\"${CASE_A_ARTIFACT_ID}\""
|
||||
|
||||
request "cleanup_case_a_dry_run" "POST" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_dry_run.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_dry_run.body" "missing_files_for_db_rows_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_dry_run.body" "deleted_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_dry_run.body" "deleted_db_rows_count" "0"
|
||||
|
||||
request "cleanup_case_a_execute" "POST" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=false" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_execute.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_execute.body" "missing_files_for_db_rows_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_execute.body" "deleted_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_a_execute.body" "deleted_db_rows_count" "1"
|
||||
assert_file_contains "${TMP_DIR}/cleanup_case_a_execute.body" "\"${CASE_A_ARTIFACT_ID}\""
|
||||
|
||||
request "audit_case_a_healthy" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_a_healthy.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_a_healthy.body" "missing_files_for_db_rows_count" "0"
|
||||
|
||||
request "publish_preview_refresh_case_b" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-preview?refresh=true&expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
||||
"200"
|
||||
request "admin_current_artifacts_case_b" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" \
|
||||
"200"
|
||||
|
||||
read -r CASE_B_ARTIFACT_ID CASE_B_STORAGE_PATH <<EOF
|
||||
$(python3 - "${TMP_DIR}/admin_current_artifacts_case_b.body" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
items = [item for item in payload.get("items", []) if item.get("artifact_type") == "publish_preview"]
|
||||
if not items:
|
||||
raise SystemExit("No publish_preview artifact found for case B")
|
||||
item = items[-1]
|
||||
print(item["artifact_id"], item["storage_path"])
|
||||
PY
|
||||
)
|
||||
EOF
|
||||
echo "CASE_B_ARTIFACT_ID=${CASE_B_ARTIFACT_ID}"
|
||||
echo "CASE_B_STORAGE_PATH=${CASE_B_STORAGE_PATH}"
|
||||
|
||||
CASE_B_DELETE_COUNT="$(docker compose exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -Atc "with deleted as (delete from scheme_artifacts where artifact_id='${CASE_B_ARTIFACT_ID}' and artifact_type='publish_preview' and scheme_id='${SCHEME_ID}' returning 1) select count(*) from deleted;")"
|
||||
if [[ "${CASE_B_DELETE_COUNT}" != "1" ]]; then
|
||||
fail "Case B expected to delete exactly one publish_preview DB row, got ${CASE_B_DELETE_COUNT}"
|
||||
fi
|
||||
echo "[OK] case B manually removed publish_preview DB row while file remains"
|
||||
|
||||
request "audit_case_b_broken" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_b_broken.body" "orphan_files_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/audit_case_b_broken.body" "missing_files_for_db_rows_count" "0"
|
||||
assert_file_contains "${TMP_DIR}/audit_case_b_broken.body" "\"${CASE_B_STORAGE_PATH}\""
|
||||
|
||||
request "cleanup_case_b_dry_run" "POST" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_dry_run.body" "orphan_files_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_dry_run.body" "missing_files_for_db_rows_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_dry_run.body" "deleted_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_dry_run.body" "deleted_db_rows_count" "0"
|
||||
|
||||
request "cleanup_case_b_execute" "POST" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=false" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_execute.body" "orphan_files_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_execute.body" "missing_files_for_db_rows_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_execute.body" "deleted_files_count" "1"
|
||||
assert_json_int_eq "${TMP_DIR}/cleanup_case_b_execute.body" "deleted_db_rows_count" "0"
|
||||
assert_file_contains "${TMP_DIR}/cleanup_case_b_execute.body" "\"${CASE_B_STORAGE_PATH}\""
|
||||
|
||||
request "final_publish_preview_audit" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"200"
|
||||
assert_json_int_eq "${TMP_DIR}/final_publish_preview_audit.body" "orphan_files_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/final_publish_preview_audit.body" "missing_files_for_db_rows_count" "0"
|
||||
|
||||
FINAL_DB_ROWS_COUNT="$(json_get "${TMP_DIR}/final_publish_preview_audit.body" "db_rows_count")"
|
||||
FINAL_DISK_FILES_COUNT="$(json_get "${TMP_DIR}/final_publish_preview_audit.body" "disk_files_count")"
|
||||
if [[ "${FINAL_DB_ROWS_COUNT}" != "${FINAL_DISK_FILES_COUNT}" ]]; then
|
||||
fail "Final publish-preview audit mismatch after remediation: db_rows_count=${FINAL_DB_ROWS_COUNT}, disk_files_count=${FINAL_DISK_FILES_COUNT}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke artifact corruption completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
echo "CASE_A_ARTIFACT_ID=${CASE_A_ARTIFACT_ID}"
|
||||
echo "CASE_B_ARTIFACT_ID=${CASE_B_ARTIFACT_ID}"
|
||||
78
backend/scripts/smoke_auth_negative.sh
Normal file
78
backend/scripts/smoke_auth_negative.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/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"
|
||||
|
||||
INVALID_API_KEY="${INVALID_API_KEY:-definitely-invalid-api-key}"
|
||||
VIEWER_API_KEY="${VIEWER_API_KEY:-viewer-local-dev-key}"
|
||||
|
||||
wait_for_health
|
||||
|
||||
create_fresh_scheme_from_upload "smoke-auth-negative"
|
||||
|
||||
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_without_api_key "manifest_missing_key" "GET" \
|
||||
"${API_URL}/api/v1/manifest" \
|
||||
"401"
|
||||
request_with_api_key "${INVALID_API_KEY}" "manifest_invalid_key" "GET" \
|
||||
"${API_URL}/api/v1/manifest" \
|
||||
"403"
|
||||
assert_file_contains "${TMP_DIR}/manifest_missing_key.body" "Missing API key"
|
||||
assert_file_contains "${TMP_DIR}/manifest_invalid_key.body" "Invalid API key"
|
||||
|
||||
request_without_api_key "editor_context_missing_key" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/editor/context" \
|
||||
"401"
|
||||
request_with_api_key "${INVALID_API_KEY}" "editor_context_invalid_key" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/editor/context" \
|
||||
"403"
|
||||
assert_file_contains "${TMP_DIR}/editor_context_missing_key.body" "Missing API key"
|
||||
assert_file_contains "${TMP_DIR}/editor_context_invalid_key.body" "Invalid API key"
|
||||
|
||||
request_without_api_key "pricing_bundle_missing_key" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" \
|
||||
"401"
|
||||
request_with_api_key "${INVALID_API_KEY}" "pricing_bundle_invalid_key" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" \
|
||||
"403"
|
||||
assert_file_contains "${TMP_DIR}/pricing_bundle_missing_key.body" "Missing API key"
|
||||
assert_file_contains "${TMP_DIR}/pricing_bundle_invalid_key.body" "Invalid API key"
|
||||
|
||||
request_without_api_key "admin_audit_missing_key" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"401"
|
||||
request_with_api_key "${INVALID_API_KEY}" "admin_audit_invalid_key" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "admin_audit_wrong_role" "GET" \
|
||||
"${API_URL}/api/v1/admin/artifacts/publish-preview/audit" \
|
||||
"403"
|
||||
assert_file_contains "${TMP_DIR}/admin_audit_missing_key.body" "Missing API key"
|
||||
assert_file_contains "${TMP_DIR}/admin_audit_invalid_key.body" "Invalid API key"
|
||||
assert_file_contains "${TMP_DIR}/admin_audit_wrong_role.body" "Admin role required"
|
||||
|
||||
request_without_api_key "admin_cleanup_preview_missing_key" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview" \
|
||||
"401"
|
||||
request_with_api_key "${INVALID_API_KEY}" "admin_cleanup_preview_invalid_key" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview" \
|
||||
"403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "admin_cleanup_preview_wrong_role" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview" \
|
||||
"403"
|
||||
assert_file_contains "${TMP_DIR}/admin_cleanup_preview_missing_key.body" "Missing API key"
|
||||
assert_file_contains "${TMP_DIR}/admin_cleanup_preview_invalid_key.body" "Invalid API key"
|
||||
assert_file_contains "${TMP_DIR}/admin_cleanup_preview_wrong_role.body" "Admin role required"
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke auth negative completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
@@ -14,7 +14,7 @@ VIEWER_API_KEY="${VIEWER_API_KEY:-viewer-local-dev-key}"
|
||||
|
||||
wait_for_health
|
||||
|
||||
create_fresh_scheme_from_upload "smoke-authz-admin-ops"
|
||||
create_fresh_scheme_from_upload "smoke-authz-admin-all"
|
||||
|
||||
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")"
|
||||
@@ -38,17 +38,17 @@ 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")
|
||||
raise SystemExit("No seat with seat_id found for authz admin all 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}"
|
||||
CLEANUP_PREFIX="AUTHZ_ADMINALL_${STAMP}_"
|
||||
DELETE_CATEGORY_NAME="authz-adminall-delete-${STAMP}"
|
||||
DELETE_CATEGORY_CODE="${CLEANUP_PREFIX}DELETE"
|
||||
KEEP_CATEGORY_NAME="authz-adminops-keep-${STAMP}"
|
||||
KEEP_CATEGORY_NAME="authz-adminall-keep-${STAMP}"
|
||||
KEEP_CATEGORY_CODE="${CLEANUP_PREFIX}KEEP"
|
||||
|
||||
request "create_delete_category" "POST" \
|
||||
@@ -68,7 +68,7 @@ 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\"}"
|
||||
"{\"pricing_category_id\":\"${KEEP_CATEGORY_ID}\",\"target_type\":\"seat\",\"target_ref\":\"${TARGET_SEAT_ID}\",\"amount\":\"777.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}"
|
||||
|
||||
@@ -79,6 +79,42 @@ 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_current_artifacts" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" "200"
|
||||
request_with_api_key "${OPERATOR_API_KEY}" "operator_current_artifacts" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" "403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "viewer_current_artifacts" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" "403"
|
||||
assert_file_contains "${TMP_DIR}/operator_current_artifacts.body" "Admin role required"
|
||||
assert_file_contains "${TMP_DIR}/viewer_current_artifacts.body" "Admin role required"
|
||||
|
||||
request_with_api_key "${ADMIN_API_KEY}" "admin_current_validation" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/validation" "200"
|
||||
request_with_api_key "${OPERATOR_API_KEY}" "operator_current_validation" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/validation" "403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "viewer_current_validation" "GET" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/validation" "403"
|
||||
assert_file_contains "${TMP_DIR}/operator_current_validation.body" "Admin role required"
|
||||
assert_file_contains "${TMP_DIR}/viewer_current_validation.body" "Admin role required"
|
||||
|
||||
request_with_api_key "${ADMIN_API_KEY}" "admin_display_regenerate" "POST" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/display/regenerate?mode=passthrough" "200"
|
||||
request_with_api_key "${OPERATOR_API_KEY}" "operator_display_regenerate" "POST" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/display/regenerate?mode=passthrough" "403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "viewer_display_regenerate" "POST" \
|
||||
"${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/display/regenerate?mode=passthrough" "403"
|
||||
assert_file_contains "${TMP_DIR}/operator_display_regenerate.body" "Admin role required"
|
||||
assert_file_contains "${TMP_DIR}/viewer_display_regenerate.body" "Admin role required"
|
||||
|
||||
request_with_api_key "${ADMIN_API_KEY}" "admin_display_backfill" "POST" \
|
||||
"${API_URL}/api/v1/admin/display/backfill?mode=passthrough&limit=1&only_missing=true" "200"
|
||||
request_with_api_key "${OPERATOR_API_KEY}" "operator_display_backfill" "POST" \
|
||||
"${API_URL}/api/v1/admin/display/backfill?mode=passthrough&limit=1&only_missing=true" "403"
|
||||
request_with_api_key "${VIEWER_API_KEY}" "viewer_display_backfill" "POST" \
|
||||
"${API_URL}/api/v1/admin/display/backfill?mode=passthrough&limit=1&only_missing=true" "403"
|
||||
assert_file_contains "${TMP_DIR}/operator_display_backfill.body" "Admin role required"
|
||||
assert_file_contains "${TMP_DIR}/viewer_display_backfill.body" "Admin role required"
|
||||
|
||||
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" \
|
||||
@@ -152,15 +188,15 @@ 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")
|
||||
raise SystemExit("Authz admin-all cleanup left deletable category behind")
|
||||
if keep_category_id not in category_ids:
|
||||
raise SystemExit("Authz cleanup execute removed protected category")
|
||||
raise SystemExit("Authz admin-all cleanup removed protected category")
|
||||
if keep_rule_id not in rule_ids:
|
||||
raise SystemExit("Authz cleanup execute removed protected rule")
|
||||
raise SystemExit("Authz admin-all cleanup 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 "[OK] smoke authz admin all completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
@@ -153,6 +153,55 @@ PY
|
||||
fi
|
||||
}
|
||||
|
||||
request_without_api_key() {
|
||||
local name="$1"
|
||||
local method="$2"
|
||||
local url="$3"
|
||||
local expected_status="$4"
|
||||
local body="${5:-}"
|
||||
local out_file="${TMP_DIR}/${name}.body"
|
||||
local status_file="${TMP_DIR}/${name}.status"
|
||||
|
||||
echo
|
||||
echo "===== ${name} ====="
|
||||
|
||||
if [[ -n "${body}" ]]; then
|
||||
curl -sS \
|
||||
-X "${method}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
"${url}" \
|
||||
--data "${body}" > "${status_file}"
|
||||
else
|
||||
curl -sS \
|
||||
-X "${method}" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
"${url}" > "${status_file}"
|
||||
fi
|
||||
|
||||
local actual_status
|
||||
actual_status="$(python3 - "$status_file" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
print(Path(sys.argv[1]).read_text(encoding="utf-8").strip())
|
||||
PY
|
||||
)"
|
||||
|
||||
echo "[${method}] ${url} -> ${actual_status}"
|
||||
python3 - "$out_file" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
print(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
PY
|
||||
echo
|
||||
|
||||
if [[ "${actual_status}" != "${expected_status}" ]]; then
|
||||
fail "Unexpected HTTP status for ${name}: expected ${expected_status}, got ${actual_status}"
|
||||
fi
|
||||
}
|
||||
|
||||
upload_svg() {
|
||||
local name="$1"
|
||||
local upload_filename="$2"
|
||||
|
||||
68
backend/scripts/smoke_lifecycle_negative.sh
Normal file
68
backend/scripts/smoke_lifecycle_negative.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/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"
|
||||
|
||||
set -a
|
||||
source "${REPO_ROOT}/.env"
|
||||
set +a
|
||||
|
||||
wait_for_health
|
||||
|
||||
create_fresh_scheme_from_upload "smoke-lifecycle-negative"
|
||||
|
||||
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 "rollback_nonexistent_version" "POST" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/rollback" \
|
||||
"404" \
|
||||
"{\"target_version_number\":999}"
|
||||
assert_file_contains "${TMP_DIR}/rollback_nonexistent_version.body" "Target scheme version not found"
|
||||
|
||||
request "ensure_draft_stale_current_version" "POST" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/ensure?expected_current_scheme_version_id=deadbeefdeadbeefdeadbeefdeadbeef" \
|
||||
"409"
|
||||
assert_json_eq "${TMP_DIR}/ensure_draft_stale_current_version.body" "detail.code" "stale_current_version"
|
||||
|
||||
request "publish_stale_expected_version" "POST" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/publish?expected_scheme_version_id=deadbeefdeadbeefdeadbeefdeadbeef" \
|
||||
"409"
|
||||
assert_json_eq "${TMP_DIR}/publish_stale_expected_version.body" "detail.code" "publish_not_ready"
|
||||
assert_file_contains "${TMP_DIR}/publish_stale_expected_version.body" "\"actual_scheme_version_id\":\"${CURRENT_VERSION_ID}\""
|
||||
|
||||
INCONSISTENT_VERSION_NUMBER="999"
|
||||
UPDATED_VERSION_NUMBER="$(docker compose exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -Atc "update schemes set current_version_number=${INCONSISTENT_VERSION_NUMBER} where scheme_id='${SCHEME_ID}' and current_version_number=1 returning current_version_number;" | python3 -c 'import sys; lines=[line.strip() for line in sys.stdin.read().splitlines() if line.strip()]; print(lines[0] if lines else "")')"
|
||||
if [[ "${UPDATED_VERSION_NUMBER}" != "${INCONSISTENT_VERSION_NUMBER}" ]]; then
|
||||
fail "Failed to introduce temporary current_version_inconsistent state for ${SCHEME_ID}"
|
||||
fi
|
||||
echo "[OK] introduced temporary current_version_inconsistent state for ${SCHEME_ID}"
|
||||
|
||||
restore_current_version_pointer() {
|
||||
docker compose exec -T postgres psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -Atc "update schemes set current_version_number=1 where scheme_id='${SCHEME_ID}' and current_version_number=${INCONSISTENT_VERSION_NUMBER};" >/dev/null
|
||||
}
|
||||
|
||||
trap 'restore_current_version_pointer; rm -rf "${TMP_DIR}"' EXIT
|
||||
|
||||
request "current_version_inconsistent" "GET" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/current" \
|
||||
"409"
|
||||
assert_json_eq "${TMP_DIR}/current_version_inconsistent.body" "detail.code" "current_version_inconsistent"
|
||||
assert_file_contains "${TMP_DIR}/current_version_inconsistent.body" "\"current_version_number\":${INCONSISTENT_VERSION_NUMBER}"
|
||||
|
||||
restore_current_version_pointer
|
||||
|
||||
request "scheme_current_restored" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
||||
assert_json_eq "${TMP_DIR}/scheme_current_restored.body" "scheme_version_id" "${CURRENT_VERSION_ID}"
|
||||
assert_json_int_eq "${TMP_DIR}/scheme_current_restored.body" "version_number" "1"
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke lifecycle negative completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
@@ -14,13 +14,25 @@ echo
|
||||
echo "===== smoke version lifecycle ====="
|
||||
bash "${SCRIPT_DIR}/smoke_version_lifecycle.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke lifecycle negative ====="
|
||||
bash "${SCRIPT_DIR}/smoke_lifecycle_negative.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke admin ops ====="
|
||||
bash "${SCRIPT_DIR}/smoke_admin_ops.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke authz admin ops ====="
|
||||
bash "${SCRIPT_DIR}/smoke_authz_admin_ops.sh"
|
||||
echo "===== smoke authz admin all ====="
|
||||
bash "${SCRIPT_DIR}/smoke_authz_admin_all.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke auth negative ====="
|
||||
bash "${SCRIPT_DIR}/smoke_auth_negative.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke artifact corruption ====="
|
||||
bash "${SCRIPT_DIR}/smoke_artifact_corruption.sh"
|
||||
|
||||
echo
|
||||
echo "===== smoke upload negative ====="
|
||||
|
||||
Reference in New Issue
Block a user