@@ -51,23 +51,23 @@ echo ""
echo ">>> БЛОК A: Фиксы v0.6.8 — регресс"
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)
check_http " $CODE " "404" "INVOKE nonexistent → 404"
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 \
-H "X-Test-Sub : alice@test.local" )
-H "X-Auth-Token : alice@test.local" )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "404" "DELETE nonexistent → 404"
# Создаём изолированные функции и проверяем route isolation
ISO_NAME = " iso- ${ RUN_ID } "
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\"} " )
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\"} " )
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)
@@ -77,9 +77,9 @@ sleep 10
# 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 \
-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_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 "
@@ -90,27 +90,27 @@ echo ""
echo ">>> БЛОК B: Валидация входных данных"
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"}' )
CODE = $( echo " $R " | tail -1) ; check_http " $CODE " "400" "Создание без name → 400"
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"}' )
CODE = $( echo " $R " | tail -1) ; check_http " $CODE " "400" "Создание без code → 400"
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"}' )
CODE = $( echo " $R " | tail -1) ; check_http " $CODE " "400" "Создание без language → 400"
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"}' )
CODE = $( echo " $R " | tail -1) ; check_http " $CODE " "400" "Создание с пустым name → 400"
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"}' )
# Fission не позволяет пробелы в имени — ожидаем 4xx или 502
CODE = $( echo " $R " | tail -1)
@@ -121,11 +121,11 @@ echo ""
echo ">>> БЛОК C: Дублирующее создание"
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
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"}' )
CODE = $( echo " $R " | tail -1)
( [ " $CODE " = "409" ] || [ " $CODE " = "502" ] ) && pass " Дублирующее создание → конфликт (got $CODE ) " || fail " Дублирующее создание → got $CODE "
@@ -135,20 +135,20 @@ echo ""
echo ">>> БЛОК D: UPDATE кода и повторный invoke"
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
sleep 3
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\"});"}' )
CODE = $( echo " $R " | tail -1) ; check_http " $CODE " "200" "UPDATE code → 200"
sleep 10
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 " = "after" ] && pass "После UPDATE — invoke возвращает новый код" || fail " После UPDATE — invoke вернул: $BODY "
@@ -157,18 +157,18 @@ echo ""
echo ">>> БЛОК E: GET функции"
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
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)
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 \
&& pass "GET — имя функции в ответе" || fail " GET — имя не найдено: $BODY "
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"
# ──────────────────────────────────────────────
@@ -178,16 +178,16 @@ echo ">>> БЛОК F: Namespace изоляция LIST"
# u1 создаёт 3 функции, u2 — 1. LIST u1 должен вернуть только свои
for i in 1 2 3; do
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
done
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
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)
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)
[ " $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
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
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"
# Alice сама удаляет — должно быть 200
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"
# Alice повторно удаляет — 404
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"
# ──────────────────────────────────────────────
@@ -222,13 +222,13 @@ echo ">>> БЛОК H: Python — разные форматы кода"
# Формат 1: def main() без аргумента — Fission Python env вызывает main() без аргументов
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
sleep 15
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 " = "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
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
# exports.handler
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
# exports.default
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
sleep 10
for fn_name in js-anon js-handler js-default; do
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 " = "200" ] && pass " NodeJS $fn_name → 200 " || fail " NodeJS $fn_name → status= $STATUS / $( echo $INV ) "
done
@@ -265,20 +265,20 @@ echo ""
echo ">>> БЛОК J: TTL — создание и проверка"
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\"} " \
| 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 отсутствует"
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\"} " \
| 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 присутствует (не ожидалось)"
# Невалидный TTL
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\"} " )
CODE = $( echo " $R " | tail -1)
[ " $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 (всегда доступен)
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
sleep 12
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"}' )
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)
@@ -307,14 +307,14 @@ echo ""
echo ">>> БЛОК L: Stress — 10 параллельных invoke"
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
sleep 8
STRESS_OK = 0
for i in $( seq 1 10) ; do
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)
[ " $ST " = "200" ] && STRESS_OK = $(( STRESS_OK+1))
done &
@@ -326,7 +326,7 @@ PIDS=()
TMPDIR_S = $( mktemp -d)
for i in $( seq 1 10) ; do
( 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 \
&& touch " ${ TMPDIR_S } /ok_ $i " || true ) &
done
@@ -339,7 +339,7 @@ rm -rf "${TMPDIR_S}"
echo ""
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)
[ " $COUNT " = "0" ] && pass "LIST новый пользователь → пустой список" || fail " LIST новый пользователь → $COUNT "
@@ -348,15 +348,15 @@ echo ""
echo ">>> БЛОК N: DELETE полная цепочка (fn + pkg + trigger)"
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
# Получаем 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)
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"
# Ждём 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
# kubectl недоступен — проверяем через API (GET должен давать 404)
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 "
else
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
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"
# ──────────────────────────────────────────────
@@ -386,7 +386,7 @@ echo ">>> БЛОК O: Auth — без заголовка"
R = $( curl -s -w "\n%{http_code}" ${ BASE } /functions)
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 ""
@@ -411,39 +411,39 @@ echo ">>> БЛОК Q: Дубликат → 409, невалидное имя →
# Создаём функцию, потом снова с тем же именем → 409
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
sleep 3
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'})\"} " )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "409" "Дубликат → 409 Conflict"
# Имя с заглавной буквой → 400
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\"}" )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Имя с заглавной буквой → 400"
# Имя начинается с дефиса → 400
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\"}" )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Имя с ведущим дефисом → 400"
# Имя заканчивается дефисом → 400
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\"}" )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Имя с завершающим дефисом → 400"
# Имя с пробелами → 400 (было 502)
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\"}" )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Имя с пробелом → 400 (не 502)"
@@ -451,7 +451,7 @@ check_http "$CODE" "400" "Имя с пробелом → 400 (не 502)"
# Имя 64 символа → 400
LONGNAME = $( python3 -c "print('a'*64)" )
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\"} " )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Имя 64 символа → 400"
@@ -459,7 +459,7 @@ check_http "$CODE" "400" "Имя 64 символа → 400"
# Имя 58 символов → 400 (route = 64 > 63, Fission webhook отклонит)
NAME58 = $( python3 -c "print('a'*58)" )
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\"} " )
CODE = $( echo " $R " | tail -1)
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)" )
# Первый прогрев namespace
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
sleep 8
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'})\"} " )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "201" "Имя 57 символов → принято (201)"
@@ -490,7 +490,7 @@ body = json.dumps({'name':'big-fn','language':'nodejs','code':code})
sys.stdout.write(body)
" > " $BIG_TMP "
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 " )
rm -f " $BIG_TMP "
CODE = $( echo " $R " | tail -1)
@@ -502,7 +502,7 @@ CODE=$(echo "$R" | tail -1)
# Код ровно 1000 символов → не 400
CODE1K = $( python3 -c "print('module.exports=async()=>({body:' + '\"x\"' + '});' + ' '*(1000-38))" )
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'})\"} " )
CODE = $( echo " $R " | tail -1)
[ " $CODE " = "201" ] && pass "Код 1000 байт → принят (201)" || fail " Код 1000 байт → got $CODE "
@@ -513,14 +513,14 @@ echo ">>> БЛОК S: TTL expire + reaper очистка"
# Создаём функцию с TTL=1m
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\"} " )
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 "
# Сразу должна быть видна в LIST
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 "
# ──────────────────────────────────────────────
@@ -533,14 +533,14 @@ RACE_RESULTS=""
for i in $( seq 1 5) ; do
RACE_CODE = " module.exports=async()=>({body:'r ${ i } '}) "
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 } \"} " &
done
wait
# Проверяем что namespace создался и функции (хоть часть) прошли
sleep 5
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 "
# ──────────────────────────────────────────────
@@ -552,7 +552,7 @@ for uid in $(seq 1 10); do
U = " u ${ uid } - ${ RUN_ID } @test.local "
CODE = " module.exports=async()=>({body:'user ${ uid } '}) "
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
) &
done
@@ -562,7 +562,7 @@ sleep 3
OK_USERS = 0
for uid in $( seq 1 10) ; do
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))
done
[ " $OK_USERS " -ge 8 ] && pass " 10 юзеров параллельно: ${ OK_USERS } /10 создали функции " || fail " 10 юзеров параллельно: только ${ OK_USERS } /10 "
@@ -574,7 +574,7 @@ echo ">>> БЛОК V: Изоляция — 10 юзеров не видят чу
ISOLATION_OK = 0
for uid in $( seq 1 10) ; do
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 от других пользователей не попадают)
# Каждый видит ровно 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)
@@ -592,7 +592,7 @@ echo ">>> БЛОК W: 5 юзеров одновременно вызывают
for uid in $( seq 1 5) ; do
(
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)
if [ -n " $NS12 " ] ; then
INVOKE_CODE = $( curl -s -o /dev/null -w "%{http_code}" \
@@ -620,20 +620,20 @@ for uid in $(seq 1 3); do
# CREATE
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 } \"} " )
# LIST — функция есть (sleep чтобы k8s успел зарегистрировать)
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)
# 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 — функции нет
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)
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 "
for i in $( seq 1 10) ; do
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 &
done
wait
sleep 3
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)
[ " ${ MFUNC_COUNT :- 0 } " -ge 8 ] && pass " 1 юзер создал 10 функций: видно ${ MFUNC_COUNT } " || fail " 1 юзер: создано только ${ MFUNC_COUNT :- 0 } "
# Удаляем все
for i in $( seq 1 10) ; do
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
wait
sleep 3
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)
[ " ${ 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 \
-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
sleep 2
# Атакующий пытается GET чужой функции
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 "
# Атакующий пытается UPDATE чужой функции (PUT /functions/{name}/code)
Z_PUT = $( curl -s -o /dev/null -w "%{http_code}" -X PUT \
" ${ 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\"})"}' )
[ " $Z_PUT " = "404" ] && pass "Cross-user UPDATE чужой функции → 404" || fail " Cross-user UPDATE → ожидали 404, got $Z_PUT "
# Атакующий пытается 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_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)
[ " ${ 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}'))" )
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\"} " )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "201" "PHP: create → 201"
@@ -731,7 +731,7 @@ STATUS=""; RAW=""
for _try in $( seq 1 20) ; do
sleep 10
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)
RAW = $( echo " $INV " | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ " $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'))" )
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\"} " )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "201" "Ruby: create → 201"
@@ -756,7 +756,7 @@ STATUS=""; RAW=""
for _try in $( seq 1 20) ; do
sleep 10
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)
RAW = $( echo " $INV " | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
[ " $STATUS " = "200" ] && break
@@ -769,7 +769,7 @@ echo ""
echo ">>> БЛОК AD: Невалидный язык → 400"
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"}' )
CODE = $( echo " $R " | tail -1)
check_http " $CODE " "400" "Невалидный язык cobol → 400"