tests: тест-скрипты v2 — полный сьют PASS=8 FAIL=0
- lib.sh: утилиты (json_field, wait_invoke, print_summary) - run_all.sh: запуск всех скриптов + финальная сводка - pre_cleanup.sh: очистка тест-namespace-ов перед прогоном - test_auth.sh: 14/14 PASS — авторизация, namespace детерминизм - test_functions_crud.sh: 29/29 PASS — CRUD, валидация, >1MB - test_ttl.sh: 10/10 PASS — TTL=15m/1h/2d, невалидные - test_isolation.sh: 11/11 PASS — изоляция пользователей - test_update.sh: 10/10 PASS — PUT /code v1→v2→v3 (livetest) - test_invoke.sh: 9/9 PASS — NodeJS/Python invoke (livetest) - test_stress.sh: 3/3 PASS — 8 пользователей параллельно (livetest) - test_linters.sh: 31/31 PASS — синтаксис 5 языков
This commit is contained in:
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib.sh — общие утилиты для тест-скриптов fission-console.
|
||||||
|
# Подключается через: source "$(dirname "$0")/lib.sh"
|
||||||
|
|
||||||
|
BASE="${BASE:-https://fission.kube5s.ru/console/api}"
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
pass() { PASS=$((PASS+1)); echo " PASS: $1"; }
|
||||||
|
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }
|
||||||
|
|
||||||
|
check_http() {
|
||||||
|
local got="$1" want="$2" label="$3"
|
||||||
|
[ "$got" = "$want" ] && pass "$label (HTTP $got)" || fail "$label — got $got, want $want"
|
||||||
|
}
|
||||||
|
|
||||||
|
# json_field PAYLOAD KEY — извлекает строковое значение ключа из JSON
|
||||||
|
json_field() {
|
||||||
|
local payload="$1" key="$2"
|
||||||
|
printf '%s' "$payload" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
key = sys.argv[1]
|
||||||
|
try:
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
v = d.get(key)
|
||||||
|
if v is None:
|
||||||
|
print('')
|
||||||
|
elif isinstance(v, (dict, list)):
|
||||||
|
print(json.dumps(v))
|
||||||
|
else:
|
||||||
|
print(v)
|
||||||
|
except Exception:
|
||||||
|
print('')
|
||||||
|
" "$key" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# json_escape — читает stdin, возвращает JSON-строку
|
||||||
|
json_escape() {
|
||||||
|
python3 -c 'import json, sys; print(json.dumps(sys.stdin.read()))'
|
||||||
|
}
|
||||||
|
|
||||||
|
# json_len PAYLOAD — длина JSON-массива
|
||||||
|
json_len() {
|
||||||
|
printf '%s' "$1" | python3 -c "import json,sys
|
||||||
|
try:
|
||||||
|
print(len(json.load(sys.stdin)))
|
||||||
|
except Exception:
|
||||||
|
print(0)" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# wait_invoke SUB FN EXPECTED [TRIES=12] [DELAY=5]
|
||||||
|
# Ждёт пока invoke вернёт status=200 и тело содержит EXPECTED.
|
||||||
|
# Возвращает 0 при успехе, 1 при таймауте.
|
||||||
|
wait_invoke() {
|
||||||
|
local sub="$1" fn="$2" expected="$3"
|
||||||
|
local tries="${4:-12}" delay="${5:-5}"
|
||||||
|
local try resp status raw err
|
||||||
|
for try in $(seq 1 "$tries"); do
|
||||||
|
resp=$(curl -s --max-time 50 -X POST "${BASE}/functions/${fn}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${sub}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
status=$(json_field "$resp" status)
|
||||||
|
raw=$(json_field "$resp" response_raw)
|
||||||
|
err=$(json_field "$resp" error)
|
||||||
|
echo " TRY=$try fn=$fn status=$status raw=$(printf '%s' "$raw" | head -c 80) err=$(printf '%s' "$err" | head -c 80)"
|
||||||
|
if [ "$status" = "200" ] && printf '%s' "$raw" | grep -qF "$expected"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[ "$try" -lt "$tries" ] && sleep "$delay"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# mk_run_id — возвращает уникальный 8-символьный hex ID
|
||||||
|
mk_run_id() {
|
||||||
|
(date +%s%N 2>/dev/null; od -An -N4 -tu4 /dev/urandom 2>/dev/null) \
|
||||||
|
| sha256sum | head -c 8
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup_cleanup CLEANUP_FN — регистрирует trap EXIT
|
||||||
|
setup_cleanup() {
|
||||||
|
trap "$1" EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
# print_summary — итог тестов
|
||||||
|
print_summary() {
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
printf " ИТОГ: PASS=%d FAIL=%d\n" "$PASS" "$FAIL"
|
||||||
|
echo "========================================"
|
||||||
|
[ "$FAIL" -eq 0 ]
|
||||||
|
}
|
||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# pre_cleanup.sh — удаляет ВСЕ тест-namespace-ы fission от прошлых прогонов.
|
||||||
|
# Паттерны: fission-<hex16> для namespace-ов созданных тест-субами вида
|
||||||
|
# crud-*/ttl-*/invoke-*/update-*/isolation-*/stress-*/alice-*/bob-*/
|
||||||
|
# linter-*@test.local
|
||||||
|
# Вызывается из run_all.sh с KUBECTL_DIRECT=1.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
if [ "${KUBECTL_DIRECT:-0}" != "1" ]; then
|
||||||
|
echo "pre_cleanup.sh: нужен KUBECTL_DIRECT=1 (должен запускаться на VM)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLEANUP="${BASH_SOURCE%/*}/cleanup_test_namespaces.sh"
|
||||||
|
if [ ! -x "$CLEANUP" ]; then
|
||||||
|
echo "pre_cleanup.sh: cleanup_test_namespaces.sh не найден" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Список тест-субов создаёт namespace с именем fission-<hex16>.
|
||||||
|
# Получить все fission-namespace-ы кроме системного fission:
|
||||||
|
NS_LIST=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null | \
|
||||||
|
grep -E '^fission-[0-9a-f]{16}$' || true)
|
||||||
|
|
||||||
|
if [ -z "$NS_LIST" ]; then
|
||||||
|
echo "pre_cleanup.sh: тест-namespace-ы не найдены, пропускаем."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
COUNT=$(echo "$NS_LIST" | wc -l)
|
||||||
|
echo "pre_cleanup.sh: найдено $COUNT тест-namespace-ов, удаляем..."
|
||||||
|
|
||||||
|
# cleanup_test_namespaces.sh принимает email-субы, а не namespace-ы.
|
||||||
|
# Но мы можем вызвать kubectl delete напрямую для namespace-ов.
|
||||||
|
echo "$NS_LIST" | xargs -r kubectl delete namespace --ignore-not-found 2>&1 | \
|
||||||
|
grep -v "^$" || true
|
||||||
|
|
||||||
|
echo "pre_cleanup.sh: очистка завершена."
|
||||||
Executable
+101
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# run_all.sh — запускает все тест-скрипты по очереди, выводит итог.
|
||||||
|
# Каждый скрипт независим и имеет собственный cleanup.
|
||||||
|
# При падении одного скрипта остальные продолжают выполняться.
|
||||||
|
#
|
||||||
|
# Запуск: bash run_all.sh
|
||||||
|
# Запуск одного: bash run_all.sh test_auth.sh
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_FAIL=0
|
||||||
|
FAILED_SCRIPTS=()
|
||||||
|
|
||||||
|
run_script() {
|
||||||
|
local script="$1"
|
||||||
|
local name
|
||||||
|
name=$(basename "$script")
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " ЗАПУСК: $name"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
local exit_code=0
|
||||||
|
bash "$script" || exit_code=$?
|
||||||
|
|
||||||
|
if [ "$exit_code" -eq 0 ]; then
|
||||||
|
echo -e "${GREEN} ✓ $name — PASSED${NC}"
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS+1))
|
||||||
|
else
|
||||||
|
echo -e "${RED} ✗ $name — FAILED (exit $exit_code)${NC}"
|
||||||
|
TOTAL_FAIL=$((TOTAL_FAIL+1))
|
||||||
|
FAILED_SCRIPTS+=("$name")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Список скриптов в порядке выполнения
|
||||||
|
# (быстрые сначала, invoke/stress — последними)
|
||||||
|
SCRIPTS=(
|
||||||
|
"${ROOT_DIR}/test_auth.sh"
|
||||||
|
"${ROOT_DIR}/test_functions_crud.sh"
|
||||||
|
"${ROOT_DIR}/test_ttl.sh"
|
||||||
|
"${ROOT_DIR}/test_isolation.sh"
|
||||||
|
"${ROOT_DIR}/test_update.sh"
|
||||||
|
"${ROOT_DIR}/test_invoke.sh"
|
||||||
|
"${ROOT_DIR}/test_linters.sh"
|
||||||
|
"${ROOT_DIR}/test_stress.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Если передан аргумент — запускаем только его
|
||||||
|
if [ "$#" -ge 1 ]; then
|
||||||
|
SCRIPTS=("${ROOT_DIR}/$1")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Предварительная очистка тест-namespace-ов от прошлых прогонов.
|
||||||
|
# Запускается только если cleanup_test_namespaces.sh умеет wildcard.
|
||||||
|
PRE_CLEANUP="${ROOT_DIR}/pre_cleanup.sh"
|
||||||
|
if [ -x "$PRE_CLEANUP" ]; then
|
||||||
|
echo ""
|
||||||
|
echo ">>> Предварительная очистка тест-namespace-ов..."
|
||||||
|
KUBECTL_DIRECT=1 bash "$PRE_CLEANUP" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════╗"
|
||||||
|
echo "║ fission-console — полный тест-сьют ║"
|
||||||
|
echo "╚══════════════════════════════════════╝"
|
||||||
|
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
|
||||||
|
for script in "${SCRIPTS[@]}"; do
|
||||||
|
if [ ! -f "$script" ]; then
|
||||||
|
echo -e "${YELLOW} SKIP: $script не найден${NC}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
run_script "$script"
|
||||||
|
done
|
||||||
|
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
ELAPSED=$((END_TIME - START_TIME))
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════╗"
|
||||||
|
printf "║ Скриптов: PASS=%-3d FAIL=%-3d %3ds ║\n" "$TOTAL_PASS" "$TOTAL_FAIL" "$ELAPSED"
|
||||||
|
echo "╚══════════════════════════════════════╝"
|
||||||
|
|
||||||
|
if [ "${#FAILED_SCRIPTS[@]}" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Упавшие скрипты:"
|
||||||
|
for s in "${FAILED_SCRIPTS[@]}"; do
|
||||||
|
echo -e " ${RED}✗ $s${NC}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$TOTAL_FAIL" -eq 0 ]
|
||||||
Executable
+107
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_auth.sh — тестирует аутентификацию:
|
||||||
|
# - POST /console/api/auth (login endpoint)
|
||||||
|
# - X-Auth-Token header (authMiddleware)
|
||||||
|
# - Authorization: Bearer header
|
||||||
|
# - DemoAuth: любая строка ≥6 символов
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
SUB="auth-test-${RUN_ID}@test.local"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -x "$NS_CLEANUP_SCRIPT" ]; then
|
||||||
|
KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "$SUB" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_auth.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# ── POST /auth ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> POST /auth: пустой токен → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/auth" \
|
||||||
|
-H "Content-Type: application/json" -d '{"token":""}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "POST /auth пустой token → 400"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> POST /auth: токен < 6 символов → 401"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/auth" \
|
||||||
|
-H "Content-Type: application/json" -d '{"token":"ab"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "401" "POST /auth токен 2 символа → 401"
|
||||||
|
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/auth" \
|
||||||
|
-H "Content-Type: application/json" -d '{"token":"abcde"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "401" "POST /auth токен 5 символов → 401"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> POST /auth: токен ≥6 символов → 200"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/auth" \
|
||||||
|
-H "Content-Type: application/json" -d "{\"token\":\"${SUB}\"}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "POST /auth валидный токен → 200"
|
||||||
|
NS=$(json_field "$BODY" namespace)
|
||||||
|
printf '%s' "$NS" | grep -q '^fission-' && pass "POST /auth namespace=fission-..." || fail "POST /auth namespace format: $NS"
|
||||||
|
OK=$(json_field "$BODY" ok)
|
||||||
|
[ "$OK" = "True" ] || [ "$OK" = "true" ] && pass "POST /auth ok=true" || fail "POST /auth ok=$OK"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> POST /auth: невалидный JSON → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/auth" \
|
||||||
|
-H "Content-Type: application/json" -d 'not-json')
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
[ "$CODE" = "400" ] && pass "POST /auth невалидный JSON → 400" || fail "POST /auth невалидный JSON → $CODE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> POST /auth: GET метод → 405"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/auth")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
[ "$CODE" = "405" ] && pass "GET /auth → 405" || fail "GET /auth → $CODE"
|
||||||
|
|
||||||
|
# ── X-Auth-Token header ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> X-Auth-Token: отсутствует → 401"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "401" "GET /functions без токена → 401"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> X-Auth-Token: токен < 6 символов → 401"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" -H "X-Auth-Token: ab")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "401" "GET /functions токен 2 символа → 401"
|
||||||
|
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" -H "X-Auth-Token: abcde")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "401" "GET /functions токен 5 символов → 401"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> X-Auth-Token: токен ≥6 символов → 200"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" -H "X-Auth-Token: ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "200" "GET /functions валидный X-Auth-Token → 200"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Authorization: Bearer → 200"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" \
|
||||||
|
-H "Authorization: Bearer ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "200" "GET /functions Authorization: Bearer → 200"
|
||||||
|
|
||||||
|
# ── Namespace детерминирован ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Namespace детерминирован (одинаковый токен → одинаковый NS)"
|
||||||
|
R1=$(curl -s -X POST "${BASE}/auth" -H "Content-Type: application/json" -d "{\"token\":\"${SUB}\"}")
|
||||||
|
R2=$(curl -s -X POST "${BASE}/auth" -H "Content-Type: application/json" -d "{\"token\":\"${SUB}\"}")
|
||||||
|
NS1=$(json_field "$R1" namespace)
|
||||||
|
NS2=$(json_field "$R2" namespace)
|
||||||
|
[ "$NS1" = "$NS2" ] && [ -n "$NS1" ] && pass "Namespace детерминирован: $NS1" || fail "Namespace не детерминирован: '$NS1' vs '$NS2'"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Executable
+207
@@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_functions_crud.sh — тестирует CRUD операции с функциями:
|
||||||
|
# POST /functions, GET /functions, GET /functions/{name},
|
||||||
|
# DELETE /functions/{name}, валидация имён, дубликаты, размер кода.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
SUB="crud-${RUN_ID}@test.local"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -x "$NS_CLEANUP_SCRIPT" ]; then
|
||||||
|
KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "$SUB" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_functions_crud.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
JS_CODE='module.exports = async function(ctx) { return { status: 200, body: "crud-ok" }; }'
|
||||||
|
|
||||||
|
# ── CREATE ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: базовый nodejs → 201"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"crud-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "201" "CREATE nodejs → 201"
|
||||||
|
FN_NAME=$(json_field "$BODY" name)
|
||||||
|
FN_ROUTE=$(json_field "$BODY" route)
|
||||||
|
FN_PKG=$(json_field "$BODY" package)
|
||||||
|
[ "$FN_NAME" = "crud-fn-${RUN_ID}" ] && pass "CREATE: name в ответе" || fail "CREATE: name='$FN_NAME'"
|
||||||
|
printf '%s' "$FN_ROUTE" | grep -q '/' && pass "CREATE: route непустой" || fail "CREATE: route='$FN_ROUTE'"
|
||||||
|
[ -n "$FN_PKG" ] && pass "CREATE: package непустой" || fail "CREATE: package пустой"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: дубликат → 409"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"crud-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "409" "CREATE дубликат → 409"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: без name → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"language":"nodejs","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE без name → 400"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: без language → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"no-lang","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE без language → 400"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: без code → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"no-code","language":"nodejs"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE без code → 400"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: невалидный язык → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"bad-lang","language":"cobol","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE язык=cobol → 400"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: валидация имени"
|
||||||
|
|
||||||
|
# Заглавная буква
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"BadName","language":"nodejs","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE имя с заглавной → 400"
|
||||||
|
|
||||||
|
# Начинается с дефиса
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"-bad","language":"nodejs","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE имя с ведущим дефисом → 400"
|
||||||
|
|
||||||
|
# Заканчивается дефисом
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"bad-","language":"nodejs","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE имя с завершающим дефисом → 400"
|
||||||
|
|
||||||
|
# Пробел в имени
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"bad name","language":"nodejs","code":"x"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE имя с пробелом → 400"
|
||||||
|
|
||||||
|
# 58 символов — должно быть 400 (лимит 57)
|
||||||
|
NAME58=$(python3 -c "print('a'*58)")
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${NAME58}\",\"language\":\"nodejs\",\"code\":\"x\"}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "CREATE имя 58 символов → 400"
|
||||||
|
|
||||||
|
# 57 символов — должно быть 201
|
||||||
|
NAME57=$(python3 -c "print('a'*57)")
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${NAME57}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "201" "CREATE имя 57 символов → 201"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: код > 1MB → 400 или 413"
|
||||||
|
# Используем pipe чтобы избежать Argument list too long
|
||||||
|
R=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
payload = json.dumps({'name':'big-code','language':'nodejs','code':'x'*(1024*1024+1)})
|
||||||
|
print(payload)
|
||||||
|
" | curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
--data-binary @- --max-time 30)
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
( [ "$CODE" = "400" ] || [ "$CODE" = "413" ] || [ "$CODE" = "000" ] ) \
|
||||||
|
&& pass "CREATE код > 1MB → отклонён ($CODE)" \
|
||||||
|
|| fail "CREATE код > 1MB → got $CODE"
|
||||||
|
|
||||||
|
# ── LIST ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> LIST: GET /functions → 200, массив"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" -H "X-Auth-Token: ${SUB}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "LIST GET /functions → 200"
|
||||||
|
COUNT=$(json_len "$BODY")
|
||||||
|
[ "${COUNT:-0}" -ge 1 ] && pass "LIST: содержит ≥1 функцию (создали выше)" || fail "LIST: пустой массив count=$COUNT"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> LIST: новый пользователь → пустой массив"
|
||||||
|
EMPTY_USER="empty-${RUN_ID}@test.local"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions" -H "X-Auth-Token: ${EMPTY_USER}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "LIST новый пользователь → 200"
|
||||||
|
COUNT=$(json_len "$BODY")
|
||||||
|
[ "${COUNT:-99}" -eq 0 ] && pass "LIST новый пользователь → []" || fail "LIST новый пользователь → count=$COUNT"
|
||||||
|
|
||||||
|
# ── GET ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> GET: /functions/{name} → 200"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions/crud-fn-${RUN_ID}" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "GET /functions/crud-fn-${RUN_ID} → 200"
|
||||||
|
GOT_NAME=$(json_field "$BODY" name)
|
||||||
|
[ "$GOT_NAME" = "crud-fn-${RUN_ID}" ] && pass "GET: name совпадает" || fail "GET: name='$GOT_NAME'"
|
||||||
|
GOT_ENV=$(json_field "$BODY" environment)
|
||||||
|
[ -n "$GOT_ENV" ] && pass "GET: environment непустой ($GOT_ENV)" || fail "GET: environment пустой"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> GET: несуществующая функция → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions/does-not-exist-xyz" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "GET несуществующая → 404"
|
||||||
|
|
||||||
|
# ── DELETE ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> DELETE: /functions/{name} → 200"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE}/functions/crud-fn-${RUN_ID}" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "DELETE crud-fn → 200"
|
||||||
|
DELETED=$(json_field "$BODY" deleted)
|
||||||
|
[ "$DELETED" = "True" ] || [ "$DELETED" = "true" ] && pass "DELETE: deleted=true" || fail "DELETE: deleted='$DELETED'"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> DELETE: повторное удаление → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE}/functions/crud-fn-${RUN_ID}" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "DELETE повторное → 404"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> GET: после DELETE → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions/crud-fn-${RUN_ID}" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "GET после DELETE → 404"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> DELETE: несуществующая функция → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE}/functions/does-not-exist-xyz" \
|
||||||
|
-H "X-Auth-Token: ${SUB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "DELETE несуществующая → 404"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Executable
+90
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_invoke.sh — тестирует вызов функций:
|
||||||
|
# NodeJS и Python: create → wait cold start → invoke → проверка тела.
|
||||||
|
# Invoke несуществующей функции → 404.
|
||||||
|
# ВАЖНО: использует livetest@test.local — прогретый namespace в kube кластере.
|
||||||
|
# Для новых namespace fission router требует перезапуска (watchedNamespaces).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
# Используем прогретый namespace — fission router его знает.
|
||||||
|
SUB="livetest@test.local"
|
||||||
|
FN_NODE="node-fn-${RUN_ID}"
|
||||||
|
FN_PY="py-fn-${RUN_ID}"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
# Удаляем только конкретные функции через API, не весь namespace.
|
||||||
|
for fn in "$FN_NODE" "$FN_PY"; do
|
||||||
|
curl -s -X DELETE "${BASE}/functions/${fn}" -H "X-Auth-Token: ${SUB}" > /dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_invoke.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# ── Invoke несуществующей ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> INVOKE несуществующей функции → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions/no-such-fn-xyz-${RUN_ID}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "INVOKE несуществующей → 404"
|
||||||
|
|
||||||
|
# ── NodeJS ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> NodeJS: create + invoke"
|
||||||
|
JS_CODE='module.exports = async function(ctx) { return { status: 200, body: "node-invoke-ok" }; }'
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${FN_NODE}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "201" "NodeJS CREATE → 201"
|
||||||
|
|
||||||
|
if wait_invoke "$SUB" "$FN_NODE" "node-invoke-ok" 12 5; then
|
||||||
|
pass "NodeJS invoke → 200, тело содержит node-invoke-ok"
|
||||||
|
else
|
||||||
|
fail "NodeJS invoke не стабилизировался за 60s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Второй вызов — проверяем что работает warm path
|
||||||
|
R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_NODE}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
STATUS=$(json_field "$R" status)
|
||||||
|
[ "$STATUS" = "200" ] && pass "NodeJS второй invoke → 200 (warm)" || fail "NodeJS второй invoke → status=$STATUS"
|
||||||
|
|
||||||
|
# ── Python ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Python: create + invoke"
|
||||||
|
PY_CODE=$(printf 'def main():\n return "py-invoke-ok", 200' | json_escape)
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${FN_PY}\",\"language\":\"python\",\"code\":${PY_CODE}}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "201" "Python CREATE → 201"
|
||||||
|
|
||||||
|
if wait_invoke "$SUB" "$FN_PY" "py-invoke-ok" 15 5; then
|
||||||
|
pass "Python invoke → 200, тело содержит py-invoke-ok"
|
||||||
|
else
|
||||||
|
fail "Python invoke не стабилизировался за 75s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Invoke возвращает корректные поля ─────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Invoke response fields"
|
||||||
|
R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_NODE}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
STATUS=$(json_field "$R" status)
|
||||||
|
LATENCY=$(json_field "$R" latency_ms)
|
||||||
|
RAW=$(json_field "$R" response_raw)
|
||||||
|
[ "$STATUS" = "200" ] && pass "invoke.status = 200" || fail "invoke.status = $STATUS"
|
||||||
|
printf '%s' "$LATENCY" | grep -qE '^[0-9]+$' && pass "invoke.latency_ms = $LATENCY ms" || fail "invoke.latency_ms не число: $LATENCY"
|
||||||
|
[ -n "$RAW" ] && pass "invoke.response_raw непустой" || fail "invoke.response_raw пустой"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Executable
+162
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_isolation.sh — тестирует namespace изоляцию:
|
||||||
|
# Пользователь A не видит и не может изменить функции пользователя B.
|
||||||
|
# LIST каждого пользователя возвращает только его функции.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
ALICE="alice-${RUN_ID}@test.local"
|
||||||
|
BOB="bob-${RUN_ID}@test.local"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -x "$NS_CLEANUP_SCRIPT" ]; then
|
||||||
|
KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "$ALICE" "$BOB" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_isolation.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
JS_CODE='module.exports = async function() { return { status: 200, body: "ok" }; }'
|
||||||
|
|
||||||
|
# ── Alice создаёт функцию ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Alice создаёт функцию"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${ALICE}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"alice-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "201" "Alice CREATE alice-fn → 201"
|
||||||
|
|
||||||
|
# ── Bob не может получить функцию Alice ──────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Bob GET alice-fn → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions/alice-fn" \
|
||||||
|
-H "X-Auth-Token: ${BOB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "Bob GET alice-fn → 404"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Bob DELETE alice-fn → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X DELETE "${BASE}/functions/alice-fn" \
|
||||||
|
-H "X-Auth-Token: ${BOB}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "Bob DELETE alice-fn → 404"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Bob PUT alice-fn/code → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X PUT "${BASE}/functions/alice-fn/code" \
|
||||||
|
-H "X-Auth-Token: ${BOB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"code":"module.exports = async () => ({ body: \"hacked\" });"}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "Bob PUT alice-fn/code → 404"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Bob INVOKE alice-fn → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions/alice-fn/invoke" \
|
||||||
|
-H "X-Auth-Token: ${BOB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "Bob INVOKE alice-fn → 404"
|
||||||
|
|
||||||
|
# ── Alice убеждается что её функция цела ────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Alice GET alice-fn → 200 (не повреждена)"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" "${BASE}/functions/alice-fn" \
|
||||||
|
-H "X-Auth-Token: ${ALICE}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "200" "Alice GET alice-fn → 200"
|
||||||
|
|
||||||
|
# ── LIST изоляция ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> LIST изоляция: Alice и Bob видят только свои функции"
|
||||||
|
|
||||||
|
# Bob создаёт свою функцию
|
||||||
|
curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${BOB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"bob-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}" > /dev/null
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
ALICE_LIST=$(curl -s "${BASE}/functions" -H "X-Auth-Token: ${ALICE}")
|
||||||
|
BOB_LIST=$(curl -s "${BASE}/functions" -H "X-Auth-Token: ${BOB}")
|
||||||
|
|
||||||
|
# Alice видит alice-fn но не bob-fn
|
||||||
|
ALICE_COUNT=$(json_len "$ALICE_LIST")
|
||||||
|
ALICE_HAS_OWN=$(printf '%s' "$ALICE_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == 'alice-fn'))
|
||||||
|
except:
|
||||||
|
print(0)" 2>/dev/null)
|
||||||
|
ALICE_HAS_BOB=$(printf '%s' "$ALICE_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == 'bob-fn'))
|
||||||
|
except:
|
||||||
|
print(0)" 2>/dev/null)
|
||||||
|
|
||||||
|
[ "${ALICE_HAS_OWN:-0}" -ge 1 ] && pass "Alice LIST содержит alice-fn" || fail "Alice LIST не содержит alice-fn (count=$ALICE_COUNT)"
|
||||||
|
[ "${ALICE_HAS_BOB:-0}" -eq 0 ] && pass "Alice LIST не содержит bob-fn" || fail "Alice LIST содержит bob-fn — УТЕЧКА ДАННЫХ!"
|
||||||
|
|
||||||
|
# Bob видит bob-fn но не alice-fn
|
||||||
|
BOB_HAS_OWN=$(printf '%s' "$BOB_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == 'bob-fn'))
|
||||||
|
except:
|
||||||
|
print(0)" 2>/dev/null)
|
||||||
|
BOB_HAS_ALICE=$(printf '%s' "$BOB_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == 'alice-fn'))
|
||||||
|
except:
|
||||||
|
print(0)" 2>/dev/null)
|
||||||
|
|
||||||
|
[ "${BOB_HAS_OWN:-0}" -ge 1 ] && pass "Bob LIST содержит bob-fn" || fail "Bob LIST не содержит bob-fn"
|
||||||
|
[ "${BOB_HAS_ALICE:-0}" -eq 0 ] && pass "Bob LIST не содержит alice-fn" || fail "Bob LIST содержит alice-fn — УТЕЧКА ДАННЫХ!"
|
||||||
|
|
||||||
|
# ── Route изоляция ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> Route изоляция: одинаковое имя → разные routes"
|
||||||
|
ROUTE_A=$(printf '%s' "$ALICE_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
for f in fns:
|
||||||
|
if f.get('metadata',{}).get('name','') == 'alice-fn':
|
||||||
|
print(f.get('spec',{}).get('relativeurl',''))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
print('')" 2>/dev/null)
|
||||||
|
|
||||||
|
ROUTE_B=$(printf '%s' "$BOB_LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
for f in fns:
|
||||||
|
if f.get('metadata',{}).get('name','') == 'bob-fn':
|
||||||
|
print(f.get('spec',{}).get('relativeurl',''))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
print('')" 2>/dev/null)
|
||||||
|
|
||||||
|
# Проверяем через GET (возвращает route)
|
||||||
|
ALICE_GET=$(curl -s "${BASE}/functions/alice-fn" -H "X-Auth-Token: ${ALICE}")
|
||||||
|
BOB_GET=$(curl -s "${BASE}/functions/bob-fn" -H "X-Auth-Token: ${BOB}")
|
||||||
|
ROUTE_A=$(json_field "$ALICE_GET" route)
|
||||||
|
ROUTE_B=$(json_field "$BOB_GET" route)
|
||||||
|
[ "$ROUTE_A" != "$ROUTE_B" ] && [ -n "$ROUTE_A" ] && [ -n "$ROUTE_B" ] \
|
||||||
|
&& pass "Route изоляция: $ROUTE_A ≠ $ROUTE_B" \
|
||||||
|
|| fail "Route коллизия: alice=$ROUTE_A bob=$ROUTE_B"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Regular → Executable
Executable
+158
@@ -0,0 +1,158 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_stress.sh — стресс-тест параллельного использования:
|
||||||
|
# COUNT пользователей создают функции параллельно
|
||||||
|
# LIST изоляция: каждый видит только свои функции
|
||||||
|
# Invoke стресс: INVOKE_COUNT параллельных invoke для livetest@test.local (watched namespace)
|
||||||
|
# Cleanup: удаляем namespace-ы тест-пользователей, функции livetest — через API DELETE.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
COUNT="${1:-8}"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
INVOKE_SUB="livetest@test.local"
|
||||||
|
INVOKE_COUNT=8
|
||||||
|
|
||||||
|
declare -a SUBS=()
|
||||||
|
for i in $(seq 1 "$COUNT"); do
|
||||||
|
SUBS+=("stress-${RUN_ID}-u${i}@test.local")
|
||||||
|
done
|
||||||
|
|
||||||
|
declare -a INVOKE_FNS=()
|
||||||
|
for i in $(seq 1 "$INVOKE_COUNT"); do
|
||||||
|
INVOKE_FNS+=("sinv-${RUN_ID}-${i}")
|
||||||
|
done
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$TMP" 2>/dev/null || true
|
||||||
|
if [ -x "$NS_CLEANUP_SCRIPT" ] && [ "${#SUBS[@]}" -gt 0 ]; then
|
||||||
|
KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "${SUBS[@]}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
for fn in "${INVOKE_FNS[@]}"; do
|
||||||
|
curl -s -X DELETE "${BASE}/functions/${fn}" -H "X-Auth-Token: ${INVOKE_SUB}" > /dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_stress.sh run=$RUN_ID users=$COUNT invoke_parallel=$INVOKE_COUNT"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
JS_CODE='module.exports = async function() { return { status: 200, body: "stress-ok" }; }'
|
||||||
|
|
||||||
|
# ── Шаг 1: параллельное создание ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> [1/3] $COUNT пользователей создают функции параллельно..."
|
||||||
|
|
||||||
|
for i in $(seq 1 "$COUNT"); do
|
||||||
|
(
|
||||||
|
SUB="${SUBS[$((i-1))]}"
|
||||||
|
FN="stress-fn-${RUN_ID}"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${FN}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}" \
|
||||||
|
2>/dev/null)
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
echo "$i:$CODE" > "${TMP}/create-$i"
|
||||||
|
) &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
|
||||||
|
CREATE_OK=0
|
||||||
|
CREATE_FAIL=0
|
||||||
|
for i in $(seq 1 "$COUNT"); do
|
||||||
|
CODE=$(cut -d: -f2 "${TMP}/create-$i" 2>/dev/null || echo "000")
|
||||||
|
if [ "$CODE" = "201" ] || [ "$CODE" = "200" ]; then
|
||||||
|
CREATE_OK=$((CREATE_OK+1))
|
||||||
|
else
|
||||||
|
CREATE_FAIL=$((CREATE_FAIL+1))
|
||||||
|
echo " user$i: CREATE HTTP $CODE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$CREATE_FAIL" -eq 0 ] \
|
||||||
|
&& pass "Параллельный CREATE: все $COUNT пользователей → 201" \
|
||||||
|
|| fail "Параллельный CREATE: $CREATE_FAIL/$COUNT упали"
|
||||||
|
|
||||||
|
# ── Шаг 2: изоляция LIST ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> [2/3] Изоляция: каждый видит только свою функцию"
|
||||||
|
|
||||||
|
ISO_OK=0
|
||||||
|
for i in $(seq 1 "$COUNT"); do
|
||||||
|
SUB="${SUBS[$((i-1))]}"
|
||||||
|
FN="stress-fn-${RUN_ID}"
|
||||||
|
LIST=$(curl -s --max-time 20 "${BASE}/functions" -H "X-Auth-Token: ${SUB}")
|
||||||
|
OWN_COUNT=$(printf '%s' "$LIST" | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
fns = json.load(sys.stdin)
|
||||||
|
print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == '${FN}'))
|
||||||
|
except:
|
||||||
|
print(0)" 2>/dev/null)
|
||||||
|
TOTAL_COUNT=$(json_len "$LIST")
|
||||||
|
if [ "${OWN_COUNT:-0}" -eq 1 ] && [ "${TOTAL_COUNT:-0}" -eq 1 ]; then
|
||||||
|
ISO_OK=$((ISO_OK+1))
|
||||||
|
else
|
||||||
|
echo " user$i: own=$OWN_COUNT total=$TOTAL_COUNT"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$ISO_OK" -eq "$COUNT" ] \
|
||||||
|
&& pass "Изоляция: все $COUNT пользователей видят только свои функции" \
|
||||||
|
|| fail "Изоляция: только $ISO_OK/$COUNT OK"
|
||||||
|
|
||||||
|
# ── Шаг 3: параллельный invoke (livetest — watched namespace) ─────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> [3/3] $INVOKE_COUNT параллельных invoke для livetest@test.local"
|
||||||
|
|
||||||
|
INV_JS='module.exports = async function() { return { status: 200, body: "sinv-ok" }; }'
|
||||||
|
for i in $(seq 1 "$INVOKE_COUNT"); do
|
||||||
|
FN_I="${INVOKE_FNS[$((i-1))]}"
|
||||||
|
curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${FN_I}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$INV_JS" | json_escape)}" > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Ждём прогрева $INVOKE_COUNT функций..."
|
||||||
|
for i in $(seq 1 "$INVOKE_COUNT"); do
|
||||||
|
FN_I="${INVOKE_FNS[$((i-1))]}"
|
||||||
|
for try in $(seq 1 12); do
|
||||||
|
R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_I}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
ST=$(json_field "$R" status)
|
||||||
|
[ "$ST" = "200" ] && break
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in $(seq 1 "$INVOKE_COUNT"); do
|
||||||
|
(
|
||||||
|
FN_I="${INVOKE_FNS[$((i-1))]}"
|
||||||
|
R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_I}/invoke" \
|
||||||
|
-H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" -d '{}')
|
||||||
|
ST=$(json_field "$R" status)
|
||||||
|
echo "$i:$ST" > "${TMP}/sinvoke-$i"
|
||||||
|
) &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
|
||||||
|
SINV_OK=0
|
||||||
|
SINV_FAIL=0
|
||||||
|
for i in $(seq 1 "$INVOKE_COUNT"); do
|
||||||
|
CODE=$(cut -d: -f2 "${TMP}/sinvoke-$i" 2>/dev/null || echo "MISS")
|
||||||
|
[ "$CODE" = "200" ] && SINV_OK=$((SINV_OK+1)) || SINV_FAIL=$((SINV_FAIL+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$SINV_FAIL" -le 1 ] \
|
||||||
|
&& pass "Параллельный INVOKE (livetest): $SINV_OK/$INVOKE_COUNT OK" \
|
||||||
|
|| fail "Параллельный INVOKE (livetest): $SINV_FAIL/$INVOKE_COUNT упали"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_ttl.sh — тестирует TTL функций:
|
||||||
|
# CREATE с TTL → expires_at присутствует в ответе
|
||||||
|
# CREATE без TTL → expires_at отсутствует
|
||||||
|
# Невалидный TTL → 400 до создания объектов в k8s
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
SUB="ttl-${RUN_ID}@test.local"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -x "$NS_CLEANUP_SCRIPT" ]; then
|
||||||
|
KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "$SUB" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_ttl.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
JS_CODE='module.exports = async function() { return { status: 200, body: "ok" }; }'
|
||||||
|
|
||||||
|
# ── Без TTL ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE без TTL → expires_at отсутствует"
|
||||||
|
R=$(curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"nttl-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}")
|
||||||
|
CODE_HTTP=$(printf '%s' "$R" | python3 -c "import sys; lines=sys.stdin.read().split('\n'); print(lines[-1] if lines else '')" 2>/dev/null || echo "?")
|
||||||
|
EXPIRES=$(json_field "$R" expires_at)
|
||||||
|
[ -z "$EXPIRES" ] && pass "CREATE без TTL: expires_at отсутствует" || fail "CREATE без TTL: expires_at='$EXPIRES'"
|
||||||
|
|
||||||
|
# ── С TTL=15m ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE с TTL=15m → expires_at присутствует"
|
||||||
|
R=$(curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"ttl15m-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape),\"ttl\":\"15m\"}")
|
||||||
|
EXPIRES=$(json_field "$R" expires_at)
|
||||||
|
[ -n "$EXPIRES" ] && pass "CREATE TTL=15m: expires_at=$EXPIRES" || fail "CREATE TTL=15m: expires_at пустой"
|
||||||
|
# expires_at должен быть RFC3339
|
||||||
|
printf '%s' "$EXPIRES" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}T' && pass "expires_at формат RFC3339" || fail "expires_at не RFC3339: $EXPIRES"
|
||||||
|
|
||||||
|
# ── С TTL=1h ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE с TTL=1h → expires_at присутствует"
|
||||||
|
R=$(curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"ttl1h-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape),\"ttl\":\"1h\"}")
|
||||||
|
EXPIRES=$(json_field "$R" expires_at)
|
||||||
|
[ -n "$EXPIRES" ] && pass "CREATE TTL=1h: expires_at=$EXPIRES" || fail "CREATE TTL=1h: expires_at пустой"
|
||||||
|
|
||||||
|
# ── С TTL=2d ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE с TTL=2d → expires_at присутствует"
|
||||||
|
R=$(curl -s -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"ttl2d-fn\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape),\"ttl\":\"2d\"}")
|
||||||
|
EXPIRES=$(json_field "$R" expires_at)
|
||||||
|
[ -n "$EXPIRES" ] && pass "CREATE TTL=2d: expires_at=$EXPIRES" || fail "CREATE TTL=2d: expires_at пустой"
|
||||||
|
|
||||||
|
# ── Невалидный TTL ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE TTL невалидный → 400 (без создания объектов)"
|
||||||
|
for badttl in "99z" "abc" "0d" "-1h" ""; do
|
||||||
|
if [ -z "$badttl" ]; then continue; fi
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"badttl-fn\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"${badttl}\"}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
[ "$CODE" = "400" ] && pass "TTL='$badttl' → 400" || fail "TTL='$badttl' → got $CODE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── expires_at в LIST response ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> LIST: функция с TTL видна в списке"
|
||||||
|
LIST=$(curl -s "${BASE}/functions" -H "X-Auth-Token: ${SUB}")
|
||||||
|
COUNT=$(json_len "$LIST")
|
||||||
|
[ "${COUNT:-0}" -ge 3 ] && pass "LIST: видно ≥3 функции (2 с TTL, 1 без)" || fail "LIST: count=$COUNT"
|
||||||
|
|
||||||
|
print_summary
|
||||||
Executable
+105
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# test_update.sh — тестирует обновление кода функции:
|
||||||
|
# PUT /functions/{name}/code → invoke возвращает новый код
|
||||||
|
# PUT несуществующей → 404
|
||||||
|
# PUT с пустым кодом → 400
|
||||||
|
# ВАЖНО: использует livetest@test.local — прогретый namespace в kube кластере.
|
||||||
|
# Для новых namespace fission router требует перезапуска (watchedNamespaces).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
source "${ROOT_DIR}/lib.sh"
|
||||||
|
|
||||||
|
RUN_ID=$(mk_run_id)
|
||||||
|
SUB="livetest@test.local"
|
||||||
|
FN="upd-fn-${RUN_ID}"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
curl -s -X DELETE "${BASE}/functions/${FN}" -H "X-Auth-Token: ${SUB}" > /dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " test_update.sh run=$RUN_ID"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# ── Создаём функцию с начальным кодом ────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> CREATE: версия v1"
|
||||||
|
CODE_V1='module.exports = async function() { return { status: 200, body: "version-one" }; }'
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\":\"${FN}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$CODE_V1" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "201" "CREATE v1 → 201"
|
||||||
|
|
||||||
|
echo "Ждём прогрева..."
|
||||||
|
if wait_invoke "$SUB" "$FN" "version-one" 12 5; then
|
||||||
|
pass "Invoke v1 → version-one"
|
||||||
|
else
|
||||||
|
fail "Invoke v1 не стабилизировался"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── PUT /code → 200 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> PUT /code: версия v2"
|
||||||
|
CODE_V2='module.exports = async function() { return { status: 200, body: "version-two" }; }'
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X PUT "${BASE}/functions/${FN}/code" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"code\":$(printf '%s' "$CODE_V2" | json_escape)}")
|
||||||
|
CODE=$(echo "$R" | tail -1)
|
||||||
|
BODY=$(echo "$R" | sed '$d')
|
||||||
|
check_http "$CODE" "200" "PUT /code v2 → 200"
|
||||||
|
UPDATED=$(json_field "$BODY" updated)
|
||||||
|
[ "$UPDATED" = "True" ] || [ "$UPDATED" = "true" ] && pass "PUT /code: updated=true" || fail "PUT /code: updated='$UPDATED'"
|
||||||
|
NEW_PKG=$(json_field "$BODY" package)
|
||||||
|
[ -n "$NEW_PKG" ] && pass "PUT /code: новый package=$NEW_PKG" || fail "PUT /code: package пустой"
|
||||||
|
|
||||||
|
echo "Ждём пока executor подхватит новый код..."
|
||||||
|
if wait_invoke "$SUB" "$FN" "version-two" 15 5; then
|
||||||
|
pass "Invoke после update → version-two"
|
||||||
|
else
|
||||||
|
fail "Invoke после update не вернул version-two"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── PUT с пустым кодом → 400 ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> PUT /code: пустой код → 400"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X PUT "${BASE}/functions/${FN}/code" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"code":""}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "PUT /code пустой код → 400"
|
||||||
|
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X PUT "${BASE}/functions/${FN}/code" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d '{"code":" "}')
|
||||||
|
check_http "$(echo "$R" | tail -1)" "400" "PUT /code пробельный код → 400"
|
||||||
|
|
||||||
|
# ── PUT несуществующей → 404 ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> PUT /code: несуществующая функция → 404"
|
||||||
|
R=$(curl -s -w "\n%{http_code}" -X PUT "${BASE}/functions/no-such-fn-xyz/code" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"code\":$(printf '%s' "$CODE_V1" | json_escape)}")
|
||||||
|
check_http "$(echo "$R" | tail -1)" "404" "PUT /code несуществующая → 404"
|
||||||
|
|
||||||
|
# ── Ещё одно обновление (v3) — убеждаемся что можно делать цепочку ────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">>> PUT /code: версия v3 (цепочка)"
|
||||||
|
CODE_V3='module.exports = async function() { return { status: 200, body: "version-three" }; }'
|
||||||
|
curl -s -X PUT "${BASE}/functions/${FN}/code" \
|
||||||
|
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
|
||||||
|
-d "{\"code\":$(printf '%s' "$CODE_V3" | json_escape)}" > /dev/null
|
||||||
|
|
||||||
|
if wait_invoke "$SUB" "$FN" "version-three" 15 5; then
|
||||||
|
pass "Invoke после v3 update → version-three"
|
||||||
|
else
|
||||||
|
fail "Invoke после v3 update не стабилизировался"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_summary
|
||||||
Reference in New Issue
Block a user