commit 0b2e8da97bac1086d50f9cfe62854c8fc12576dc Author: “Naeel” Date: Thu Apr 9 18:21:10 2026 +0300 add README, quick_test, hardcore_test for shared-sqs multi-tenant SQS service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d088e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.log +*.tmp +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd9a3bc --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +# NGCloud Shared SQS — Multi-Tenant Message Queue Service + +Совместимый с Amazon SQS мультитенантный сервис очередей сообщений. +Один кластер — много тенантов, каждый с изолированными очередями и credentials. + +## Демо-стенд + +| | | +|---|---| +| **Web UI** | https://qu.kube5s.ru/ui/ | +| **SQS Endpoint** | https://qu.kube5s.ru | +| **Access Key** | `SSAK-dbdee63f39621705826a759e` | +| **Secret Key** | `faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f` | +| **Region** | `us-east-1` (любая) | + +## AWS CLI + +Самый простой способ работы — через стандартный AWS CLI. + +### Установка + +Linux / macOS (pip): + +```bash +pip install awscli +``` + +Linux (пакет): + +```bash +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip +unzip awscliv2.zip && sudo ./aws/install +``` + +macOS (brew): + +```bash +brew install awscli +``` + +Windows: скачайте установщик с [https://aws.amazon.com/cli/](https://aws.amazon.com/cli/) + +Проверка: `aws --version` + +### Настройка + +```bash +export AWS_ACCESS_KEY_ID="SSAK-dbdee63f39621705826a759e" +export AWS_SECRET_ACCESS_KEY="faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f" +export AWS_DEFAULT_REGION="us-east-1" +ENDPOINT="--endpoint-url https://qu.kube5s.ru" +``` + +### Создать очередь + +```bash +aws sqs create-queue --queue-name myqueue $ENDPOINT +``` + +### Список очередей + +```bash +aws sqs list-queues $ENDPOINT +``` + +### Отправить сообщение + +```bash +aws sqs send-message \ + --queue-url "$(aws sqs get-queue-url --queue-name myqueue $ENDPOINT --query QueueUrl --output text)" \ + --message-body "Hello from CLI!" \ + $ENDPOINT +``` + +### Получить сообщение + +```bash +aws sqs receive-message \ + --queue-url "$(aws sqs get-queue-url --queue-name myqueue $ENDPOINT --query QueueUrl --output text)" \ + $ENDPOINT +``` + +### Удалить очередь + +```bash +aws sqs delete-queue \ + --queue-url "$(aws sqs get-queue-url --queue-name myqueue $ENDPOINT --query QueueUrl --output text)" \ + $ENDPOINT +``` + +> **Совет:** `--endpoint-url` — единственное отличие от работы с Amazon SQS. +> Весь остальной код без изменений. + +## curl + +### Создать очередь + +```bash +curl -s -u "SSAK-dbdee63f39621705826a759e:faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f" \ + "https://qu.kube5s.ru/?Action=CreateQueue&QueueName=myqueue&Version=2012-11-05" +``` + +### Отправить сообщение + +```bash +curl -s -u "SSAK-dbdee63f39621705826a759e:faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f" \ + "https://qu.kube5s.ru/?Action=SendMessage&QueueUrl=https://qu.kube5s.ru/queue/myqueue&MessageBody=HelloWorld&Version=2012-11-05" +``` + +### Получить сообщение + +```bash +curl -s -u "SSAK-dbdee63f39621705826a759e:faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f" \ + "https://qu.kube5s.ru/?Action=ReceiveMessage&QueueUrl=https://qu.kube5s.ru/queue/myqueue&Version=2012-11-05" +``` + +## Python (boto3) + +```python +import boto3 + +sqs = boto3.client( + "sqs", + endpoint_url="https://qu.kube5s.ru", + region_name="us-east-1", + aws_access_key_id="SSAK-dbdee63f39621705826a759e", + aws_secret_access_key="faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f", +) + +# Создать очередь +queue = sqs.create_queue(QueueName="myqueue") +print(queue["QueueUrl"]) + +# Отправить сообщение +sqs.send_message(QueueUrl=queue["QueueUrl"], MessageBody="Hello from Python!") + +# Получить сообщение +msgs = sqs.receive_message(QueueUrl=queue["QueueUrl"]) +print(msgs["Messages"][0]["Body"]) +``` + +## Node.js (@aws-sdk/client-sqs) + +```javascript +const { SQSClient, CreateQueueCommand, SendMessageCommand, ReceiveMessageCommand } = require("@aws-sdk/client-sqs"); + +const sqs = new SQSClient({ + endpoint: "https://qu.kube5s.ru", + region: "us-east-1", + credentials: { + accessKeyId: "SSAK-dbdee63f39621705826a759e", + secretAccessKey: "faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f", + }, +}); + +const queue = await sqs.send(new CreateQueueCommand({ QueueName: "myqueue" })); +await sqs.send(new SendMessageCommand({ QueueUrl: queue.QueueUrl, MessageBody: "Hello!" })); +const res = await sqs.send(new ReceiveMessageCommand({ QueueUrl: queue.QueueUrl })); +console.log(res.Messages[0].Body); +``` + +## Тесты + +В репозитории три тестовых скрипта — от простого к сложному. + +### quick_test.sh — быстрая проверка + +Требования: `aws cli`, `bash` + +```bash +bash quick_test.sh +``` + +Проверяет 6 базовых операций: создание очереди → список → отправка → получение → удаление → проверка удаления. + +### hardcore_test.sh — полный тест (AWS CLI + curl) + +Требования: `aws cli`, `curl`, `bash` + +```bash +bash hardcore_test.sh +``` + +86 тестов в 23 группах: + +- Admin API: создание, список, удаление тенантов +- CRUD через AWS CLI и через curl +- Кросс-доставка: CLI отправляет → curl получает (и наоборот) +- Изоляция тенантов: тенант A не видит очереди тенанта B +- Невалидный ввод: неавторизованный доступ, несуществующие очереди +- Спецсимволы: JSON, XML-сущности, Unicode, эмодзи +- Batch операции: SendMessageBatch, DeleteMessageBatch +- Visibility timeout: сообщение скрыто → снова доступно +- Лимиты: максимум очередей на тенанта +- Load test: 20 сообщений throughput +- Целостность: проверка MD5 +- Edge cases: длинные имена, большие сообщения, граничные параметры + +## Особенности + +- **Полная совместимость с Amazon SQS API** — AWS SDK и CLI работают без изменений, только endpoint меняется +- **Мультитенантность** — каждый тенант с изолированными credentials (Access Key / Secret Key) и лимитами +- **Изоляция** — тенант видит только свои очереди, чужие недоступны +- **Web UI** — визуальное управление тенантами и мониторинг очередей +- **Admin API** — программное управление тенантами (создание, удаление, просмотр) +- **Batch операции** — SendMessageBatch, DeleteMessageBatch для высокой пропускной способности diff --git a/hardcore_test.sh b/hardcore_test.sh new file mode 100644 index 0000000..57c142d --- /dev/null +++ b/hardcore_test.sh @@ -0,0 +1,922 @@ +#!/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 diff --git a/quick_test.sh b/quick_test.sh new file mode 100644 index 0000000..58f1db0 --- /dev/null +++ b/quick_test.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# quick_test.sh — быстрая проверка shared-sqs (6 тестов) +# Требования: aws cli, bash +# Запуск: bash quick_test.sh + +set -uo pipefail + +# Конфигурация — можно переопределить через переменные окружения +BASE_URL="${BASE_URL:-https://qu.kube5s.ru}" +export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-SSAK-dbdee63f39621705826a759e}" +export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-faaae34c905f467636cce1db09a66f35113bee6f61efecbe1dcd30ed8d11c33f}" +export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}" +ENDPOINT="--endpoint-url ${BASE_URL}" + +QUEUE_NAME="quick-test-$(date +%s)" +PASS=0 +FAIL=0 +TOTAL=0 + +# check — проверяет что вывод содержит паттерн +check() { + local name="$1" output="$2" pattern="$3" + TOTAL=$((TOTAL + 1)) + if echo "$output" | head -20 | grep -qiE "$pattern"; then + echo " [PASS] $name" + PASS=$((PASS + 1)) + else + echo " [FAIL] $name" + echo " pattern: $pattern" + echo " output: $(echo "$output" | head -3)" + FAIL=$((FAIL + 1)) + fi +} + +echo "═══════════════════════════════════════════" +echo " Shared SQS — Quick Test" +echo " Endpoint: $BASE_URL" +echo " Queue: $QUEUE_NAME" +echo "═══════════════════════════════════════════" +echo "" + +# 1. Создать очередь +echo "▶ Создание очереди" +OUT=$(aws sqs create-queue --queue-name "$QUEUE_NAME" $ENDPOINT 2>&1) +check "create-queue" "$OUT" "QueueUrl" +QUEUE_URL=$(echo "$OUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])" 2>/dev/null) + +# 2. Список очередей — должна быть наша +echo "" +echo "▶ Список очередей" +OUT=$(aws sqs list-queues $ENDPOINT 2>&1) +check "list-queues contains $QUEUE_NAME" "$OUT" "$QUEUE_NAME" + +# 3. Отправить сообщение +echo "" +echo "▶ Отправка сообщения" +OUT=$(aws sqs send-message --queue-url "$QUEUE_URL" --message-body "Hello Quick Test!" $ENDPOINT 2>&1) +check "send-message" "$OUT" "MessageId" + +# 4. Получить сообщение +echo "" +echo "▶ Получение сообщения" +OUT=$(aws sqs receive-message --queue-url "$QUEUE_URL" --wait-time-seconds 5 $ENDPOINT 2>&1) +check "receive-message" "$OUT" "Hello Quick Test" +RECEIPT=$(echo "$OUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Messages'][0]['ReceiptHandle'])" 2>/dev/null) + +# 5. Удалить сообщение (confirm delivery) +echo "" +echo "▶ Удаление сообщения" +if [[ -n "$RECEIPT" ]]; then + OUT=$(aws sqs delete-message --queue-url "$QUEUE_URL" --receipt-handle "$RECEIPT" $ENDPOINT 2>&1) + # delete-message returns empty on success + if [[ -z "$OUT" || "$OUT" == "" ]]; then + TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1)) + echo " [PASS] delete-message" + else + check "delete-message" "$OUT" "^$" + fi +else + TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) + echo " [FAIL] delete-message — no receipt handle" +fi + +# 6. Удалить очередь +echo "" +echo "▶ Удаление очереди" +OUT=$(aws sqs delete-queue --queue-url "$QUEUE_URL" $ENDPOINT 2>&1) +if [[ -z "$OUT" || "$OUT" == "" ]]; then + TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1)) + echo " [PASS] delete-queue" +else + check "delete-queue" "$OUT" "^$" +fi + +# Итог +echo "" +echo "═══════════════════════════════════════════" +echo " ИТОГО: $PASS/$TOTAL PASS, $FAIL FAIL" +echo "═══════════════════════════════════════════" +[[ $FAIL -eq 0 ]] && exit 0 || exit 1