test(backend): split smoke regression into core and pricing publish flows
separate smoke coverage into core backend checks and pricing publish flow checks make regression runs more focused and easier to maintain improve troubleshooting when a smoke stage fails
This commit is contained in:
354
backend/scripts/smoke_common.sh
Normal file
354
backend/scripts/smoke_common.sh
Normal file
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
API_URL="${API_URL:-http://127.0.0.1:9020}"
|
||||
API_KEY="${API_KEY:-admin-local-dev-key}"
|
||||
FIXTURE_SVG_PATH="${FIXTURE_SVG_PATH:-${REPO_ROOT}/sample-contract.svg}"
|
||||
HEALTH_MAX_ATTEMPTS="${HEALTH_MAX_ATTEMPTS:-20}"
|
||||
HEALTH_RETRY_DELAY_SECONDS="${HEALTH_RETRY_DELAY_SECONDS:-1}"
|
||||
|
||||
log() {
|
||||
echo
|
||||
echo "===== $* ====="
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo
|
||||
echo "[FAIL] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_fixture_svg() {
|
||||
if [[ ! -f "${FIXTURE_SVG_PATH}" ]]; then
|
||||
fail "Fixture SVG not found: ${FIXTURE_SVG_PATH}"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
log "health"
|
||||
echo "waiting for API to be ready..."
|
||||
local health_ready="false"
|
||||
local health_status=""
|
||||
|
||||
for ((i = 1; i <= HEALTH_MAX_ATTEMPTS; i++)); do
|
||||
health_status="$(curl -sS -o /dev/null -w "%{http_code}" "${API_URL}/healthz" || true)"
|
||||
if [[ "${health_status}" == "200" ]]; then
|
||||
health_ready="true"
|
||||
echo "API is ready"
|
||||
break
|
||||
fi
|
||||
echo "waiting... (${i}/${HEALTH_MAX_ATTEMPTS}) healthz=${health_status}"
|
||||
sleep "${HEALTH_RETRY_DELAY_SECONDS}"
|
||||
done
|
||||
|
||||
if [[ "${health_ready}" != "true" ]]; then
|
||||
fail "API did not become ready on ${API_URL}/healthz after ${HEALTH_MAX_ATTEMPTS} attempts"
|
||||
fi
|
||||
|
||||
curl -sS -i "${API_URL}/healthz"
|
||||
}
|
||||
|
||||
request() {
|
||||
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 "X-API-Key: ${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
"${url}" \
|
||||
--data "${body}" > "${status_file}"
|
||||
else
|
||||
curl -sS \
|
||||
-X "${method}" \
|
||||
-H "X-API-Key: ${API_KEY}" \
|
||||
-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"
|
||||
local out_file="${TMP_DIR}/${name}.body"
|
||||
local status_file="${TMP_DIR}/${name}.status"
|
||||
|
||||
require_fixture_svg
|
||||
|
||||
echo
|
||||
echo "===== ${name} ====="
|
||||
|
||||
curl -sS \
|
||||
-X POST \
|
||||
-H "X-API-Key: ${API_KEY}" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
-F "file=@${FIXTURE_SVG_PATH};filename=${upload_filename};type=image/svg+xml" \
|
||||
"${API_URL}/api/v1/schemes/upload" > "${status_file}"
|
||||
|
||||
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 "[POST] ${API_URL}/api/v1/schemes/upload -> ${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}" != "200" ]]; then
|
||||
fail "Upload failed for ${upload_filename}: expected 200, got ${actual_status}"
|
||||
fi
|
||||
}
|
||||
|
||||
json_get() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
python3 - "$file" "$expr" <<'PY'
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
expr = sys.argv[2]
|
||||
|
||||
def apply_selector(value, selector):
|
||||
if value is None:
|
||||
return None
|
||||
if selector == "LAST":
|
||||
return value[-1] if value else None
|
||||
if selector.isdigit():
|
||||
idx = int(selector)
|
||||
return value[idx] if len(value) > idx else None
|
||||
|
||||
match = re.fullmatch(r"([^!=]+?)(==|!=)(.+)", selector)
|
||||
if not match:
|
||||
return value[0] if value else None
|
||||
|
||||
key, op, raw_expected = match.groups()
|
||||
key = key.strip()
|
||||
raw_expected = raw_expected.strip()
|
||||
if raw_expected == "null":
|
||||
expected = None
|
||||
else:
|
||||
expected = raw_expected
|
||||
|
||||
for item in value:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
item_value = item.get(key)
|
||||
matched = item_value == expected if op == "==" else item_value != expected
|
||||
if matched:
|
||||
return item
|
||||
return None
|
||||
|
||||
value = data
|
||||
for part in expr.split("."):
|
||||
if not part:
|
||||
continue
|
||||
if part.startswith("[") and part.endswith("]"):
|
||||
value = apply_selector(value, part[1:-1])
|
||||
elif part.isdigit():
|
||||
idx = int(part)
|
||||
value = value[idx] if value is not None and len(value) > idx else None
|
||||
elif isinstance(value, dict):
|
||||
value = value.get(part)
|
||||
else:
|
||||
value = None
|
||||
|
||||
if isinstance(value, bool):
|
||||
print("true" if value else "false")
|
||||
elif value is None:
|
||||
print("")
|
||||
elif isinstance(value, (dict, list)):
|
||||
print(json.dumps(value, ensure_ascii=False))
|
||||
else:
|
||||
print(value)
|
||||
PY
|
||||
}
|
||||
|
||||
json_len() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
python3 - "$file" "$expr" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
expr = sys.argv[2]
|
||||
value = data
|
||||
for part in expr.split("."):
|
||||
if not part:
|
||||
continue
|
||||
if part.isdigit():
|
||||
value = value[int(part)]
|
||||
else:
|
||||
value = value.get(part) if isinstance(value, dict) else None
|
||||
|
||||
if value is None:
|
||||
print(0)
|
||||
elif isinstance(value, (list, dict, str)):
|
||||
print(len(value))
|
||||
else:
|
||||
print(0)
|
||||
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 "${expr}: expected '${expected}', got '${actual}'"
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual}"
|
||||
}
|
||||
|
||||
assert_json_int_eq() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local expected="$3"
|
||||
local actual
|
||||
actual="$(json_get "${file}" "${expr}")"
|
||||
if ! [[ "${actual}" =~ ^[0-9]+$ ]]; then
|
||||
fail "${expr}: expected integer, got '${actual}'"
|
||||
fi
|
||||
if (( actual != expected )); then
|
||||
fail "${expr}: expected ${expected}, got ${actual}"
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual}"
|
||||
}
|
||||
|
||||
assert_json_int_gt() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local threshold="$3"
|
||||
local actual
|
||||
actual="$(json_get "${file}" "${expr}")"
|
||||
if ! [[ "${actual}" =~ ^[0-9]+$ ]]; then
|
||||
fail "${expr}: expected integer, got '${actual}'"
|
||||
fi
|
||||
if (( actual <= threshold )); then
|
||||
fail "${expr}: expected > ${threshold}, got ${actual}"
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual} (> ${threshold})"
|
||||
}
|
||||
|
||||
assert_json_int_ge() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local threshold="$3"
|
||||
local actual
|
||||
actual="$(json_get "${file}" "${expr}")"
|
||||
if ! [[ "${actual}" =~ ^[0-9]+$ ]]; then
|
||||
fail "${expr}: expected integer, got '${actual}'"
|
||||
fi
|
||||
if (( actual < threshold )); then
|
||||
fail "${expr}: expected >= ${threshold}, got ${actual}"
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual} (>= ${threshold})"
|
||||
}
|
||||
|
||||
assert_json_len_eq() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local expected="$3"
|
||||
local actual
|
||||
actual="$(json_len "${file}" "${expr}")"
|
||||
if (( actual != expected )); then
|
||||
fail "len(${expr}): expected ${expected}, got ${actual}"
|
||||
fi
|
||||
echo "[OK] len(${expr})=${actual}"
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! python3 - "$file" "$needle" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
haystack = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
needle = sys.argv[2]
|
||||
if needle not in haystack:
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
then
|
||||
fail "Expected '${needle}' in ${file}"
|
||||
fi
|
||||
echo "[OK] found '${needle}'"
|
||||
}
|
||||
|
||||
create_fresh_scheme_from_upload() {
|
||||
local scenario_prefix="$1"
|
||||
local stamp
|
||||
stamp="$(date +%s)-$$"
|
||||
FRESH_SCHEME_NAME="${scenario_prefix}-${stamp}"
|
||||
local upload_filename="${FRESH_SCHEME_NAME}.svg"
|
||||
|
||||
upload_svg "upload_${scenario_prefix}" "${upload_filename}"
|
||||
request "schemes_after_upload_${scenario_prefix}" "GET" "${API_URL}/api/v1/schemes?limit=200&offset=0" "200"
|
||||
|
||||
if ! SCHEME_ID="$(python3 - "${TMP_DIR}/schemes_after_upload_${scenario_prefix}.body" "${FRESH_SCHEME_NAME}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
target_name = sys.argv[2]
|
||||
for item in payload.get("items", []):
|
||||
if item.get("name") == target_name:
|
||||
print(item["scheme_id"])
|
||||
raise SystemExit(0)
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
)"; then
|
||||
fail "Unable to resolve uploaded scheme_id for ${FRESH_SCHEME_NAME}"
|
||||
fi
|
||||
|
||||
echo "FRESH_SCHEME_NAME=${FRESH_SCHEME_NAME}"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
}
|
||||
133
backend/scripts/smoke_core.sh
Normal file
133
backend/scripts/smoke_core.sh
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/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
|
||||
|
||||
request "ping" "GET" "${API_URL}/api/v1/ping" "200"
|
||||
request "db_ping" "GET" "${API_URL}/api/v1/db/ping" "200"
|
||||
request "manifest" "GET" "${API_URL}/api/v1/manifest" "200"
|
||||
|
||||
create_fresh_scheme_from_upload "smoke-core"
|
||||
|
||||
request "scheme_detail" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/scheme_detail.body" "scheme_id" "${SCHEME_ID}"
|
||||
assert_json_eq "${TMP_DIR}/scheme_detail.body" "name" "${FRESH_SCHEME_NAME}"
|
||||
assert_json_eq "${TMP_DIR}/scheme_detail.body" "status" "draft"
|
||||
|
||||
request "scheme_versions" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/versions?limit=20&offset=0" "200"
|
||||
assert_json_len_eq "${TMP_DIR}/scheme_versions.body" "items" "1"
|
||||
|
||||
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")"
|
||||
CURRENT_STATUS="$(json_get "${TMP_DIR}/scheme_current.body" "status")"
|
||||
echo "CURRENT_VERSION_ID=${CURRENT_VERSION_ID}"
|
||||
echo "CURRENT_STATUS=${CURRENT_STATUS}"
|
||||
assert_json_eq "${TMP_DIR}/scheme_current.body" "status" "draft"
|
||||
|
||||
request "editor_context" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/editor/context" "200"
|
||||
assert_json_eq "${TMP_DIR}/editor_context.body" "current_scheme_version_id" "${CURRENT_VERSION_ID}"
|
||||
assert_json_eq "${TMP_DIR}/editor_context.body" "current_is_draft" "true"
|
||||
|
||||
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}"
|
||||
assert_json_eq "${TMP_DIR}/ensure_draft.body" "scheme_version_id" "${CURRENT_VERSION_ID}"
|
||||
assert_json_eq "${TMP_DIR}/ensure_draft.body" "created" "false"
|
||||
|
||||
request "draft_summary" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/summary?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_structure" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?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_structure.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}"
|
||||
|
||||
TOTAL_SEATS="$(json_get "${TMP_DIR}/draft_summary.body" "total_seats")"
|
||||
echo "TOTAL_SEATS=${TOTAL_SEATS}"
|
||||
|
||||
read -r SEAT_RECORD_ID SECTOR_RECORD_ID GROUP_RECORD_ID EXPLAIN_SEAT_ID <<EOF
|
||||
$(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"))
|
||||
seats = payload.get("seats", [])
|
||||
sectors = payload.get("sectors", [])
|
||||
groups = payload.get("groups", [])
|
||||
|
||||
seat_with_id = next((seat for seat in seats if seat.get("seat_id")), None)
|
||||
if seat_with_id is None:
|
||||
raise SystemExit("No seat with seat_id found in fresh draft structure")
|
||||
|
||||
print(
|
||||
seat_with_id["seat_record_id"],
|
||||
sectors[0]["sector_record_id"],
|
||||
groups[0]["group_record_id"],
|
||||
seat_with_id["seat_id"],
|
||||
)
|
||||
PY
|
||||
)
|
||||
EOF
|
||||
|
||||
echo "SEAT_RECORD_ID=${SEAT_RECORD_ID}"
|
||||
echo "SECTOR_RECORD_ID=${SECTOR_RECORD_ID}"
|
||||
echo "GROUP_RECORD_ID=${GROUP_RECORD_ID}"
|
||||
echo "EXPLAIN_SEAT_ID=${EXPLAIN_SEAT_ID}"
|
||||
|
||||
request "stale_draft_conflict" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/summary?expected_scheme_version_id=deadbeefdeadbeefdeadbeefdeadbeef" "409"
|
||||
assert_json_eq "${TMP_DIR}/stale_draft_conflict.body" "detail.code" "stale_draft_version"
|
||||
|
||||
request "draft_seat_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_sector_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors/records/${SECTOR_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_group_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/groups/records/${GROUP_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_unknown_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/deadbeefdeadbeefdeadbeefdeadbeef" "404"
|
||||
|
||||
request "current_sectors" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/sectors" "200"
|
||||
request "current_groups" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/groups" "200"
|
||||
request "current_seats" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/seats" "200"
|
||||
request "display_meta" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/svg/display/meta" "200"
|
||||
|
||||
request "pricing_bundle" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" "200"
|
||||
assert_json_len_eq "${TMP_DIR}/pricing_bundle.body" "categories" "0"
|
||||
assert_json_len_eq "${TMP_DIR}/pricing_bundle.body" "rules" "0"
|
||||
|
||||
request "pricing_coverage" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/coverage" "200"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_coverage.body" "priced_seats" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_coverage.body" "unpriced_seats" "${TOTAL_SEATS}"
|
||||
|
||||
request "pricing_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/unpriced-seats" "200"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_unpriced.body" "total" "${TOTAL_SEATS}"
|
||||
|
||||
request "pricing_explain_empty" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/explain/${EXPLAIN_SEAT_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_empty.body" "has_price" "false"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_empty.body" "reason_code" "no_price_rule"
|
||||
|
||||
request "pricing_rule_diagnostics" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules/diagnostics" "200"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_rule_diagnostics.body" "summary.total_rules" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_rule_diagnostics.body" "summary.active_rules_count" "0"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_rule_diagnostics.body" "summary.matched_seats_total" "0"
|
||||
assert_json_len_eq "${TMP_DIR}/pricing_rule_diagnostics.body" "items" "0"
|
||||
|
||||
request "audit_trail" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/audit" "200"
|
||||
assert_json_int_ge "${TMP_DIR}/audit_trail.body" "total" "0"
|
||||
|
||||
log "editor mutation regression"
|
||||
API_URL="${API_URL}" API_KEY="${API_KEY}" SCHEME_ID="${SCHEME_ID}" \
|
||||
bash "${SCRIPT_DIR}/editor_mutation_regression.sh"
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke core completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
117
backend/scripts/smoke_pricing_publish.sh
Normal file
117
backend/scripts/smoke_pricing_publish.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/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-pricing-publish"
|
||||
|
||||
request "scheme_current" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
||||
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")"
|
||||
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"
|
||||
|
||||
read -r PRICED_SEAT_ID UNPRICED_SEAT_ID <<EOF
|
||||
$(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_ids = [seat["seat_id"] for seat in payload.get("seats", []) if seat.get("seat_id")]
|
||||
if len(seat_ids) < 2:
|
||||
raise SystemExit("Fixture must contain at least two seats with seat_id for pricing smoke")
|
||||
print(seat_ids[0], seat_ids[1])
|
||||
PY
|
||||
)
|
||||
EOF
|
||||
|
||||
echo "PRICED_SEAT_ID=${PRICED_SEAT_ID}"
|
||||
echo "UNPRICED_SEAT_ID=${UNPRICED_SEAT_ID}"
|
||||
|
||||
STAMP="$(date +%s)-$$"
|
||||
PRICING_CATEGORY_NAME="smoke-pricing-${STAMP}"
|
||||
PRICING_CATEGORY_CODE="SMOKE_${STAMP}"
|
||||
|
||||
request "create_pricing_category" "POST" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/categories?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
||||
"200" \
|
||||
"{\"name\":\"${PRICING_CATEGORY_NAME}\",\"code\":\"${PRICING_CATEGORY_CODE}\"}"
|
||||
PRICING_CATEGORY_ID="$(json_get "${TMP_DIR}/create_pricing_category.body" "pricing_category_id")"
|
||||
echo "PRICING_CATEGORY_ID=${PRICING_CATEGORY_ID}"
|
||||
|
||||
request "create_price_rule" "POST" \
|
||||
"${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules?expected_scheme_version_id=${DRAFT_VERSION_ID}" \
|
||||
"200" \
|
||||
"{\"pricing_category_id\":\"${PRICING_CATEGORY_ID}\",\"target_type\":\"seat\",\"target_ref\":\"${PRICED_SEAT_ID}\",\"amount\":\"1234.56\",\"currency\":\"RUB\"}"
|
||||
PRICE_RULE_ID="$(json_get "${TMP_DIR}/create_price_rule.body" "price_rule_id")"
|
||||
echo "PRICE_RULE_ID=${PRICE_RULE_ID}"
|
||||
|
||||
request "pricing_bundle" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" "200"
|
||||
assert_json_len_eq "${TMP_DIR}/pricing_bundle.body" "categories" "1"
|
||||
assert_json_len_eq "${TMP_DIR}/pricing_bundle.body" "rules" "1"
|
||||
|
||||
request "pricing_coverage" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/coverage" "200"
|
||||
assert_json_int_gt "${TMP_DIR}/pricing_coverage.body" "priced_seats" "0"
|
||||
assert_json_int_gt "${TMP_DIR}/pricing_coverage.body" "unpriced_seats" "0"
|
||||
|
||||
request "pricing_explain_priced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/explain/${PRICED_SEAT_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_priced.body" "has_price" "true"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_priced.body" "reason_code" "ok"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_priced.body" "matched_rule.matched_rule_level" "seat"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_priced.body" "matched_rule.matched_target_ref" "${PRICED_SEAT_ID}"
|
||||
|
||||
request "pricing_explain_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/explain/${UNPRICED_SEAT_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_unpriced.body" "has_price" "false"
|
||||
assert_json_eq "${TMP_DIR}/pricing_explain_unpriced.body" "reason_code" "no_price_rule"
|
||||
|
||||
request "pricing_rule_diagnostics" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules/diagnostics" "200"
|
||||
assert_json_int_eq "${TMP_DIR}/pricing_rule_diagnostics.body" "summary.total_rules" "1"
|
||||
assert_json_int_gt "${TMP_DIR}/pricing_rule_diagnostics.body" "summary.matched_seats_total" "0"
|
||||
|
||||
request "seat_price" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/seats/${PRICED_SEAT_ID}/price" "200"
|
||||
assert_json_eq "${TMP_DIR}/seat_price.body" "matched_rule_level" "seat"
|
||||
assert_json_eq "${TMP_DIR}/seat_price.body" "matched_target_ref" "${PRICED_SEAT_ID}"
|
||||
assert_json_eq "${TMP_DIR}/seat_price.body" "amount" "1234.56"
|
||||
|
||||
request "test_mode_priced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/test/seats/${PRICED_SEAT_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/test_mode_priced.body" "has_price" "true"
|
||||
assert_json_eq "${TMP_DIR}/test_mode_priced.body" "selectable" "true"
|
||||
|
||||
request "test_mode_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/test/seats/${UNPRICED_SEAT_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/test_mode_unpriced.body" "has_price" "false"
|
||||
assert_json_eq "${TMP_DIR}/test_mode_unpriced.body" "reason_code" "no_price_rule"
|
||||
|
||||
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_readiness" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-readiness?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/publish_readiness.body" "snapshot.available" "true"
|
||||
assert_json_eq "${TMP_DIR}/publish_readiness.body" "readiness.is_ready_to_publish" "true"
|
||||
|
||||
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 "publish_preview_cached" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
|
||||
request "publish_scheme" "POST" "${API_URL}/api/v1/schemes/${SCHEME_ID}/publish?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/publish_scheme.body" "scheme_version_id" "${DRAFT_VERSION_ID}"
|
||||
assert_json_eq "${TMP_DIR}/publish_scheme.body" "status" "published"
|
||||
|
||||
request "scheme_detail" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/scheme_detail.body" "status" "published"
|
||||
|
||||
request "scheme_current" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current" "200"
|
||||
assert_json_eq "${TMP_DIR}/scheme_current.body" "status" "published"
|
||||
|
||||
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\""
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke pricing/publish completed successfully"
|
||||
echo "FRESH_SCHEME_ID=${SCHEME_ID}"
|
||||
@@ -1,233 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
API_URL="${API_URL:-http://127.0.0.1:9020}"
|
||||
API_KEY="${API_KEY:-admin-local-dev-key}"
|
||||
SCHEME_ID="${SCHEME_ID:-82086336d385427f9d56244f9e1dd772}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||||
HEALTH_MAX_ATTEMPTS="${HEALTH_MAX_ATTEMPTS:-20}"
|
||||
HEALTH_RETRY_DELAY_SECONDS="${HEALTH_RETRY_DELAY_SECONDS:-1}"
|
||||
echo "===== smoke core ====="
|
||||
bash "${SCRIPT_DIR}/smoke_core.sh"
|
||||
|
||||
request() {
|
||||
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 "X-API-Key: ${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
"${url}" \
|
||||
--data "${body}" > "${status_file}"
|
||||
else
|
||||
curl -sS \
|
||||
-X "${method}" \
|
||||
-H "X-API-Key: ${API_KEY}" \
|
||||
-o "${out_file}" \
|
||||
-w "%{http_code}" \
|
||||
"${url}" > "${status_file}"
|
||||
fi
|
||||
|
||||
local actual_status
|
||||
actual_status="$(cat "${status_file}")"
|
||||
|
||||
echo "[${method}] ${url} -> ${actual_status}"
|
||||
cat "${out_file}"
|
||||
echo
|
||||
|
||||
if [[ "${actual_status}" != "${expected_status}" ]]; then
|
||||
echo "[FAIL] Unexpected HTTP status for ${name}: expected ${expected_status}, got ${actual_status}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
json_get() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
python3 - "$file" "$expr" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
path = sys.argv[2].split(".")
|
||||
value = json.load(open(sys.argv[1], "r", encoding="utf-8"))
|
||||
for part in path:
|
||||
if not part:
|
||||
continue
|
||||
if part.isdigit():
|
||||
value = value[int(part)]
|
||||
else:
|
||||
value = value[part]
|
||||
if isinstance(value, bool):
|
||||
print("true" if value else "false")
|
||||
elif value is None:
|
||||
print("null")
|
||||
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
|
||||
echo "[FAIL] ${expr}: expected '${expected}', got '${actual}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual}"
|
||||
}
|
||||
|
||||
assert_json_int_gt() {
|
||||
local file="$1"
|
||||
local expr="$2"
|
||||
local threshold="$3"
|
||||
local actual
|
||||
actual="$(json_get "${file}" "${expr}")"
|
||||
if ! [[ "${actual}" =~ ^[0-9]+$ ]]; then
|
||||
echo "[FAIL] ${expr}: expected integer, got '${actual}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( actual <= threshold )); then
|
||||
echo "[FAIL] ${expr}: expected > ${threshold}, got ${actual}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[OK] ${expr}=${actual} (> ${threshold})"
|
||||
}
|
||||
|
||||
echo "===== health ====="
|
||||
echo "waiting for API to be ready..."
|
||||
health_ready="false"
|
||||
for ((i = 1; i <= HEALTH_MAX_ATTEMPTS; i++)); do
|
||||
health_status="$(curl -sS -o /dev/null -w "%{http_code}" "${API_URL}/healthz" || true)"
|
||||
if [[ "${health_status}" == "200" ]]; then
|
||||
health_ready="true"
|
||||
echo "API is ready"
|
||||
break
|
||||
fi
|
||||
echo "waiting... (${i}/${HEALTH_MAX_ATTEMPTS}) healthz=${health_status}"
|
||||
sleep "${HEALTH_RETRY_DELAY_SECONDS}"
|
||||
done
|
||||
|
||||
if [[ "${health_ready}" != "true" ]]; then
|
||||
echo "[FAIL] API did not become ready on ${API_URL}/healthz after ${HEALTH_MAX_ATTEMPTS} attempts" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -sS -i "${API_URL}/healthz"
|
||||
|
||||
request "ping" "GET" "${API_URL}/api/v1/ping" "200"
|
||||
request "db_ping" "GET" "${API_URL}/api/v1/db/ping" "200"
|
||||
request "manifest" "GET" "${API_URL}/api/v1/manifest" "200"
|
||||
|
||||
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")"
|
||||
CURRENT_STATUS="$(json_get "${TMP_DIR}/scheme_current.body" "status")"
|
||||
echo "CURRENT_VERSION_ID=${CURRENT_VERSION_ID}"
|
||||
echo "CURRENT_STATUS=${CURRENT_STATUS}"
|
||||
|
||||
request "editor_context" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/editor/context" "200"
|
||||
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}"
|
||||
|
||||
request "draft_summary" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/summary?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_summary.body" "status" "draft"
|
||||
|
||||
request "draft_structure" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/structure?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/draft_structure.body" "scheme_version_id" "${DRAFT_VERSION_ID}"
|
||||
|
||||
SEAT_RECORD_ID="$(json_get "${TMP_DIR}/draft_structure.body" "seats.0.seat_record_id")"
|
||||
SECTOR_RECORD_ID="$(json_get "${TMP_DIR}/draft_structure.body" "sectors.0.sector_record_id")"
|
||||
GROUP_RECORD_ID="$(json_get "${TMP_DIR}/draft_structure.body" "groups.0.group_record_id")"
|
||||
PRICED_SEAT_ID="$(json_get "${TMP_DIR}/draft_structure.body" "seats.0.seat_id")"
|
||||
UNPRICED_SEAT_ID="$(json_get "${TMP_DIR}/draft_structure.body" "seats.2.seat_id")"
|
||||
|
||||
echo "SEAT_RECORD_ID=${SEAT_RECORD_ID}"
|
||||
echo "SECTOR_RECORD_ID=${SECTOR_RECORD_ID}"
|
||||
echo "GROUP_RECORD_ID=${GROUP_RECORD_ID}"
|
||||
echo "PRICED_SEAT_ID=${PRICED_SEAT_ID}"
|
||||
echo "UNPRICED_SEAT_ID=${UNPRICED_SEAT_ID}"
|
||||
|
||||
request "draft_validation" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/validation?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
assert_json_eq "${TMP_DIR}/draft_validation.body" "status" "draft"
|
||||
|
||||
request "draft_compare_preview" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/compare-preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
|
||||
request "stale_draft_conflict" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/summary?expected_scheme_version_id=deadbeefdeadbeefdeadbeefdeadbeef" "409"
|
||||
assert_json_eq "${TMP_DIR}/stale_draft_conflict.body" "detail.code" "stale_draft_version"
|
||||
|
||||
request "draft_seat_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/${SEAT_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_sector_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/sectors/records/${SECTOR_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_group_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/groups/records/${GROUP_RECORD_ID}?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
request "draft_unknown_record" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/seats/records/deadbeefdeadbeefdeadbeefdeadbeef" "404"
|
||||
|
||||
request "current_sectors" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/sectors" "200"
|
||||
request "current_groups" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/groups" "200"
|
||||
request "current_seats" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/seats" "200"
|
||||
request "display_meta" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/svg/display/meta" "200"
|
||||
|
||||
request "pricing_bundle" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing" "200"
|
||||
request "pricing_coverage" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/coverage" "200"
|
||||
request "pricing_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/unpriced-seats" "200"
|
||||
request "pricing_explain_priced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/explain/${PRICED_SEAT_ID}" "200"
|
||||
request "pricing_explain_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/explain/${UNPRICED_SEAT_ID}" "200"
|
||||
request "pricing_rule_diagnostics" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules/diagnostics" "200"
|
||||
request "seat_price" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/current/seats/${PRICED_SEAT_ID}/price" "200"
|
||||
request "test_mode_priced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/test/seats/${PRICED_SEAT_ID}" "200"
|
||||
request "test_mode_unpriced" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/test/seats/${UNPRICED_SEAT_ID}" "200"
|
||||
|
||||
request "typed_invalid_amount" "POST" "${API_URL}/api/v1/schemes/${SCHEME_ID}/pricing/rules?expected_scheme_version_id=${DRAFT_VERSION_ID}" "422" '{"pricing_category_id":"4ef9e2b78fe0447f9f5db02714c7cad5","target_type":"sector","target_ref":"vip","amount":"bad","currency":"RUB"}'
|
||||
assert_json_eq "${TMP_DIR}/typed_invalid_amount.body" "detail.code" "invalid_amount"
|
||||
|
||||
request "typed_remap_filter_required" "POST" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/remap/preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" "422" '{}'
|
||||
assert_json_eq "${TMP_DIR}/typed_remap_filter_required.body" "detail.code" "remap_filter_required"
|
||||
|
||||
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_readiness" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-readiness?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 "publish_preview_cached" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/draft/publish-preview?expected_scheme_version_id=${DRAFT_VERSION_ID}" "200"
|
||||
|
||||
request "admin_artifacts" "GET" "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/artifacts" "200"
|
||||
request "admin_validation" "GET" "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/current/validation" "200"
|
||||
request "admin_preview_audit" "GET" "${API_URL}/api/v1/admin/artifacts/publish-preview/audit" "200"
|
||||
request "admin_preview_cleanup_dry_run" "POST" "${API_URL}/api/v1/admin/artifacts/publish-preview/cleanup?dry_run=true" "200"
|
||||
|
||||
request "admin_cleanup_preview" "GET" "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup-preview?code_prefix=FAIL_&code_prefix=DIAG_&code_prefix=AUTO_&code_prefix=TYPED_&name_prefix=should-fail-&name_prefix=diag-&name_prefix=auto%20&name_prefix=typed-response-" "200"
|
||||
request "admin_cleanup_dry_run" "POST" "${API_URL}/api/v1/admin/schemes/${SCHEME_ID}/pricing/categories/cleanup" "200" '{"code_prefixes":["FAIL_","DIAG_","AUTO_","TYPED_"],"name_prefixes":["should-fail-","diag-","auto ","typed-response-"],"delete_only_without_rules":true,"dry_run":true}'
|
||||
|
||||
assert_json_eq "${TMP_DIR}/admin_cleanup_dry_run.body" "dry_run" "true"
|
||||
assert_json_eq "${TMP_DIR}/admin_cleanup_dry_run.body" "deleted_count" "0"
|
||||
MATCHED_TOTAL="$(json_get "${TMP_DIR}/admin_cleanup_dry_run.body" "matched_total")"
|
||||
WOULD_DELETE="$(json_get "${TMP_DIR}/admin_cleanup_dry_run.body" "would_delete_count")"
|
||||
if [[ "${MATCHED_TOTAL}" == "0" ]]; then
|
||||
if [[ "${WOULD_DELETE}" != "0" ]]; then
|
||||
echo "[FAIL] would_delete_count expected 0 when matched_total is 0, got ${WOULD_DELETE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[OK] matched_total=0, would_delete_count=0 (clean state)"
|
||||
else
|
||||
assert_json_int_gt "${TMP_DIR}/admin_cleanup_dry_run.body" "would_delete_count" "0"
|
||||
fi
|
||||
|
||||
request "audit_trail" "GET" "${API_URL}/api/v1/schemes/${SCHEME_ID}/audit" "200"
|
||||
echo
|
||||
echo "===== smoke pricing/publish ====="
|
||||
bash "${SCRIPT_DIR}/smoke_pricing_publish.sh"
|
||||
|
||||
echo
|
||||
echo "===== done ====="
|
||||
echo "[OK] smoke regression completed successfully"
|
||||
echo "[OK] smoke regression orchestration completed successfully"
|
||||
|
||||
Reference in New Issue
Block a user