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:
2026-06-04 13:53:44 +03:00
parent 878e46287b
commit a8a07fe019
15 changed files with 1404 additions and 164 deletions
+99
View File
@@ -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 "ЕСТЬ ОШИБКИ"