chore: initial import from sless/shared-sqs (v0.1.14)
- Standalone SQS-service repository - Multi-tenant message queue service, AWS SQS compatible - Based on GoAws, with mutable tenants, auth, WebUI, Redis persistence - Ready for independent development and deployment - See doc/ and README.md for architecture and usage
This commit is contained in:
File diff suppressed because it is too large
Load Diff
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
# tests/quick_test.sh — Быстрая проверка shared-sqs (smoke test)
|
||||
# Created: 2026-04-09
|
||||
# Покрывает: создание тенанта + очереди, send/receive/delete сообщения,
|
||||
# UI API peek/send/purge, удаление очереди и тенанта.
|
||||
# Требования: curl, aws CLI, python3
|
||||
# Запуск:
|
||||
# bash tests/quick_test.sh
|
||||
# BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/quick_test.sh
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
BASE_URL="${BASE_URL:-https://qu.kube5s.ru}"
|
||||
ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}"
|
||||
REGION="us-east-1"
|
||||
TS=$(date +%s)
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
ok() { echo " ✅ $1"; PASS=$((PASS+1)); }
|
||||
fail() { echo " ❌ $1"; FAIL=$((FAIL+1)); }
|
||||
|
||||
check() {
|
||||
local label="$1" body="$2" pattern="$3"
|
||||
if echo "$body" | grep -qE "$pattern"; then ok "$label"; else fail "$label"; fi
|
||||
}
|
||||
|
||||
check_not() {
|
||||
local label="$1" body="$2" pattern="$3"
|
||||
if echo "$body" | grep -qE "$pattern"; then fail "$label"; else ok "$label"; fi
|
||||
}
|
||||
|
||||
check_http() {
|
||||
local label="$1" want="$2" got="$3"
|
||||
if [[ "$got" == "$want" ]]; then ok "$label (HTTP $got)"; else fail "$label — ожидалось $want, получено $got"; fi
|
||||
}
|
||||
|
||||
# aws CLI с credentials тенанта
|
||||
sqs() {
|
||||
local ak="$1" sk="$2"; shift 2
|
||||
AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" AWS_DEFAULT_REGION="$REGION" \
|
||||
aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1
|
||||
}
|
||||
|
||||
admin() {
|
||||
local method="$1" path="$2" body="${3:-}"
|
||||
if [[ -n "$body" ]]; then
|
||||
curl -sf --max-time 15 -X "$method" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body" "${BASE_URL}${path}" 2>&1
|
||||
else
|
||||
curl -sf --max-time 15 -X "$method" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
"${BASE_URL}${path}" 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
# ui_api — UI API без авторизации (публичный)
|
||||
ui() {
|
||||
local method="$1" path="$2" body="${3:-}"
|
||||
if [[ -n "$body" ]]; then
|
||||
curl -sf --max-time 15 -X "$method" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body" "${BASE_URL}/ui/api${path}" 2>&1
|
||||
else
|
||||
curl -sf --max-time 15 -X "$method" "${BASE_URL}/ui/api${path}" 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "════════════════════════════════════════"
|
||||
echo " shared-sqs Quick Test"
|
||||
echo " Endpoint: $BASE_URL"
|
||||
echo "════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# ── 1. Health ──
|
||||
echo "── 1. Health ──"
|
||||
R=$(curl -sf --max-time 10 "${BASE_URL}/health" 2>&1)
|
||||
check "GET /health → OK" "$R" "[Oo][Kk]|status"
|
||||
R=$(ui GET /health)
|
||||
check "GET /ui/api/health → ok" "$R" "ok"
|
||||
echo ""
|
||||
|
||||
# ── 2. Создание тенанта ──
|
||||
echo "── 2. Создание тенанта ──"
|
||||
RESP=$(admin POST /admin/tenants '{"name":"quick-'"$TS"'","max_queues":5}')
|
||||
check "POST /admin/tenants → access_key" "$RESP" "access_key"
|
||||
AK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])")
|
||||
SK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])")
|
||||
TID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||
echo " Tenant ID: $TID"
|
||||
echo " Access Key: $AK"
|
||||
echo ""
|
||||
|
||||
# ── 3. AWS CLI CRUD ──
|
||||
echo "── 3. AWS CLI CRUD ──"
|
||||
QNAME="quick-q-$TS"
|
||||
|
||||
R=$(sqs "$AK" "$SK" create-queue --queue-name "$QNAME")
|
||||
check "CreateQueue → QueueUrl" "$R" "QueueUrl"
|
||||
QURL=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])")
|
||||
|
||||
R=$(sqs "$AK" "$SK" list-queues)
|
||||
check "ListQueues → очередь $QNAME в списке" "$R" "$QNAME"
|
||||
|
||||
R=$(sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "hello-quick-$TS")
|
||||
check "SendMessage → MessageId" "$R" "MessageId"
|
||||
|
||||
R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL")
|
||||
check "ReceiveMessage → тело сообщения" "$R" "hello-quick-$TS"
|
||||
RECEIPT=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')" 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$RECEIPT" ]]; then
|
||||
sqs "$AK" "$SK" delete-message --queue-url "$QURL" --receipt-handle "$RECEIPT" >/dev/null 2>&1
|
||||
ok "DeleteMessage → без ошибок"
|
||||
else
|
||||
fail "DeleteMessage — нет ReceiptHandle"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ── 4. UI API — создание очереди ──
|
||||
echo "── 4. UI API очереди ──"
|
||||
R=$(ui POST "/tenants/$TID/queues" '{"name":"ui-quick-q"}')
|
||||
check "UI POST /queues → создана" "$R" "ui-quick-q"
|
||||
|
||||
R=$(ui GET "/tenants/$TID/queues")
|
||||
check "UI GET /queues → ui-quick-q в списке" "$R" "ui-quick-q"
|
||||
echo ""
|
||||
|
||||
# ── 5. UI API — send/peek/purge ──
|
||||
echo "── 5. UI API send/peek/purge ──"
|
||||
R=$(ui POST "/tenants/$TID/queues/ui-quick-q/messages" '{"body":"msg-a"}')
|
||||
check "UI POST /messages → id" "$R" '"id"'
|
||||
ui POST "/tenants/$TID/queues/ui-quick-q/messages" '{"body":"msg-b"}' >/dev/null 2>&1
|
||||
|
||||
R=$(ui GET "/tenants/$TID/queues/ui-quick-q/messages")
|
||||
check "UI GET /messages → msg-a" "$R" "msg-a"
|
||||
check "UI GET /messages → msg-b" "$R" "msg-b"
|
||||
check_not "UI GET /messages → нет receipt_handle" "$R" "receipt_handle"
|
||||
|
||||
# Purge
|
||||
ui DELETE "/tenants/$TID/queues/ui-quick-q/messages" >/dev/null 2>&1
|
||||
R=$(ui GET "/tenants/$TID/queues/ui-quick-q/messages")
|
||||
check_not "UI DELETE /messages (purge) → msg-a исчезло" "$R" "msg-a"
|
||||
echo ""
|
||||
|
||||
# ── 6. UI API — удаление очереди ──
|
||||
echo "── 6. UI API удаление очереди ──"
|
||||
HTTP=$(curl -sf --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
"${BASE_URL}/ui/api/tenants/${TID}/queues/ui-quick-q")
|
||||
check_http "UI DELETE /queues/ui-quick-q → 204/200" "204" "$HTTP" 2>/dev/null || \
|
||||
check_http "UI DELETE /queues/ui-quick-q → 200" "200" "$HTTP"
|
||||
|
||||
R=$(ui GET "/tenants/$TID/queues")
|
||||
check_not "Очередь ui-quick-q исчезла из списка" "$R" "ui-quick-q"
|
||||
echo ""
|
||||
|
||||
# ── 7. AWS CLI DeleteQueue ──
|
||||
echo "── 7. DeleteQueue ──"
|
||||
AWS_ACCESS_KEY_ID="$AK" AWS_SECRET_ACCESS_KEY="$SK" AWS_DEFAULT_REGION="$REGION" \
|
||||
aws --endpoint-url "$BASE_URL" --output json sqs delete-queue --queue-url "$QURL" >/dev/null 2>&1
|
||||
if [[ $? -eq 0 ]]; then ok "DeleteQueue → без ошибки"; else fail "DeleteQueue → ошибка"; fi
|
||||
echo ""
|
||||
|
||||
# ── Cleanup ──
|
||||
echo "── Cleanup ──"
|
||||
admin DELETE "/admin/tenants/$TID" >/dev/null 2>&1 && ok "DELETE тенанта" || fail "DELETE тенанта"
|
||||
echo ""
|
||||
|
||||
echo "════════════════════════════════════════"
|
||||
printf " Результат: ✅ %d ❌ %d\n" "$PASS" "$FAIL"
|
||||
echo "════════════════════════════════════════"
|
||||
|
||||
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
||||
Executable
+211
@@ -0,0 +1,211 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user