feat: IAM API интеграция + обновление ТЗ + тесты
- src/auth.js: fetchIamUser(), switchProfile(), userFromPayload(iamData) - src/config.js: IAM_API_URL из env - src/routes/oidc.js: обогащение сессии через IAM (с fallback на JWT) - ui/routes/auth.js: обогащение при токен-логине - ui/routes/entries.js: switchTo через POST /switch-profile - src/api/routes/entries.js: resolveCompany через profiles[] - views/index.ejs: переключатель компаний с названиями из profiles - .env.example: IAM_API_URL - docs: обновлены ТЗ-реализация.md, ТЗ-плюс.md, добавлен iam-integration.md - tests: api-crud.sh (14 тестов CRUD + валидации) - .gitignore: исключены .env.test, DEPLOY-TESTING.md
This commit is contained in:
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/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]
|
||||
#
|
||||
# Если токен не указан — использовать prod IAM токен (только для DEV_MODE)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${1:-https://italo.kube5s.ru}"
|
||||
TOKEN="${2:-}"
|
||||
|
||||
# ── Prod IAM токен (работает в DEV_MODE) ──────────────────────────────
|
||||
if [ -z "$TOKEN" ]; then
|
||||
TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWNjMjY4LTZjNmEtNzgxZS04NjEzLTRiZWQ0ZWM3Y2QyMCIsImV4cCI6MTc5NjExODA2NywiaWF0IjoxNzgwNTY2MDY3LCJqdGkiOiJjMzJlNWIwMy05YjBlLTRjNGEtYTY0MS1lOTE0YWQ4MWRiMDQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAzNzA5IiwiY29tcGFueV9pZCI6IjAxOWNjMjRhLTcyN2UtNzQwZi1iNDA3LWJlYzc5ZGFiNDE2MiIsImNvbXBhbnlfbmFtZSI6Im5hZWVsX3Rlc3QiLCJ0b2tlbl90eXBlIjoidGVjaCIsImlkcF91c3JfdWlkIjoiMDE5Y2MyNjgtNmM2YS03ODFlLTg2MTMtNGJlZDRlYzdjZDIwIiwibG9naW4iOiJ0YXpldGRpbm92bkBnbWFpbC5jb20iLCJmaXJzdG5hbWUiOiLQndCw0LjQu9GMIiwibWlkZGxlbmFtZSI6ItCk0LDRgNC40YLQvtCy0LjRhyIsImxhc3RuYW1lIjoi0KLQsNC30LXRgtC00LjQvdC-0LIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRkaW5vdm5AZ21haWwuY29tIn0.Pc0NJsauTQQApbPgDjFZd9phceMvfN4usaa8Rw9wGeMUvKR6EOAC6F_9in6zhcgK0zFsdbpsSzeGaNLoJNpadbshAWmGKVxprbMZbCuuxvDFzraIHTw0okgvh-4XPN8NhzqI0taTWjN9Wdl5hWBpHBQFpgRlf9u-jMwqmSQQ15wZHiOc_x1Xh_IKhtBGC4duYlvnzVzzWgRVvCNzs73dnPhCAKNn17epZujq0QHlzkkg2WeCo24iiiKrIpssW6YUiM9aKezwbYxGvZZOePX-RjcapBKFjNmpjI2UjEOMroQUtS7ei1cgU9VN5_QjmGKmSSXF2JBtcSGb52qAhYDwQQ"
|
||||
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 "ЕСТЬ ОШИБКИ"
|
||||
Reference in New Issue
Block a user