#!/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 'A&B' >/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