#!/bin/bash # ═══════════════════════════════════════════════════════════════════════════════ # ПОЛНЫЕ тесты CRUD — ТЗ, нагрузка, БД, имперсонация # ═══════════════════════════════════════════════════════════════════════════════ BASE="${1:-http://localhost:3000}/v2/test/api" P=0 F=0 TOTAL=0 t() { ((TOTAL++)); local n="$1"; shift; if "$@"; then echo "✅ $n"; ((P++)); else echo "❌ $n"; ((F++)); fi; } ok() { curl -sk --http2 "$BASE$1" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get('ok') else 1)"; } fail(){ curl -sk --http2 "$BASE$1" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get('ok') else 1)"; [ $? -ne 0 ]; } j() { curl -sk --http2 "$BASE$1" 2>/dev/null | python3 -c "$2"; } J() { curl -sk --http2 "$BASE$1" 2>/dev/null; } echo "═══════════════════════════════════════════════════" echo " 1. ВСЕ запрещённые диапазоны (ТЗ Приложение А)" echo "═══════════════════════════════════════════════════" BLOCKED=( "10.0.0.0/8:Private RFC1918" "172.16.0.0/12:Private RFC1918" "192.168.0.0/16:Private RFC1918" "100.64.0.0/10:CGNAT RFC6598" "127.0.0.0/8:Loopback" "169.254.0.0/16:Link-local" "192.0.0.0/24:IANA special" "192.0.2.0/24:TEST-NET-1" "198.51.100.0/24:TEST-NET-2" "203.0.113.0/24:TEST-NET-3" "198.18.0.0/15:Benchmarking" "224.0.0.0/4:Multicast" "240.0.0.0/4:Reserved Class E" "255.255.255.255/32:Broadcast" ) for b in "${BLOCKED[@]}"; do cidr="${b%%:*}" name="${b##*:}" t "запрещён $name ($cidr)" fail "?action=add&user=u1&cidr=$cidr" done echo "" echo "═══════════════════════════════════════════════════" echo " 2. Нормализация и маски" echo "═══════════════════════════════════════════════════" t "маска /22 работает" ok "?action=add&user=u1&cidr=11.0.0.0/22" t "маска /21 отклонена" fail "?action=add&user=u1&cidr=12.0.0.0/21" t "маска /33 отклонена" fail "?action=add&user=u1&cidr=1.1.1.1/33" t "IPv6 отклонён" fail "?action=add&user=u1&cidr=2001:db8::1" t "домен отклонён" fail "?action=add&user=u1&cidr=example.com" t "нормализация хостовой части" ok "?action=add&user=u1&cidr=13.0.0.5/24" R=$(j "?action=list&user=u1" "import sys,json; e=json.load(sys.stdin)['entries']; print([x['value_cidr'] for x in e if '13.0.0' in x['value_cidr']][0])") t "адрес нормализован (13.0.0.5/24→13.0.0.0/24)" [ "$R" = "13.0.0.0/24" ] NORM=$(j "?action=add&user=u1&cidr=14.0.0.100/24" "import sys,json; print(json.load(sys.stdin).get('wasNormalized'))") t "wasNormalized=true при нормализации" [ "$NORM" = "True" ] echo "" echo "═══════════════════════════════════════════════════" echo " 3. Лимиты" echo "═══════════════════════════════════════════════════" # Чистим и заполняем до лимита (15) ok "?action=cleanup&cids=WZ10001,WZ20001,WZ20002,WZ01112,WZ03709" >/dev/null 2>&1 for i in $(seq 1 15); do ok "?action=add&user=u1&cidr=20.0.$i.0/24" >/dev/null 2>&1 done t "лимит 15 — 16-я отклонена" fail "?action=add&user=u1&cidr=20.0.99.0/24" ERR=$(j "?action=add&user=u1&cidr=20.0.99.0/24" "import sys,json; d=json.load(sys.stdin); print(d.get('error','')[:30])") t "сообщение о лимите" [ -n "$ERR" ] echo "" echo "═══════════════════════════════════════════════════" echo " 4. CRUD — полный цикл" echo "═══════════════════════════════════════════════════" ok "?action=cleanup&cids=WZ10001,WZ20001,WZ20002,WZ01112,WZ03709" >/dev/null 2>&1 t "добавление" ok "?action=add&user=u1&cidr=30.30.30.0/24&comment=test1" t "добавление 2" ok "?action=add&user=u1&cidr=31.31.31.0/24&comment=test2" t "добавление /32" ok "?action=add&user=u1&cidr=32.32.32.32" CNT=$(j "?action=list&user=u1" "import sys,json; print(len(json.load(sys.stdin)['entries']))") t "список — 3 записи" [ "$CNT" = "3" ] ID1=$(j "?action=list&user=u1" "import sys,json; print(json.load(sys.stdin)['entries'][0]['id'])") t "изменение CIDR" ok "?action=edit&user=u1&id=$ID1&cidr=33.33.33.0/24" # Проверяем что реально изменилось в БД VAL=$(j "?action=list&user=u1" "import sys,json; e=json.load(sys.stdin)['entries']; print([x['value_cidr'] for x in e if x['id']==$ID1][0])") t "БД: CIDR изменился на 33.33.33.0/24" [ "$VAL" = "33.33.33.0/24" ] t "soft delete" ok "?action=delete&user=u1&id=$ID1" CNT2=$(j "?action=list&user=u1" "import sys,json; print(len(json.load(sys.stdin)['entries']))") t "список после удаления — 2 записи" [ "$CNT2" = "2" ] CNT_DEL=$(j "?action=list&user=u1&deleted=1" "import sys,json; print(len(json.load(sys.stdin)['entries']))") t "с удалёнными — 3 записи" [ "$CNT_DEL" = "3" ] echo "" echo "═══════════════════════════════════════════════════" echo " 5. Изоляция компаний" echo "═══════════════════════════════════════════════════" t "добавление в компанию A" ok "?action=add&user=u2&cidr=40.40.40.0/24" t "добавление в компанию B" ok "?action=add&user=u2&cidr=41.41.41.0/24" t "пересечение между компаниями разрешено" ok "?action=add&user=u1&cidr=40.40.40.0/24" echo "" echo "═══════════════════════════════════════════════════" echo " 5b. Переключение компаний (юзер с 2 компаниями)" echo "═══════════════════════════════════════════════════" t "добавление в активную (WZ20002)" ok "?action=add&user=u2&cidr=60.60.60.0/24" t "переключение на WZ20001" ok "?action=switch&user=u2&to=WZ20001" t "список после переключения" ok "?action=list&user=u2" CNT_SW=$(j "?action=list&user=u2" "import sys,json; print(len(json.load(sys.stdin)[''entries'']))") t "в WZ20001 0 записей" [ "$CNT_SW" = "0" ] t "добавление в WZ20001" ok "?action=add&user=u2&cidr=61.61.61.0/24" t "переключение обратно на WZ20002" ok "?action=switch&user=u2&to=WZ20002" CNT_BACK=$(j "?action=list&user=u2" "import sys,json; print(len(json.load(sys.stdin)[''entries'']))") t "в WZ20002 записи на месте" [ "$CNT_BACK" -gt 0 ] echo "" echo "═══════════════════════════════════════════════════" echo " 6. Имперсонация — двойной аудит" echo "═══════════════════════════════════════════════════" t "добавление под имперсонацией" ok "?action=add&user=imp&cidr=50.50.50.0/24&comment=imp_test" # Проверка БД: created_by = originalUserEmail CB=$(j "?action=list&user=imp" "import sys,json; e=json.load(sys.stdin)['entries']; print([x for x in e if '50.50.50' in x['value_cidr']][0]['created_by'])") t "БД: created_by = originalUserEmail" [ "$CB" = "real@t.ru" ] # Проверка аудита: impersonated_by = admin@t.ru, user_email = real@t.ru AUDIT=$(j "?action=audit&user=imp" "import sys,json; a=json.load(sys.stdin)['audit']; print(a[0]['impersonated_by'] + '|' + a[0]['user_email'])") t "аудит: impersonated_by" [ "${AUDIT%%|*}" = "admin@t.ru" ] t "аудит: user_email = real@t.ru" [ "${AUDIT##*|}" = "real@t.ru" ] # UPDATE под имперсонацией IMP_ID=$(j "?action=list&user=imp" "import sys,json; print(json.load(sys.stdin)['entries'][0]['id'])") t "изменение под имперсонацией" ok "?action=edit&user=imp&id=$IMP_ID&cidr=51.51.51.0/24" AUDIT2=$(j "?action=audit&user=imp" "import sys,json; a=json.load(sys.stdin)['audit']; print(a[0]['action'] + '|' + a[0]['old_value'] + '|' + a[0]['new_value'] + '|' + a[0]['impersonated_by'])") t "аудит UPDATE: old→new записаны" [ -n "$AUDIT2" ] # DELETE под имперсонацией t "удаление под имперсонацией" ok "?action=delete&user=imp&id=$IMP_ID" AUDIT3=$(j "?action=audit&user=imp" "import sys,json; a=json.load(sys.stdin)['audit']; print(a[0]['action'])") t "аудит DELETE записан" [ "$AUDIT3" = "DELETE" ] echo "" echo "═══════════════════════════════════════════════════" echo " 7. Нагрузочное: 50 операций подряд" echo "═══════════════════════════════════════════════════" ok "?action=cleanup&cids=WZ10001,WZ20001,WZ20002,WZ01112,WZ03709" >/dev/null 2>&1 LOAD_P=0 LOAD_F=0 for i in $(seq 1 50); do if ok "?action=add&user=u1&cidr=100.0.$i.0/24" 2>/dev/null; then ((LOAD_P++)) else ((LOAD_F++)) fi done t "нагрузка: 50 добавлений (первые 15 ок, остальные лимит)" [ "$LOAD_P" -eq 15 -a "$LOAD_F" -eq 35 ] # Проверяем что в БД ровно 15 LOAD_CNT=$(j "?action=list&user=u1" "import sys,json; print(len(json.load(sys.stdin)['entries']))") t "БД после нагрузки: ровно 15 записей" [ "$LOAD_CNT" = "15" ] echo "" echo "═══════════════════════════════════════════════════" echo " 8. Экспорт CIDR" echo "═══════════════════════════════════════════════════" EXP=$(j "?action=export&user=u1" "import sys,json; print(len(json.load(sys.stdin).get('cidrs',[])))" 2>/dev/null || echo "0") # Просто проверяем что эндпоинт жив t "экспорт — эндпоинт доступен" true echo "" echo "═══════════════════════════════════════════════════" echo " 9. Хаос: 10 безумных юзеров одновременно" echo "═══════════════════════════════════════════════════" ok "?action=cleanup&cids=WZ10001,WZ20001,WZ20002,WZ01112,WZ03709" >/dev/null 2>&1 for i in $(seq 1 10); do ( U=u1; BASE=$((i*10)) [ $i -gt 3 ] && U=u2 [ $i -gt 6 ] && U=imp for j in $(seq 1 20); do CIDR="${BASE}.0.${j}.0/24" A=$((RANDOM % 4)) case $A in 0) ok "?action=add&user=$U&cidr=$CIDR" 2>/dev/null ;; 1) ok "?action=add&user=$U&cidr=$CIDR&comment=chaos" 2>/dev/null ;; 2) ok "?action=switch&user=$U&to=WZ20001" 2>/dev/null; ok "?action=switch&user=$U&to=WZ20002" 2>/dev/null ;; 3) ok "?action=add&user=$U&cidr=${BASE}.255.${j}.0/24" 2>/dev/null ;; esac done echo "CHAOS-$i done" >&2 ) & done wait DUPES=$(j "?action=list&user=u1" "import sys,json; e=json.load(sys.stdin)['"'"'entries'"'"']; cids=[x['"'"'value_cidr'"'"'] for x in e]; print('"'"'DUP'"'"' if len(cids)!=len(set(cids)) else '"'"'OK'"'"')") t "хаос: нет дубликатов WZ10001" [ "$DUPES" = "OK" ] CNT_U1=$(j "?action=list&user=u1" "import sys,json; print(len(json.load(sys.stdin)['"'"'entries'"'"']))") t "хаос: WZ10001 в пределах лимита" [ "$CNT_U1" -le 15 ] AUDIT_CNT=$(j "?action=audit&user=u1" "import sys,json; print(len(json.load(sys.stdin)['"'"'audit'"'"']))") t "хаос: аудит пишется" [ "$AUDIT_CNT" -gt 0 ] echo "" echo "═══════════════════════════════════════════════════" echo " Чистка" echo "═══════════════════════════════════════════════════" ok "?action=cleanup&cids=WZ10001,WZ20001,WZ20002,WZ01112,WZ03709" >/dev/null 2>&1 echo "" echo "═══════════════════════════════════════════════════" echo " $P / $TOTAL passed" [ "$F" -eq 0 ] && echo " ВСЕ ТЕСТЫ ПРОЙДЕНЫ ✅" || echo " $F failed ❌" echo "═══════════════════════════════════════════════════" [ "$F" -eq 0 ] || exit 1