102 lines
4.9 KiB
Bash
Executable File
102 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
API_ORIGIN="${API_ORIGIN:-}"
|
|
SMOKE_ALLOWED_ORIGIN="${SMOKE_ALLOWED_ORIGIN:-}"
|
|
SMOKE_BLOCKED_ORIGIN="${SMOKE_BLOCKED_ORIGIN:-https://blocked.petstore.invalid}"
|
|
SMOKE_BEARER_TOKEN="${SMOKE_BEARER_TOKEN:-}"
|
|
|
|
[[ "$API_ORIGIN" == https://* ]] || {
|
|
printf 'SMOKE FAIL: API_ORIGIN must be an https origin\n' >&2
|
|
exit 1
|
|
}
|
|
[[ "$API_ORIGIN" != *localhost* && "$API_ORIGIN" != *127.0.0.1* && "$API_ORIGIN" != *.invalid* ]] || {
|
|
printf 'SMOKE FAIL: API_ORIGIN must use a production domain\n' >&2
|
|
exit 1
|
|
}
|
|
[[ "$API_ORIGIN" != *.example* && "$API_ORIGIN" != *.test* ]] || {
|
|
printf 'SMOKE FAIL: API_ORIGIN must not use a reserved example/test domain\n' >&2
|
|
exit 1
|
|
}
|
|
API_ORIGIN="${API_ORIGIN%/}"
|
|
|
|
[[ "$SMOKE_ALLOWED_ORIGIN" == https://* ]] || {
|
|
printf 'SMOKE FAIL: SMOKE_ALLOWED_ORIGIN must be an https origin\n' >&2
|
|
exit 1
|
|
}
|
|
[[ "$SMOKE_ALLOWED_ORIGIN" != *localhost* && "$SMOKE_ALLOWED_ORIGIN" != *127.0.0.1* \
|
|
&& "$SMOKE_ALLOWED_ORIGIN" != *.invalid* && "$SMOKE_ALLOWED_ORIGIN" != *.example* \
|
|
&& "$SMOKE_ALLOWED_ORIGIN" != *.test* ]] || {
|
|
printf 'SMOKE FAIL: SMOKE_ALLOWED_ORIGIN must use a production domain\n' >&2
|
|
exit 1
|
|
}
|
|
SMOKE_ALLOWED_ORIGIN="${SMOKE_ALLOWED_ORIGIN%/}"
|
|
[[ "$SMOKE_BLOCKED_ORIGIN" != "$SMOKE_ALLOWED_ORIGIN" ]] || {
|
|
printf 'SMOKE FAIL: blocked and allowed origins must differ\n' >&2
|
|
exit 1
|
|
}
|
|
python3 -c 'import sys,urllib.parse
|
|
for raw in sys.argv[1:]:
|
|
u=urllib.parse.urlsplit(raw)
|
|
assert u.scheme == "https" and u.hostname and not u.username and not u.password
|
|
assert u.path in ("", "/") and not u.query and not u.fragment' \
|
|
"$SMOKE_ALLOWED_ORIGIN" "$SMOKE_BLOCKED_ORIGIN" || {
|
|
printf 'SMOKE FAIL: CORS smoke values must be explicit https origins\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
json_assert_status_up() {
|
|
python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("status") == "UP", d; print("UP")'
|
|
}
|
|
|
|
printf '1/7 liveness: '
|
|
curl --fail --silent --show-error "$API_ORIGIN/actuator/health/liveness" | json_assert_status_up
|
|
|
|
printf '2/7 readiness: '
|
|
curl --fail --silent --show-error "$API_ORIGIN/actuator/health/readiness" | json_assert_status_up
|
|
|
|
STORES="$(curl --fail --silent --show-error "$API_ORIGIN/api/store/list")"
|
|
STORE_ID="$(printf '%s' "$STORES" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200,d; rows=d.get("data") or []; assert rows,"no pilot store"; print(rows[0]["id"])')"
|
|
printf '3/7 public store list: PASS\n'
|
|
|
|
SERVICES="$(curl --fail --silent --show-error "$API_ORIGIN/api/service-type/list?storeId=$STORE_ID")"
|
|
SERVICE_NAME="$(printf '%s' "$SERVICES" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200,d; rows=d.get("data") or []; assert rows,"no service type"; assert all(int(x.get("durationMinutes") or 0)>=30 for x in rows),rows; print(rows[0]["name"])')"
|
|
printf '4/7 service duration contract: PASS\n'
|
|
|
|
if date -v+1d +%F >/dev/null 2>&1; then
|
|
TOMORROW="$(date -v+1d +%F)"
|
|
else
|
|
TOMORROW="$(date -d tomorrow +%F)"
|
|
fi
|
|
curl --fail --silent --show-error --get "$API_ORIGIN/api/appointment/available-slots" \
|
|
--data-urlencode "storeId=$STORE_ID" \
|
|
--data-urlencode "date=$TOMORROW" \
|
|
--data-urlencode "serviceType=$SERVICE_NAME" \
|
|
| python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200,d; data=d["data"]; assert int(data["durationMinutes"])>=30; assert int(data["bookingCapacity"])>=1; assert isinstance(data.get("slots"),list); print("5/7 capacity slot contract: PASS")'
|
|
|
|
curl --fail --silent --show-error --dump-header - --output /dev/null \
|
|
-H "Origin: $SMOKE_ALLOWED_ORIGIN" \
|
|
"$API_ORIGIN/api/store/list" \
|
|
| SMOKE_EXPECTED_ORIGIN="$SMOKE_ALLOWED_ORIGIN" python3 -c 'import os,sys; expected=os.environ["SMOKE_EXPECTED_ORIGIN"].lower(); headers=sys.stdin.read().lower(); assert f"access-control-allow-origin: {expected}" in headers,headers; assert "access-control-allow-origin: *" not in headers,headers; print("6/7 allowed CORS origin: PASS")'
|
|
|
|
BLOCKED_STATUS="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
|
|
-H "Origin: $SMOKE_BLOCKED_ORIGIN" \
|
|
"$API_ORIGIN/api/store/list")"
|
|
[[ "$BLOCKED_STATUS" == "403" ]] || {
|
|
printf 'SMOKE FAIL: blocked CORS origin returned HTTP %s\n' "$BLOCKED_STATUS" >&2
|
|
exit 1
|
|
}
|
|
printf '7/7 blocked CORS origin: PASS\n'
|
|
|
|
if [[ -n "$SMOKE_BEARER_TOKEN" ]]; then
|
|
AUTH_HEADER="Authorization: Bearer $SMOKE_BEARER_TOKEN"
|
|
curl --fail --silent --show-error -H "$AUTH_HEADER" "$API_ORIGIN/api/admin/store" \
|
|
| python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200,d; assert int(d["data"]["bookingCapacity"])>=1; print("protected admin store: PASS")'
|
|
curl --fail --silent --show-error -H "$AUTH_HEADER" "$API_ORIGIN/api/appointment/list?page=1&pageSize=5" \
|
|
| python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200,d; print("protected appointment list: PASS")'
|
|
else
|
|
printf 'Protected smoke SKIPPED: SMOKE_BEARER_TOKEN not provided\n'
|
|
fi
|
|
|
|
printf 'Production read-only smoke PASS\n'
|