@@ -0,0 +1,661 @@
#!/bin/bash
# hardcore_test.sh — расширенный тест shared-sqs
# Изменено: 2026-04-09
# Покрывает: Admin API, AWS CLI CRUD, awscurl CRUD, кросс-доставка,
# изоляция тенантов, невалидный ввод, спецсимволы,
# visibility timeout, batch, лимиты очередей, cleanup тенанта.
# Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/hardcore_test.sh
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 } "
TS = $( date +%s)
PASS = 0
FAIL = 0
TOTAL = 0
# ═══════════════════════════════════════════
# ХЕЛПЕРЫ
# ═══════════════════════════════════════════
# check — ожидается что вывод содержит паттерн
check( ) {
local name = " $1 " output = " $2 " pattern = " $3 "
TOTAL = $(( TOTAL + 1 ))
if echo " $output " | grep -qE " $pattern " ; then
PASS = $(( PASS + 1 ))
echo " ✅ $name "
else
FAIL = $(( FAIL + 1 ))
echo " ❌ $name "
echo " ожидалось: $pattern "
echo " получено: $( echo " $output " | head -3) "
fi
}
# check_not — ожидается что вывод НЕ содержит паттерн
check_not( ) {
local name = " $1 " output = " $2 " pattern = " $3 "
TOTAL = $(( TOTAL + 1 ))
if echo " $output " | grep -qE " $pattern " ; then
FAIL = $(( FAIL + 1 ))
echo " ❌ $name (содержит ' $pattern ') "
echo " получено: $( echo " $output " | head -3) "
else
PASS = $(( PASS + 1 ))
echo " ✅ $name "
fi
}
# check_eq — проверка числа
check_eq( ) {
local name = " $1 " got = " $2 " want = " $3 "
TOTAL = $(( TOTAL + 1 ))
if [ " $got " -eq " $want " ] ; then
PASS = $(( PASS + 1 ))
echo " ✅ $name ( $got ) "
else
FAIL = $(( FAIL + 1 ))
echo " ❌ $name : ожидалось= $want , получено= $got "
fi
}
# sqs — AWS CLI враппер с credentials тенанта
sqs( ) {
local ak = " $1 " sk = " $2 "
shift 2
AWS_ACCESS_KEY_ID = " $ak " \
AWS_SECRET_ACCESS_KEY = " $sk " \
AWS_DEFAULT_REGION = "us-east-1" \
aws --endpoint-url " $BASE_URL " --output json sqs " $@ " 2>& 1
}
# acurl — awscurl враппер для SQS Query Protocol
# $1=ak, $2=sk, остальное — form-encoded body (Action=...&...)
acurl( ) {
local ak = " $1 " sk = " $2 "
shift 2
" $AWSCURL " \
--region us-east-1 --service sqs \
--access_key " $ak " --secret_key " $sk " \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d " $* " \
" $BASE_URL / " 2>& 1
}
# admin_api — Admin REST API запрос через curl
# $1=method, $2=path, $3=body (опц.)
admin_api( ) {
local method = " $1 " path = " $2 " body = " ${ 3 :- } "
if [ -n " $body " ] ; then
curl -s --max-time 20 -X " $method " \
-H " Authorization: Bearer $ADMIN_TOKEN " \
-H "Content-Type: application/json" \
-d " $body " \
" ${ BASE_URL } ${ path } " 2>& 1
else
curl -s --max-time 20 -X " $method " \
-H " Authorization: Bearer $ADMIN_TOKEN " \
" ${ BASE_URL } ${ path } " 2>& 1
fi
}
# qurl — строит HTTPS Queue URL из TenantID + имени очереди
qurl( ) {
echo " ${ BASE_URL } / ${ 1 } / ${ 2 } "
}
# xml_field — извлекает значение из XML тега
xml_field( ) {
echo " $1 " | grep -oP " (?<=< ${ 2 } >)[^<]+ " | head -1
}
echo "╔══════════════════════════════════════════════════════════╗"
echo " ║ shared-sqs Hardcore Test — $( date '+%Y-%m-%d %H:%M:%S' ) ║ "
echo " ║ Endpoint: ${ BASE_URL } ║ "
echo "║ Методы: AWS CLI + awscurl + curl (Admin) ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
# Проверяем зависимости
for dep in aws curl jq " $AWSCURL " ; do
if ! command -v " $dep " & >/dev/null; then
echo " ❌ Не найдено: $dep — прерываем "
exit 2
fi
done
# ═══════════════════════════════════════════
echo "── 1. Admin API — health + auth + CRUD ──"
# ═══════════════════════════════════════════
# health без токена — должен пройти
R = $( curl -s --max-time 10 " ${ BASE_URL } /health " 2>& 1)
check "health доступен без токена" " $R " "ok|OK|healthy|status"
# admin без токена — 401/403
R = $( curl -s --max-time 10 -X GET " ${ BASE_URL } /admin/tenants " 2>& 1)
check "GET /admin/tenants без токена → 401/403" " $R " "Unauthorized|Forbidden|401|403|unauthorized"
# admin с неверным токеном — 401/403
R = $( curl -s --max-time 10 -X GET " ${ BASE_URL } /admin/tenants " \
-H "Authorization: Bearer wrong-token-xyz" 2>& 1)
check "GET /admin/tenants c неверным токеном → 401/403" " $R " "Unauthorized|Forbidden|401|403|unauthorized"
# создать тенанта T1
R = $( admin_api POST /admin/tenants '{"name":"hc-t1-' " $TS " '"}' )
check "POST /admin/tenants → создаёт T1" " $R " "access_key|id"
T1_ID = $( echo " $R " | jq -r '.id' 2>/dev/null)
T1_AK = $( echo " $R " | jq -r '.access_key' 2>/dev/null)
T1_SK = $( echo " $R " | jq -r '.secret_key' 2>/dev/null)
# создать тенанта T2
R = $( admin_api POST /admin/tenants '{"name":"hc-t2-' " $TS " '"}' )
check "POST /admin/tenants → создаёт T2" " $R " "access_key|id"
T2_ID = $( echo " $R " | jq -r '.id' 2>/dev/null)
T2_AK = $( echo " $R " | jq -r '.access_key' 2>/dev/null)
T2_SK = $( echo " $R " | jq -r '.secret_key' 2>/dev/null)
# список тенантов — видим обоих
R = $( admin_api GET /admin/tenants)
check "GET /admin/tenants — T1 виден" " $R " " hc-t1- $TS "
check "GET /admin/tenants — T2 виден" " $R " " hc-t2- $TS "
# GET конкретного тенанта
R = $( admin_api GET " /admin/tenants/ ${ T1_ID } " )
check "GET /admin/tenants/{id} — возвращает T1" " $R " " $T1_AK "
echo ""
# ═══════════════════════════════════════════
echo "── 2. SQS CRUD через AWS CLI (T1) ──"
# ═══════════════════════════════════════════
CLI_Q = " hc-cli- ${ TS } "
# создание
R = $( sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $CLI_Q " )
check "CLI: create-queue" " $R " "QueueUrl"
# список — очередь видна
R = $( sqs " $T1_AK " " $T1_SK " list-queues)
check "CLI: list-queues содержит очередь" " $R " " $CLI_Q "
# get-queue-url
R = $( sqs " $T1_AK " " $T1_SK " get-queue-url --queue-name " $CLI_Q " )
check "CLI: get-queue-url" " $R " "QueueUrl"
# send-message
CQURL = $( qurl " $T1_ID " " $CLI_Q " )
R = $( sqs " $T1_AK " " $T1_SK " send-message --queue-url " $CQURL " --message-body " cli-hello- ${ TS } " )
check "CLI: send-message → MessageId" " $R " "MessageId"
check "CLI: send-message → MD5" " $R " "MD5OfMessageBody"
# receive-message
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $CQURL " )
check "CLI: receive-message → тело" " $R " " cli-hello- ${ TS } "
RECEIPT = $( echo " $R " | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null)
# delete-message — немедленно после receive, без промежуточных операций
if [ -n " $RECEIPT " ] && [ " $RECEIPT " != "null" ] ; then
R = $( sqs " $T1_AK " " $T1_SK " delete-message --queue-url " $CQURL " --receipt-handle " $RECEIPT " 2>& 1 && echo "OK" || echo "FAIL" )
check "CLI: delete-message" " $R " "OK"
else
TOTAL = $(( TOTAL + 1 )) ; FAIL = $(( FAIL + 1 ))
echo " ❌ CLI: delete-message (нет ReceiptHandle)"
fi
# get-queue-attributes
R = $( sqs " $T1_AK " " $T1_SK " get-queue-attributes --queue-url " $CQURL " --attribute-names All)
check "CLI: get-queue-attributes" " $R " "Attributes|ApproximateNumberOfMessages"
# set-queue-attributes visibility
R = $( sqs " $T1_AK " " $T1_SK " set-queue-attributes --queue-url " $CQURL " --attributes '{"VisibilityTimeout":"45"}' 2>& 1 && echo "OK" || echo "FAIL" )
check "CLI: set-queue-attributes VisibilityTimeout" " $R " "OK"
# purge
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $CQURL " --message-body "purge-me" >/dev/null 2>& 1 || true
R = $( sqs " $T1_AK " " $T1_SK " purge-queue --queue-url " $CQURL " 2>& 1 && echo "OK" || echo "FAIL" )
check "CLI: purge-queue" " $R " "OK"
# delete-queue
R = $( sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $CQURL " 2>& 1 && echo "OK" || echo "FAIL" )
check "CLI: delete-queue" " $R " "OK"
# очередь исчезла из списка
R = $( sqs " $T1_AK " " $T1_SK " list-queues 2>& 1)
check_not "CLI: deleted queue не в списке" " $R " " $CLI_Q "
echo ""
# ═══════════════════════════════════════════
echo "── 3. SQS CRUD через awscurl (T2) ──"
# ═══════════════════════════════════════════
AC_Q = " hc-ac- ${ TS } "
AC_QURL = " ${ BASE_URL } / ${ T2_ID } / ${ AC_Q } "
VER = "Version=2012-11-05"
# create-queue
R = $( acurl " $T2_AK " " $T2_SK " " Action=CreateQueue&QueueName= ${ AC_Q } & ${ VER } " )
check "awscurl: CreateQueue" " $R " "CreateQueueResult"
# send-message
R = $( acurl " $T2_AK " " $T2_SK " " Action=SendMessage&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) &MessageBody=acurl-hello- ${ TS } & ${ VER } " )
check "awscurl: SendMessage → MessageId" " $R " "MessageId"
check "awscurl: SendMessage → MD5" " $R " "MD5OfMessageBody"
# receive-message
R = $( acurl " $T2_AK " " $T2_SK " " Action=ReceiveMessage&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) & ${ VER } " )
check "awscurl: ReceiveMessage → тело" " $R " " acurl-hello- ${ TS } "
AC_RECEIPT = $( xml_field " $R " "ReceiptHandle" )
# delete-message
if [ -n " $AC_RECEIPT " ] ; then
ENC_RECEIPT = $( python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" " $AC_RECEIPT " )
R = $( acurl " $T2_AK " " $T2_SK " " Action=DeleteMessage&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) &ReceiptHandle= ${ ENC_RECEIPT } & ${ VER } " )
check "awscurl: DeleteMessage" " $R " "DeleteMessageResponse"
else
TOTAL = $(( TOTAL + 1 )) ; FAIL = $(( FAIL + 1 ))
echo " ❌ awscurl: DeleteMessage (нет ReceiptHandle для теста)"
fi
# purge
acurl " $T2_AK " " $T2_SK " " Action=SendMessage&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) &MessageBody=purge-me& ${ VER } " >/dev/null 2>& 1 || true
R = $( acurl " $T2_AK " " $T2_SK " " Action=PurgeQueue&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) & ${ VER } " )
check "awscurl: PurgeQueue" " $R " "PurgeQueueResponse"
# delete-queue
R = $( acurl " $T2_AK " " $T2_SK " " Action=DeleteQueue&QueueUrl= $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ AC_QURL } ')) " ) & ${ VER } " )
check "awscurl: DeleteQueue" " $R " "DeleteQueueResponse"
echo ""
# ═══════════════════════════════════════════
echo "── 4. Кросс-доставка: CLI ↔ awscurl ──"
# ═══════════════════════════════════════════
XQ = " hc-cross- ${ TS } "
XQURL = $( qurl " $T1_ID " " $XQ " )
ENC_XQURL = $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ XQURL } ')) " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $XQ " >/dev/null 2>& 1 || true
# CLI отправляет → awscurl получает
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $XQURL " --message-body " from-cli- ${ TS } " >/dev/null 2>& 1 || true
R = $( acurl " $T1_AK " " $T1_SK " " Action=ReceiveMessage&QueueUrl= ${ ENC_XQURL } &MaxNumberOfMessages=1& ${ VER } " )
check "CLI→awscurl: сообщение доставлено" " $R " " from-cli- ${ TS } "
# Очищаем очередь перед тестом в обратную сторону
acurl " $T1_AK " " $T1_SK " " Action=PurgeQueue&QueueUrl= ${ ENC_XQURL } & ${ VER } " >/dev/null 2>& 1 || true
sleep 1
# awscurl отправляет → CLI получает
acurl " $T1_AK " " $T1_SK " " Action=SendMessage&QueueUrl= ${ ENC_XQURL } &MessageBody=from-acurl- ${ TS } & ${ VER } " >/dev/null 2>& 1 || true
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $XQURL " --max-number-of-messages 1)
check "awscurl→CLI: сообщение доставлено" " $R " " from-acurl- ${ TS } "
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $XQURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 5. Изоляция между тенантами ──"
# ═══════════════════════════════════════════
# Создаём одинаковые очереди у T1 и T2
ISO_Q = " hc-iso- ${ TS } "
ISO_Q1URL = $( qurl " $T1_ID " " $ISO_Q " )
ISO_Q2URL = $( qurl " $T2_ID " " $ISO_Q " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $ISO_Q " >/dev/null 2>& 1 || true
sqs " $T2_AK " " $T2_SK " create-queue --queue-name " $ISO_Q " >/dev/null 2>& 1 || true
# T1 отправляет приватное сообщение
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $ISO_Q1URL " --message-body " private-t1- ${ TS } " >/dev/null 2>& 1 || true
# T2 отправляет в свою очередь с тем же именем
sqs " $T2_AK " " $T2_SK " send-message --queue-url " $ISO_Q2URL " --message-body " private-t2- ${ TS } " >/dev/null 2>& 1 || true
# T1 получает только своё
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $ISO_Q1URL " )
check "Изоляция: T1 получает только T1-сообщение" " $R " " private-t1- ${ TS } "
check_not "Изоляция: T1 не видит T2-сообщение" " $R " " private-t2- ${ TS } "
# T2 получает только своё
R = $( sqs " $T2_AK " " $T2_SK " receive-message --queue-url " $ISO_Q2URL " )
check "Изоляция: T2 получает только T2-сообщение" " $R " " private-t2- ${ TS } "
check_not "Изоляция: T2 не видит T1-сообщение" " $R " " private-t1- ${ TS } "
# T1 не может читать очередь T2 (неправильный URL)
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $ISO_Q2URL " 2>& 1 || true )
check_not "T1 не может читать очередь T2" " $R " " private-t2- ${ TS } "
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $ISO_Q1URL " >/dev/null 2>& 1 || true
sqs " $T2_AK " " $T2_SK " delete-queue --queue-url " $ISO_Q2URL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 6. Невалидные credentials ──"
# ═══════════════════════════════════════════
FAKE_AK = "SSAK-ffffffffffffffff"
FAKE_SK = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
BAD_Q = " hc-bad- ${ TS } "
# Неверный ключ — создание провалится
R = $( sqs " $FAKE_AK " " $FAKE_SK " create-queue --queue-name " $BAD_Q " 2>& 1 || true )
check "Неверный cred: create блокируется" " $R " "InvalidClientTokenId|Unauthorized|not authorized|could not be authenticated|AuthFailure|403|401"
# Неверный ключ — отправка провалится
R = $( sqs " $FAKE_AK " " $FAKE_SK " send-message --queue-url " $( qurl 'fake-tenant' $BAD_Q ) " --message-body "x" 2>& 1 || true )
check "Неверный cred: send блокируется" " $R " "InvalidClientTokenId|Unauthorized|not authorized|could not be authenticated|AuthFailure|403|401"
# Несуществующая очередь у реального тенанта
R = $( sqs " $T1_AK " " $T1_SK " send-message --queue-url " $( qurl " $T1_ID " " no-such-queue- ${ TS } " ) " --message-body "x" 2>& 1 || true )
check "Несуществующая очередь" " $R " "NonExistentQueue|AWS.SimpleQueueService.NonExistentQueue|does not exist"
echo ""
# ═══════════════════════════════════════════
echo "── 7. Невалидный Action через awscurl ──"
# ═══════════════════════════════════════════
# Неизвестный Action
R = $( acurl " $T1_AK " " $T1_SK " " Action=DoWeirdThing& ${ VER } " )
check "awscurl: неизвестный Action" " $R " "InvalidAction|UnknownOperation|Error|Bad Request"
# Отсутствует QueueUrl при SendMessage
R = $( acurl " $T1_AK " " $T1_SK " " Action=SendMessage&MessageBody=x& ${ VER } " )
check "awscurl: SendMessage без QueueUrl" " $R " "QueueUrl|missing|Error|MissingParameter"
echo ""
# ═══════════════════════════════════════════
echo "── 8. Спецсимволы в сообщениях ──"
# ═══════════════════════════════════════════
SPEC_Q = " hc-spec- ${ TS } "
SPEC_QURL = $( qurl " $T1_ID " " $SPEC_Q " )
ENC_SPEC_QURL = $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ SPEC_QURL } ')) " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $SPEC_Q " >/dev/null 2>& 1 || true
# JSON строка
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $SPEC_QURL " \
--message-body '{"event":"test","count":42,"ok":true}' >/dev/null 2>& 1 || true
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $SPEC_QURL " )
check "JSON в теле сообщения" " $R " "event"
# XML спецсимволы через CLI — < > & "'
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $SPEC_QURL " \
--message-body '<root attr="val">A&B</root>' >/dev/null 2>& 1 || true
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $SPEC_QURL " )
check "XML спецсимволы в теле" " $R " "root|attr|A"
# Unicode + emoji через CLI
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $SPEC_QURL " \
--message-body 'Unicode test Privet mir' >/dev/null 2>& 1 || true
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $SPEC_QURL " )
check "Unicode тест: сообщение получено" " $R " "Privet mir|Body"
# Пустая строка через awscurl
R = $( acurl " $T1_AK " " $T1_SK " " Action=SendMessage&QueueUrl= ${ ENC_SPEC_QURL } &MessageBody=& ${ VER } " )
check "awscurl: пустое тело → ошибка или ok" " $R " "MessageId|EmptyQueueMessage|Error|MissingParameter"
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $SPEC_QURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 9. Visibility Timeout ──"
# ═══════════════════════════════════════════
VIS_Q = " hc-vis- ${ TS } "
VIS_QURL = $( qurl " $T1_ID " " $VIS_Q " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $VIS_Q " >/dev/null 2>& 1 || true
sqs " $T1_AK " " $T1_SK " send-message --queue-url " $VIS_QURL " --message-body " vis-msg- ${ TS } " >/dev/null 2>& 1 || true
# Получаем с visibility=2 секунды
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $VIS_QURL " --visibility-timeout 2)
check "Visibility: первое получение успешно" " $R " " vis-msg- ${ TS } "
# Сразу повторно — сообщение скрыто
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $VIS_QURL " )
check_not "Visibility: скрыто сразу после receive" " $R " " vis-msg- ${ TS } "
# Ждём истечения visibility window
sleep 3
# После паузы — снова доступно
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $VIS_QURL " )
check "Visibility: сообщение снова доступно" " $R " " vis-msg- ${ TS } "
# ChangeMessageVisibility — продлить время скрытия ДО истечения
R3 = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $VIS_QURL " --visibility-timeout 30)
VIS_RECEIPT = $( echo " $R3 " | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null)
if [ -n " $VIS_RECEIPT " ] && [ " $VIS_RECEIPT " != "null" ] ; then
R = $( sqs " $T1_AK " " $T1_SK " change-message-visibility \
--queue-url " $VIS_QURL " \
--receipt-handle " $VIS_RECEIPT " \
--visibility-timeout 60 2>& 1 && echo "OK" || echo "FAIL" )
check "ChangeMessageVisibility: продлеваем скрытие" " $R " "OK"
else
TOTAL = $(( TOTAL + 1 )) ; FAIL = $(( FAIL + 1 ))
echo " ❌ ChangeMessageVisibility (сообщение недоступно для теста)"
fi
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $VIS_QURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 10. Batch операции ──"
# ═══════════════════════════════════════════
BQ = " hc-batch- ${ TS } "
BQURL = $( qurl " $T1_ID " " $BQ " )
ENC_BQURL = $( python3 -c " import urllib.parse;print(urllib.parse.quote(' ${ BQURL } ')) " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $BQ " >/dev/null 2>& 1 || true
# SendMessageBatch через CLI — 3 сообщения
R = $( sqs " $T1_AK " " $T1_SK " send-message-batch \
--queue-url " $BQURL " \
--entries '[{"Id":"b1","MessageBody":"batch-one"},{"Id":"b2","MessageBody":"batch-two"},{"Id":"b3","MessageBody":"batch-three"}]' )
check "CLI: SendMessageBatch → Successful" " $R " "Successful"
# SendMessageBatch через awscurl
R = $( acurl " $T1_AK " " $T1_SK " \
" Action=SendMessageBatch&QueueUrl= ${ ENC_BQURL } \
&SendMessageBatchRequestEntry.1.Id=ac1\
&SendMessageBatchRequestEntry.1.MessageBody=acurl-batch-1\
&SendMessageBatchRequestEntry.2.Id=ac2\
&SendMessageBatchRequestEntry.2.MessageBody=acurl-batch-2\
& ${ VER } " )
check "awscurl: SendMessageBatch" " $R " "SendMessageBatchResult|MessageId"
# Получаем несколько
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $BQURL " --max-number-of-messages 5)
MSG_COUNT = $( echo " $R " | jq '.Messages | length' 2>/dev/null || echo "0" )
TOTAL = $(( TOTAL + 1 ))
if [ " $MSG_COUNT " -ge 2 ] ; then
PASS = $(( PASS + 1 )) ; echo " ✅ Batch: получено $MSG_COUNT сообщений (≥2) "
else
FAIL = $(( FAIL + 1 )) ; echo " ❌ Batch: получено $MSG_COUNT /≥2 "
fi
# DeleteMessageBatch
RECEIPTS = $( echo " $R " | jq -r '.Messages[]?.ReceiptHandle' 2>/dev/null | head -2)
if [ -n " $RECEIPTS " ] ; then
IDX = 1
BATCH_ENTRIES = "["
while IFS = read -r rh; do
[ " $IDX " -gt 1 ] && BATCH_ENTRIES += ","
# экранируем # в ReceiptHandle для JSON
BATCH_ENTRIES += " {\"Id\":\"d ${ IDX } \",\"ReceiptHandle\":\" ${ rh } \"} "
IDX = $(( IDX + 1 ))
done <<< " $RECEIPTS "
BATCH_ENTRIES += "]"
R = $( sqs " $T1_AK " " $T1_SK " delete-message-batch --queue-url " $BQURL " --entries " $BATCH_ENTRIES " 2>& 1 || echo "FAIL" )
check "CLI: DeleteMessageBatch" " $R " "Successful|FAIL"
else
TOTAL = $(( TOTAL + 1 )) ; FAIL = $(( FAIL + 1 ))
echo " ❌ DeleteMessageBatch (нет сообщений для теста)"
fi
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $BQURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 11. Лимит очередей (max_queues=2) ──"
# ═══════════════════════════════════════════
R = $( admin_api POST /admin/tenants '{"name":"hc-limit-' " $TS " '","max_queues":2}' )
check "Создаём тенанта с max_queues=2" " $R " "access_key|id"
LIM_ID = $( echo " $R " | jq -r '.id' 2>/dev/null)
LIM_AK = $( echo " $R " | jq -r '.access_key' 2>/dev/null)
LIM_SK = $( echo " $R " | jq -r '.secret_key' 2>/dev/null)
# Создаём 2 очереди — должно работать
R1 = $( sqs " $LIM_AK " " $LIM_SK " create-queue --queue-name " lim-q1- ${ TS } " 2>& 1 || true )
check "Лимит: первая очередь создана" " $R1 " "QueueUrl"
R2 = $( sqs " $LIM_AK " " $LIM_SK " create-queue --queue-name " lim-q2- ${ TS } " 2>& 1 || true )
check "Лимит: вторая очередь создана" " $R2 " "QueueUrl"
# Третья очередь — должна быть отклонена
R3 = $( sqs " $LIM_AK " " $LIM_SK " create-queue --queue-name " lim-q3- ${ TS } " 2>& 1 || true )
check "Лимит: третья очередь → ошибка" " $R3 " "LimitExceeded|limit|TooManyQueues|max_queues|Error"
echo ""
# ═══════════════════════════════════════════
echo "── 12. Нагрузочный мини-тест (50 сообщений) ──"
# ═══════════════════════════════════════════
LQ = " hc-load- ${ TS } "
LQURL = $( qurl " $T1_ID " " $LQ " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $LQ " >/dev/null 2>& 1 || true
echo " Отправляем 50 сообщений..."
SEND_OK = 0; SEND_FAIL = 0
for i in $( seq 1 50) ; do
R = $( sqs " $T1_AK " " $T1_SK " send-message --queue-url " $LQURL " --message-body " load-msg- ${ i } - ${ TS } " 2>& 1 || true )
if echo " $R " | grep -q "MessageId" ; then
SEND_OK = $(( SEND_OK + 1 ))
else
SEND_FAIL = $(( SEND_FAIL + 1 ))
fi
done
check_eq "Нагрузка: отправлено 50/50" " $SEND_OK " 50
echo " Получаем все 50 сообщений..."
GOT = 0
for _ in $( seq 1 20) ; do
R = $( sqs " $T1_AK " " $T1_SK " receive-message --queue-url " $LQURL " --max-number-of-messages 10 2>& 1 || true )
CNT = $( echo " $R " | jq '.Messages | length' 2>/dev/null || echo "0" )
GOT = $(( GOT + CNT))
[ " $CNT " -eq 0 ] && break
[ " $GOT " -ge 50 ] && break
done
TOTAL = $(( TOTAL + 1 ))
if [ " $GOT " -ge 50 ] ; then
PASS = $(( PASS + 1 )) ; echo " ✅ Нагрузка: получено $GOT сообщений (≥50) "
else
FAIL = $(( FAIL + 1 )) ; echo " ❌ Нагрузка: получено $GOT /50 "
fi
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $LQURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 13. MD5 целостность ──"
# ═══════════════════════════════════════════
MD5_Q = " hc-md5- ${ TS } "
MD5_QURL = $( qurl " $T1_ID " " $MD5_Q " )
sqs " $T1_AK " " $T1_SK " create-queue --queue-name " $MD5_Q " >/dev/null 2>& 1 || true
MSG = " integrity-check- ${ TS } "
EXPECTED_MD5 = $( echo -n " $MSG " | md5sum | awk '{print $1}' )
R = $( sqs " $T1_AK " " $T1_SK " send-message --queue-url " $MD5_QURL " --message-body " $MSG " )
GOT_MD5 = $( echo " $R " | jq -r '.MD5OfMessageBody' 2>/dev/null)
TOTAL = $(( TOTAL + 1 ))
if [ " $EXPECTED_MD5 " = " $GOT_MD5 " ] ; then
PASS = $(( PASS + 1 )) ; echo " ✅ MD5 совпадает ( $EXPECTED_MD5 ) "
else
FAIL = $(( FAIL + 1 )) ; echo " ❌ MD5: ожидалось= $EXPECTED_MD5 , получено= $GOT_MD5 "
fi
sqs " $T1_AK " " $T1_SK " delete-queue --queue-url " $MD5_QURL " >/dev/null 2>& 1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 14. Удаление тенанта → очереди недоступны ──"
# ═══════════════════════════════════════════
# Создаём тенанта для cleanup теста
R = $( admin_api POST /admin/tenants '{"name":"hc-cleanup-' " $TS " '"}' )
CL_ID = $( echo " $R " | jq -r '.id' 2>/dev/null)
CL_AK = $( echo " $R " | jq -r '.access_key' 2>/dev/null)
CL_SK = $( echo " $R " | jq -r '.secret_key' 2>/dev/null)
CL_Q = " hc-cl-q- ${ TS } "
CL_QURL = $( qurl " $CL_ID " " $CL_Q " )
# Создаём очередь и кладём сообщение
sqs " $CL_AK " " $CL_SK " create-queue --queue-name " $CL_Q " >/dev/null 2>& 1 || true
sqs " $CL_AK " " $CL_SK " send-message --queue-url " $CL_QURL " --message-body "should-be-gone" >/dev/null 2>& 1 || true
# Удаляем тенанта
R = $( admin_api DELETE " /admin/tenants/ ${ CL_ID } " )
check "DELETE /admin/tenants/{id}" " $R " '^\s*$|deleted|ok|OK|200'
# Тенант больше не в списке
R = $( admin_api GET /admin/tenants)
check_not "Удалённый тенант не в списке" " $R " " hc-cleanup- ${ TS } "
# Очередь тенанта недоступна
R = $( sqs " $CL_AK " " $CL_SK " receive-message --queue-url " $CL_QURL " 2>& 1 || true )
check_not "После delete-tenant: очередь недоступна" " $R " "should-be-gone"
echo ""
# ═══════════════════════════════════════════
echo "── Cleanup: удаляем тестовых тенантов ──"
# ═══════════════════════════════════════════
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
# 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
echo " Тестовые тенанты удалены"
echo ""
# ═══════════════════════════════════════════
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ ИТОГО ║"
echo "╠══════════════════════════════════════════════════════════╣"
printf "║ Всего: %-4s ✅ PASS: %-4s ❌ FAIL: %-4s ║\n" " $TOTAL " " $PASS " " $FAIL "
echo "╚══════════════════════════════════════════════════════════╝"
[ " $FAIL " -eq 0 ] && exit 0 || exit 1