#!/usr/bin/env bash # tests/api-crud.sh — Комплексный тест CRUD API ipwhitelist-app # Дата: 2026-06-04 # Стенд: https://italo.kube5s.ru (DEV_MODE) # # Использование: # bash tests/api-crud.sh [BASE_URL] [TOKEN] # TOKEN=eyJ... bash tests/api-crud.sh # # Токен обязателен. В DEV_MODE подойдёт любой декодируемый JWT. # Получить: залогиниться на стенде, скопировать из session.token set -euo pipefail BASE="${1:-https://italo.kube5s.ru}" TOKEN="${2:-${TEST_TOKEN:-}}" if [ -z "$TOKEN" ]; then echo "Ошибка: укажите TOKEN аргументом или через TEST_TOKEN=..." echo "" echo " # В DEV_MODE можно сгенерировать локально:" echo ' TOKEN=$(node -e "const a=require(\"./src/auth\"); a.initAuth().then(x=>console.log(x.issueMockToken({clientId:\"WZ01112\",companyId:\"a\",companyName:\"Nubes\",email:\"a@b\"})))" 2>/dev/null)' echo " bash tests/api-crud.sh https://italo.kube5s.ru \$TOKEN" echo "" exit 1 fi PASS=0 FAIL=0 api() { curl -s --http2 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "$@" } check() { local name="$1" expected="$2" actual="$3" if echo "$actual" | grep -q "$expected"; then echo "✅ $name" PASS=$((PASS+1)) else echo "❌ $name — expected: '$expected', got: $(echo "$actual" | head -c 100)" FAIL=$((FAIL+1)) fi } echo "=== API CRUD Tests — $BASE ===" echo "" # 1. Health R=$(api -X GET "$BASE/healthz" -H "Content-Type:" -H "Authorization:") check "1. healthz" "OK" "$R" # 2. GET entries R=$(api -X GET "$BASE/api/v1/entries") check "2. GET entries" '"entries"' "$R" check "2b. limit" '"limit"' "$R" # 3. POST create R=$(api -X POST -d '{"value":"203.0.116.1","comment":"crud-test"}' "$BASE/api/v1/entries") check "3. POST create" '"id"' "$R" ENTRY_ID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin)['entry']['id'])" 2>/dev/null || echo "") # 4. POST duplicate R=$(api -X POST -d '{"value":"203.0.116.1"}' "$BASE/api/v1/entries") check "4. POST duplicate" 'уже существует' "$R" # 5. POST intersection R=$(api -X POST -d '{"value":"203.0.116.0/24"}' "$BASE/api/v1/entries") check "5. POST intersection" 'Пересечение' "$R" # 6. POST private IP R=$(api -X POST -d '{"value":"10.0.0.1"}' "$BASE/api/v1/entries") check "6. POST private" 'запрещённым' "$R" # 7. POST /21 R=$(api -X POST -d '{"value":"5.0.0.0/21"}' "$BASE/api/v1/entries") check "7. POST /21" '/22 до /32' "$R" # 8. POST invalid IP R=$(api -X POST -d '{"value":"999.999.999.999"}' "$BASE/api/v1/entries") check "8. POST invalid" 'Некорректный' "$R" # 9. PATCH update if [ -n "$ENTRY_ID" ]; then R=$(api -X PATCH -d '{"value":"203.0.116.2","comment":"updated"}' "$BASE/api/v1/entries/$ENTRY_ID") check "9. PATCH update" '"id"' "$R" check "9b. new value" '203.0.116.2/32' "$R" fi # 10. DELETE if [ -n "$ENTRY_ID" ]; then R=$(api -X DELETE "$BASE/api/v1/entries/$ENTRY_ID" -w "%{http_code}" -o /dev/null) check "10. DELETE" "204" "$R" fi # 11. Export R=$(api -X GET "$BASE/export" -H "Content-Type:" -H "Authorization:") check "11. export" "/32" "$R" # 12. No auth R=$(curl -s --http2 "$BASE/api/v1/entries" -w "\n%{http_code}") check "12. no auth" "Bearer token required" "$R" echo "" echo "=== Результат: $PASS / $((PASS+FAIL)) ===" [ "$FAIL" -eq 0 ] && echo "ВСЕ ТЕСТЫ ПРОЙДЕНЫ" || echo "ЕСТЬ ОШИБКИ"