#!/usr/bin/env bash # tests/shared_sqs_test.sh # Интеграционные тесты shared-sqs: Admin API, изоляция тенантов, CRUD, лимиты # Created: 2026-04-09 set -euo pipefail BASE_URL="${BASE_URL:-http://localhost:4100}" ADMIN_TOKEN="${ADMIN_TOKEN:-dev-token-123}" PASS=0 FAIL=0 pass() { echo " [PASS] $1"; PASS=$((PASS+1)); } fail() { echo " [FAIL] $1"; FAIL=$((FAIL+1)); } check_status() { local label="$1" expected="$2" actual="$3" if [[ "$actual" == "$expected" ]]; then pass "$label (HTTP $actual)" else fail "$label -- expected HTTP $expected, got $actual" fi } check_contains() { local label="$1" pattern="$2" body="$3" if echo "$body" | grep -q "$pattern"; then pass "$label" else fail "$label -- pattern '$pattern' not found in: $body" fi } check_not_contains() { local label="$1" pattern="$2" body="$3" if ! echo "$body" | grep -q "$pattern"; then pass "$label" else fail "$label -- pattern '$pattern' FOUND but should be absent" fi } sqs_req() { local access_key="$1" secret_key="$2" action="$3" shift 3 AWS_ACCESS_KEY_ID="$access_key" \ AWS_SECRET_ACCESS_KEY="$secret_key" \ AWS_DEFAULT_REGION="us-east-1" \ aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" 2>&1 } sqs_req_status() { local access_key="$1" secret_key="$2" action="$3" shift 3 AWS_ACCESS_KEY_ID="$access_key" \ AWS_SECRET_ACCESS_KEY="$secret_key" \ AWS_DEFAULT_REGION="us-east-1" \ aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" > /dev/null 2>&1 echo $? } echo "========================================" echo " shared-sqs integration tests" echo " BASE_URL=$BASE_URL" echo "========================================" echo "" echo "--- 1. Health check ---" STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health") check_status "GET /health" "200" "$STATUS" echo "" echo "--- 2. Admin API -- без токена ---" STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$BASE_URL/admin/tenants") check_status "GET /admin/tenants без токена -> 401" "401" "$STATUS" STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/admin/tenants" \ -H "Content-Type: application/json" -d '{"name":"test"}') check_status "POST /admin/tenants без токена -> 401" "401" "$STATUS" echo "" echo "--- 3. Admin API -- создание тенантов ---" RESP_A=$(curl -s -X POST "$BASE_URL/admin/tenants" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"tenant-a","max_queues":10}') check_contains "Создать tenant-a -- access_key" "access_key" "$RESP_A" check_contains "Создать tenant-a -- secret_key" "secret_key" "$RESP_A" check_contains "Создать tenant-a -- id" "\"id\"" "$RESP_A" ACCESS_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])") SECRET_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])") ID_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") RESP_B=$(curl -s -X POST "$BASE_URL/admin/tenants" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"tenant-b","max_queues":10}') check_contains "Создать tenant-b -- access_key" "access_key" "$RESP_B" ACCESS_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])") SECRET_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])") ID_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") echo "" echo "--- 4. Admin API -- list/get ---" LIST=$(curl -s "$BASE_URL/admin/tenants" -H "Authorization: Bearer $ADMIN_TOKEN") check_contains "GET /admin/tenants -- tenant-a" "tenant-a" "$LIST" check_contains "GET /admin/tenants -- tenant-b" "tenant-b" "$LIST" check_not_contains "GET /admin/tenants -- НЕТ secret_key" "secret_key" "$LIST" GET_A=$(curl -s "$BASE_URL/admin/tenants/$ID_A" -H "Authorization: Bearer $ADMIN_TOKEN") check_contains "GET /admin/tenants/{id} -- tenant-a" "tenant-a" "$GET_A" check_not_contains "GET /admin/tenants/{id} -- нет secret_key" "secret_key" "$GET_A" STATUS404=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/admin/tenants/no-such-id" \ -H "Authorization: Bearer $ADMIN_TOKEN") check_status "GET несуществующего тенанта -> 404" "404" "$STATUS404" echo "" echo "--- 5. CRUD flow (tenant-a) ---" QUEUE_URL_A=$(sqs_req "$ACCESS_A" "$SECRET_A" create-queue --queue-name my-queue \ | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])") check_contains "CreateQueue -> QueueUrl содержит my-queue" "my-queue" "$QUEUE_URL_A" check_contains "CreateQueue -> QueueUrl содержит ID тенанта" "$ID_A" "$QUEUE_URL_A" MSG_ID=$(sqs_req "$ACCESS_A" "$SECRET_A" send-message \ --queue-url "$QUEUE_URL_A" --message-body "hello-world" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['MessageId'])") check_contains "SendMessage -> MessageId" "-" "$MSG_ID" RECV=$(sqs_req "$ACCESS_A" "$SECRET_A" receive-message --queue-url "$QUEUE_URL_A") check_contains "ReceiveMessage -> тело hello-world" "hello-world" "$RECV" RECEIPT=$(echo "$RECV" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')") if [[ -n "$RECEIPT" ]]; then sqs_req "$ACCESS_A" "$SECRET_A" delete-message \ --queue-url "$QUEUE_URL_A" --receipt-handle "$RECEIPT" > /dev/null 2>&1 pass "DeleteMessage -- без ошибок" else fail "DeleteMessage -- нет ReceiptHandle" fi echo "" echo "--- 6. Изоляция тенантов ---" QUEUE_URL_B=$(sqs_req "$ACCESS_B" "$SECRET_B" create-queue --queue-name my-queue \ | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])") check_contains "tenant-B CreateQueue my-queue -> ID tenant-B в URL" "$ID_B" "$QUEUE_URL_B" sqs_req "$ACCESS_A" "$SECRET_A" send-message \ --queue-url "$QUEUE_URL_A" --message-body "msg-for-A" > /dev/null 2>&1 RECV_B=$(sqs_req "$ACCESS_B" "$SECRET_B" receive-message --queue-url "$QUEUE_URL_B" 2>&1 || true) check_not_contains "Изоляция: tenant-B НЕ получает msg-for-A" "msg-for-A" "$RECV_B" LIST_B=$(sqs_req "$ACCESS_B" "$SECRET_B" list-queues 2>&1 || true) check_not_contains "ListQueues tenant-B -- нет ID tenant-A" "$ID_A" "$LIST_B" LIST_A=$(sqs_req "$ACCESS_A" "$SECRET_A" list-queues 2>&1 || true) check_not_contains "ListQueues tenant-A -- нет ID tenant-B" "$ID_B" "$LIST_A" echo "" echo "--- 7. SQS с невалидным ключом -> ошибка ---" EXIT_FAKE=$(sqs_req_status "FAKE-KEY-0000000000" "fakesecret0000" list-queues) if [[ "$EXIT_FAKE" != "0" ]]; then pass "Невалидный access_key -> ошибка auth" else fail "Невалидный access_key -> ожидалась ошибка, получен 200" fi echo "" echo "--- 8. Лимит очередей (max_queues=2) ---" RESP_LIM=$(curl -s -X POST "$BASE_URL/admin/tenants" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"limited","max_queues":2}') ACCESS_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])") SECRET_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])") sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q1 > /dev/null 2>&1 pass "Очередь 1 из 2 -- создана" sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q2 > /dev/null 2>&1 pass "Очередь 2 из 2 -- создана" EXIT3=$(sqs_req_status "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q3) if [[ "$EXIT3" != "0" ]]; then pass "Очередь 3 при max_queues=2 -> лимит (ошибка)" else fail "Очередь 3 при max_queues=2 -> ожидалась ошибка, создалась!" fi echo "" echo "--- 9. DELETE tenant -> очереди удалены ---" STATUS_DEL=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ "$BASE_URL/admin/tenants/$ID_B" \ -H "Authorization: Bearer $ADMIN_TOKEN") check_status "DELETE tenant-b -> 204" "204" "$STATUS_DEL" EXIT_AFTER=$(sqs_req_status "$ACCESS_B" "$SECRET_B" list-queues) if [[ "$EXIT_AFTER" != "0" ]]; then pass "После DELETE tenant -- его ключи -> ошибка auth" else fail "После DELETE tenant -- его ключи вернули 200" fi echo "" echo "========================================" echo " Результат: PASS=$PASS FAIL=$FAIL" echo "========================================" [[ $FAIL -eq 0 ]] && exit 0 || exit 1