fix(tests): X-Test-Sub → X-Auth-Token во всех тест-скриптах, cleanup для test59.sh

This commit is contained in:
“Naeel”
2026-05-03 13:50:32 +04:00
parent a09291e208
commit f18508d2cb
8 changed files with 117 additions and 106 deletions
+2 -2
View File
@@ -1083,7 +1083,7 @@ print("=== Удаляем старые bench-функции ===")
for name, _, _ in functions: for name, _, _ in functions:
result = subprocess.run( result = subprocess.run(
["curl", "-s", "-X", "DELETE", f"{BASE}/functions/{name}", ["curl", "-s", "-X", "DELETE", f"{BASE}/functions/{name}",
"-H", f"X-Test-Sub: {SUB}"], "-H", f"X-Auth-Token: {SUB}"],
capture_output=True, text=True capture_output=True, text=True
) )
print(f" DELETE {name}: {result.stdout.strip()[:80]}") print(f" DELETE {name}: {result.stdout.strip()[:80]}")
@@ -1094,7 +1094,7 @@ for name, lang, code in functions:
size_kb = len(code) / 1024 size_kb = len(code) / 1024
result = subprocess.run( result = subprocess.run(
["curl", "-s", "-X", "POST", f"{BASE}/functions", ["curl", "-s", "-X", "POST", f"{BASE}/functions",
"-H", f"X-Test-Sub: {SUB}", "-H", "Content-Type: application/json", "-H", f"X-Auth-Token: {SUB}", "-H", "Content-Type: application/json",
"-d", payload], "-d", payload],
capture_output=True, text=True capture_output=True, text=True
) )
+3 -3
View File
@@ -27,7 +27,7 @@ init_call() {
local code="" local code=""
local try local try
for try in 1 2 3; do for try in 1 2 3; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 -X GET "${BASE}/functions" -H "X-Test-Sub: ${user}" 2>/dev/null || echo 000) code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 -X GET "${BASE}/functions" -H "X-Auth-Token: ${user}" 2>/dev/null || echo 000)
echo "INIT_TRY user=${user} try=${try} http=${code}" >&2 echo "INIT_TRY user=${user} try=${try} http=${code}" >&2
if [ "$code" = "200" ]; then if [ "$code" = "200" ]; then
printf '%s' "$code" printf '%s' "$code"
@@ -44,7 +44,7 @@ invoke_call() {
local code="" local code=""
local try local try
for try in 1 2 3 4; do for try in 1 2 3 4; do
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 40 -X POST "${BASE}/functions/${fn}/invoke" -H "X-Test-Sub: ${user}" -H "Content-Type: application/json" -d '{}' 2>/dev/null || echo 000) code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 40 -X POST "${BASE}/functions/${fn}/invoke" -H "X-Auth-Token: ${user}" -H "Content-Type: application/json" -d '{}' 2>/dev/null || echo 000)
echo "INVOKE_TRY user=${user} fn=${fn} try=${try} http=${code}" >&2 echo "INVOKE_TRY user=${user} fn=${fn} try=${try} http=${code}" >&2
if [ "$code" = "200" ]; then if [ "$code" = "200" ]; then
printf '%s' "$code" printf '%s' "$code"
@@ -86,7 +86,7 @@ for i in $(seq 1 "$COUNT"); do
CODE_TEXT=$(printf 'def main():\n return "ok-%s-%s"\n' "$RUN_ID" "$i") CODE_TEXT=$(printf 'def main():\n return "ok-%s-%s"\n' "$RUN_ID" "$i")
CODE_JSON=$(printf '%s' "$CODE_TEXT" | json_escape) CODE_JSON=$(printf '%s' "$CODE_TEXT" | json_escape)
BODY=$(printf '{"name":"%s","language":"python","code":%s}' "$FN" "$CODE_JSON") BODY=$(printf '{"name":"%s","language":"python","code":%s}' "$FN" "$CODE_JSON")
C=$(curl -s -o /dev/null -w "%{http_code}" --max-time 60 -X POST "${BASE}/functions" -H "X-Test-Sub: ${U}" -H "Content-Type: application/json" -d "$BODY" 2>/dev/null || echo 000) C=$(curl -s -o /dev/null -w "%{http_code}" --max-time 60 -X POST "${BASE}/functions" -H "X-Auth-Token: ${U}" -H "Content-Type: application/json" -d "$BODY" 2>/dev/null || echo 000)
echo "$i:$C:$FN:$U" > "${TMP}/create-$i" echo "$i:$C:$FN:$U" > "${TMP}/create-$i"
) & ) &
done done
+4 -4
View File
@@ -39,14 +39,14 @@ create_call() {
local sub="$1" local sub="$1"
local body="$2" local body="$2"
curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \ curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \
-H "X-Test-Sub: ${sub}" -H 'Content-Type: application/json' -d "$body" -H "X-Auth-Token: ${sub}" -H 'Content-Type: application/json' -d "$body"
} }
invoke_call() { invoke_call() {
local sub="$1" local sub="$1"
local name="$2" local name="$2"
curl -s --max-time 45 -X POST "${BASE}/functions/${name}/invoke" \ curl -s --max-time 45 -X POST "${BASE}/functions/${name}/invoke" \
-H "X-Test-Sub: ${sub}" -H 'Content-Type: application/json' -d '{}' || true -H "X-Auth-Token: ${sub}" -H 'Content-Type: application/json' -d '{}' || true
} }
echo "=== USER JOURNEYS START run=$RUN_ID count=$COUNT ===" echo "=== USER JOURNEYS START run=$RUN_ID count=$COUNT ==="
@@ -90,9 +90,9 @@ for i in $(seq 1 "$COUNT"); do
done done
[ "$INV_STATUS" = "200" ] || RESULT="FAIL invoke=${INV_STATUS}" [ "$INV_STATUS" = "200" ] || RESULT="FAIL invoke=${INV_STATUS}"
DEL1=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/${FN}" -H "X-Test-Sub: ${U}") DEL1=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/${FN}" -H "X-Auth-Token: ${U}")
[ "$DEL1" = "200" ] || RESULT="FAIL delete1=${DEL1}" [ "$DEL1" = "200" ] || RESULT="FAIL delete1=${DEL1}"
DEL2=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/${FN}" -H "X-Test-Sub: ${U}") DEL2=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/${FN}" -H "X-Auth-Token: ${U}")
[ "$DEL2" = "404" ] || RESULT="FAIL delete2=${DEL2}" [ "$DEL2" = "404" ] || RESULT="FAIL delete2=${DEL2}"
echo "$i:$RESULT" > "${TMP}/user-$i" echo "$i:$RESULT" > "${TMP}/user-$i"
+12 -1
View File
@@ -1,10 +1,21 @@
#!/bin/bash #!/bin/bash
BASE="https://fission.kube5s.ru/console/api" BASE="https://fission.kube5s.ru/console/api"
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh"
TEST_SUB="len59direct@test.local"
cleanup() {
if [ -x "$NS_CLEANUP_SCRIPT" ]; then
"$NS_CLEANUP_SCRIPT" "$TEST_SUB" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
NAME59=$(python3 -c "print('a'*59)") NAME59=$(python3 -c "print('a'*59)")
echo "Name len: ${#NAME59}" echo "Name len: ${#NAME59}"
echo "Pkg name would be: ${NAME59}-pkg (len=$(python3 -c "print(59+4)"))" echo "Pkg name would be: ${NAME59}-pkg (len=$(python3 -c "print(59+4)"))"
curl -s -w "\nHTTP:%{http_code}" -X POST "${BASE}/functions" \ curl -s -w "\nHTTP:%{http_code}" -X POST "${BASE}/functions" \
-H "X-Test-Sub: len59direct@test.local" \ -H "X-Auth-Token: ${TEST_SUB}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data-raw "{\"name\":\"${NAME59}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>42\"}" --data-raw "{\"name\":\"${NAME59}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>42\"}"
echo echo
+1 -1
View File
@@ -10,7 +10,7 @@ chk() {
escaped=$(printf '%s' "$code" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") escaped=$(printf '%s' "$code" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
local resp local resp
resp=$(curl -s -X POST "$API" \ resp=$(curl -s -X POST "$API" \
-H "X-Test-Sub: livetest@test.local" \ -H "X-Auth-Token: livetest@test.local" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"language\":\"${lang}\",\"code\":${escaped}}") -d "{\"language\":\"${lang}\",\"code\":${escaped}}")
local ok_val result_val local ok_val result_val
+4 -4
View File
@@ -52,7 +52,7 @@ echo " Layer 1 NSWatcher — стресс-тест"
echo " Пользователей: $COUNT run: $RUN_ID" echo " Пользователей: $COUNT run: $RUN_ID"
echo "========================================" echo "========================================"
# Каждый "пользователь" — уникальный X-Test-Sub → уникальный NS # Каждый "пользователь" — уникальный X-Auth-Token → уникальный NS
# Консоль вызывает SetupFissionNamespace → NS получает fission.io/managed=true # Консоль вызывает SetupFissionNamespace → NS получает fission.io/managed=true
# Executor NSWatcher подхватывает → регистрирует без рестарта # Executor NSWatcher подхватывает → регистрирует без рестарта
@@ -68,7 +68,7 @@ for i in $(seq 1 $COUNT); do
( (
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 \ CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 \
-X GET "${BASE}/functions" \ -X GET "${BASE}/functions" \
-H "X-Test-Sub: ${USER}" 2>/dev/null || echo 000) -H "X-Auth-Token: ${USER}" 2>/dev/null || echo 000)
echo "$i:$CODE:$USER" > "${TMPDIR_RESULTS}/init-${i}" echo "$i:$CODE:$USER" > "${TMPDIR_RESULTS}/init-${i}"
) & ) &
done done
@@ -139,7 +139,7 @@ for i in $(seq 1 $COUNT); do
# Создаём функцию # Создаём функцию
CREATE=$(curl -s -w "\n%{http_code}" --max-time 60 \ CREATE=$(curl -s -w "\n%{http_code}" --max-time 60 \
-X POST "${BASE}/functions" \ -X POST "${BASE}/functions" \
-H "X-Test-Sub: ${USER}" \ -H "X-Auth-Token: ${USER}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"name\":\"${FN_NAME}\",\"language\":\"python\",\"code\":$(printf '%s' \"${PYTHON_CODE}\" | json_escape)}" \ -d "{\"name\":\"${FN_NAME}\",\"language\":\"python\",\"code\":$(printf '%s' \"${PYTHON_CODE}\" | json_escape)}" \
2>/dev/null) 2>/dev/null)
@@ -180,7 +180,7 @@ for i in $(seq 1 $COUNT); do
( (
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 \ CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 \
-X POST "${BASE}/functions/${FN_NAME}/invoke" \ -X POST "${BASE}/functions/${FN_NAME}/invoke" \
-H "X-Test-Sub: ${USER}" \ -H "X-Auth-Token: ${USER}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{}' 2>/dev/null || echo 000) -d '{}' 2>/dev/null || echo 000)
echo "$i:$CODE" > "${TMPDIR_RESULTS}/invoke-${i}" echo "$i:$CODE" > "${TMPDIR_RESULTS}/invoke-${i}"
+5 -5
View File
@@ -64,7 +64,7 @@ create_call() {
local sub="$1" local sub="$1"
local body="$2" local body="$2"
curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \ curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \
-H "X-Test-Sub: ${sub}" -H 'Content-Type: application/json' -d "$body" -H "X-Auth-Token: ${sub}" -H 'Content-Type: application/json' -d "$body"
} }
invoke_call() { invoke_call() {
@@ -76,7 +76,7 @@ invoke_call() {
err_file=$(mktemp) err_file=$(mktemp)
rc=0 rc=0
http_code=$(curl -sS -o "$out_file" -w "%{http_code}" --max-time 60 -X POST "${BASE}/functions/${name}/invoke" \ http_code=$(curl -sS -o "$out_file" -w "%{http_code}" --max-time 60 -X POST "${BASE}/functions/${name}/invoke" \
-H "X-Test-Sub: ${sub}" -H 'Content-Type: application/json' -d "$body" 2>"$err_file") || rc=$? -H "X-Auth-Token: ${sub}" -H 'Content-Type: application/json' -d "$body" 2>"$err_file") || rc=$?
resp=$(cat "$out_file" 2>/dev/null || true) resp=$(cat "$out_file" 2>/dev/null || true)
err_text=$(tr '\n' ' ' <"$err_file" | sed 's/[[:space:]]\+/ /g') err_text=$(tr '\n' ' ' <"$err_file" | sed 's/[[:space:]]\+/ /g')
rm -f "$out_file" "$err_file" rm -f "$out_file" "$err_file"
@@ -243,9 +243,9 @@ R=$(create_call "$NEG_SUB" '{"name":"dup-ui","language":"python","entrypoint":"m
CODE=$(printf '%s' "$R" | tail -1) CODE=$(printf '%s' "$R" | tail -1)
([ "$CODE" = "409" ] || [ "$CODE" = "502" ]) && pass "duplicate create rejected" || fail "duplicate create -> got $CODE" ([ "$CODE" = "409" ] || [ "$CODE" = "502" ]) && pass "duplicate create rejected" || fail "duplicate create -> got $CODE"
DC=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/dup-ui" -H "X-Test-Sub: ${NEG_SUB}") DC=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/dup-ui" -H "X-Auth-Token: ${NEG_SUB}")
[ "$DC" = "200" ] && pass "first delete -> 200" || fail "first delete -> got $DC" [ "$DC" = "200" ] && pass "first delete -> 200" || fail "first delete -> got $DC"
DC2=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/dup-ui" -H "X-Test-Sub: ${NEG_SUB}") DC2=$(curl -s -o /dev/null -w "%{http_code}" --max-time 45 -X DELETE "${BASE}/functions/dup-ui" -H "X-Auth-Token: ${NEG_SUB}")
[ "$DC2" = "404" ] && pass "second delete -> 404" || fail "second delete -> got $DC2" [ "$DC2" = "404" ] && pass "second delete -> 404" || fail "second delete -> got $DC2"
echo "" echo ""
@@ -274,7 +274,7 @@ else
fail "nodejs invoke did not stabilize" fail "nodejs invoke did not stabilize"
fi fi
UPD=$(curl -s -w "\n%{http_code}" --max-time 60 -X PUT "${BASE}/functions/node-ui/code" \ UPD=$(curl -s -w "\n%{http_code}" --max-time 60 -X PUT "${BASE}/functions/node-ui/code" \
-H "X-Test-Sub: ${NODE_SUB}" -H 'Content-Type: application/json' \ -H "X-Auth-Token: ${NODE_SUB}" -H 'Content-Type: application/json' \
-d '{"code":"module.exports = async function() { return { status: 200, body: \"node-ui-updated\" }; }"}') -d '{"code":"module.exports = async function() { return { status: 200, body: \"node-ui-updated\" }; }"}')
CODE=$(printf '%s' "$UPD" | tail -1) CODE=$(printf '%s' "$UPD" | tail -1)
[ "$CODE" = "200" ] && pass "nodejs update -> 200" || fail "nodejs update -> got $CODE" [ "$CODE" = "200" ] && pass "nodejs update -> 200" || fail "nodejs update -> got $CODE"
+86 -86
View File
@@ -51,23 +51,23 @@ echo ""
echo ">>> БЛОК A: Фиксы v0.6.8 — регресс" echo ">>> БЛОК A: Фиксы v0.6.8 — регресс"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions/nonexistent-xyz-abc/invoke \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions/nonexistent-xyz-abc/invoke \
-H "X-Test-Sub: alice@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: alice@test.local" -H "Content-Type: application/json" -d '{}')
CODE=$(echo "$R" | tail -1); BODY=$(echo "$R" | head -1) CODE=$(echo "$R" | tail -1); BODY=$(echo "$R" | head -1)
check_http "$CODE" "404" "INVOKE nonexistent → 404" check_http "$CODE" "404" "INVOKE nonexistent → 404"
echo "$BODY" | grep -q "not found" && pass "INVOKE nonexistent — тело содержит 'not found'" || fail "INVOKE nonexistent — тело: $BODY" echo "$BODY" | grep -q "not found" && pass "INVOKE nonexistent — тело содержит 'not found'" || fail "INVOKE nonexistent — тело: $BODY"
R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/nonexistent-xyz-abc \ R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/nonexistent-xyz-abc \
-H "X-Test-Sub: alice@test.local") -H "X-Auth-Token: alice@test.local")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "404" "DELETE nonexistent → 404" check_http "$CODE" "404" "DELETE nonexistent → 404"
# Создаём изолированные функции и проверяем route isolation # Создаём изолированные функции и проверяем route isolation
ISO_NAME="iso-${RUN_ID}" ISO_NAME="iso-${RUN_ID}"
R_A=$(curl -s -X POST ${BASE}/functions \ R_A=$(curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: routetest-alice@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: routetest-alice@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"${ISO_NAME}\",\"language\":\"nodejs\",\"code\":\"module.exports=async(ctx)=>({body:\\\"alice-iso\\\"})\",\"ttl\":\"5m\"}") -d "{\"name\":\"${ISO_NAME}\",\"language\":\"nodejs\",\"code\":\"module.exports=async(ctx)=>({body:\\\"alice-iso\\\"})\",\"ttl\":\"5m\"}")
R_B=$(curl -s -X POST ${BASE}/functions \ R_B=$(curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: routetest-bob@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: routetest-bob@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"${ISO_NAME}\",\"language\":\"nodejs\",\"code\":\"module.exports=async(ctx)=>({body:\\\"bob-iso\\\"})\",\"ttl\":\"5m\"}") -d "{\"name\":\"${ISO_NAME}\",\"language\":\"nodejs\",\"code\":\"module.exports=async(ctx)=>({body:\\\"bob-iso\\\"})\",\"ttl\":\"5m\"}")
ROUTE_A=$(echo "$R_A" | python3 -c "import sys,json; print(json.load(sys.stdin).get('route','?'))" 2>/dev/null) ROUTE_A=$(echo "$R_A" | python3 -c "import sys,json; print(json.load(sys.stdin).get('route','?'))" 2>/dev/null)
ROUTE_B=$(echo "$R_B" | python3 -c "import sys,json; print(json.load(sys.stdin).get('route','?'))" 2>/dev/null) ROUTE_B=$(echo "$R_B" | python3 -c "import sys,json; print(json.load(sys.stdin).get('route','?'))" 2>/dev/null)
@@ -77,9 +77,9 @@ sleep 10
# Invoke после изоляции # Invoke после изоляции
INV_A=$(curl -s -X POST ${BASE}/functions/${ISO_NAME}/invoke \ INV_A=$(curl -s -X POST ${BASE}/functions/${ISO_NAME}/invoke \
-H "X-Test-Sub: routetest-alice@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: routetest-alice@test.local" -H "Content-Type: application/json" -d '{}')
INV_B=$(curl -s -X POST ${BASE}/functions/${ISO_NAME}/invoke \ INV_B=$(curl -s -X POST ${BASE}/functions/${ISO_NAME}/invoke \
-H "X-Test-Sub: routetest-bob@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: routetest-bob@test.local" -H "Content-Type: application/json" -d '{}')
BODY_A=$(echo "$INV_A" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) BODY_A=$(echo "$INV_A" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
BODY_B=$(echo "$INV_B" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) BODY_B=$(echo "$INV_B" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ "$BODY_A" = "alice-iso" ] && pass "Route isolation invoke — alice получает alice" || fail "Route isolation — alice получила: $BODY_A" [ "$BODY_A" = "alice-iso" ] && pass "Route isolation invoke — alice получает alice" || fail "Route isolation — alice получила: $BODY_A"
@@ -90,27 +90,27 @@ echo ""
echo ">>> БЛОК B: Валидация входных данных" echo ">>> БЛОК B: Валидация входных данных"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: val-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: val-user@test.local" -H "Content-Type: application/json" \
-d '{"language":"nodejs","code":"x"}') -d '{"language":"nodejs","code":"x"}')
CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без name → 400" CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без name → 400"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: val-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: val-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"nocode-fn","language":"nodejs"}') -d '{"name":"nocode-fn","language":"nodejs"}')
CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без code → 400" CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без code → 400"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: val-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: val-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"nolang-fn","code":"x"}') -d '{"name":"nolang-fn","code":"x"}')
CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без language → 400" CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание без language → 400"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: val-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: val-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"","language":"nodejs","code":"x"}') -d '{"name":"","language":"nodejs","code":"x"}')
CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание с пустым name → 400" CODE=$(echo "$R" | tail -1); check_http "$CODE" "400" "Создание с пустым name → 400"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: val-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: val-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"bad lang fn","language":"nodejs","code":"x"}') -d '{"name":"bad lang fn","language":"nodejs","code":"x"}')
# Fission не позволяет пробелы в имени — ожидаем 4xx или 502 # Fission не позволяет пробелы в имени — ожидаем 4xx или 502
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
@@ -121,11 +121,11 @@ echo ""
echo ">>> БЛОК C: Дублирующее создание" echo ">>> БЛОК C: Дублирующее создание"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: dup-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: dup-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"dup-fn","language":"nodejs","code":"module.exports=async()=>({body:\"v1\"});","ttl":"5m"}' > /dev/null -d '{"name":"dup-fn","language":"nodejs","code":"module.exports=async()=>({body:\"v1\"});","ttl":"5m"}' > /dev/null
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: dup-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: dup-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"dup-fn","language":"nodejs","code":"module.exports=async()=>({body:\"v2\"});","ttl":"5m"}') -d '{"name":"dup-fn","language":"nodejs","code":"module.exports=async()=>({body:\"v2\"});","ttl":"5m"}')
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
( [ "$CODE" = "409" ] || [ "$CODE" = "502" ] ) && pass "Дублирующее создание → конфликт (got $CODE)" || fail "Дублирующее создание → got $CODE" ( [ "$CODE" = "409" ] || [ "$CODE" = "502" ] ) && pass "Дублирующее создание → конфликт (got $CODE)" || fail "Дублирующее создание → got $CODE"
@@ -135,20 +135,20 @@ echo ""
echo ">>> БЛОК D: UPDATE кода и повторный invoke" echo ">>> БЛОК D: UPDATE кода и повторный invoke"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: upd-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: upd-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"upd-fn","language":"nodejs","code":"module.exports=async()=>({body:\"before\"});","ttl":"10m"}' > /dev/null -d '{"name":"upd-fn","language":"nodejs","code":"module.exports=async()=>({body:\"before\"});","ttl":"10m"}' > /dev/null
sleep 3 sleep 3
R=$(curl -s -w "\n%{http_code}" -X PUT ${BASE}/functions/upd-fn/code \ R=$(curl -s -w "\n%{http_code}" -X PUT ${BASE}/functions/upd-fn/code \
-H "X-Test-Sub: upd-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: upd-user@test.local" -H "Content-Type: application/json" \
-d '{"code":"module.exports=async()=>({body:\"after\"});"}') -d '{"code":"module.exports=async()=>({body:\"after\"});"}')
CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "UPDATE code → 200" CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "UPDATE code → 200"
sleep 10 sleep 10
INV=$(curl -s -X POST ${BASE}/functions/upd-fn/invoke \ INV=$(curl -s -X POST ${BASE}/functions/upd-fn/invoke \
-H "X-Test-Sub: upd-user@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: upd-user@test.local" -H "Content-Type: application/json" -d '{}')
BODY=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) BODY=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ "$BODY" = "after" ] && pass "После UPDATE — invoke возвращает новый код" || fail "После UPDATE — invoke вернул: $BODY" [ "$BODY" = "after" ] && pass "После UPDATE — invoke возвращает новый код" || fail "После UPDATE — invoke вернул: $BODY"
@@ -157,18 +157,18 @@ echo ""
echo ">>> БЛОК E: GET функции" echo ">>> БЛОК E: GET функции"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: get-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: get-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"get-fn","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null -d '{"name":"get-fn","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null
R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/get-fn \ R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/get-fn \
-H "X-Test-Sub: get-user@test.local") -H "X-Auth-Token: get-user@test.local")
CODE=$(echo "$R" | tail -1); BODY=$(echo "$R" | head -1) CODE=$(echo "$R" | tail -1); BODY=$(echo "$R" | head -1)
check_http "$CODE" "200" "GET существующей функции → 200" check_http "$CODE" "200" "GET существующей функции → 200"
echo "$BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('name')=='get-fn' else 1)" 2>/dev/null \ echo "$BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('name')=='get-fn' else 1)" 2>/dev/null \
&& pass "GET — имя функции в ответе" || fail "GET — имя не найдено: $BODY" && pass "GET — имя функции в ответе" || fail "GET — имя не найдено: $BODY"
R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/nonexist-get-xyz \ R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/nonexist-get-xyz \
-H "X-Test-Sub: get-user@test.local") -H "X-Auth-Token: get-user@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "GET несуществующей → 404" CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "GET несуществующей → 404"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -178,16 +178,16 @@ echo ">>> БЛОК F: Namespace изоляция LIST"
# u1 создаёт 3 функции, u2 — 1. LIST u1 должен вернуть только свои # u1 создаёт 3 функции, u2 — 1. LIST u1 должен вернуть только свои
for i in 1 2 3; do for i in 1 2 3; do
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ns-u1@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ns-u1@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"ns-fn-$i\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"5m\"}" > /dev/null -d "{\"name\":\"ns-fn-$i\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"5m\"}" > /dev/null
done done
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ns-u2@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ns-u2@test.local" -H "Content-Type: application/json" \
-d '{"name":"ns-fn-u2","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null -d '{"name":"ns-fn-u2","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null
COUNT_U1=$(curl -s ${BASE}/functions -H "X-Test-Sub: ns-u1@test.local" \ COUNT_U1=$(curl -s ${BASE}/functions -H "X-Auth-Token: ns-u1@test.local" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
COUNT_U2=$(curl -s ${BASE}/functions -H "X-Test-Sub: ns-u2@test.local" \ COUNT_U2=$(curl -s ${BASE}/functions -H "X-Auth-Token: ns-u2@test.local" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "$COUNT_U1" -ge 3 ] 2>/dev/null && pass "LIST изоляция: u1 видит $COUNT_U1 функций (>= 3)" || fail "LIST u1 — $COUNT_U1" [ "$COUNT_U1" -ge 3 ] 2>/dev/null && pass "LIST изоляция: u1 видит $COUNT_U1 функций (>= 3)" || fail "LIST u1 — $COUNT_U1"
@@ -199,21 +199,21 @@ echo ">>> БЛОК G: Namespace изоляция DELETE"
# Carol пытается удалить функцию Alice # Carol пытается удалить функцию Alice
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: alice-del@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: alice-del@test.local" -H "Content-Type: application/json" \
-d '{"name":"alice-secret","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null -d '{"name":"alice-secret","language":"nodejs","code":"x","ttl":"5m"}' > /dev/null
R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \ R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \
-H "X-Test-Sub: carol-del@test.local") -H "X-Auth-Token: carol-del@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "Carol DELETE alice функции → 404" CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "Carol DELETE alice функции → 404"
# Alice сама удаляет — должно быть 200 # Alice сама удаляет — должно быть 200
R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \ R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \
-H "X-Test-Sub: alice-del@test.local") -H "X-Auth-Token: alice-del@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "Alice DELETE своей функции → 200" CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "Alice DELETE своей функции → 200"
# Alice повторно удаляет — 404 # Alice повторно удаляет — 404
R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \ R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/alice-secret \
-H "X-Test-Sub: alice-del@test.local") -H "X-Auth-Token: alice-del@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "Alice повторный DELETE → 404" CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "Alice повторный DELETE → 404"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -222,13 +222,13 @@ echo ">>> БЛОК H: Python — разные форматы кода"
# Формат 1: def main() без аргумента — Fission Python env вызывает main() без аргументов # Формат 1: def main() без аргумента — Fission Python env вызывает main() без аргументов
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: py-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: py-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"py-main-'${RUN_ID}'","language":"python","code":"def main():\n return \"py-main\", 200","ttl":"10m"}' > /dev/null -d '{"name":"py-main-'${RUN_ID}'","language":"python","code":"def main():\n return \"py-main\", 200","ttl":"10m"}' > /dev/null
sleep 15 sleep 15
INV=$(curl -s -X POST ${BASE}/functions/py-main-${RUN_ID}/invoke \ INV=$(curl -s -X POST ${BASE}/functions/py-main-${RUN_ID}/invoke \
-H "X-Test-Sub: py-user@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: py-user@test.local" -H "Content-Type: application/json" -d '{}')
STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
[ "$STATUS" = "200" ] && pass "Python def main() (no-arg) → invoke 200" || fail "Python def main → status=$STATUS body=$(echo $INV)" [ "$STATUS" = "200" ] && pass "Python def main() (no-arg) → invoke 200" || fail "Python def main → status=$STATUS body=$(echo $INV)"
@@ -238,24 +238,24 @@ echo ">>> БЛОК I: NodeJS — несколько export-паттернов"
# module.exports = async function # module.exports = async function
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: js-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: js-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"js-anon","language":"nodejs","code":"module.exports=async function(ctx){return {body:\"anon\"};}","ttl":"10m"}' > /dev/null -d '{"name":"js-anon","language":"nodejs","code":"module.exports=async function(ctx){return {body:\"anon\"};}","ttl":"10m"}' > /dev/null
# exports.handler # exports.handler
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: js-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: js-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"js-handler","language":"nodejs","code":"exports.handler=async(ctx)=>({body:\"handler\"});","ttl":"10m"}' > /dev/null -d '{"name":"js-handler","language":"nodejs","code":"exports.handler=async(ctx)=>({body:\"handler\"});","ttl":"10m"}' > /dev/null
# exports.default # exports.default
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: js-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: js-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"js-default","language":"nodejs","code":"exports.default=async(ctx)=>({body:\"default\"});","ttl":"10m"}' > /dev/null -d '{"name":"js-default","language":"nodejs","code":"exports.default=async(ctx)=>({body:\"default\"});","ttl":"10m"}' > /dev/null
sleep 10 sleep 10
for fn_name in js-anon js-handler js-default; do for fn_name in js-anon js-handler js-default; do
INV=$(curl -s -X POST ${BASE}/functions/${fn_name}/invoke \ INV=$(curl -s -X POST ${BASE}/functions/${fn_name}/invoke \
-H "X-Test-Sub: js-user@test.local" -H "Content-Type: application/json" -d '{}') -H "X-Auth-Token: js-user@test.local" -H "Content-Type: application/json" -d '{}')
STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
[ "$STATUS" = "200" ] && pass "NodeJS $fn_name → 200" || fail "NodeJS $fn_name → status=$STATUS / $(echo $INV)" [ "$STATUS" = "200" ] && pass "NodeJS $fn_name → 200" || fail "NodeJS $fn_name → status=$STATUS / $(echo $INV)"
done done
@@ -265,20 +265,20 @@ echo ""
echo ">>> БЛОК J: TTL — создание и проверка" echo ">>> БЛОК J: TTL — создание и проверка"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ttl-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ttl-user@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"ttl-has-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"15m\"}" \ -d "{\"name\":\"ttl-has-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"15m\"}" \
| python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('expires_at') else 1)" 2>/dev/null \ | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('expires_at') else 1)" 2>/dev/null \
&& pass "TTL=15m — expires_at присутствует" || fail "TTL=15m — expires_at отсутствует" && pass "TTL=15m — expires_at присутствует" || fail "TTL=15m — expires_at отсутствует"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ttl-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ttl-user@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"ttl-noexp-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\"}" \ -d "{\"name\":\"ttl-noexp-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\"}" \
| python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if not d.get('expires_at') else 1)" 2>/dev/null \ | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if not d.get('expires_at') else 1)" 2>/dev/null \
&& pass "Без TTL — expires_at отсутствует" || fail "Без TTL — expires_at присутствует (не ожидалось)" && pass "Без TTL — expires_at отсутствует" || fail "Без TTL — expires_at присутствует (не ожидалось)"
# Невалидный TTL # Невалидный TTL
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: ttl-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ttl-user@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"ttl-bad-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"99z\"}") -d "{\"name\":\"ttl-bad-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"x\",\"ttl\":\"99z\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
[ "$CODE" = "400" ] && pass "Невалидный TTL=99z → 400" || fail "Невалидный TTL=99z → got $CODE" [ "$CODE" = "400" ] && pass "Невалидный TTL=99z → 400" || fail "Невалидный TTL=99z → got $CODE"
@@ -289,13 +289,13 @@ echo ">>> БЛОК K: Invoke — тело запроса передаётся в
# ctx.request.body может быть undefined — тестируем через ctx.request.url (всегда доступен) # ctx.request.body может быть undefined — тестируем через ctx.request.url (всегда доступен)
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: body-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: body-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"echo-url","language":"nodejs","code":"module.exports=async(ctx)=>({body:\"got:\"+ctx.request.url});","ttl":"10m"}' > /dev/null -d '{"name":"echo-url","language":"nodejs","code":"module.exports=async(ctx)=>({body:\"got:\"+ctx.request.url});","ttl":"10m"}' > /dev/null
sleep 12 sleep 12
INV=$(curl -s -X POST ${BASE}/functions/echo-url/invoke \ INV=$(curl -s -X POST ${BASE}/functions/echo-url/invoke \
-H "X-Test-Sub: body-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: body-user@test.local" -H "Content-Type: application/json" \
-d '{"hello":"world"}') -d '{"hello":"world"}')
RESP=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) RESP=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
@@ -307,14 +307,14 @@ echo ""
echo ">>> БЛОК L: Stress — 10 параллельных invoke" echo ">>> БЛОК L: Stress — 10 параллельных invoke"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: stress-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: stress-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"stress-fn","language":"nodejs","code":"module.exports=async()=>({body:\"ok\"});","ttl":"10m"}' > /dev/null -d '{"name":"stress-fn","language":"nodejs","code":"module.exports=async()=>({body:\"ok\"});","ttl":"10m"}' > /dev/null
sleep 8 sleep 8
STRESS_OK=0 STRESS_OK=0
for i in $(seq 1 10); do for i in $(seq 1 10); do
ST=$(curl -s -X POST ${BASE}/functions/stress-fn/invoke \ ST=$(curl -s -X POST ${BASE}/functions/stress-fn/invoke \
-H "X-Test-Sub: stress-user@test.local" -H "Content-Type: application/json" -d '{}' \ -H "X-Auth-Token: stress-user@test.local" -H "Content-Type: application/json" -d '{}' \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
[ "$ST" = "200" ] && STRESS_OK=$((STRESS_OK+1)) [ "$ST" = "200" ] && STRESS_OK=$((STRESS_OK+1))
done & done &
@@ -326,7 +326,7 @@ PIDS=()
TMPDIR_S=$(mktemp -d) TMPDIR_S=$(mktemp -d)
for i in $(seq 1 10); do for i in $(seq 1 10); do
( curl -s -X POST ${BASE}/functions/stress-fn/invoke \ ( curl -s -X POST ${BASE}/functions/stress-fn/invoke \
-H "X-Test-Sub: stress-user@test.local" -H "Content-Type: application/json" -d '{}' \ -H "X-Auth-Token: stress-user@test.local" -H "Content-Type: application/json" -d '{}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('status')==200 else 1)" 2>/dev/null \ | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('status')==200 else 1)" 2>/dev/null \
&& touch "${TMPDIR_S}/ok_$i" || true ) & && touch "${TMPDIR_S}/ok_$i" || true ) &
done done
@@ -339,7 +339,7 @@ rm -rf "${TMPDIR_S}"
echo "" echo ""
echo ">>> БЛОК M: LIST пустого namespace" echo ">>> БЛОК M: LIST пустого namespace"
COUNT=$(curl -s ${BASE}/functions -H "X-Test-Sub: brand-new-empty-user-xyzqwerty@test.local" \ COUNT=$(curl -s ${BASE}/functions -H "X-Auth-Token: brand-new-empty-user-xyzqwerty@test.local" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "$COUNT" = "0" ] && pass "LIST новый пользователь → пустой список" || fail "LIST новый пользователь → $COUNT" [ "$COUNT" = "0" ] && pass "LIST новый пользователь → пустой список" || fail "LIST новый пользователь → $COUNT"
@@ -348,15 +348,15 @@ echo ""
echo ">>> БЛОК N: DELETE полная цепочка (fn + pkg + trigger)" echo ">>> БЛОК N: DELETE полная цепочка (fn + pkg + trigger)"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: chain-user@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: chain-user@test.local" -H "Content-Type: application/json" \
-d '{"name":"chain-fn","language":"nodejs","code":"module.exports=async()=>({body:\"ok\"});","ttl":"5m"}' > /dev/null -d '{"name":"chain-fn","language":"nodejs","code":"module.exports=async()=>({body:\"ok\"});","ttl":"5m"}' > /dev/null
# Получаем NS для chain-user из LIST (до DELETE) # Получаем NS для chain-user из LIST (до DELETE)
NS_CHAIN=$(curl -s ${BASE}/functions -H "X-Test-Sub: chain-user@test.local" \ NS_CHAIN=$(curl -s ${BASE}/functions -H "X-Auth-Token: chain-user@test.local" \
| python3 -c "import sys,json; items=json.load(sys.stdin); print(items[0]['metadata']['namespace'] if items else '')" 2>/dev/null) | python3 -c "import sys,json; items=json.load(sys.stdin); print(items[0]['metadata']['namespace'] if items else '')" 2>/dev/null)
R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/chain-fn \ R=$(curl -s -w "\n%{http_code}" -X DELETE ${BASE}/functions/chain-fn \
-H "X-Test-Sub: chain-user@test.local") -H "X-Auth-Token: chain-user@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "DELETE chain-fn → 200" CODE=$(echo "$R" | tail -1); check_http "$CODE" "200" "DELETE chain-fn → 200"
# Ждём propagation удаления в k8s # Ждём propagation удаления в k8s
@@ -368,7 +368,7 @@ if echo "$FN_RAW" | grep -q "not found"; then
elif echo "$FN_RAW" | grep -q "credentials\|logged in\|Unauthorized"; then elif echo "$FN_RAW" | grep -q "credentials\|logged in\|Unauthorized"; then
# kubectl недоступен — проверяем через API (GET должен давать 404) # kubectl недоступен — проверяем через API (GET должен давать 404)
FN_API=$(curl -s -o /dev/null -w "%{http_code}" ${BASE}/functions/chain-fn \ FN_API=$(curl -s -o /dev/null -w "%{http_code}" ${BASE}/functions/chain-fn \
-H "X-Test-Sub: chain-user@test.local") -H "X-Auth-Token: chain-user@test.local")
[ "$FN_API" = "404" ] && pass "k8s: Function удалена (подтверждено API, kubectl без прав)" || fail "k8s: Function не удалена, API=$FN_API" [ "$FN_API" = "404" ] && pass "k8s: Function удалена (подтверждено API, kubectl без прав)" || fail "k8s: Function не удалена, API=$FN_API"
else else
fail "k8s: Function ещё существует в $NS_CHAIN" fail "k8s: Function ещё существует в $NS_CHAIN"
@@ -377,7 +377,7 @@ TRG_EXISTS=$(kubectl get httptriggers.fission.io -n ${NS_CHAIN} 2>&1 | grep -c "
# После удаления GET должен вернуть 404 # После удаления GET должен вернуть 404
R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/chain-fn \ R=$(curl -s -w "\n%{http_code}" ${BASE}/functions/chain-fn \
-H "X-Test-Sub: chain-user@test.local") -H "X-Auth-Token: chain-user@test.local")
CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "GET после DELETE → 404" CODE=$(echo "$R" | tail -1); check_http "$CODE" "404" "GET после DELETE → 404"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -386,7 +386,7 @@ echo ">>> БЛОК O: Auth — без заголовка"
R=$(curl -s -w "\n%{http_code}" ${BASE}/functions) R=$(curl -s -w "\n%{http_code}" ${BASE}/functions)
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
( [ "$CODE" = "401" ] || [ "$CODE" = "403" ] ) && pass "Без X-Test-Sub → 401/403 (got $CODE)" || fail "Без X-Test-Sub → got $CODE" ( [ "$CODE" = "401" ] || [ "$CODE" = "403" ] ) && pass "Без X-Auth-Token → 401/403 (got $CODE)" || fail "Без X-Auth-Token → got $CODE"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
echo "" echo ""
@@ -411,39 +411,39 @@ echo ">>> БЛОК Q: Дубликат → 409, невалидное имя →
# Создаём функцию, потом снова с тем же именем → 409 # Создаём функцию, потом снова с тем же именем → 409
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: dup2-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: dup2-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"dup-q-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'x'})\"}" > /dev/null -d "{\"name\":\"dup-q-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'x'})\"}" > /dev/null
sleep 3 sleep 3
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: dup2-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: dup2-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"dup-q-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'x'})\"}") -d "{\"name\":\"dup-q-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'x'})\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "409" "Дубликат → 409 Conflict" check_http "$CODE" "409" "Дубликат → 409 Conflict"
# Имя с заглавной буквой → 400 # Имя с заглавной буквой → 400
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"BadName\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"BadName\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя с заглавной буквой → 400" check_http "$CODE" "400" "Имя с заглавной буквой → 400"
# Имя начинается с дефиса → 400 # Имя начинается с дефиса → 400
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"-badstart\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"-badstart\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя с ведущим дефисом → 400" check_http "$CODE" "400" "Имя с ведущим дефисом → 400"
# Имя заканчивается дефисом → 400 # Имя заканчивается дефисом → 400
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"badend-\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"badend-\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя с завершающим дефисом → 400" check_http "$CODE" "400" "Имя с завершающим дефисом → 400"
# Имя с пробелами → 400 (было 502) # Имя с пробелами → 400 (было 502)
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"bad name\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"bad name\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя с пробелом → 400 (не 502)" check_http "$CODE" "400" "Имя с пробелом → 400 (не 502)"
@@ -451,7 +451,7 @@ check_http "$CODE" "400" "Имя с пробелом → 400 (не 502)"
# Имя 64 символа → 400 # Имя 64 символа → 400
LONGNAME=$(python3 -c "print('a'*64)") LONGNAME=$(python3 -c "print('a'*64)")
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"${LONGNAME}\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"${LONGNAME}\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя 64 символа → 400" check_http "$CODE" "400" "Имя 64 символа → 400"
@@ -459,7 +459,7 @@ check_http "$CODE" "400" "Имя 64 символа → 400"
# Имя 58 символов → 400 (route = 64 > 63, Fission webhook отклонит) # Имя 58 символов → 400 (route = 64 > 63, Fission webhook отклонит)
NAME58=$(python3 -c "print('a'*58)") NAME58=$(python3 -c "print('a'*58)")
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: name-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"${NAME58}\",\"language\":\"nodejs\",\"code\":\"x\"}") -d "{\"name\":\"${NAME58}\",\"language\":\"nodejs\",\"code\":\"x\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Имя 58 символов → 400 (route будет 64 символа)" check_http "$CODE" "400" "Имя 58 символов → 400 (route будет 64 символа)"
@@ -468,11 +468,11 @@ check_http "$CODE" "400" "Имя 58 символов → 400 (route будет 6
NAME57=$(python3 -c "print('a'*57)") NAME57=$(python3 -c "print('a'*57)")
# Первый прогрев namespace # Первый прогрев namespace
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: len57-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: len57-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d '{"name":"warmup","language":"nodejs","code":"module.exports=async()=>({body:\"w\"})"}' > /dev/null -d '{"name":"warmup","language":"nodejs","code":"module.exports=async()=>({body:\"w\"})"}' > /dev/null
sleep 8 sleep 8
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: len57-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: len57-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"${NAME57}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'ok'})\"}") -d "{\"name\":\"${NAME57}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'ok'})\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "201" "Имя 57 символов → принято (201)" check_http "$CODE" "201" "Имя 57 символов → принято (201)"
@@ -490,7 +490,7 @@ body = json.dumps({'name':'big-fn','language':'nodejs','code':code})
sys.stdout.write(body) sys.stdout.write(body)
" > "$BIG_TMP" " > "$BIG_TMP"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: big-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: big-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
--data @"$BIG_TMP") --data @"$BIG_TMP")
rm -f "$BIG_TMP" rm -f "$BIG_TMP"
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
@@ -502,7 +502,7 @@ CODE=$(echo "$R" | tail -1)
# Код ровно 1000 символов → не 400 # Код ровно 1000 символов → не 400
CODE1K=$(python3 -c "print('module.exports=async()=>({body:' + '\"x\"' + '});' + ' '*(1000-38))") CODE1K=$(python3 -c "print('module.exports=async()=>({body:' + '\"x\"' + '});' + ' '*(1000-38))")
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: size-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: size-user-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"size-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'ok'})\"}") -d "{\"name\":\"size-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'ok'})\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
[ "$CODE" = "201" ] && pass "Код 1000 байт → принят (201)" || fail "Код 1000 байт → got $CODE" [ "$CODE" = "201" ] && pass "Код 1000 байт → принят (201)" || fail "Код 1000 байт → got $CODE"
@@ -513,14 +513,14 @@ echo ">>> БЛОК S: TTL expire + reaper очистка"
# Создаём функцию с TTL=1m # Создаём функцию с TTL=1m
R=$(curl -s -X POST ${BASE}/functions \ R=$(curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ttl-exp-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: ttl-exp-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d "{\"name\":\"expire-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'bye'})\",\"ttl\":\"1m\"}") -d "{\"name\":\"expire-fn-${RUN_ID}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:'bye'})\",\"ttl\":\"1m\"}")
CREATED=$(echo "$R" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name',''))" 2>/dev/null) CREATED=$(echo "$R" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name',''))" 2>/dev/null)
[ "$CREATED" = "expire-fn-${RUN_ID}" ] && pass "TTL=1m функция создана" || fail "TTL=1m создание: $R" [ "$CREATED" = "expire-fn-${RUN_ID}" ] && pass "TTL=1m функция создана" || fail "TTL=1m создание: $R"
# Сразу должна быть видна в LIST # Сразу должна быть видна в LIST
COUNT=$(curl -s ${BASE}/functions \ COUNT=$(curl -s ${BASE}/functions \
-H "X-Test-Sub: ttl-exp-${RUN_ID}@test.local" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) -H "X-Auth-Token: ttl-exp-${RUN_ID}@test.local" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "$COUNT" -ge 1 ] 2>/dev/null && pass "TTL=1m функция видна в LIST" || fail "TTL=1m LIST count=$COUNT" [ "$COUNT" -ge 1 ] 2>/dev/null && pass "TTL=1m функция видна в LIST" || fail "TTL=1m LIST count=$COUNT"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -533,14 +533,14 @@ RACE_RESULTS=""
for i in $(seq 1 5); do for i in $(seq 1 5); do
RACE_CODE="module.exports=async()=>({body:'r${i}'})" RACE_CODE="module.exports=async()=>({body:'r${i}'})"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ${NS_USER}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${NS_USER}" -H "Content-Type: application/json" \
-d "{\"name\":\"race-fn-${i}\",\"language\":\"nodejs\",\"code\":\"${RACE_CODE}\"}" & -d "{\"name\":\"race-fn-${i}\",\"language\":\"nodejs\",\"code\":\"${RACE_CODE}\"}" &
done done
wait wait
# Проверяем что namespace создался и функции (хоть часть) прошли # Проверяем что namespace создался и функции (хоть часть) прошли
sleep 5 sleep 5
COUNT=$(curl -s ${BASE}/functions \ COUNT=$(curl -s ${BASE}/functions \
-H "X-Test-Sub: ${NS_USER}" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) -H "X-Auth-Token: ${NS_USER}" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "$COUNT" -ge 1 ] 2>/dev/null && pass "Race condition: namespace создан, функций: $COUNT" || fail "Race condition: functions=$COUNT" [ "$COUNT" -ge 1 ] 2>/dev/null && pass "Race condition: namespace создан, функций: $COUNT" || fail "Race condition: functions=$COUNT"
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -552,7 +552,7 @@ for uid in $(seq 1 10); do
U="u${uid}-${RUN_ID}@test.local" U="u${uid}-${RUN_ID}@test.local"
CODE="module.exports=async()=>({body:'user${uid}'})" CODE="module.exports=async()=>({body:'user${uid}'})"
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ${U}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${U}" -H "Content-Type: application/json" \
-d "{\"name\":\"fn-${uid}\",\"language\":\"nodejs\",\"code\":\"${CODE}\"}" > /dev/null -d "{\"name\":\"fn-${uid}\",\"language\":\"nodejs\",\"code\":\"${CODE}\"}" > /dev/null
) & ) &
done done
@@ -562,7 +562,7 @@ sleep 3
OK_USERS=0 OK_USERS=0
for uid in $(seq 1 10); do for uid in $(seq 1 10); do
U="u${uid}-${RUN_ID}@test.local" U="u${uid}-${RUN_ID}@test.local"
C=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${U}" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) C=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${U}" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "$C" -ge 1 ] 2>/dev/null && OK_USERS=$((OK_USERS+1)) [ "$C" -ge 1 ] 2>/dev/null && OK_USERS=$((OK_USERS+1))
done done
[ "$OK_USERS" -ge 8 ] && pass "10 юзеров параллельно: ${OK_USERS}/10 создали функции" || fail "10 юзеров параллельно: только ${OK_USERS}/10" [ "$OK_USERS" -ge 8 ] && pass "10 юзеров параллельно: ${OK_USERS}/10 создали функции" || fail "10 юзеров параллельно: только ${OK_USERS}/10"
@@ -574,7 +574,7 @@ echo ">>> БЛОК V: Изоляция — 10 юзеров не видят чу
ISOLATION_OK=0 ISOLATION_OK=0
for uid in $(seq 1 10); do for uid in $(seq 1 10); do
U="u${uid}-${RUN_ID}@test.local" U="u${uid}-${RUN_ID}@test.local"
LIST=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${U}") LIST=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${U}")
# Проверяем что НЕТ чужих функций (fn-X от других пользователей не попадают) # Проверяем что НЕТ чужих функций (fn-X от других пользователей не попадают)
# Каждый видит ровно 1 свою (fn-{uid}) и ни одной fn-{other} # Каждый видит ровно 1 свою (fn-{uid}) и ни одной fn-{other}
MY_COUNT=$(echo "$LIST" | python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='fn-${uid}'))" 2>/dev/null) MY_COUNT=$(echo "$LIST" | python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='fn-${uid}'))" 2>/dev/null)
@@ -592,7 +592,7 @@ echo ">>> БЛОК W: 5 юзеров одновременно вызывают
for uid in $(seq 1 5); do for uid in $(seq 1 5); do
( (
U="u${uid}-${RUN_ID}@test.local" U="u${uid}-${RUN_ID}@test.local"
NS12=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${U}" \ NS12=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${U}" \
| python3 -c "import sys,json; fns=json.load(sys.stdin); print(fns[0]['route'].split('/')[1] if fns else '')" 2>/dev/null) | python3 -c "import sys,json; fns=json.load(sys.stdin); print(fns[0]['route'].split('/')[1] if fns else '')" 2>/dev/null)
if [ -n "$NS12" ]; then if [ -n "$NS12" ]; then
INVOKE_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ INVOKE_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
@@ -620,20 +620,20 @@ for uid in $(seq 1 3); do
# CREATE # CREATE
CR=$(curl -s -o /dev/null -w "%{http_code}" -X POST ${BASE}/functions \ CR=$(curl -s -o /dev/null -w "%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: ${U}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${U}" -H "Content-Type: application/json" \
-d "{\"name\":\"${FN}\",\"language\":\"nodejs\",\"code\":\"${CODE}\"}") -d "{\"name\":\"${FN}\",\"language\":\"nodejs\",\"code\":\"${CODE}\"}")
# LIST — функция есть (sleep чтобы k8s успел зарегистрировать) # LIST — функция есть (sleep чтобы k8s успел зарегистрировать)
sleep 1 sleep 1
LC=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${U}" \ LC=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${U}" \
| python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='${FN}'))" 2>/dev/null) | python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='${FN}'))" 2>/dev/null)
# DELETE # DELETE
DC=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ DC=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
"${BASE}/functions/${FN}" -H "X-Test-Sub: ${U}") "${BASE}/functions/${FN}" -H "X-Auth-Token: ${U}")
# LIST после DELETE — функции нет # LIST после DELETE — функции нет
LC2=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${U}" \ LC2=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${U}" \
| python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='${FN}'))" 2>/dev/null) | python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='${FN}'))" 2>/dev/null)
if [ "$CR" = "201" ] && [ "${LC:-0}" -ge 1 ] && [ "$DC" = "200" ] && [ "${LC2:-1}" -eq 0 ]; then if [ "$CR" = "201" ] && [ "${LC:-0}" -ge 1 ] && [ "$DC" = "200" ] && [ "${LC2:-1}" -eq 0 ]; then
@@ -651,26 +651,26 @@ echo ">>> БЛОК Y: Один юзер создаёт 10 функций, про
MFUNC_USER="mfunc-${RUN_ID}@test.local" MFUNC_USER="mfunc-${RUN_ID}@test.local"
for i in $(seq 1 10); do for i in $(seq 1 10); do
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ${MFUNC_USER}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${MFUNC_USER}" -H "Content-Type: application/json" \
-d "{\"name\":\"mf-${i}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:${i}})\"}" > /dev/null & -d "{\"name\":\"mf-${i}\",\"language\":\"nodejs\",\"code\":\"module.exports=async()=>({body:${i}})\"}" > /dev/null &
done done
wait wait
sleep 3 sleep 3
MFUNC_COUNT=$(curl -s ${BASE}/functions \ MFUNC_COUNT=$(curl -s ${BASE}/functions \
-H "X-Test-Sub: ${MFUNC_USER}" \ -H "X-Auth-Token: ${MFUNC_USER}" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "${MFUNC_COUNT:-0}" -ge 8 ] && pass "1 юзер создал 10 функций: видно ${MFUNC_COUNT}" || fail "1 юзер: создано только ${MFUNC_COUNT:-0}" [ "${MFUNC_COUNT:-0}" -ge 8 ] && pass "1 юзер создал 10 функций: видно ${MFUNC_COUNT}" || fail "1 юзер: создано только ${MFUNC_COUNT:-0}"
# Удаляем все # Удаляем все
for i in $(seq 1 10); do for i in $(seq 1 10); do
curl -s -X DELETE "${BASE}/functions/mf-${i}" \ curl -s -X DELETE "${BASE}/functions/mf-${i}" \
-H "X-Test-Sub: ${MFUNC_USER}" > /dev/null & -H "X-Auth-Token: ${MFUNC_USER}" > /dev/null &
done done
wait wait
sleep 3 sleep 3
MFUNC_AFTER=$(curl -s ${BASE}/functions \ MFUNC_AFTER=$(curl -s ${BASE}/functions \
-H "X-Test-Sub: ${MFUNC_USER}" \ -H "X-Auth-Token: ${MFUNC_USER}" \
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null) | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null)
[ "${MFUNC_AFTER:-1}" -eq 0 ] && pass "DELETE all: список пуст после удаления 10 функций" || fail "DELETE all: осталось ${MFUNC_AFTER:-?} функций" [ "${MFUNC_AFTER:-1}" -eq 0 ] && pass "DELETE all: список пуст после удаления 10 функций" || fail "DELETE all: осталось ${MFUNC_AFTER:-?} функций"
@@ -683,29 +683,29 @@ ATTACKER_USER="attacker-${RUN_ID}@test.local"
# Жертва создаёт функцию # Жертва создаёт функцию
curl -s -X POST ${BASE}/functions \ curl -s -X POST ${BASE}/functions \
-H "X-Test-Sub: ${VICTIM_USER}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${VICTIM_USER}" -H "Content-Type: application/json" \
-d '{"name":"secret-fn","language":"nodejs","code":"module.exports=async()=>({body:42})"}' > /dev/null -d '{"name":"secret-fn","language":"nodejs","code":"module.exports=async()=>({body:42})"}' > /dev/null
sleep 2 sleep 2
# Атакующий пытается GET чужой функции # Атакующий пытается GET чужой функции
Z_GET=$(curl -s -o /dev/null -w "%{http_code}" \ Z_GET=$(curl -s -o /dev/null -w "%{http_code}" \
"${BASE}/functions/secret-fn" -H "X-Test-Sub: ${ATTACKER_USER}") "${BASE}/functions/secret-fn" -H "X-Auth-Token: ${ATTACKER_USER}")
[ "$Z_GET" = "404" ] && pass "Cross-user GET чужой функции → 404" || fail "Cross-user GET → ожидали 404, got $Z_GET" [ "$Z_GET" = "404" ] && pass "Cross-user GET чужой функции → 404" || fail "Cross-user GET → ожидали 404, got $Z_GET"
# Атакующий пытается UPDATE чужой функции (PUT /functions/{name}/code) # Атакующий пытается UPDATE чужой функции (PUT /functions/{name}/code)
Z_PUT=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \ Z_PUT=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
"${BASE}/functions/secret-fn/code" \ "${BASE}/functions/secret-fn/code" \
-H "X-Test-Sub: ${ATTACKER_USER}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${ATTACKER_USER}" -H "Content-Type: application/json" \
-d '{"code":"module.exports=async()=>({body:\"hacked\"})"}') -d '{"code":"module.exports=async()=>({body:\"hacked\"})"}')
[ "$Z_PUT" = "404" ] && pass "Cross-user UPDATE чужой функции → 404" || fail "Cross-user UPDATE → ожидали 404, got $Z_PUT" [ "$Z_PUT" = "404" ] && pass "Cross-user UPDATE чужой функции → 404" || fail "Cross-user UPDATE → ожидали 404, got $Z_PUT"
# Атакующий пытается DELETE чужой функции # Атакующий пытается DELETE чужой функции
Z_DEL=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ Z_DEL=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
"${BASE}/functions/secret-fn" -H "X-Test-Sub: ${ATTACKER_USER}") "${BASE}/functions/secret-fn" -H "X-Auth-Token: ${ATTACKER_USER}")
[ "$Z_DEL" = "404" ] && pass "Cross-user DELETE чужой функции → 404" || fail "Cross-user DELETE → ожидали 404, got $Z_DEL" [ "$Z_DEL" = "404" ] && pass "Cross-user DELETE чужой функции → 404" || fail "Cross-user DELETE → ожидали 404, got $Z_DEL"
# Жертва проверяет что функция цела # Жертва проверяет что функция цела
Z_VICTIM_LIST=$(curl -s ${BASE}/functions -H "X-Test-Sub: ${VICTIM_USER}" \ Z_VICTIM_LIST=$(curl -s ${BASE}/functions -H "X-Auth-Token: ${VICTIM_USER}" \
| python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='secret-fn'))" 2>/dev/null) | python3 -c "import sys,json; fns=json.load(sys.stdin); print(sum(1 for f in fns if f['metadata']['name']=='secret-fn'))" 2>/dev/null)
[ "${Z_VICTIM_LIST:-0}" -eq 1 ] && pass "Cross-user: функция жертвы цела после атаки" || fail "Cross-user: функция жертвы исчезла!" [ "${Z_VICTIM_LIST:-0}" -eq 1 ] && pass "Cross-user: функция жертвы цела после атаки" || fail "Cross-user: функция жертвы исчезла!"
@@ -720,7 +720,7 @@ LANG_NS="lang-multi-${RUN_ID}@test.local"
PHP_CODE=$(python3 -c "import json; print(json.dumps('<?php\nfunction handler(\$context) {\n \$context[\"response\"]->getBody()->write(\"php-ok\");\n}'))") PHP_CODE=$(python3 -c "import json; print(json.dumps('<?php\nfunction handler(\$context) {\n \$context[\"response\"]->getBody()->write(\"php-ok\");\n}'))")
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: ${LANG_NS}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${LANG_NS}" -H "Content-Type: application/json" \
-d "{\"name\":\"php-fn\",\"language\":\"php\",\"code\":${PHP_CODE},\"ttl\":\"10m\"}") -d "{\"name\":\"php-fn\",\"language\":\"php\",\"code\":${PHP_CODE},\"ttl\":\"10m\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "201" "PHP: create → 201" check_http "$CODE" "201" "PHP: create → 201"
@@ -731,7 +731,7 @@ STATUS=""; RAW=""
for _try in $(seq 1 20); do for _try in $(seq 1 20); do
sleep 10 sleep 10
INV=$(curl -s -X POST ${BASE}/functions/php-fn/invoke \ INV=$(curl -s -X POST ${BASE}/functions/php-fn/invoke \
-H "X-Test-Sub: ${LANG_NS}") -H "X-Auth-Token: ${LANG_NS}")
STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
RAW=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) RAW=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ "$STATUS" = "200" ] && break [ "$STATUS" = "200" ] && break
@@ -745,7 +745,7 @@ echo ">>> БЛОК AB: Ruby — создание и вызов"
RUBY_CODE=$(python3 -c "import json; print(json.dumps('def handler\n \"ruby-ok\"\nend'))") RUBY_CODE=$(python3 -c "import json; print(json.dumps('def handler\n \"ruby-ok\"\nend'))")
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: ${LANG_NS}" -H "Content-Type: application/json" \ -H "X-Auth-Token: ${LANG_NS}" -H "Content-Type: application/json" \
-d "{\"name\":\"ruby-fn\",\"language\":\"ruby\",\"code\":${RUBY_CODE},\"ttl\":\"10m\"}") -d "{\"name\":\"ruby-fn\",\"language\":\"ruby\",\"code\":${RUBY_CODE},\"ttl\":\"10m\"}")
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "201" "Ruby: create → 201" check_http "$CODE" "201" "Ruby: create → 201"
@@ -756,7 +756,7 @@ STATUS=""; RAW=""
for _try in $(seq 1 20); do for _try in $(seq 1 20); do
sleep 10 sleep 10
INV=$(curl -s -X POST ${BASE}/functions/ruby-fn/invoke \ INV=$(curl -s -X POST ${BASE}/functions/ruby-fn/invoke \
-H "X-Test-Sub: ${LANG_NS}") -H "X-Auth-Token: ${LANG_NS}")
STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null) STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
RAW=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null) RAW=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ "$STATUS" = "200" ] && break [ "$STATUS" = "200" ] && break
@@ -769,7 +769,7 @@ echo ""
echo ">>> БЛОК AD: Невалидный язык → 400" echo ">>> БЛОК AD: Невалидный язык → 400"
R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \ R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
-H "X-Test-Sub: lang-inv-${RUN_ID}@test.local" -H "Content-Type: application/json" \ -H "X-Auth-Token: lang-inv-${RUN_ID}@test.local" -H "Content-Type: application/json" \
-d '{"name":"bad-lang-fn","language":"cobol","code":"x"}') -d '{"name":"bad-lang-fn","language":"cobol","code":"x"}')
CODE=$(echo "$R" | tail -1) CODE=$(echo "$R" | tail -1)
check_http "$CODE" "400" "Невалидный язык cobol → 400" check_http "$CODE" "400" "Невалидный язык cobol → 400"