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:
Naeel
2026-05-03 14:02:24 +03:00
parent f18508d2cb
commit c1e3039d15
11 changed files with 1152 additions and 0 deletions
Executable
+91
View File
@@ -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 ]
}
+38
View File
@@ -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: очистка завершена."
+101
View File
@@ -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 ]
+107
View File
@@ -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
+207
View File
@@ -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
+90
View File
@@ -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
+162
View File
@@ -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
View File
+158
View File
@@ -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
+93
View File
@@ -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
+105
View File
@@ -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