diff --git a/tests/api_test.sh b/tests/api_test.sh new file mode 100755 index 0000000..2161054 --- /dev/null +++ b/tests/api_test.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# tests/api_test.sh — программная проверка SQS API (как сервис используют приложения). +# Не консоль: aws cli = тот же SDK-путь (Signature V4), что у boto3/aws-sdk-go и др. +# +# Использование: +# AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... ./tests/api_test.sh +set -u + +ENDPOINT="${ENDPOINT:-https://sqs.containerk8s.dev.nubes.ru}" +REGION="${REGION:-us-east-1}" + +PASS=0; FAIL=0 +ok() { PASS=$((PASS+1)); echo " ✅ $1"; } +bad() { FAIL=$((FAIL+1)); echo " ❌ $1"; } + +json() { python3 -c "import sys,json; d=json.load(sys.stdin); print($1)" 2>/dev/null; } + +AWS=(aws --endpoint-url "$ENDPOINT" --region "$REGION") + +if [[ -z "${AWS_ACCESS_KEY_ID:-}" || -z "${AWS_SECRET_ACCESS_KEY:-}" ]]; then + echo "Нужны AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY" + exit 1 +fi + +Q="api-test-$(date +%s)" +QF="$Q.fifo" + +echo "== Базовая очередь ==" +# ListQueues +"${AWS[@]}" sqs list-queues >/dev/null 2>&1 && ok "ListQueues" || bad "ListQueues" + +# CreateQueue +CQ=$("${AWS[@]}" sqs create-queue --queue-name "$Q" 2>&1) +QUEUE_URL=$(echo "$CQ" | json 'd["QueueUrl"]') +[[ -n "$QUEUE_URL" ]] && ok "CreateQueue" || bad "CreateQueue: $CQ" + +# GetQueueUrl — должен совпасть с URL из create-queue +GUQ=$("${AWS[@]}" sqs get-queue-url --queue-name "$Q" 2>&1) +GUQ_URL=$(echo "$GUQ" | json 'd["QueueUrl"]') +[[ "$GUQ_URL" == "$QUEUE_URL" ]] && ok "GetQueueUrl" || bad "GetQueueUrl" + +# GetQueueAttributes (All) +GQA=$("${AWS[@]}" sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names All 2>&1) +echo "$GQA" | grep -qF '"VisibilityTimeout"' && ok "GetQueueAttributes" || bad "GetQueueAttributes: $GQA" + +# SetQueueAttributes (VisibilityTimeout=42) + проверка +"${AWS[@]}" sqs set-queue-attributes --queue-url "$QUEUE_URL" --attributes VisibilityTimeout=42 >/dev/null 2>&1 \ + && ok "SetQueueAttributes" || bad "SetQueueAttributes" +GQA2=$("${AWS[@]}" sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names VisibilityTimeout 2>&1) +echo "$GQA2" | grep -qF '"42"' && ok "SetQueueAttributes проверен (42)" || bad "VisibilityTimeout != 42" + +# SendMessage +"${AWS[@]}" sqs send-message --queue-url "$QUEUE_URL" --message-body "single-msg" >/dev/null 2>&1 \ + && ok "SendMessage" || bad "SendMessage" + +# SendMessageBatch (2 сообщения) +SMB=$("${AWS[@]}" sqs send-message-batch --queue-url "$QUEUE_URL" \ + --entries Id=1,MessageBody=batch-1 Id=2,MessageBody=batch-2 2>&1) +echo "$SMB" | grep -qF '"Successful"' && ok "SendMessageBatch (2)" || bad "SendMessageBatch: $SMB" + +# ReceiveMessage (до 10) — должны прийти все 3 +for i in 1 2 3 4 5; do + RM=$("${AWS[@]}" sqs receive-message --queue-url "$QUEUE_URL" --max-number-of-messages 10 2>&1) + N=$(echo "$RM" | json 'len(d.get("Messages", []))') + [[ -n "$N" && "$N" -gt 0 ]] && break + sleep 1 +done +N=$(echo "$RM" | json 'len(d.get("Messages", []))') +[[ "$N" == "3" ]] && ok "ReceiveMessage (3 сообщения)" || bad "ReceiveMessage: получили $N" + +# ChangeMessageVisibility по первому handle +RH1=$(echo "$RM" | json 'd["Messages"][0]["ReceiptHandle"]') +if [[ -n "$RH1" ]]; then + "${AWS[@]}" sqs change-message-visibility --queue-url "$QUEUE_URL" --receipt-handle "$RH1" --visibility-timeout 1 >/dev/null 2>&1 \ + && ok "ChangeMessageVisibility" || bad "ChangeMessageVisibility" +fi + +# DeleteMessage (первое) + DeleteMessageBatch (остальные два) +"${AWS[@]}" sqs delete-message --queue-url "$QUEUE_URL" --receipt-handle "$RH1" >/dev/null 2>&1 \ + && ok "DeleteMessage" || bad "DeleteMessage" +RH2=$(echo "$RM" | json 'd["Messages"][1]["ReceiptHandle"]') +RH3=$(echo "$RM" | json 'd["Messages"][2]["ReceiptHandle"]') +if [[ -n "$RH2" && -n "$RH3" ]]; then + DMB=$("${AWS[@]}" sqs delete-message-batch --queue-url "$QUEUE_URL" \ + --entries Id=1,ReceiptHandle="$RH2" Id=2,ReceiptHandle="$RH3" 2>&1) + echo "$DMB" | grep -qF '"Successful"' && ok "DeleteMessageBatch (2)" || bad "DeleteMessageBatch: $DMB" +fi + +# TagQueue / ListQueueTags / UntagQueue +"${AWS[@]}" sqs tag-queue --queue-url "$QUEUE_URL" --tags env=test,owner=api-test >/dev/null 2>&1 \ + && ok "TagQueue" || bad "TagQueue" +LQT=$("${AWS[@]}" sqs list-queue-tags --queue-url "$QUEUE_URL" 2>&1) +echo "$LQT" | grep -qF '"env"' && echo "$LQT" | grep -qF '"api-test"' && ok "ListQueueTags" || bad "ListQueueTags: $LQT" +"${AWS[@]}" sqs untag-queue --queue-url "$QUEUE_URL" --tag-keys env owner >/dev/null 2>&1 \ + && ok "UntagQueue" || bad "UntagQueue" + +# PurgeQueue +"${AWS[@]}" sqs purge-queue --queue-url "$QUEUE_URL" >/dev/null 2>&1 \ + && ok "PurgeQueue" || bad "PurgeQueue" + +# DeleteQueue +"${AWS[@]}" sqs delete-queue --queue-url "$QUEUE_URL" >/dev/null 2>&1 \ + && ok "DeleteQueue" || bad "DeleteQueue" + +echo "== FIFO-очередь ==" +CFQ=$("${AWS[@]}" sqs create-queue --queue-name "$QF" --attributes FifoQueue=true 2>&1) +FIFO_URL=$(echo "$CFQ" | json 'd["QueueUrl"]') +[[ -n "$FIFO_URL" ]] && ok "CreateQueue (FIFO)" || bad "CreateQueue FIFO: $CFQ" + +"${AWS[@]}" sqs send-message --queue-url "$FIFO_URL" --message-body "fifo-msg" \ + --message-group-id g1 --message-deduplication-id d1 >/dev/null 2>&1 \ + && ok "SendMessage (FIFO)" || bad "SendMessage FIFO" + +RF=$("${AWS[@]}" sqs receive-message --queue-url "$FIFO_URL" --max-number-of-messages 1 2>&1) +echo "$RF" | grep -qF '"fifo-msg"' && ok "ReceiveMessage (FIFO)" || bad "ReceiveMessage FIFO: $RF" +RFH=$(echo "$RF" | json 'd["Messages"][0]["ReceiptHandle"]') +[[ -n "$RFH" ]] && "${AWS[@]}" sqs delete-message --queue-url "$FIFO_URL" --receipt-handle "$RFH" >/dev/null 2>&1 \ + && ok "DeleteMessage (FIFO)" || bad "DeleteMessage FIFO" + +"${AWS[@]}" sqs delete-queue --queue-url "$FIFO_URL" >/dev/null 2>&1 \ + && ok "DeleteQueue (FIFO)" || bad "DeleteQueue FIFO" + +echo +echo "Итог: PASS=$PASS FAIL=$FAIL" +[[ $FAIL -eq 0 ]] diff --git a/tests/sdk_test.py b/tests/sdk_test.py new file mode 100644 index 0000000..c2b21bb --- /dev/null +++ b/tests/sdk_test.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""tests/sdk_test.py — программная проверка SQS API через boto3 (как реальное приложение). + +Использование: + AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... \ + ENDPOINT_URL=https://sqs.containerk8s.dev.nubes.ru python3 tests/sdk_test.py +""" +import os +import sys +import time + +import boto3 +from botocore.config import Config + +ENDPOINT = os.environ.get("ENDPOINT_URL", "https://sqs.containerk8s.dev.nubes.ru") +REGION = os.environ.get("REGION", "us-east-1") + +PASS = 0 +FAIL = 0 + + +def ok(name): + global PASS + PASS += 1 + print(f" OK {name}") + + +def bad(name, err=""): + global FAIL + FAIL += 1 + print(f" FAIL {name} {err}") + + +def main(): + if not os.environ.get("AWS_ACCESS_KEY_ID") or not os.environ.get("AWS_SECRET_ACCESS_KEY"): + print("Нужны AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY") + return 1 + + # Таймауты на уровне SDK: запросы не зависают. + cfg = Config(connect_timeout=10, read_timeout=20, retries={"max_attempts": 2}) + sqs = boto3.client("sqs", endpoint_url=ENDPOINT, region_name=REGION, config=cfg) + + q = f"sdk-test-{int(time.time())}" + qf = q + ".fifo" + + def step(name, fn): + try: + fn() + ok(name) + except Exception as e: + bad(name, f"— {type(e).__name__}: {e}") + + url = {} + + def list_queues(): + resp = sqs.list_queues() + assert isinstance(resp.get("QueueUrls"), list) + + def create_queue(): + url["u"] = sqs.create_queue(QueueName=q)["QueueUrl"] + + def get_queue_url(): + assert sqs.get_queue_url(QueueName=q)["QueueUrl"] == url["u"] + + def get_queue_attributes(): + attrs = sqs.get_queue_attributes(QueueUrl=url["u"], AttributeNames=["All"])["Attributes"] + assert "VisibilityTimeout" in attrs + + def set_queue_attributes(): + sqs.set_queue_attributes(QueueUrl=url["u"], Attributes={"VisibilityTimeout": "42"}) + vt = sqs.get_queue_attributes(QueueUrl=url["u"], AttributeNames=["VisibilityTimeout"])["Attributes"]["VisibilityTimeout"] + assert vt == "42", f"VisibilityTimeout={vt}" + + def send_message(): + sqs.send_message(QueueUrl=url["u"], MessageBody="sdk-single") + + def send_message_batch(): + resp = sqs.send_message_batch( + QueueUrl=url["u"], + Entries=[{"Id": "1", "MessageBody": "sdk-batch-1"}, {"Id": "2", "MessageBody": "sdk-batch-2"}], + ) + assert len(resp["Successful"]) == 2 + + received = {} + + def receive_messages(): + msgs = None + for _ in range(5): + msgs = sqs.receive_message(QueueUrl=url["u"], MaxNumberOfMessages=10).get("Messages", []) + if msgs: + break + time.sleep(1) + assert len(msgs) == 3, f"получено {len(msgs)}" + received["m"] = msgs + + def change_visibility(): + sqs.change_message_visibility( + QueueUrl=url["u"], ReceiptHandle=received["m"][0]["ReceiptHandle"], VisibilityTimeout=1 + ) + + def delete_message(): + sqs.delete_message(QueueUrl=url["u"], ReceiptHandle=received["m"][0]["ReceiptHandle"]) + + def delete_message_batch(): + resp = sqs.delete_message_batch( + QueueUrl=url["u"], + Entries=[ + {"Id": "1", "ReceiptHandle": received["m"][1]["ReceiptHandle"]}, + {"Id": "2", "ReceiptHandle": received["m"][2]["ReceiptHandle"]}, + ], + ) + assert len(resp["Successful"]) == 2 + + def tag_queue(): + sqs.tag_queue(QueueUrl=url["u"], Tags={"env": "sdk", "owner": "test"}) + tags = sqs.list_queue_tags(QueueUrl=url["u"]).get("Tags", {}) + assert tags.get("env") == "sdk" + sqs.untag_queue(QueueUrl=url["u"], TagKeys=["env", "owner"]) + + def purge_queue(): + sqs.purge_queue(QueueUrl=url["u"]) + + def delete_queue(): + sqs.delete_queue(QueueUrl=url["u"]) + + def fifo_cycle(): + furl = sqs.create_queue(QueueName=qf, Attributes={"FifoQueue": "true"})["QueueUrl"] + try: + sqs.send_message( + QueueUrl=furl, MessageBody="sdk-fifo", + MessageGroupId="g1", MessageDeduplicationId="d1", + ) + msgs = sqs.receive_message(QueueUrl=furl, MaxNumberOfMessages=1).get("Messages", []) + assert msgs and msgs[0]["Body"] == "sdk-fifo" + sqs.delete_message(QueueUrl=furl, ReceiptHandle=msgs[0]["ReceiptHandle"]) + finally: + sqs.delete_queue(QueueUrl=furl) + + print("== Стандартная очередь ==") + step("list_queues", list_queues) + step("create_queue", create_queue) + step("get_queue_url", get_queue_url) + step("get_queue_attributes", get_queue_attributes) + step("set_queue_attributes", set_queue_attributes) + step("send_message", send_message) + step("send_message_batch", send_message_batch) + step("receive_message (3)", receive_messages) + step("change_message_visibility", change_visibility) + step("delete_message", delete_message) + step("delete_message_batch", delete_message_batch) + step("tag/list/untag", tag_queue) + step("purge_queue", purge_queue) + step("delete_queue", delete_queue) + + print("== FIFO-очередь ==") + step("fifo: create/send/receive/delete", fifo_cycle) + + print(f"\nИтог: PASS={PASS} FAIL={FAIL}") + return 1 if FAIL else 0 + + +if __name__ == "__main__": + sys.exit(main())