Files
shared-SQS/hardcore_test.sh
T

923 lines
44 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 -10)"
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 -10)"
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
# После паузы — снова доступно (получаем с VT=30 и сохраняем receipt для CMV)
R3=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$VIS_QURL" --visibility-timeout 30)
check "Visibility: сообщение снова доступно" "$R3" "vis-msg-${TS}"
# ChangeMessageVisibility — продлить время скрытия ДО истечения
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 "── 15. Граничные случаи: имена, пустые тела ──"
# ═══════════════════════════════════════════
# Очень длинное имя очереди (AWS позволяет до 80 символов)
LONG_Q=$(printf 'q%.0s' {1..70})"-${TS}"
LONG_QURL=$(qurl "$T1_ID" "$LONG_Q")
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONG_Q" 2>&1)
check "Граница: длинное имя очереди (70 сим)" "$R" "Queue"
# Пустое тело сообщения
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONG_QURL" --message-body "" 2>&1)
check "Граница: пустое тело сообщения" "$R" "MessageId"
# Receive из пустой очереди
sqs "$T1_AK" "$T1_SK" purge-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONG_QURL" 2>&1)
check "Граница: receive из пустой очереди (нет ошибки)" "$R" "Messages|^\s*$"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 16. Большие сообщения (близко к 256KB) ──"
# ═══════════════════════════════════════════
BIG_Q="hc-big-${TS}"
BIG_QURL=$(qurl "$T1_ID" "$BIG_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BIG_Q" >/dev/null 2>&1 || true
# 100KB (в пределах лимита) через JSON структуру
python3 << EOF > /tmp/big_msg.json
import json
msg = "x" * 100000
print(json.dumps({"MessageBody": msg}))
EOF
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BIG_QURL" --message-body "$(python3 -c 'print("x" * 100000)')" 2>&1)
[ -z "$R" ] || check "Граница: 100KB сообщение" "$R" "MessageId"
# Отправка через меньший размер работает, проверим что получается
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BIG_QURL" 2>&1)
SIZE=$(echo "$R" | jq -r '.Messages[0].Body' 2>/dev/null | wc -c)
TOTAL=$((TOTAL + 1))
if [ "$SIZE" -gt 99000 ]; then
PASS=$((PASS + 1)); echo " ✅ Большое сообщение получено (≈${SIZE} bytes)"
else
FAIL=$((FAIL + 1)); echo " ❌ Размер не совпадает (ожидалось ≈100KB, получено $SIZE)"
fi
# Проверим что очень большое требует ошибку (тестируем логику, не реальный размер)
# AWS CLI не позволит отправить > 256KB из-за своей валидации, поэтому ошибка будет на клиенте
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BIG_QURL" --message-body "test" 2>&1 && echo "OK")
check "Граница: обычное сообщение работает" "$R" "OK|MessageId"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BIG_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 17. Невалидные параметры (timeout, delay) ──"
# ═══════════════════════════════════════════
INV_Q="hc-inv-${TS}"
INV_QURL=$(qurl "$T1_ID" "$INV_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$INV_Q" >/dev/null 2>&1 || true
# Receive с невалидными параметрами
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --visibility-timeout -1 2>&1)
check "Невалид: отрицательный VisibilityTimeout (ошибка)" "$R" "Invalid|invalid|Error|Parameter"
# Visibility timeout слишком большой (43200 = 12 часов — AWS позволяет)
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$INV_QURL" --message-body "msg" >/dev/null 2>&1 || true
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --visibility-timeout 43200 2>&1)
check "Невалид: VisibilityTimeout=43200 (должно быть OK или ошибка)" "$R" "Messages|Error|Invalid"
# Receive с WaitTimeSeconds > 20 (AWS max 20)
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --wait-time-seconds 25 2>&1)
check "Невалид: WaitTimeSeconds=25 (max 20, ошибка)" "$R" "Invalid|Error|Parameter|out.of"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$INV_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 18. Batch граничные случаи ──"
# ═══════════════════════════════════════════
BAT_Q="hc-bat-${TS}"
BAT_QURL=$(qurl "$T1_ID" "$BAT_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BAT_Q" >/dev/null 2>&1 || true
# Batch с 1 сообщением (граница минимума)
BAT_1=$(python3 << 'EOF'
import json, sys
entries = [{"Id": "1", "MessageBody": "msg1"}]
print(json.dumps({"Entries": entries}))
EOF
)
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_1" 2>&1)
check "Batch: 1 сообщение (минимум)" "$R" "Successful|msg1|Entry"
# Batch с максимумом 10
python3 << EOF > /tmp/bat10.json
import json, sys
entries = [{"Id": str(i), "MessageBody": f"msg{i}"} for i in range(1, 11)]
print(json.dumps({"Entries": entries}))
EOF
BAT_10=$(cat /tmp/bat10.json)
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_10" 2>&1)
check "Batch: 10 сообщений (максимум)" "$R" "Successful"
# Batch с 11 (должна ошибка)
python3 << EOF > /tmp/bat11.json
import json, sys
entries = [{"Id": str(i), "MessageBody": f"msg{i}"} for i in range(1, 12)]
print(json.dumps({"Entries": entries}))
EOF
BAT_11=$(cat /tmp/bat11.json)
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_11" 2>&1)
check "Batch: 11 сообщений → ошибка (TooMany)" "$R" "TooMany|exceed|Maximum"
# Batch с дублирующимися ID (должна ошибка)
python3 << EOF > /tmp/batdup.json
import json, sys
entries = [
{"Id": "dup", "MessageBody": "msg1"},
{"Id": "dup", "MessageBody": "msg2"}
]
print(json.dumps({"Entries": entries}))
EOF
BAT_DUP=$(cat /tmp/batdup.json)
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_DUP" 2>&1)
check "Batch: дублирующиеся ID → ошибка" "$R" "Distinct|duplicate|Two.or.more"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BAT_Q" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 19. Receive с параметрами MaxMessages ──"
# ═══════════════════════════════════════════
REC_Q="hc-rec-${TS}"
REC_QURL=$(qurl "$T1_ID" "$REC_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$REC_Q" >/dev/null 2>&1 || true
# Отправляем 5 сообщений
for i in {1..5}; do
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$REC_QURL" --message-body "msg$i" >/dev/null 2>&1 || true
done
# Receive с MaxNumberOfMessages=2 (должны быть ≤2 сообщения, зависит от реализации)
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$REC_QURL" --max-number-of-messages 2 2>&1)
COUNT=$(echo "$R" | jq '.Messages | length' 2>/dev/null || echo 0)
TOTAL=$((TOTAL + 1))
if [ "$COUNT" -le 2 ]; then
PASS=$((PASS + 1)); echo " ✅ MaxMessages=2 → получено $COUNT (≤2)"
else
FAIL=$((FAIL + 1)); echo " ❌ MaxMessages (ожидалось ≤2, получено $COUNT)"
fi
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$REC_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 20. Двойные операции: double delete, non-existent ──"
# ═══════════════════════════════════════════
DBL_Q="hc-dbl-${TS}"
DBL_QURL=$(qurl "$T1_ID" "$DBL_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DBL_Q" >/dev/null 2>&1 || true
# Отправляем и получаем сообщение
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$DBL_QURL" --message-body "test" >/dev/null 2>&1 || true
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$DBL_QURL" 2>&1)
RH=$(echo "$R" | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null)
# Удаляем сообщение дважды
sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "$RH" >/dev/null 2>&1 || true
R=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "$RH" 2>&1)
check "Double delete: второй delete → ошибка (MessageDoesNotExist)" "$R" "does.not|MessageDoesNotExist|not.found"
# Delete с невалидным ReceiptHandle
R=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "invalid-rh-12345" 2>&1)
check "Delete: невалидный handle → ошибка" "$R" "does.not|MessageDoesNotExist|not.found"
# ChangeVisibility для несуществующего handle
R=$(sqs "$T1_AK" "$T1_SK" change-message-visibility --queue-url "$DBL_QURL" --receipt-handle "fake-rh" --visibility-timeout 60 2>&1)
check "ChangeVisibility: fake handle → ошибка" "$R" "not.found|MessageNotInFlight|Invalid|in.flight"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$DBL_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 21. Попытка создать очередь с дублирующимся именем ──"
# ═══════════════════════════════════════════
DUP_Q="hc-dupq-${TS}"
DUP_QURL=$(qurl "$T1_ID" "$DUP_Q")
# Первая попытка — успех
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DUP_Q" 2>&1)
check "Create: первая очередь создана" "$R" "Queue"
# Вторая попытка с тем же именем
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DUP_Q" 2>&1)
check "Create: дублирующееся имя (QueueExists или OK)" "$R" "QueueExists|Queue|already"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$DUP_QURL" >/dev/null 2>&1 || true
echo ""
# ═══════════════════════════════════════════
echo "── 22. Операции на несуществующей очереди ──"
# ═══════════════════════════════════════════
FAKE_QURL="http://localhost:4100/queue/fake-q-never-existed-${TS}"
# Send на несуществующую
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$FAKE_QURL" --message-body "msg" 2>&1)
check "Send: на несуществующую → QueueNotFound" "$R" "QueueNotFound|does.not|specified"
# Receive из несуществующей
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$FAKE_QURL" 2>&1)
check "Receive: из несуществующей → QueueNotFound" "$R" "QueueNotFound|does.not|specified"
# SetAttributes на несуществующей
R=$(sqs "$T1_AK" "$T1_SK" set-queue-attributes --queue-url "$FAKE_QURL" --attributes VisibilityTimeout=30 2>&1)
check "SetAttributes: на несуществующей → ошибка" "$R" "QueueNotFound|not.found|error"
echo ""
# ═══════════════════════════════════════════
echo "── 23. Специальные символы и кодировки ──"
# ═══════════════════════════════════════════
ENC_Q="hc-enc-${TS}"
ENC_QURL=$(qurl "$T1_ID" "$ENC_Q")
sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$ENC_Q" >/dev/null 2>&1 || true
# Base64 закодированное
B64_MSG=$(echo -n "binary-data-test" | base64)
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$B64_MSG" 2>&1)
check "Encoding: Base64 строка в теле" "$R" "MessageId"
# Очень много спецсимволов
SPEC_MSG='!@#$%^&*()_+-=[]{}|;:,.<>?/~`"\'"'"
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$SPEC_MSG" 2>&1)
check "Encoding: спецсимволы в теле" "$R" "MessageId"
# Tabs, newlines, etc.
TAB_MSG="line1\tline2\nline3"
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$TAB_MSG" 2>&1)
check "Encoding: whitespace (tab, newline)" "$R" "MessageId"
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$ENC_QURL" >/dev/null 2>&1 || true
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