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:
Naeel
2026-04-10 16:47:27 +03:00
commit c3ba2dcae4
81 changed files with 14253 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+176
View File
@@ -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
+211
View File
@@ -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