feat: v0.1.18 — TagQueue/UntagQueue/ListQueueTags/ChangeMessageVisibilityBatch, JWT auth in UI API tests, hardcore_test JWT fix, Helm chart v0.1.18

This commit is contained in:
Naeel
2026-04-11 09:44:38 +03:00
parent fbecde72eb
commit 38194fb2fa
5 changed files with 340 additions and 136 deletions
+150 -62
View File
@@ -1,10 +1,10 @@
#!/bin/bash
# hardcore_test.sh — расширенный тест shared-sqs
# Изменено: 2026-04-09
# Покрывает: Admin API, AWS CLI CRUD, awscurl CRUD, кросс-доставка,
# изоляция тенантов, невалидный ввод, спецсимволы,
# visibility timeout, batch, лимиты очередей, cleanup тенанта,
# UI API (create/delete queue, send/peek/purge messages, изоляция).
# Created: 2026-04-09
# Updated: 2026-04-11 — добавлены TagQueue/UntagQueue/ListQueueTags/ChangeMessageVisibilityBatch,
# UI API с JWT авторизацией (nubes api_token).
# Покрывает: все 17 SQS команд, Admin API, AWS CLI + awscurl, кросс-доставка,
# изоляция тенантов, невалидный ввод, batch, лимиты, UI API с JWT.
# Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/hardcore_test.sh
set -uo pipefail
@@ -15,6 +15,8 @@ set -uo pipefail
BASE_URL="${BASE_URL:-https://qu.kube5s.ru}"
ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}"
AWSCURL="${AWSCURL:-/home/naeel/.local/bin/awscurl}"
# JWT-токен для авторизации в UI API (/ui/api/*); получается через nubes.ru → api_token
NUBES_JWT="${NUBES_JWT:-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImV4cCI6MTc4NjkzMjI2MCwiaWF0IjoxNzcxMzgwMjYwLCJqdGkiOiIzOTQ3ZTgyMy0yNjljLTQ0MTAtYmU0My1iNGVkNTc1Njg0ZTQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRAbmFyb2QucnUifQ.hzpIIqNWkKIoYUXDaLY7DLyGKH70rz0ZTqanv19qxF10i3N1t1g_KknA4Qsw1MduTyLzIz7y5SRSr4PSQ1gzR0vB_C0GudSFUhyBNNKkS4ClhRDWW9eN_IIEljbiJMLQi2L07XJ7Y5DQ0sIHRAPLkreCDFMKQ0yTCrKoScCJIDuUqzaTcOaX-hfjaxW8iV0SZMDxl0C5O3tke0btxkaLBaAcWH0V-1yu2r2m29fyU33FqikF0xAcDXiuZphfsrShKQYArZjKAphYCP_Vpmr-1sdjinkn8sPSk1qZny0rka8G6WVZUGaZSOnW8SYNLVUwdqtuQmK-Y18o7U0Suzrsjg}"
TS=$(date +%s)
PASS=0
@@ -77,6 +79,21 @@ sqs() {
aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1
}
# ui_jwt — UI API запрос с JWT авторизацией
ui_jwt() {
local method="$1" path="$2" body="${3:-}"
if [[ -n "$body" ]]; then
curl -s --max-time 15 -X "$method" \
-H "Authorization: Bearer $NUBES_JWT" \
-H "Content-Type: application/json" \
-d "$body" "${BASE_URL}/ui/api${path}" 2>&1
else
curl -s --max-time 15 -X "$method" \
-H "Authorization: Bearer $NUBES_JWT" \
"${BASE_URL}/ui/api${path}" 2>&1
fi
}
# acurl — awscurl враппер для SQS Query Protocol
# $1=ak, $2=sk, остальное — form-encoded body (Action=...&...)
acurl() {
@@ -516,6 +533,85 @@ sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BQURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 10б. TagQueue / UntagQueue / ListQueueTags ──"
# ═══════════════════════════════════════════
TAG_Q="hc-tag-${TS}"
TAG_QURL=$(qurl "$T1_ID" "$TAG_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$TAG_Q" >/dev/null 2>&1 || true
# TagQueue — добавить теги
R=$(sqs "$T1_AK" "$T1_SK" tag-queue --queue-url "$TAG_QURL" --tags env=prod,team=backend 2>&1 && echo "OK" || echo "FAIL")
check "TagQueue: добавить теги → OK" "$R" "OK"
# ListQueueTags — теги видны
R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL")
check "ListQueueTags: env=prod видна" "$R" "env|prod"
check "ListQueueTags: team=backend видна" "$R" "team|backend"
# TagQueue — обновить существующий тег (merge)
R=$(sqs "$T1_AK" "$T1_SK" tag-queue --queue-url "$TAG_QURL" --tags env=staging 2>&1 && echo "OK" || echo "FAIL")
check "TagQueue: обновить тег env → OK" "$R" "OK"
R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL")
check "ListQueueTags: env обновлён до staging" "$R" "staging"
check "ListQueueTags: team не удалён при обновлении" "$R" "backend"
# UntagQueue — удалить один тег
R=$(sqs "$T1_AK" "$T1_SK" untag-queue --queue-url "$TAG_QURL" --tag-keys env 2>&1 && echo "OK" || echo "FAIL")
check "UntagQueue: удалить тег env → OK" "$R" "OK"
R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL")
check_not "ListQueueTags: env удалён" "$R" '"env"'
check "ListQueueTags: team остался" "$R" "backend"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$TAG_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 10в. ChangeMessageVisibilityBatch ──"
# ═══════════════════════════════════════════
CMVB_Q="hc-cmvb-${TS}"
CMVB_QURL=$(qurl "$T1_ID" "$CMVB_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$CMVB_Q" >/dev/null 2>&1 || true
# Отправляем 3 сообщения
for i in 1 2 3; do
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$CMVB_QURL" --message-body "cmvb-msg-${i}" >/dev/null 2>&1 || true
done
# Получаем все 3 (visibility=60 чтобы не конкурировали)
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$CMVB_QURL" --max-number-of-messages 3 --visibility-timeout 60)
CMVB_RECEIPTS=$(echo "$R" | jq -r '.Messages[]?.ReceiptHandle' 2>/dev/null || true)
if [[ -n "$CMVB_RECEIPTS" ]]; then
CMVB_ENTRIES=$(echo "$CMVB_RECEIPTS" | python3 -c "
import sys
entries = []
for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1):
if rh:
entries.append('{\"Id\":\"e%d\",\"ReceiptHandle\":\"%s\",\"VisibilityTimeout\":120}' % (i, rh))
print('[' + ','.join(entries) + ']')
" 2>/dev/null || echo "[]")
if [[ "$CMVB_ENTRIES" != "[]" ]]; then
R=$(sqs "$T1_AK" "$T1_SK" change-message-visibility-batch \
--queue-url "$CMVB_QURL" \
--entries "$CMVB_ENTRIES")
check "ChangeMessageVisibilityBatch → Successful" "$R" "Successful"
check_not "ChangeMessageVisibilityBatch → нет Failed" "$R" '"Failed"'
fi
else
TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1))
echo " ❌ ChangeMessageVisibilityBatch (нет сообщений для теста)"
fi
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$CMVB_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 11. Лимит очередей (max_queues=2) ──"
# ═══════════════════════════════════════════
@@ -900,28 +996,37 @@ sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$ENC_QURL" >/dev/null 2>&1 || tr
echo ""
# ═══════════════════════════════════════════
echo "── 24. UI API — создание очереди через /ui/api ──"
echo "── 24. UI API — авторизация через JWT ──"
# ═══════════════════════════════════════════
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" \
# Авторизуемся через nubes JWT
UI_AUTH=$(curl -s --max-time 15 -X POST \
-H "Content-Type: application/json" \
-d '{"name":"ui-queue-1"}')
-d "{\"token\":\"$NUBES_JWT\"}" \
"${BASE_URL}/ui/api/auth" 2>&1)
check "UI API: POST /auth → tenant_id" "$UI_AUTH" "tenant_id"
UI_TID=$(echo "$UI_AUTH" | jq -r '.tenant_id' 2>/dev/null || true)
UI_AK=$(echo "$UI_AUTH" | jq -r '.access_key' 2>/dev/null || true)
UI_SK=$(echo "$UI_AUTH" | jq -r '.secret_key' 2>/dev/null || true)
echo " UI Tenant ID: $UI_TID"
echo ""
# ═══════════════════════════════════════════
echo "── 25б. UI API — создание очереди ──"
# ═══════════════════════════════════════════
# Создать очередь через UI API с JWT
R=$(ui_jwt POST "/tenants/${UI_TID}/queues" '{"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"}')
R=$(ui_jwt POST "/tenants/${UI_TID}/queues" '{"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")
R=$(ui_jwt GET "/tenants/${UI_TID}/queues")
check "UI API: GET /queues содержит ui-queue-1" "$R" "ui-queue-1"
echo ""
@@ -931,27 +1036,20 @@ 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"}')
R=$(ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"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
ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"body":"second-message"}' >/dev/null 2>&1 || true
# Peek — должны видеть оба, без ReceiptHandle
R=$(curl -s --max-time 15 \
"${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-queue-1/messages")
R=$(ui_jwt GET "/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 (сообщения не зафиксированы)
# Peek не consumed — SQS receive-message тоже видит сообщения
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
@@ -965,24 +1063,18 @@ 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")
R=$(ui_jwt DELETE "/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")
R=$(ui_jwt GET "/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"}')
R=$(ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"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")
R=$(ui_jwt GET "/tenants/${UI_TID}/queues/ui-queue-1/messages")
check "UI API: peek после purge → after-purge" "$R" "after-purge"
echo ""
@@ -992,15 +1084,14 @@ 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
ui_jwt POST "/tenants/${UI_TID}/queues" '{"name":"ui-to-delete"}' >/dev/null 2>&1 || true
R=$(curl -s --max-time 15 "${BASE_URL}/ui/api/tenants/${UI_TID}/queues")
R=$(ui_jwt GET "/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 \
-H "Authorization: Bearer $NUBES_JWT" \
"${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-to-delete")
if [[ "$HTTP" == "204" || "$HTTP" == "200" ]]; then
PASS=$((PASS+1)); TOTAL=$((TOTAL+1))
@@ -1010,11 +1101,12 @@ else
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")
R=$(ui_jwt GET "/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 \
-H "Authorization: Bearer $NUBES_JWT" \
"${BASE_URL}/ui/api/tenants/${UI_TID}/queues/no-such-queue")
if [[ "$HTTP" == "404" ]]; then
PASS=$((PASS+1)); TOTAL=$((TOTAL+1))
@@ -1030,28 +1122,24 @@ echo ""
echo "── 28. UI API — изоляция: чужой тенант недоступен ──"
# ═══════════════════════════════════════════
# Создать второго тенанта и попробовать смотреть его очереди через UI API чужим ID
# Создать второго admin тенанта для теста изоляции
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
UI_T2AK=$(echo "$UI_T2" | jq -r '.access_key')
UI_T2SK=$(echo "$UI_T2" | jq -r '.secret_key')
# 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"
# Создаём очередь во втором тенанте через admin API (не через UI JWT — JWT привязан к одному тенанту)
T2_Q_URL="${BASE_URL}/${UI_T2ID}/secret-queue"
sqs "$UI_T2AK" "$UI_T2SK" create-queue --queue-name "secret-queue" >/dev/null 2>&1 || true
sqs "$UI_T2AK" "$UI_T2SK" send-message --queue-url "$T2_Q_URL" --message-body "secret-data" >/dev/null 2>&1 || true
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"
# Текущий JWT тенант не видит очереди другого admin-created тенанта
R=$(ui_jwt GET "/tenants/${UI_TID}/queues")
check_not "UI API: JWT тенант не видит очереди T2" "$R" "secret-queue"
# peek чужой очереди через JWT тенант → ошибка
R=$(ui_jwt GET "/tenants/${UI_TID}/queues/secret-queue/messages")
check "UI API: peek несуществующей очереди → ошибка" "$R" "not found|error|404"
# Cleanup UI тенантов
admin_api DELETE "/admin/tenants/${UI_T2ID}" >/dev/null 2>&1 || true