ops(dev): add redis idempotency A/B smoke script
Made-with: Cursor
This commit is contained in:
200
ops/redis_idempotency_smoke.sh
Executable file
200
ops/redis_idempotency_smoke.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env bash
|
||||
# ops/redis_idempotency_smoke.sh — A/B smoke for Redis idempotency
|
||||
#
|
||||
# Usage:
|
||||
# bash ops/redis_idempotency_smoke.sh
|
||||
# BFF_A=http://127.0.0.1:8002 BFF_B=http://127.0.0.1:8003 bash ops/redis_idempotency_smoke.sh
|
||||
#
|
||||
# Optional args (override env):
|
||||
# $1 = BFF_A
|
||||
# $2 = BFF_B
|
||||
# $3 = AGENT_ID
|
||||
# $4 = NODE_ID
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 = PASS (distributed replay works)
|
||||
# 1 = FAIL
|
||||
# 2 = prerequisites missing
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BFF_A="${BFF_A:-${1:-http://127.0.0.1:8002}}"
|
||||
BFF_B="${BFF_B:-${2:-http://127.0.0.1:8003}}"
|
||||
AGENT_ID="${AGENT_ID:-${3:-sofiia}}"
|
||||
NODE_ID="${NODE_ID:-${4:-NODA2}}"
|
||||
|
||||
# Optional auth header if environment needs it:
|
||||
# export AUTH_HEADER_VALUE="Bearer <token>" or "ApiKey <key>"
|
||||
AUTH_HEADER_VALUE="${AUTH_HEADER_VALUE:-}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
_pass() { echo -e "${GREEN}PASS${NC}: $1"; }
|
||||
_fail() { echo -e "${RED}FAIL${NC}: $1"; }
|
||||
_warn() { echo -e "${YELLOW}WARN${NC}: $1"; }
|
||||
|
||||
for cmd in curl python3; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || { echo "Missing prerequisite: $cmd"; exit 2; }
|
||||
done
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
HAS_JQ=1
|
||||
else
|
||||
HAS_JQ=0
|
||||
_warn "jq not found, using python3 JSON fallback"
|
||||
fi
|
||||
|
||||
json_get() {
|
||||
local json="$1"
|
||||
local path="$2"
|
||||
if [ "$HAS_JQ" = "1" ]; then
|
||||
echo "$json" | jq -r "$path // empty" 2>/dev/null || true
|
||||
else
|
||||
python3 - "$path" <<'PYEOF' <<<"$json"
|
||||
import json, sys
|
||||
path = sys.argv[1].strip()
|
||||
data = json.load(sys.stdin)
|
||||
if path.startswith("."):
|
||||
path = path[1:]
|
||||
if not path:
|
||||
print("")
|
||||
raise SystemExit(0)
|
||||
cur = data
|
||||
for part in path.split("."):
|
||||
if part == "":
|
||||
continue
|
||||
if not isinstance(cur, dict):
|
||||
print("")
|
||||
raise SystemExit(0)
|
||||
cur = cur.get(part)
|
||||
if cur is None:
|
||||
print("")
|
||||
raise SystemExit(0)
|
||||
if isinstance(cur, bool):
|
||||
print("true" if cur else "false")
|
||||
elif isinstance(cur, (dict, list)):
|
||||
print(json.dumps(cur, ensure_ascii=True))
|
||||
else:
|
||||
print(str(cur))
|
||||
PYEOF
|
||||
fi
|
||||
}
|
||||
|
||||
post_json() {
|
||||
local url="$1"
|
||||
local body="$2"
|
||||
local out
|
||||
if [ -n "$AUTH_HEADER_VALUE" ]; then
|
||||
out=$(curl -sS -w $'\n__HTTP__%{http_code}' \
|
||||
-X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: ${AUTH_HEADER_VALUE}" \
|
||||
-d "$body" || true)
|
||||
else
|
||||
out=$(curl -sS -w $'\n__HTTP__%{http_code}' \
|
||||
-X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body" || true)
|
||||
fi
|
||||
HTTP_CODE=$(echo "$out" | awk -F'__HTTP__' 'NF>1{print $2}' | tail -n1 | tr -d '\r')
|
||||
HTTP_BODY=$(echo "$out" | awk 'BEGIN{p=1} /__HTTP__/{p=0} {if(p)print}')
|
||||
}
|
||||
|
||||
post_json_with_idem() {
|
||||
local url="$1"
|
||||
local body="$2"
|
||||
local idem_key="$3"
|
||||
local out
|
||||
if [ -n "$AUTH_HEADER_VALUE" ]; then
|
||||
out=$(curl -sS -w $'\n__HTTP__%{http_code}' \
|
||||
-X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: ${AUTH_HEADER_VALUE}" \
|
||||
-H "Idempotency-Key: ${idem_key}" \
|
||||
-d "$body" || true)
|
||||
else
|
||||
out=$(curl -sS -w $'\n__HTTP__%{http_code}' \
|
||||
-X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: ${idem_key}" \
|
||||
-d "$body" || true)
|
||||
fi
|
||||
HTTP_CODE=$(echo "$out" | awk -F'__HTTP__' 'NF>1{print $2}' | tail -n1 | tr -d '\r')
|
||||
HTTP_BODY=$(echo "$out" | awk 'BEGIN{p=1} /__HTTP__/{p=0} {if(p)print}')
|
||||
}
|
||||
|
||||
echo "Redis Idempotency A/B Smoke"
|
||||
echo " BFF_A = $BFF_A"
|
||||
echo " BFF_B = $BFF_B"
|
||||
echo " AGENT_ID = $AGENT_ID"
|
||||
echo " NODE_ID = $NODE_ID"
|
||||
echo " $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
EXT_REF="redis-smoke-$(date +%s)-$RANDOM"
|
||||
IDEMPOTENCY_KEY="redis-smoke-key-$(date +%s)-$RANDOM"
|
||||
|
||||
post_json "$BFF_A/api/chats" "{\"agent_id\":\"${AGENT_ID}\",\"node_id\":\"${NODE_ID}\",\"source\":\"web\",\"external_chat_ref\":\"${EXT_REF}\"}"
|
||||
if [ "${HTTP_CODE:-000}" != "200" ]; then
|
||||
_fail "create chat on A failed: HTTP ${HTTP_CODE:-000}"
|
||||
echo "$HTTP_BODY"
|
||||
exit 1
|
||||
fi
|
||||
CHAT_ID=$(json_get "$HTTP_BODY" ".chat.chat_id")
|
||||
if [ -z "$CHAT_ID" ]; then
|
||||
_fail "chat_id missing in create-chat response"
|
||||
echo "$HTTP_BODY"
|
||||
exit 1
|
||||
fi
|
||||
_pass "chat created: $CHAT_ID"
|
||||
|
||||
post_json_with_idem "$BFF_A/api/chats/${CHAT_ID}/send" "{\"text\":\"redis-smoke-a\"}" "$IDEMPOTENCY_KEY"
|
||||
if [ "${HTTP_CODE:-000}" != "200" ]; then
|
||||
_fail "first keyed send on A failed: HTTP ${HTTP_CODE:-000}"
|
||||
echo "$HTTP_BODY"
|
||||
exit 1
|
||||
fi
|
||||
A_BODY="$HTTP_BODY"
|
||||
_pass "first keyed send via A completed"
|
||||
|
||||
post_json_with_idem "$BFF_B/api/chats/${CHAT_ID}/send" "{\"text\":\"redis-smoke-b\"}" "$IDEMPOTENCY_KEY"
|
||||
if [ "${HTTP_CODE:-000}" != "200" ]; then
|
||||
_fail "replay send on B failed: HTTP ${HTTP_CODE:-000}"
|
||||
echo "$HTTP_BODY"
|
||||
exit 1
|
||||
fi
|
||||
B_BODY="$HTTP_BODY"
|
||||
_pass "replay send via B completed"
|
||||
|
||||
A_MSG_ID=$(json_get "$A_BODY" ".message.message_id")
|
||||
B_MSG_ID=$(json_get "$B_BODY" ".message.message_id")
|
||||
B_REPLAYED=$(json_get "$B_BODY" ".idempotency.replayed")
|
||||
|
||||
echo "A.message_id = ${A_MSG_ID:-<missing>}"
|
||||
echo "B.message_id = ${B_MSG_ID:-<missing>}"
|
||||
echo "B.replayed = ${B_REPLAYED:-<missing>}"
|
||||
|
||||
if [ -z "$A_MSG_ID" ] || [ -z "$B_MSG_ID" ]; then
|
||||
_fail "message_id missing in A or B response"
|
||||
echo "A body: $A_BODY"
|
||||
echo "B body: $B_BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$A_MSG_ID" != "$B_MSG_ID" ]; then
|
||||
_fail "message_id mismatch (A != B)"
|
||||
echo "A body: $A_BODY"
|
||||
echo "B body: $B_BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$B_REPLAYED" != "true" ]; then
|
||||
_fail "B response is not replayed=true"
|
||||
echo "B body: $B_BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_pass "distributed replay verified (same message_id + replayed=true on B)"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user