From 0ec7c2fbbb7b46f641ebbbadc7f912c02ded8cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 9 Apr 2026 19:32:42 +0300 Subject: [PATCH] test(shared-sqs): add quick_test.sh (19 tests), extend hardcore_test.sh (groups 24-28, UI API) --- shared-sqs/tests/hardcore_test.sh | 162 ++++++++++++++++++++++++++- shared-sqs/tests/quick_test.sh | 176 ++++++++++++++++++++++++++++++ 2 files changed, 336 insertions(+), 2 deletions(-) create mode 100755 shared-sqs/tests/quick_test.sh diff --git a/shared-sqs/tests/hardcore_test.sh b/shared-sqs/tests/hardcore_test.sh index 57c142d..f832dff 100644 --- a/shared-sqs/tests/hardcore_test.sh +++ b/shared-sqs/tests/hardcore_test.sh @@ -3,7 +3,8 @@ # Изменено: 2026-04-09 # Покрывает: Admin API, AWS CLI CRUD, awscurl CRUD, кросс-доставка, # изоляция тенантов, невалидный ввод, спецсимволы, -# visibility timeout, batch, лимиты очередей, cleanup тенанта. +# visibility timeout, batch, лимиты очередей, cleanup тенанта, +# UI API (create/delete queue, send/peek/purge messages, изоляция). # Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/hardcore_test.sh set -uo pipefail @@ -899,12 +900,169 @@ sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$ENC_QURL" >/dev/null 2>&1 || tr echo "" # ═══════════════════════════════════════════ -echo "── Cleanup: удаляем тестовых тенантов ──" +echo "── 24. UI API — создание очереди через /ui/api ──" +# ═══════════════════════════════════════════ + +UI_T=$(admin_api POST /admin/tenants '{"name":"ui-test-'"$TS"'","max_queues":10}') +UI_TID=$(echo "$UI_T" | jq -r '.id') +UI_AK=$(echo "$UI_T" | jq -r '.access_key') +UI_SK=$(echo "$UI_T" | jq -r '.secret_key') + +# Создать очередь через UI API +R=$(curl -s --max-time 15 -X POST "${BASE_URL}/ui/api/tenants/${UI_TID}/queues" \ + -H "Content-Type: application/json" \ + -d '{"name":"ui-queue-1"}') +check "UI API: создать очередь ui-queue-1 → имя" "$R" "ui-queue-1" + +# Дубль — та же очередь, не ошибка (идемпотентно) +R=$(curl -s --max-time 15 -X POST "${BASE_URL}/ui/api/tenants/${UI_TID}/queues" \ + -H "Content-Type: application/json" \ + -d '{"name":"ui-queue-1"}') +check "UI API: повторное создание — idempotent (нет 500)" "$R" "ui-queue-1|error" + +# Список очередей через /ui/api содержит созданную +R=$(curl -s --max-time 15 "${BASE_URL}/ui/api/tenants/${UI_TID}/queues") +check "UI API: GET /queues содержит ui-queue-1" "$R" "ui-queue-1" + +echo "" + +# ═══════════════════════════════════════════ +echo "── 25. UI API — отправка и peek сообщений ──" +# ═══════════════════════════════════════════ + +# Отправить сообщение через UI +R=$(curl -s --max-time 15 -X POST \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages" \ + -H "Content-Type: application/json" \ + -d '{"body":"hello-from-ui"}') +check "UI API: send message → id" "$R" '"id"' +check "UI API: send message → status sent" "$R" "sent" + +# Отправить ещё одно +curl -s --max-time 15 -X POST \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages" \ + -H "Content-Type: application/json" \ + -d '{"body":"second-message"}' >/dev/null 2>&1 + +# Peek — должны видеть оба, без ReceiptHandle +R=$(curl -s --max-time 15 \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages") +check "UI API: peek — hello-from-ui" "$R" "hello-from-ui" +check "UI API: peek — second-message" "$R" "second-message" +check_not "UI API: peek — НЕТ receipt_handle" "$R" "receipt_handle" + +# Peek не даёт возможности ReceiveMessage через SQS (сообщения не зафиксированы) +QURL_UI=$(sqs "$UI_AK" "$UI_SK" list-queues 2>&1 \ + | python3 -c "import sys,json; d=json.load(sys.stdin); urls=d.get('QueueUrls',[]); print(next((u for u in urls if 'ui-queue-1' in u),''))" 2>/dev/null || true) +if [[ -n "$QURL_UI" ]]; then + RECV_AFTER_PEEK=$(sqs "$UI_AK" "$UI_SK" receive-message --queue-url "$QURL_UI" 2>&1) + check "UI API: peek не consumed — SQS receive-message видит сообщение" "$RECV_AFTER_PEEK" "hello-from-ui|Messages" +fi + +echo "" + +# ═══════════════════════════════════════════ +echo "── 26. UI API — purge очереди ──" +# ═══════════════════════════════════════════ + +R=$(curl -s --max-time 15 -X DELETE \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages") +check "UI API: purge → 200/204 (нет ошибки)" "$R" "^\s*$|purged|ok|{}" + +# После purge peek должен вернуть пустой массив +R=$(curl -s --max-time 15 \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages") +check_not "UI API: после purge peek пуст (нет hello-from-ui)" "$R" "hello-from-ui" + +# Отправим ещё одно после purge — должно работать +R=$(curl -s --max-time 15 -X POST \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages" \ + -H "Content-Type: application/json" \ + -d '{"body":"after-purge"}') +check "UI API: send после purge работает" "$R" '"id"' + +R=$(curl -s --max-time 15 \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages") +check "UI API: peek после purge → after-purge" "$R" "after-purge" + +echo "" + +# ═══════════════════════════════════════════ +echo "── 27. UI API — удаление очереди ──" +# ═══════════════════════════════════════════ + +# Создать ещё одну очередь чтобы удалить +curl -s --max-time 15 -X POST "${BASE_URL}/ui/api/tenants/${UI_TID}/queues" \ + -H "Content-Type: application/json" \ + -d '{"name":"ui-to-delete"}' >/dev/null 2>&1 + +R=$(curl -s --max-time 15 "${BASE_URL}/ui/api/tenants/${UI_TID}/queues") +check "UI API: ui-to-delete создана" "$R" "ui-to-delete" + +# Удалить +HTTP=$(curl -s --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-to-delete") +if [[ "$HTTP" == "204" || "$HTTP" == "200" ]]; then + PASS=$((PASS+1)); TOTAL=$((TOTAL+1)) + echo " ✅ UI API: DELETE /queues/ui-to-delete → HTTP $HTTP" +else + FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1)) + echo " ❌ UI API: DELETE /queues/ui-to-delete → HTTP $HTTP" +fi + +R=$(curl -s --max-time 15 "${BASE_URL}/ui/api/tenants/${UI_TID}/queues") +check_not "UI API: ui-to-delete больше не в списке" "$R" "ui-to-delete" + +# Удаление несуществующей очереди → 404 +HTTP=$(curl -s --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/no-such-queue") +if [[ "$HTTP" == "404" ]]; then + PASS=$((PASS+1)); TOTAL=$((TOTAL+1)) + echo " ✅ UI API: DELETE несуществующей очереди → 404" +else + FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1)) + echo " ❌ UI API: DELETE несуществующей очереди → HTTP $HTTP (ожидалось 404)" +fi + +echo "" + +# ═══════════════════════════════════════════ +echo "── 28. UI API — изоляция: чужой тенант недоступен ──" +# ═══════════════════════════════════════════ + +# Создать второго тенанта и попробовать смотреть его очереди через UI API чужим ID +UI_T2=$(admin_api POST /admin/tenants '{"name":"ui-other-'"$TS"'","max_queues":5}') +UI_T2ID=$(echo "$UI_T2" | jq -r '.id') +curl -s --max-time 15 -X POST "${BASE_URL}/ui/api/tenants/${UI_T2ID}/queues" \ + -H "Content-Type: application/json" \ + -d '{"name":"secret-queue"}' >/dev/null 2>&1 +curl -s --max-time 15 -X POST \ + "${BASE_URL}/ui/api/tenants/${UI_T2ID}/queues/secret-queue/messages" \ + -H "Content-Type: application/json" \ + -d '{"body":"secret-data"}' >/dev/null 2>&1 + +# T1 пытается peek очереди T2 — должен получить 404 (тенант не найден) +HTTP=$(curl -s --max-time 15 -o /dev/null -w "%{http_code}" \ + "${BASE_URL}/ui/api/tenants/${UI_T2ID}/queues/secret-queue/messages") +# UI API публичный (без auth), но очередь принадлежит другому тенанту — данные не должны пересекаться +# Проверяем что данные T2 недоступны через UI T1 +R=$(curl -s --max-time 15 "${BASE_URL}/ui/api/tenants/${UI_TID}/queues") +check_not "UI API: T1 не видит очереди T2" "$R" "secret-queue" + +R_T1_PEEK=$(curl -s --max-time 15 \ + "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/secret-queue/messages") +check "UI API: peek чужой очереди через ID T1 → 404/error" "$R_T1_PEEK" "not found|error|404" + +# Cleanup UI тенантов +admin_api DELETE "/admin/tenants/${UI_T2ID}" >/dev/null 2>&1 || true + +echo "" # ═══════════════════════════════════════════ admin_api DELETE "/admin/tenants/${T1_ID}" >/dev/null 2>&1 || true admin_api DELETE "/admin/tenants/${T2_ID}" >/dev/null 2>&1 || true admin_api DELETE "/admin/tenants/${LIM_ID}" >/dev/null 2>&1 || true +admin_api DELETE "/admin/tenants/${UI_TID}" >/dev/null 2>&1 || true # probe-test тенант (если оставался от предыдущих запусков) PROBE_ID=$(admin_api GET /admin/tenants 2>/dev/null | jq -r '.[] | select(.name=="probe-test") | .id' 2>/dev/null || true) [ -n "$PROBE_ID" ] && admin_api DELETE "/admin/tenants/${PROBE_ID}" >/dev/null 2>&1 || true diff --git a/shared-sqs/tests/quick_test.sh b/shared-sqs/tests/quick_test.sh new file mode 100755 index 0000000..4af184f --- /dev/null +++ b/shared-sqs/tests/quick_test.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# tests/quick_test.sh — Быстрая проверка shared-sqs (smoke test) +# Created: 2026-04-09 +# Покрывает: создание тенанта + очереди, send/receive/delete сообщения, +# UI API peek/send/purge, удаление очереди и тенанта. +# Требования: curl, aws CLI, python3 +# Запуск: +# bash tests/quick_test.sh +# BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/quick_test.sh + +set -uo pipefail + +BASE_URL="${BASE_URL:-https://qu.kube5s.ru}" +ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}" +REGION="us-east-1" +TS=$(date +%s) + +PASS=0 +FAIL=0 + +ok() { echo " ✅ $1"; PASS=$((PASS+1)); } +fail() { echo " ❌ $1"; FAIL=$((FAIL+1)); } + +check() { + local label="$1" body="$2" pattern="$3" + if echo "$body" | grep -qE "$pattern"; then ok "$label"; else fail "$label"; fi +} + +check_not() { + local label="$1" body="$2" pattern="$3" + if echo "$body" | grep -qE "$pattern"; then fail "$label"; else ok "$label"; fi +} + +check_http() { + local label="$1" want="$2" got="$3" + if [[ "$got" == "$want" ]]; then ok "$label (HTTP $got)"; else fail "$label — ожидалось $want, получено $got"; fi +} + +# aws CLI с credentials тенанта +sqs() { + local ak="$1" sk="$2"; shift 2 + AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" AWS_DEFAULT_REGION="$REGION" \ + aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1 +} + +admin() { + local method="$1" path="$2" body="${3:-}" + if [[ -n "$body" ]]; then + curl -sf --max-time 15 -X "$method" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d "$body" "${BASE_URL}${path}" 2>&1 + else + curl -sf --max-time 15 -X "$method" \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + "${BASE_URL}${path}" 2>&1 + fi +} + +# ui_api — UI API без авторизации (публичный) +ui() { + local method="$1" path="$2" body="${3:-}" + if [[ -n "$body" ]]; then + curl -sf --max-time 15 -X "$method" \ + -H "Content-Type: application/json" \ + -d "$body" "${BASE_URL}/ui/api${path}" 2>&1 + else + curl -sf --max-time 15 -X "$method" "${BASE_URL}/ui/api${path}" 2>&1 + fi +} + +echo "════════════════════════════════════════" +echo " shared-sqs Quick Test" +echo " Endpoint: $BASE_URL" +echo "════════════════════════════════════════" +echo "" + +# ── 1. Health ── +echo "── 1. Health ──" +R=$(curl -sf --max-time 10 "${BASE_URL}/health" 2>&1) +check "GET /health → OK" "$R" "[Oo][Kk]|status" +R=$(ui GET /health) +check "GET /ui/api/health → ok" "$R" "ok" +echo "" + +# ── 2. Создание тенанта ── +echo "── 2. Создание тенанта ──" +RESP=$(admin POST /admin/tenants '{"name":"quick-'"$TS"'","max_queues":5}') +check "POST /admin/tenants → access_key" "$RESP" "access_key" +AK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])") +SK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])") +TID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") +echo " Tenant ID: $TID" +echo " Access Key: $AK" +echo "" + +# ── 3. AWS CLI CRUD ── +echo "── 3. AWS CLI CRUD ──" +QNAME="quick-q-$TS" + +R=$(sqs "$AK" "$SK" create-queue --queue-name "$QNAME") +check "CreateQueue → QueueUrl" "$R" "QueueUrl" +QURL=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])") + +R=$(sqs "$AK" "$SK" list-queues) +check "ListQueues → очередь $QNAME в списке" "$R" "$QNAME" + +R=$(sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "hello-quick-$TS") +check "SendMessage → MessageId" "$R" "MessageId" + +R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL") +check "ReceiveMessage → тело сообщения" "$R" "hello-quick-$TS" +RECEIPT=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')" 2>/dev/null || true) + +if [[ -n "$RECEIPT" ]]; then + sqs "$AK" "$SK" delete-message --queue-url "$QURL" --receipt-handle "$RECEIPT" >/dev/null 2>&1 + ok "DeleteMessage → без ошибок" +else + fail "DeleteMessage — нет ReceiptHandle" +fi +echo "" + +# ── 4. UI API — создание очереди ── +echo "── 4. UI API очереди ──" +R=$(ui POST "/tenants/$TID/queues" '{"name":"ui-quick-q"}') +check "UI POST /queues → создана" "$R" "ui-quick-q" + +R=$(ui GET "/tenants/$TID/queues") +check "UI GET /queues → ui-quick-q в списке" "$R" "ui-quick-q" +echo "" + +# ── 5. UI API — send/peek/purge ── +echo "── 5. UI API send/peek/purge ──" +R=$(ui POST "/tenants/$TID/queues/ui-quick-q/messages" '{"body":"msg-a"}') +check "UI POST /messages → id" "$R" '"id"' +ui POST "/tenants/$TID/queues/ui-quick-q/messages" '{"body":"msg-b"}' >/dev/null 2>&1 + +R=$(ui GET "/tenants/$TID/queues/ui-quick-q/messages") +check "UI GET /messages → msg-a" "$R" "msg-a" +check "UI GET /messages → msg-b" "$R" "msg-b" +check_not "UI GET /messages → нет receipt_handle" "$R" "receipt_handle" + +# Purge +ui DELETE "/tenants/$TID/queues/ui-quick-q/messages" >/dev/null 2>&1 +R=$(ui GET "/tenants/$TID/queues/ui-quick-q/messages") +check_not "UI DELETE /messages (purge) → msg-a исчезло" "$R" "msg-a" +echo "" + +# ── 6. UI API — удаление очереди ── +echo "── 6. UI API удаление очереди ──" +HTTP=$(curl -sf --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ + "${BASE_URL}/ui/api/tenants/${TID}/queues/ui-quick-q") +check_http "UI DELETE /queues/ui-quick-q → 204/200" "204" "$HTTP" 2>/dev/null || \ +check_http "UI DELETE /queues/ui-quick-q → 200" "200" "$HTTP" + +R=$(ui GET "/tenants/$TID/queues") +check_not "Очередь ui-quick-q исчезла из списка" "$R" "ui-quick-q" +echo "" + +# ── 7. AWS CLI DeleteQueue ── +echo "── 7. DeleteQueue ──" +AWS_ACCESS_KEY_ID="$AK" AWS_SECRET_ACCESS_KEY="$SK" AWS_DEFAULT_REGION="$REGION" \ + aws --endpoint-url "$BASE_URL" --output json sqs delete-queue --queue-url "$QURL" >/dev/null 2>&1 +if [[ $? -eq 0 ]]; then ok "DeleteQueue → без ошибки"; else fail "DeleteQueue → ошибка"; fi +echo "" + +# ── Cleanup ── +echo "── Cleanup ──" +admin DELETE "/admin/tenants/$TID" >/dev/null 2>&1 && ok "DELETE тенанта" || fail "DELETE тенанта" +echo "" + +echo "════════════════════════════════════════" +printf " Результат: ✅ %d ❌ %d\n" "$PASS" "$FAIL" +echo "════════════════════════════════════════" + +[[ $FAIL -eq 0 ]] && exit 0 || exit 1