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:
greebo
2026-03-20 16:46:24 +03:00
parent 5aa35b1d04
commit 54b36ba76c
8 changed files with 1103 additions and 23 deletions

View 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}"

View 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}"

View File

@@ -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}"

View File

@@ -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"

View 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}"

View File

@@ -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 ====="