#!/usr/bin/env bash set -Eeuo pipefail # ------------------------------------------------------------ # E2E smoke test for the matchmaking microservices # Services via gateway: /auth, /profiles, /match, /chat, /payments # ------------------------------------------------------------ BASE_URL="${BASE_URL:-http://localhost:8080}" AUTH="$BASE_URL/auth" PROFILES="$BASE_URL/profiles" MATCH="$BASE_URL/match" CHAT="$BASE_URL/chat" PAYMENTS="$BASE_URL/payments" # Colors NC='\033[0m'; B='\033[1m'; G='\033[0;32m'; Y='\033[0;33m'; R='\033[0;31m'; C='\033[0;36m' TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT require() { command -v "$1" >/dev/null 2>&1 || { echo -e "${R}ERROR:${NC} '$1' is required"; exit 1; } } require curl require python3 log() { echo -e "${C}[$(date +%H:%M:%S)]${NC} $*"; } ok() { echo -e "${G}✔${NC} $*"; } warn(){ echo -e "${Y}⚠${NC} $*"; } fail(){ echo -e "${R}✖${NC} $*"; exit 1; } json_get() { # json_get (dot notation; arrays allowed by numeric index) python3 - "$1" "$2" <<'PY' import sys, json f, path = sys.argv[1], sys.argv[2] with open(f, 'r') as fh: try: data = json.load(fh) except Exception: print(""); sys.exit(0) cur = data for key in path.split('.'): if isinstance(cur, list): try: key = int(key) except: print(""); sys.exit(0) cur = cur[key] if 0 <= key < len(cur) else None elif isinstance(cur, dict): cur = cur.get(key) else: cur = None if cur is None: break print("" if cur is None else cur) PY } http_req() { # http_req [] [] -> prints HTTP code; body to $RESP local METHOD="$1"; shift local URL="$1"; shift local TOKEN="${1:-}"; shift || true local BODY="${1:-}"; shift || true local RESP="${TMP_DIR}/resp_$(date +%s%N).json" local args=(-sS -X "$METHOD" "$URL" -o "$RESP" -w "%{http_code}") if [[ -n "$TOKEN" ]]; then args+=(-H "Authorization: Bearer $TOKEN"); fi if [[ -n "$BODY" ]]; then args+=(-H "Content-Type: application/json" -d "$BODY"); fi local CODE CODE="$(curl "${args[@]}")" echo "$CODE|$RESP" } expect_code() { # expect_code "" "||..." local ACT="$1"; local ALLOWED="$2" if [[ "$ALLOWED" == *"|${ACT}|"* || "$ALLOWED" == "${ACT}|"* || "$ALLOWED" == *"|${ACT}" || "$ALLOWED" == "${ACT}" ]]; then return 0 fi return 1 } wait_health() { local NAME="$1"; local URL="$2"; local tries=60 log "Waiting ${NAME} health: ${URL}" for ((i=1; i<=tries; i++)); do local CODE CODE="$(curl -s -o /dev/null -w "%{http_code}" "$URL" || true)" if [[ "$CODE" == "200" ]]; then ok "${NAME} is healthy"; return 0; fi sleep 1 done fail "${NAME} not healthy in time: ${URL}" } register_or_login() { # register_or_login -> echoes "|" local EMAIL="$1" PASS="$2" FULL="$3" ROLE="$4" local BODY REG RESPCODE RESP REG_ID BODY=$(printf '{"email":"%s","password":"%s","full_name":"%s","role":"%s"}' "$EMAIL" "$PASS" "$FULL" "$ROLE") REG="$(http_req POST "$AUTH/v1/register" "" "$BODY")" RESPCODE="${REG%%|*}"; RESP="${REG##*|}" if expect_code "$RESPCODE" "201|200"; then ok "Registered user ${EMAIL}" else # maybe already exists local MSG MSG="$(json_get "$RESP" "detail")" if [[ "$RESPCODE" == "400" && "$MSG" == "Email already in use" ]]; then warn "User ${EMAIL} already exists, will login" else warn "Register response ($RESPCODE): $(cat "$RESP" 2>/dev/null || true)" fi fi # token local TOK TOKCODE TOKRESP BODY=$(printf '{"email":"%s","password":"%s"}' "$EMAIL" "$PASS") TOK="$(http_req POST "$AUTH/v1/token" "" "$BODY")" TOKCODE="${TOK%%|*}"; TOKRESP="${TOK##*|}" expect_code "$TOKCODE" "200" || fail "Token failed (${TOKCODE}): $(cat "$TOKRESP")" local ACCESS REFRESH ACCESS="$(json_get "$TOKRESP" "access_token")" REFRESH="$(json_get "$TOKRESP" "refresh_token")" [[ -n "$ACCESS" ]] || fail "Empty access token for ${EMAIL}" # resolve user id via /me local ME MECODE MERESP UID ME="$(http_req GET "$AUTH/v1/me" "$ACCESS")" MECODE="${ME%%|*}"; MERESP="${ME##*|}" expect_code "$MECODE" "200" || fail "/me failed for ${EMAIL} (${MECODE}): $(cat "$MERESP")" UID="$(json_get "$MERESP" "id")" [[ -n "$UID" ]] || fail "Failed to parse user id for ${EMAIL}" echo "${UID}|${ACCESS}" } ensure_profile() { # ensure_profile local TOKEN="$1" G="$2" CITY="$3" LANGS_CSV="$4" INTERESTS_CSV="$5" # GET /profiles/me: 200 or 404 local ME MECODE MERESP ME="$(http_req GET "$PROFILES/v1/profiles/me" "$TOKEN")" MECODE="${ME%%|*}"; MERESP="${ME##*|}" if [[ "$MECODE" == "200" ]]; then ok "Profile already exists" echo "$MERESP" > "${TMP_DIR}/last_profile.json" return 0 elif [[ "$MECODE" != "404" ]]; then warn "Unexpected /profiles/me code ${MECODE}: $(cat "$MERESP")" fi # Create profile IFS=',' read -r -a langs <<< "$LANGS_CSV" IFS=',' read -r -a intrs <<< "$INTERESTS_CSV" local langs_json intrs_json langs_json="$(printf '%s\n' "${langs[@]}" | sed 's/^/"/;s/$/"/' | paste -sd, -)" intrs_json="$(printf '%s\n' "${intrs[@]}" | sed 's/^/"/;s/$/"/' | paste -sd, -)" local BODY BODY=$(cat < "${TMP_DIR}/last_profile.json" } main() { echo -e "${B}=== E2E smoke test start ===${NC}" echo "BASE_URL: $BASE_URL" echo # 0) Wait for services wait_health "gateway" "$BASE_URL/" wait_health "auth" "$AUTH/health" wait_health "profiles" "$PROFILES/health" wait_health "match" "$MATCH/health" wait_health "chat" "$CHAT/health" wait_health "payments" "$PAYMENTS/health" # 1) Register/login users TS="$(date +%s)" ADMIN_EMAIL="${ADMIN_EMAIL:-admin+$TS@agency.dev}" ALICE_EMAIL="${ALICE_EMAIL:-alice+$TS@agency.dev}" BOB_EMAIL="${BOB_EMAIL:-bob+$TS@agency.dev}" PASS="${PASS:-secret123}" log "Register/login admin: ${ADMIN_EMAIL}" IFS='|' read -r ADMIN_ID ADMIN_ACCESS < <(register_or_login "$ADMIN_EMAIL" "$PASS" "Admin" "ADMIN") ok "Admin id: $ADMIN_ID" log "Register/login Alice: ${ALICE_EMAIL}" IFS='|' read -r ALICE_ID ALICE_ACCESS < <(register_or_login "$ALICE_EMAIL" "$PASS" "Alice" "CLIENT") ok "Alice id: $ALICE_ID" log "Register/login Bob: ${BOB_EMAIL}" IFS='|' read -r BOB_ID BOB_ACCESS < <(register_or_login "$BOB_EMAIL" "$PASS" "Bob" "CLIENT") ok "Bob id: $BOB_ID" # 2) Ensure profiles for all three log "Ensure profile for Admin" ensure_profile "$ADMIN_ACCESS" "other" "Moscow" "ru,en" "admin,ops" log "Ensure profile for Alice" ensure_profile "$ALICE_ACCESS" "female" "Moscow" "ru,en" "music,travel" log "Ensure profile for Bob" ensure_profile "$BOB_ACCESS" "male" "Moscow" "ru" "sports,reading" # 3) Create match pair (admin) log "Create match pair (Alice ↔ Bob)" BODY=$(printf '{"user_id_a":"%s","user_id_b":"%s","score":%.2f,"notes":"e2e smoke"}' "$ALICE_ID" "$BOB_ID" 0.87) PAIR="$(http_req POST "$MATCH/v1/pairs" "$ADMIN_ACCESS" "$BODY")" PCODE="${PAIR%%|*}"; PRESP="${PAIR##*|}" expect_code "$PCODE" "201|200" || fail "Create pair failed (${PCODE}): $(cat "$PRESP")" PAIR_ID="$(json_get "$PRESP" "id")" ok "Pair created: $PAIR_ID" # 4) Create chat room and send a message (admin) log "Create chat room (Admin + Alice + Bob)" BODY=$(printf '{"title":"Alice & Bob","participants":["%s","%s"]}' "$ALICE_ID" "$BOB_ID") ROOM="$(http_req POST "$CHAT/v1/rooms" "$ADMIN_ACCESS" "$BODY")" RCODE="${ROOM%%|*}"; RRESP="${ROOM##*|}" expect_code "$RCODE" "201|200" || fail "Create room failed (${RCODE}): $(cat "$RRESP")" ROOM_ID="$(json_get "$RRESP" "id")" ok "Room created: $ROOM_ID" log "Send message to room" BODY='{"content":"Hello from admin (e2e)"}' MSG="$(http_req POST "$CHAT/v1/rooms/$ROOM_ID/messages" "$ADMIN_ACCESS" "$BODY")" MCODE="${MSG%%|*}"; MRESP="${MSG##*|}" expect_code "$MCODE" "201|200" || fail "Send message failed (${MCODE}): $(cat "$MRESP")" MSG_ID="$(json_get "$MRESP" "id")" ok "Message sent: $MSG_ID" # 5) Create invoice for Alice and mark paid (admin) log "Create invoice for Alice" BODY=$(printf '{"client_id":"%s","amount":199.00,"currency":"USD","description":"Consultation (e2e)"}' "$ALICE_ID") INV="$(http_req POST "$PAYMENTS/v1/invoices" "$ADMIN_ACCESS" "$BODY")" INVCODE="${INV%%|*}"; INVRESP="${INV##*|}" expect_code "$INVCODE" "201|200" || fail "Create invoice failed (${INVCODE}): $(cat "$INVRESP")" INV_ID="$(json_get "$INVRESP" "id")" ok "Invoice created: $INV_ID" log "Mark invoice paid" PAID="$(http_req POST "$PAYMENTS/v1/invoices/$INV_ID/mark-paid" "$ADMIN_ACCESS")" PDCODE="${PAID%%|*}"; PDRESP="${PAID##*|}" expect_code "$PDCODE" "200" || fail "Mark paid failed (${PDCODE}): $(cat "$PDRESP")" STATUS="$(json_get "$PDRESP" "status")" [[ "$STATUS" == "paid" ]] || fail "Invoice status not 'paid' (got '$STATUS')" ok "Invoice marked paid" echo echo -e "${B}=== E2E summary ===${NC}" echo -e "Admin: ${G}${ADMIN_EMAIL}${NC} (id: ${ADMIN_ID})" echo -e "Alice: ${G}${ALICE_EMAIL}${NC} (id: ${ALICE_ID})" echo -e "Bob: ${G}${BOB_EMAIL}${NC} (id: ${BOB_ID})" echo -e "Pair: ${C}${PAIR_ID}${NC}" echo -e "Room: ${C}${ROOM_ID}${NC} Message: ${C}${MSG_ID}${NC}" echo -e "Invoice:${C}${INV_ID}${NC} Status: ${G}${STATUS}${NC}" echo ok "E2E smoke test finished successfully." } main "$@"