docs: agent models summary, available models list, cluster migration guide (2026-04-13)

This commit is contained in:
Repinoid
2026-04-13 12:22:45 +03:00
parent 4cd16521e6
commit 06767fdac9
7 changed files with 1761 additions and 0 deletions
+486
View File
@@ -0,0 +1,486 @@
#!/bin/bash
# 2026-04-09 — Полный набор тестов SQS API для ручного / автоматического запуска
# Покрывает: happy path, невалидный ввод, edge cases, безопасность
# Запуск: bash sqs_api_tests.sh
# Требования: curl, base64, доступ к https://sqs.kube5s.ru
set -euo pipefail
# ═══════════════════════════════════════════
# КОНФИГУРАЦИЯ
# ═══════════════════════════════════════════
SQS_HOST="https://sqs.kube5s.ru"
SQS_BASE="${SQS_HOST}/sqs/test001"
AK="SQSAK-test001-ae4dc8"
SK="J8mElXq8Nwww8iFRtGJCm8KfxM9XSTluTqS2A67xsEE"
AUTH="${AK}:${SK}"
VERSION="2012-11-05"
# Счётчики результатов
PASS=0
FAIL=0
SKIP=0
TOTAL=0
# Уникальный суффикс чтобы тесты не конфликтовали при повторном запуске
TS=$(date +%s)
# ═══════════════════════════════════════════
# УТИЛИТЫ
# ═══════════════════════════════════════════
sqs_call() {
# $1 = параметры запроса (после ?)
# $2 = auth (опционально, по умолчанию $AUTH)
local auth="${2:-$AUTH}"
curl -s --max-time 15 -u "$auth" "${SQS_BASE}?${1}&Version=${VERSION}" 2>&1
}
assert_contains() {
# $1 = название теста
# $2 = ответ
# $3 = ожидаемая подстрока
TOTAL=$((TOTAL + 1))
if echo "$2" | grep -q "$3"; then
PASS=$((PASS + 1))
echo " ✅ PASS: $1"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: $1"
echo " Ожидалось: '$3'"
echo " Получено: $(echo "$2" | head -3)"
fi
}
assert_not_contains() {
# $1 = название теста
# $2 = ответ
# $3 = подстрока которой НЕ должно быть
TOTAL=$((TOTAL + 1))
if echo "$2" | grep -q "$3"; then
FAIL=$((FAIL + 1))
echo " ❌ FAIL: $1"
echo " НЕ ожидалось: '$3'"
echo " Получено: $(echo "$2" | head -3)"
else
PASS=$((PASS + 1))
echo " ✅ PASS: $1"
fi
}
assert_http_code() {
# $1 = название теста
# $2 = URL (полный)
# $3 = ожидаемый HTTP код
# $4 = auth (опционально)
TOTAL=$((TOTAL + 1))
local auth="${4:-$AUTH}"
local code
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 -u "$auth" "$2" 2>&1)
if [ "$code" = "$3" ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: $1 (HTTP $code)"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: $1 (ожидался HTTP $3, получен $code)"
fi
}
cleanup_queue() {
# Тихое удаление очереди
sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/$1" >/dev/null 2>&1 || true
}
# ═══════════════════════════════════════════
echo "╔══════════════════════════════════════════════════╗"
echo "║ SQS API Test Suite — $(date '+%Y-%m-%d %H:%M:%S') ║"
echo "║ Endpoint: ${SQS_BASE} ║"
echo "╚══════════════════════════════════════════════════╝"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 1: БАЗОВЫЕ ОПЕРАЦИИ (Happy Path)
# ═══════════════════════════════════════════
echo "── ГРУППА 1: Базовые операции (Happy Path) ──"
Q1="testq-hp-${TS}"
# T01: Создание очереди
R=$(sqs_call "Action=CreateQueue&QueueName=${Q1}")
assert_contains "T01 CreateQueue" "$R" "CreateQueueResult"
assert_contains "T01 CreateQueue URL" "$R" "${Q1}"
# T02: Повторное создание той же очереди — не ошибка, возвращает ту же URL
R=$(sqs_call "Action=CreateQueue&QueueName=${Q1}")
assert_contains "T02 CreateQueue idempotent" "$R" "CreateQueueResult"
# T03: ListQueues — очередь видна
R=$(sqs_call "Action=ListQueues")
assert_contains "T03 ListQueues" "$R" "${Q1}"
# T04: SendMessage
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q1}&MessageBody=Hello")
assert_contains "T04 SendMessage" "$R" "MessageId"
assert_contains "T04 SendMessage MD5" "$R" "MD5OfMessageBody"
# T05: ReceiveMessage
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q1}")
assert_contains "T05 ReceiveMessage Body" "$R" "Hello"
assert_contains "T05 ReceiveMessage ReceiptHandle" "$R" "ReceiptHandle"
# Извлекаем ReceiptHandle для T06
RH=$(echo "$R" | grep -oP '(?<=<ReceiptHandle>)[^<]+' | head -1)
# T06: DeleteMessage
# ReceiptHandle содержит +/= — URL-кодируем чтобы не сломать query string
if [ -n "$RH" ]; then
RH_ENC=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$RH")
R=$(sqs_call "Action=DeleteMessage&QueueUrl=${SQS_BASE}/test001/${Q1}&ReceiptHandle=${RH_ENC}")
assert_contains "T06 DeleteMessage" "$R" "DeleteMessageResponse"
else
TOTAL=$((TOTAL + 1)); SKIP=$((SKIP + 1))
echo " ⚠️ SKIP: T06 DeleteMessage — не удалось получить ReceiptHandle"
fi
# T07: ReceiveMessage из пустой очереди — пустой результат
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q1}")
assert_not_contains "T07 ReceiveMessage empty queue" "$R" "<Body>"
# T08: GetQueueAttributes
R=$(sqs_call "Action=GetQueueAttributes&QueueUrl=${SQS_BASE}/test001/${Q1}&AttributeName.1=All")
assert_contains "T08 GetQueueAttributes" "$R" "GetQueueAttributesResponse"
# T09: PurgeQueue
sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q1}&MessageBody=ToPurge" >/dev/null
R=$(sqs_call "Action=PurgeQueue&QueueUrl=${SQS_BASE}/test001/${Q1}")
assert_contains "T09 PurgeQueue" "$R" "PurgeQueueResponse"
# T10: DeleteQueue
R=$(sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q1}")
assert_contains "T10 DeleteQueue" "$R" "DeleteQueueResponse"
# T11: ListQueues — очередь исчезла
R=$(sqs_call "Action=ListQueues")
assert_not_contains "T11 ListQueues after delete" "$R" "${Q1}"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 2: НЕВАЛИДНЫЙ ВВОД
# ═══════════════════════════════════════════
echo "── ГРУППА 2: Невалидный ввод ──"
# T12: Неизвестный Action
R=$(sqs_call "Action=BogusAction")
assert_contains "T12 Unknown Action" "$R" "InvalidAction\|AWS.SimpleQueueService.NonExistentAction\|UnknownAction\|InvalidParameterValue"
# T13: Пустой Action
R=$(sqs_call "Action=")
assert_contains "T13 Empty Action" "$R" "Error\|MissingAction\|InvalidAction\|Value for parameter Action is not valid"
# T14: Без Action вообще
R=$(curl -s --max-time 15 -u "$AUTH" "${SQS_BASE}?Version=${VERSION}" 2>&1)
assert_contains "T14 Missing Action" "$R" "Error\|MissingAction\|request"
# T15: CreateQueue без QueueName
R=$(sqs_call "Action=CreateQueue")
assert_contains "T15 CreateQueue no name" "$R" "Error\|MissingParameter\|Value for parameter QueueName is not valid"
# T16: CreateQueue с пустым QueueName
R=$(sqs_call "Action=CreateQueue&QueueName=")
assert_contains "T16 CreateQueue empty name" "$R" "Error\|InvalidParameterValue"
# T17: CreateQueue с недопустимыми символами (пробелы, кириллица)
# Akka HTTP (ElasticMQ) отвечает "Illegal request-target" на raw кириллицу
R=$(sqs_call "Action=CreateQueue&QueueName=очередь%20тест")
assert_contains "T17 CreateQueue invalid chars" "$R" "Error\|InvalidParameterValue\|Illegal"
# T18: CreateQueue с очень длинным именем (>80 символов)
LONGNAME=$(printf 'q%.0s' {1..100})
R=$(sqs_call "Action=CreateQueue&QueueName=${LONGNAME}")
assert_contains "T18 CreateQueue name too long" "$R" "Error\|InvalidParameterValue\|CreateQueueResult"
# Примечание: ElasticMQ может создать — это допустимо если не strict AWS
# T19: SendMessage без MessageBody
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/nonexist")
assert_contains "T19 SendMessage no body" "$R" "Error\|MissingParameter\|The request must contain the parameter MessageBody"
# T20: SendMessage без QueueUrl
R=$(sqs_call "Action=SendMessage&MessageBody=test")
assert_contains "T20 SendMessage no QueueUrl" "$R" "Error\|MissingParameter\|AWS.SimpleQueueService.NonExistentQueue\|The request must contain"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 3: НЕСУЩЕСТВУЮЩИЕ РЕСУРСЫ
# ═══════════════════════════════════════════
echo "── ГРУППА 3: Несуществующие ресурсы ──"
# T21: SendMessage в несуществующую очередь (autoCreateQueues=false!)
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/nonexist-${TS}&MessageBody=fail")
assert_contains "T21 SendMessage nonexistent queue" "$R" "NonExistentQueue\|AWS.SimpleQueueService.NonExistentQueue\|does not exist\|Error"
# T22: ReceiveMessage из несуществующей очереди
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/nonexist-${TS}")
assert_contains "T22 ReceiveMessage nonexistent queue" "$R" "NonExistentQueue\|AWS.SimpleQueueService.NonExistentQueue\|does not exist\|Error"
# T23: DeleteQueue несуществующей очереди
R=$(sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/nonexist-${TS}")
assert_contains "T23 DeleteQueue nonexistent" "$R" "NonExistentQueue\|AWS.SimpleQueueService.NonExistentQueue\|does not exist\|Error"
# T24: GetQueueAttributes несуществующей очереди
R=$(sqs_call "Action=GetQueueAttributes&QueueUrl=${SQS_BASE}/test001/nonexist-${TS}&AttributeName.1=All")
assert_contains "T24 GetQueueAttributes nonexistent" "$R" "NonExistentQueue\|AWS.SimpleQueueService.NonExistentQueue\|does not exist\|Error"
# T25: DeleteMessage с фейковым ReceiptHandle
Q25="testq-rh-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q25}" >/dev/null
R=$(sqs_call "Action=DeleteMessage&QueueUrl=${SQS_BASE}/test001/${Q25}&ReceiptHandle=fake-receipt-handle-12345")
assert_contains "T25 DeleteMessage fake receipt" "$R" "Error\|ReceiptHandleIsInvalid\|DeleteMessageResponse"
# Примечание: ElasticMQ может молча игнорировать — это допустимо
cleanup_queue "$Q25"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 4: АУТЕНТИФИКАЦИЯ И БЕЗОПАСНОСТЬ
# ═══════════════════════════════════════════
echo "── ГРУППА 4: Аутентификация и безопасность ──"
# T26: Неправильный Access Key
R=$(sqs_call "Action=ListQueues" "WRONG-KEY:${SK}")
assert_contains "T26 Wrong access key" "$R" "InvalidClientTokenId\|SignatureDoesNotMatch\|Error\|403\|401\|Unauthorized"
# T27: Неправильный Secret Key
R=$(sqs_call "Action=ListQueues" "${AK}:WRONG-SECRET")
assert_contains "T27 Wrong secret key" "$R" "SignatureDoesNotMatch\|Error\|403\|401\|Unauthorized"
# T28: Пустые credentials
R=$(curl -s --max-time 15 "${SQS_BASE}?Action=ListQueues&Version=${VERSION}" 2>&1)
assert_contains "T28 No credentials" "$R" "Error\|AuthFailure\|MissingAuthenticationToken\|ListQueuesResult\|403\|401"
# Примечание: ElasticMQ может не требовать auth — если так, это проблема безопасности
# T29: SQL injection в QueueName
R=$(sqs_call "Action=CreateQueue&QueueName=test';DROP%20TABLE%20queues;--")
assert_contains "T29 SQL injection in QueueName" "$R" "Error\|InvalidParameterValue\|CreateQueueResult"
# Если создалась — OK, ElasticMQ in-memory, нет SQL. Убираем.
cleanup_queue "test';DROP TABLE queues;--"
# T30: XSS в MessageBody
Q30="testq-xss-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q30}" >/dev/null
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q30}&MessageBody=%3Cscript%3Ealert(1)%3C/script%3E")
assert_contains "T30 XSS in MessageBody — accepted" "$R" "MessageId"
# SQS должен принять любой body — это не HTML, а очередь. Проверяем что возвращает как есть:
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q30}")
assert_contains "T30 XSS body returned" "$R" "script\|&lt;script"
cleanup_queue "$Q30"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 5: СПЕЦСИМВОЛЫ И КОДИРОВКИ
# ═══════════════════════════════════════════
echo "── ГРУППА 5: Спецсимволы и кодировки ──"
Q5="testq-enc-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q5}" >/dev/null
# T31: Unicode в MessageBody
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MessageBody=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82")
assert_contains "T31 Unicode body" "$R" "MessageId"
# T32: Receive Unicode
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q5}")
assert_contains "T32 Receive Unicode" "$R" "Привет\|%D0%9F"
# T33: Амперсанд и XML-спецсимволы в body
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MessageBody=a%26b%3Cc%3Ed")
assert_contains "T33 XML special chars" "$R" "MessageId"
# T34: JSON в body
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MessageBody=%7B%22key%22%3A%22value%22%7D")
assert_contains "T34 JSON body" "$R" "MessageId"
# T35: Пустой MessageBody
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MessageBody=")
assert_contains "T35 Empty body" "$R" "Error\|MissingParameter\|MessageId"
cleanup_queue "$Q5"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 6: НАГРУЗКА И ПОРЯДОК
# ═══════════════════════════════════════════
echo "── ГРУППА 6: Множественные сообщения ──"
Q6="testq-multi-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q6}" >/dev/null
# T36: Отправить 10 сообщений подряд
ALL_OK=true
for i in $(seq 1 10); do
R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q6}&MessageBody=msg-${i}")
if ! echo "$R" | grep -q "MessageId"; then
ALL_OK=false
break
fi
done
TOTAL=$((TOTAL + 1))
if $ALL_OK; then
PASS=$((PASS + 1))
echo " ✅ PASS: T36 Send 10 messages"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: T36 Send 10 messages"
fi
# T37: Receive все 10 (может потребовать несколько вызовов)
RECEIVED=0
for _ in $(seq 1 15); do
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q6}&MaxNumberOfMessages=10")
COUNT=$(echo "$R" | grep -c "<Message>" || true)
RECEIVED=$((RECEIVED + COUNT))
if [ "$RECEIVED" -ge 10 ]; then break; fi
done
TOTAL=$((TOTAL + 1))
if [ "$RECEIVED" -ge 10 ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: T37 Receive all 10 messages (got ${RECEIVED})"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: T37 Receive all 10 messages (got ${RECEIVED}/10)"
fi
cleanup_queue "$Q6"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 7: HTTP-УРОВЕНЬ
# ═══════════════════════════════════════════
echo "── ГРУППА 7: HTTP-уровень ──"
# T38: Проверка HTTPS
assert_http_code "T38 HTTPS endpoint" "${SQS_BASE}?Action=ListQueues&Version=${VERSION}" "200"
# T39: Health endpoint
TOTAL=$((TOTAL + 1))
HC=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${SQS_BASE%/sqs/test001}/sqs/test001/health" 2>&1 || echo "000")
# Примечание: health может быть на другом пути
if [ "$HC" = "200" ] || [ "$HC" = "404" ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: T39 Health endpoint (HTTP $HC)"
else
SKIP=$((SKIP + 1))
echo " ⚠️ SKIP: T39 Health endpoint (HTTP $HC — может быть не на этом пути)"
fi
# T40: Очень большой запрос (64KB body)
Q40="testq-big-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q40}" >/dev/null
BIGBODY=$(python3 -c "print('A'*65000)" 2>/dev/null || printf 'A%.0s' $(seq 1 65000))
R=$(curl -s --max-time 15 -u "$AUTH" -X POST "${SQS_BASE}?Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q40}&Version=${VERSION}" --data-urlencode "MessageBody=${BIGBODY}" 2>&1)
assert_contains "T40 Large body 64KB" "$R" "MessageId\|Error\|TooLong\|InvalidParameterValue"
cleanup_queue "$Q40"
# T41: POST вместо GET
R=$(curl -s --max-time 15 -u "$AUTH" -X POST "${SQS_BASE}?Action=ListQueues&Version=${VERSION}" 2>&1)
assert_contains "T41 POST method" "$R" "ListQueuesResult\|ListQueuesResponse"
# T42: Несуществующий путь
TOTAL=$((TOTAL + 1))
HC=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -u "$AUTH" "${SQS_HOST}/sqs/nonexistent-tenant?Action=ListQueues&Version=${VERSION}" 2>&1)
if [ "$HC" = "404" ] || [ "$HC" = "403" ] || [ "$HC" = "502" ] || [ "$HC" = "503" ] || [ "$HC" = "302" ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: T42 Wrong tenant path (HTTP $HC)"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: T42 Wrong tenant path (HTTP $HC — ожидался 302/404/403)"
fi
echo ""
# ═══════════════════════════════════════════
# ГРУППА 8: ТАЙМАУТЫ И LONG POLLING
# ═══════════════════════════════════════════
echo "── ГРУППА 8: Long polling и таймауты ──"
Q8="testq-poll-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q8}" >/dev/null
# T43: WaitTimeSeconds=1 на пустой очереди — должен вернуть пустой через ~1 сек
TOTAL=$((TOTAL + 1))
T_START=$(date +%s)
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q8}&WaitTimeSeconds=1")
T_END=$(date +%s)
ELAPSED=$((T_END - T_START))
if [ "$ELAPSED" -ge 1 ] && [ "$ELAPSED" -le 5 ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: T43 Long poll 1s empty queue (${ELAPSED}s)"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: T43 Long poll 1s (${ELAPSED}s — ожидалось 1-5s)"
fi
# T44: WaitTimeSeconds=0 — мгновенный ответ
TOTAL=$((TOTAL + 1))
T_START=$(date +%s)
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q8}&WaitTimeSeconds=0")
T_END=$(date +%s)
ELAPSED=$((T_END - T_START))
if [ "$ELAPSED" -le 2 ]; then
PASS=$((PASS + 1))
echo " ✅ PASS: T44 Short poll instant response (${ELAPSED}s)"
else
FAIL=$((FAIL + 1))
echo " ❌ FAIL: T44 Short poll (${ELAPSED}s — ожидалось <2s)"
fi
cleanup_queue "$Q8"
echo ""
# ═══════════════════════════════════════════
# ГРУППА 9: VISIBILITY TIMEOUT
# ═══════════════════════════════════════════
echo "── ГРУППА 9: Visibility timeout ──"
Q9="testq-vis-${TS}"
sqs_call "Action=CreateQueue&QueueName=${Q9}" >/dev/null
sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q9}&MessageBody=VisTest" >/dev/null
# T45: Receive с VisibilityTimeout=2 — сообщение скрыто, повторный receive пуст
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q9}&VisibilityTimeout=2")
assert_contains "T45a Receive with visibility" "$R" "VisTest"
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q9}&WaitTimeSeconds=0")
assert_not_contains "T45b Hidden during visibility" "$R" "VisTest"
# T46: После visibility timeout — сообщение снова доступно
sleep 3
R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q9}&WaitTimeSeconds=0")
assert_contains "T46 Re-visible after timeout" "$R" "VisTest"
cleanup_queue "$Q9"
echo ""
# ═══════════════════════════════════════════
# РЕЗУЛЬТАТЫ
# ═══════════════════════════════════════════
echo "╔══════════════════════════════════════════════════╗"
echo "║ РЕЗУЛЬТАТЫ ║"
echo "╠══════════════════════════════════════════════════╣"
echo "║ Всего: ${TOTAL} ║"
echo "║ ✅ PASS: ${PASS} ║"
echo "║ ❌ FAIL: ${FAIL} ║"
echo "║ ⚠️ SKIP: ${SKIP} ║"
echo "╚══════════════════════════════════════════════════╝"
if [ "$FAIL" -gt 0 ]; then
exit 1
else
exit 0
fi