tests: добавить все тест-скрипты локально

This commit is contained in:
“Naeel”
2026-05-03 16:39:35 +04:00
parent 8211485501
commit bf9ada20a2
19 changed files with 1051 additions and 3673 deletions
-812
View File
@@ -1,812 +0,0 @@
#!/usr/bin/env bash
# test_simple_function.sh — интеграционные тесты fission_simple_function
# Запуск: bash test_simple_function.sh
# Требования: terraform + kubectl в PATH, kubeconfig в ~/.kube/config
set -uo pipefail
PASS=0; FAIL=0; SKIP=0
NAMESPACE="default"
TESTDIR="/tmp/sless-sf-tests"
KUBECONFIG_PATH="/home/naeel/.kube/config"
PROVIDER_NS="default"
# уникальный префикс — не пересекаемся с prod
RUN_ID="sf$(date +%s | tail -c 6)"
TF_PROVIDER='
terraform {
required_providers {
fission = { source = "nail/fission" }
}
}
provider "fission" {
kubeconfig_path = "/home/naeel/.kube/config"
namespace = "default"
}
'
green() { echo -e "\033[32m$*\033[0m"; }
red() { echo -e "\033[31m$*\033[0m"; }
yellow(){ echo -e "\033[33m$*\033[0m"; }
pass() { PASS=$((PASS+1)); green " PASS: $1"; }
fail() { FAIL=$((FAIL+1)); red " FAIL: $1"; }
skip() { SKIP=$((SKIP+1)); yellow " SKIP: $1"; }
# ── helpers ──────────────────────────────────────────────────────────────────
kube_exists() {
local kind=$1 name=$2
kubectl get "${kind}" -n "${NAMESPACE}" "${name}" &>/dev/null
}
kube_gone() {
local kind=$1 name=$2
! kubectl get "${kind}" -n "${NAMESPACE}" "${name}" &>/dev/null
}
tf_apply() {
local dir=$1
terraform -chdir="${dir}" apply -auto-approve -no-color 2>&1
}
tf_destroy() {
local dir=$1
terraform -chdir="${dir}" destroy -auto-approve -no-color 2>&1
}
tf_plan_exitcode() {
# 0=no-changes, 1=error, 2=changes
local dir=$1
terraform -chdir="${dir}" plan -detailed-exitcode -no-color 2>&1
echo $?
}
wait_package_ready() {
local name=$1 retries=30
for ((i=0; i<retries; i++)); do
local status
status=$(kubectl get packages.fission.io -n "${NAMESPACE}" "${name}" \
-o jsonpath='{.status.buildstatus}' 2>/dev/null || echo "")
# для literal packages buildstatus пустой — это нормально
[[ "$status" == "succeeded" || "$status" == "" ]] && return 0
sleep 2
done
return 1
}
# ── setup / teardown ─────────────────────────────────────────────────────────
setup_workdir() {
local name=$1
local dir="${TESTDIR}/${name}"
rm -rf "${dir}"
mkdir -p "${dir}"
echo "${dir}"
}
write_provider() {
local dir=$1
cat > "${dir}/main.tf" <<EOF
${TF_PROVIDER}
EOF
}
# ── code fixtures ─────────────────────────────────────────────────────────────
write_js_v1() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.js" <<'EOF'
exports.handler = async (ctx) => ({
body: JSON.stringify({ msg: "hello-v1", ts: Date.now() }),
headers: { "Content-Type": "application/json" },
});
EOF
}
write_js_v2() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.js" <<'EOF'
exports.handler = async (ctx) => ({
body: JSON.stringify({ msg: "hello-v2-updated", ts: Date.now() }),
headers: { "Content-Type": "application/json" },
});
EOF
}
write_python() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.py" <<'EOF'
def main():
return {"status": 200, "body": '{"msg":"hello-python"}'}
EOF
}
write_ruby() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.rb" <<'EOF'
def main(ctx)
{ body: "hello-ruby", status_code: 200 }
end
EOF
}
write_php() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.php" <<'EOF'
<?php
function main($context) {
return ["body" => "hello-php"];
}
EOF
}
write_perl() {
local dir=$1
mkdir -p "${dir}"
cat > "${dir}/main.pl" <<'EOF'
sub main {
my ($context) = @_;
return { body => "hello-perl" };
}
1;
EOF
}
EOF
cat > "${dir}/go.mod" <<'EOF'
module fn
go 1.21
EOF
}
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo "================================================================"
echo " fission_simple_function — интеграционные тесты"
echo " Run ID: ${RUN_ID}"
echo "================================================================"
mkdir -p "${TESTDIR}"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T01: nodejs — create с HTTPTrigger, verify k8s, destroy"
# ════════════════════════════════════════════════════════════════════════
T01="${RUN_ID}-node-basic"
D=$(setup_workdir "t01")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T01}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T01}"
methods = ["GET"]
}
EOF
OUT=$(tf_apply "${D}")
if echo "${OUT}" | grep -q "Apply complete"; then
pass "T01: terraform apply OK"
else
fail "T01: terraform apply FAILED"
echo "${OUT}" | tail -20
fi
kube_exists "functions.fission.io" "${T01}" \
&& pass "T01: Function создана в k8s" || fail "T01: Function не найдена в k8s"
kube_exists "packages.fission.io" "${T01}-pkg" \
&& pass "T01: Package создан в k8s" || fail "T01: Package не найден в k8s"
kube_exists "httptriggers.fission.io" "${T01}-trigger" \
&& pass "T01: HTTPTrigger создан в k8s" || fail "T01: HTTPTrigger не найден в k8s"
kube_exists "environments.fission.io" "simple-nodejs-env" \
&& pass "T01: Environment simple-nodejs-env создан" || fail "T01: Environment не найден"
OUT=$(tf_destroy "${D}")
echo "${OUT}" | grep -q "Destroy complete" \
&& pass "T01: terraform destroy OK" || fail "T01: terraform destroy FAILED"
kube_gone "functions.fission.io" "${T01}" && pass "T01: Function удалена после destroy" || fail "T01: Function осталась после destroy"
kube_gone "packages.fission.io" "${T01}-pkg" && pass "T01: Package удалён после destroy" || fail "T01: Package остался после destroy"
kube_gone "httptriggers.fission.io" "${T01}-trigger" && pass "T01: Trigger удалён после destroy" || fail "T01: Trigger остался после destroy"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T02: python — create БЕЗ url (нет HTTPTrigger)"
# ════════════════════════════════════════════════════════════════════════
T02="${RUN_ID}-py-notrigger"
D=$(setup_workdir "t02")
write_provider "${D}"
write_python "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T02}"
runtime = "python"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" \
&& pass "T02: apply OK" || fail "T02: apply FAILED"
kube_exists "functions.fission.io" "${T02}" && pass "T02: Function создана" || fail "T02: Function не найдена"
kube_exists "packages.fission.io" "${T02}-pkg" && pass "T02: Package создан" || fail "T02: Package не найден"
kube_gone "httptriggers.fission.io" "${T02}-trigger" \
&& pass "T02: HTTPTrigger НЕ создан (url не задан)" || fail "T02: HTTPTrigger создан — не должен был"
kube_exists "environments.fission.io" "simple-python-env" \
&& pass "T02: Environment simple-python-env создан" || fail "T02: Environment не найден"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T02: destroy OK" || fail "T02: destroy FAILED"
kube_gone "functions.fission.io" "${T02}" && pass "T02: Function удалена" || fail "T02: Function осталась"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T04: ruby — create с URL"
# ════════════════════════════════════════════════════════════════════════
T04="${RUN_ID}-ruby"
D=$(setup_workdir "t04")
write_provider "${D}"
write_ruby "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T04}"
runtime = "ruby"
code_dir = "\${path.module}/code"
url = "/test/${T04}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" \
&& pass "T04: ruby apply OK" || fail "T04: ruby apply FAILED"
kube_exists "environments.fission.io" "simple-ruby-env" \
&& pass "T04: simple-ruby-env создан" || fail "T04: simple-ruby-env не найден"
kube_exists "httptriggers.fission.io" "${T04}-trigger" \
&& pass "T04: Trigger создан" || fail "T04: Trigger не найден"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T04: destroy OK" || fail "T04: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T05: php — create с URL"
# ════════════════════════════════════════════════════════════════════════
T05="${RUN_ID}-php"
D=$(setup_workdir "t05")
write_provider "${D}"
write_php "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T05}"
runtime = "php"
code_dir = "\${path.module}/code"
url = "/test/${T05}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" \
&& pass "T05: php apply OK" || fail "T05: php apply FAILED"
kube_exists "environments.fission.io" "simple-php-env" \
&& pass "T05: simple-php-env создан" || fail "T05: simple-php-env не найден"
kube_exists "functions.fission.io" "${T05}" \
&& pass "T05: Function создана" || fail "T05: Function не найдена"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T05: destroy OK" || fail "T05: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T06: perl — create с URL"
# ════════════════════════════════════════════════════════════════════════
T06="${RUN_ID}-perl"
D=$(setup_workdir "t06")
write_provider "${D}"
write_perl "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T06}"
runtime = "perl"
code_dir = "\${path.module}/code"
url = "/test/${T06}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" \
&& pass "T06: perl apply OK" || fail "T06: perl apply FAILED"
kube_exists "environments.fission.io" "simple-perl-env" \
&& pass "T06: simple-perl-env создан" || fail "T06: simple-perl-env не найден"
kube_exists "functions.fission.io" "${T06}" \
&& pass "T06: Function создана" || fail "T06: Function не найдена"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T06: destroy OK" || fail "T06: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T07: code_hash drift detection — update пересчитывает хеш и обновляет Package"
# ════════════════════════════════════════════════════════════════════════
T07="${RUN_ID}-drift"
D=$(setup_workdir "t07")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T07}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T07: apply v1 OK" || fail "T07: apply v1 FAILED"
HASH1=$(terraform -chdir="${D}" show -json 2>/dev/null \
| python3 -c "import sys,json; r=json.load(sys.stdin)['values']['root_module']['resources']; print(r[0]['values']['code_hash'])" 2>/dev/null || echo "?")
# Обновляем код (v2)
write_js_v2 "${D}/code"
OUT=$(tf_apply "${D}")
echo "${OUT}" | grep -q "Apply complete" && pass "T07: apply v2 OK" || fail "T07: apply v2 FAILED"
HASH2=$(terraform -chdir="${D}" show -json 2>/dev/null \
| python3 -c "import sys,json; r=json.load(sys.stdin)['values']['root_module']['resources']; print(r[0]['values']['code_hash'])" 2>/dev/null || echo "?")
[ "${HASH1}" != "${HASH2}" ] && [ "${HASH1}" != "?" ] && [ "${HASH2}" != "?" ] \
&& pass "T07: code_hash изменился (${HASH1:0:8}… → ${HASH2:0:8}…)" \
|| fail "T07: code_hash не изменился или не читается (h1=${HASH1:0:8}, h2=${HASH2:0:8})"
echo "${OUT}" | grep -qE "fission_simple_function.fn.*updated|updated.*fission_simple_function.fn" \
&& pass "T07: Package был обновлён (update in apply output)" \
|| { echo "${OUT}" | grep -q "1 updated" && pass "T07: 1 ресурс обновлён" || fail "T07: Обновление не обнаружено в output"; }
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T07: destroy OK" || fail "T07: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T08: добавление url (update — создание Trigger)"
# ════════════════════════════════════════════════════════════════════════
T08="${RUN_ID}-addurl"
D=$(setup_workdir "t08")
write_provider "${D}"
write_js_v1 "${D}/code"
# Apply без url
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T08}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T08: apply (no url) OK" || fail "T08: apply (no url) FAILED"
kube_gone "httptriggers.fission.io" "${T08}-trigger" \
&& pass "T08: Trigger не создан (нет url)" || fail "T08: Trigger создан без url"
# Добавляем url
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T08}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T08}"
methods = ["GET", "POST"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T08: apply (with url) OK" || fail "T08: apply (with url) FAILED"
kube_exists "httptriggers.fission.io" "${T08}-trigger" \
&& pass "T08: Trigger создан после добавления url" || fail "T08: Trigger не создан после update"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T08: destroy OK" || fail "T08: destroy FAILED"
kube_gone "httptriggers.fission.io" "${T08}-trigger" \
&& pass "T08: Trigger удалён при destroy" || fail "T08: Trigger остался"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T09: удаление url (update — удаление Trigger)"
# ════════════════════════════════════════════════════════════════════════
T09="${RUN_ID}-rmurl"
D=$(setup_workdir "t09")
write_provider "${D}"
write_js_v1 "${D}/code"
# Apply с url
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T09}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T09}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T09: apply (with url) OK" || fail "T09: apply (with url) FAILED"
kube_exists "httptriggers.fission.io" "${T09}-trigger" \
&& pass "T09: Trigger создан" || fail "T09: Trigger не создан"
# Убираем url
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T09}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T09: apply (url removed) OK" || fail "T09: apply (url removed) FAILED"
kube_gone "httptriggers.fission.io" "${T09}-trigger" \
&& pass "T09: Trigger удалён после убирания url" || fail "T09: Trigger остался после убирания url"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T09: destroy OK" || fail "T09: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T10: изменение url (update Trigger)"
# ════════════════════════════════════════════════════════════════════════
T10="${RUN_ID}-churl"
D=$(setup_workdir "t10")
write_provider "${D}"
write_js_v1 "${D}/code"
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T10}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T10}/v1"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T10: apply v1 url OK" || fail "T10: apply v1 url FAILED"
URL_BEFORE=$(kubectl get httptriggers.fission.io -n "${NAMESPACE}" "${T10}-trigger" \
-o jsonpath='{.spec.relativeurl}' 2>/dev/null || echo "?")
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T10}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T10}/v2"
methods = ["GET", "PUT"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T10: apply v2 url OK" || fail "T10: apply v2 url FAILED"
URL_AFTER=$(kubectl get httptriggers.fission.io -n "${NAMESPACE}" "${T10}-trigger" \
-o jsonpath='{.spec.relativeurl}' 2>/dev/null || echo "?")
[ "${URL_BEFORE}" = "/test/${T10}/v1" ] && [ "${URL_AFTER}" = "/test/${T10}/v2" ] \
&& pass "T10: URL обновлён (/v1 → /v2)" \
|| fail "T10: URL не обновлён (before='${URL_BEFORE}', after='${URL_AFTER}')"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T10: destroy OK" || fail "T10: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T11: plan без изменений → 0 changes"
# ════════════════════════════════════════════════════════════════════════
T11="${RUN_ID}-nochange"
D=$(setup_workdir "t11")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T11}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T11}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T11: initial apply OK" || fail "T11: initial apply FAILED"
PLAN_OUT=$(terraform -chdir="${D}" plan -no-color 2>&1)
echo "${PLAN_OUT}" | grep -qE "No changes|0 to add, 0 to change, 0 to destroy" \
&& pass "T11: повторный plan показывает 0 changes" \
|| fail "T11: повторный plan показывает изменения (не idempotent)"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T11: destroy OK" || fail "T11: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T12: auto-env idempotency — два ресурса делят один environment"
# ════════════════════════════════════════════════════════════════════════
T12A="${RUN_ID}-shared-a"
T12B="${RUN_ID}-shared-b"
D=$(setup_workdir "t12")
write_provider "${D}"
write_js_v1 "${D}/code-a"
write_js_v1 "${D}/code-b"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn_a" {
name = "${T12A}"
runtime = "nodejs"
code_dir = "\${path.module}/code-a"
}
resource "fission_simple_function" "fn_b" {
name = "${T12B}"
runtime = "nodejs"
code_dir = "\${path.module}/code-b"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T12: apply двух функций OK" || fail "T12: apply двух функций FAILED"
kube_exists "functions.fission.io" "${T12A}" && pass "T12: fn_a создана" || fail "T12: fn_a не создана"
kube_exists "functions.fission.io" "${T12B}" && pass "T12: fn_b создана" || fail "T12: fn_b не создана"
# Environment должен быть один (один simple-nodejs-env)
ENV_COUNT=$(kubectl get environments.fission.io -n "${NAMESPACE}" -o name 2>/dev/null \
| grep -c "simple-nodejs-env" || echo "0")
[ "${ENV_COUNT}" -eq 1 ] \
&& pass "T12: ровно 1 экземпляр simple-nodejs-env (shared)" \
|| fail "T12: ENV_COUNT=${ENV_COUNT} (ожидалось 1)"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T12: destroy OK" || fail "T12: destroy FAILED"
kube_gone "functions.fission.io" "${T12A}" && pass "T12: fn_a удалена" || fail "T12: fn_a осталась"
kube_gone "functions.fission.io" "${T12B}" && pass "T12: fn_b удалена" || fail "T12: fn_b осталась"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T13: невалидный runtime → apply завершается ошибкой"
# ════════════════════════════════════════════════════════════════════════
T13="${RUN_ID}-badruntime"
D=$(setup_workdir "t13")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T13}"
runtime = "cobol"
code_dir = "\${path.module}/code"
}
EOF
OUT=$(tf_apply "${D}" 2>&1 || true)
echo "${OUT}" | grep -qiE "Error|error|cobol|неподдерживаемый|unsupported" \
&& pass "T13: apply с runtime=cobol выдал ошибку" \
|| fail "T13: apply не выдал ошибку для runtime=cobol"
kube_gone "functions.fission.io" "${T13}" \
&& pass "T13: Function не создана" || fail "T13: Function создана для плохого runtime"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T14: terraform import (namespace/name)"
# ════════════════════════════════════════════════════════════════════════
T14="${RUN_ID}-import"
D=$(setup_workdir "t14")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T14}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T14}"
methods = ["GET"]
}
EOF
# Создаём через apply
tf_apply "${D}" | grep -q "Apply complete" && pass "T14: initial apply OK" || fail "T14: initial apply FAILED"
# Удаляем state, импортируем обратно
terraform -chdir="${D}" state rm fission_simple_function.fn &>/dev/null || true
IMP_OUT=$(terraform -chdir="${D}" import -no-color \
fission_simple_function.fn "default/${T14}" 2>&1)
echo "${IMP_OUT}" | grep -qiE "Import successful|import" \
&& pass "T14: import выполнен" || fail "T14: import не прошёл: $(echo "${IMP_OUT}" | tail -5)"
# Plan после import должен показать изменения (code_dir/hash не в k8s state)
# — это нормально, просто убеждаемся что plan не падает с ошибкой
PLAN_OUT=$(terraform -chdir="${D}" plan -no-color 2>&1)
echo $? | grep -qE "^[012]$" && pass "T14: plan после import не падает" || fail "T14: plan после import упал"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T14: destroy OK" || fail "T14: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T15: nodejs — методы по умолчанию [GET]"
# ════════════════════════════════════════════════════════════════════════
T15="${RUN_ID}-defmethods"
D=$(setup_workdir "t15")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T15}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T15}"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T15: apply (no methods) OK" || fail "T15: apply FAILED"
METHODS=$(kubectl get httptriggers.fission.io -n "${NAMESPACE}" "${T15}-trigger" \
-o jsonpath='{.spec.methods[*]}' 2>/dev/null || echo "?")
echo "${METHODS}" | grep -q "GET" \
&& pass "T15: default method=GET в k8s: '${METHODS}'" \
|| fail "T15: методы не GET: '${METHODS}'"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T15: destroy OK" || fail "T15: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T16: Package.spec для nodejs — deployment (не source)"
# ════════════════════════════════════════════════════════════════════════
T16="${RUN_ID}-nodepkg"
D=$(setup_workdir "t16")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T16}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T16: apply OK" || fail "T16: apply FAILED"
SRC=$(kubectl get packages.fission.io -n "${NAMESPACE}" "${T16}-pkg" \
-o jsonpath='{.spec.source.type}' 2>/dev/null || echo "")
DEP=$(kubectl get packages.fission.io -n "${NAMESPACE}" "${T16}-pkg" \
-o jsonpath='{.spec.deployment.type}' 2>/dev/null || echo "")
[ -z "${SRC}" ] && [ "${DEP}" = "literal" ] \
&& pass "T16: nodejs Package использует deployment.literal (не source)" \
|| fail "T16: pkg spec — source.type='${SRC}', deployment.type='${DEP}'"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T16: destroy OK" || fail "T16: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T17: namespace вычисляется из провайдера"
# ════════════════════════════════════════════════════════════════════════
T17="${RUN_ID}-ns"
D=$(setup_workdir "t17")
write_js_v1 "${D}/code"
# Провайдер без явного namespace в ресурсе
cat > "${D}/main.tf" <<EOF
${TF_PROVIDER}
resource "fission_simple_function" "fn" {
name = "${T17}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T17: apply OK" || fail "T17: apply FAILED"
NS=$(terraform -chdir="${D}" show -json 2>/dev/null \
| python3 -c "import sys,json; r=json.load(sys.stdin)['values']['root_module']['resources']; print(r[0]['values']['namespace'])" 2>/dev/null || echo "?")
[ "${NS}" = "default" ] \
&& pass "T17: namespace=default из провайдера (state: ${NS})" \
|| fail "T17: namespace='${NS}' (ожидалось 'default')"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T17: destroy OK" || fail "T17: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T18: computed поля environment_name/package_name/trigger_name в state"
# ════════════════════════════════════════════════════════════════════════
T18="${RUN_ID}-computed"
D=$(setup_workdir "t18")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T18}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T18}"
methods = ["GET"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T18: apply OK" || fail "T18: apply FAILED"
SHOW=$(terraform -chdir="${D}" show -json 2>/dev/null \
| python3 -c "
import sys, json
r = json.load(sys.stdin)['values']['root_module']['resources'][0]['values']
print(r.get('environment_name','?'))
print(r.get('package_name','?'))
print(r.get('trigger_name','?'))
print(r.get('code_hash','?'))
" 2>/dev/null || echo "?
?
?
?")
ENV_NAME=$(echo "${SHOW}" | sed -n '1p')
PKG_NAME=$(echo "${SHOW}" | sed -n '2p')
TRG_NAME=$(echo "${SHOW}" | sed -n '3p')
HASH_VAL=$(echo "${SHOW}" | sed -n '4p')
[ "${ENV_NAME}" = "simple-nodejs-env" ] && pass "T18: environment_name='${ENV_NAME}'" || fail "T18: environment_name='${ENV_NAME}'"
[ "${PKG_NAME}" = "${T18}-pkg" ] && pass "T18: package_name='${PKG_NAME}'" || fail "T18: package_name='${PKG_NAME}'"
[ "${TRG_NAME}" = "${T18}-trigger" ] && pass "T18: trigger_name='${TRG_NAME}'" || fail "T18: trigger_name='${TRG_NAME}'"
[[ "${HASH_VAL}" =~ ^[0-9a-f]{64}$ ]] && pass "T18: code_hash выглядит как sha256" || fail "T18: code_hash='${HASH_VAL}'"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T18: destroy OK" || fail "T18: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T19: повторный apply одного ресурса — idempotency (no drift)"
# ════════════════════════════════════════════════════════════════════════
T19="${RUN_ID}-idem"
D=$(setup_workdir "t19")
write_provider "${D}"
write_js_v1 "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T19}"
runtime = "nodejs"
code_dir = "\${path.module}/code"
url = "/test/${T19}"
methods = ["GET", "POST"]
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T19: первый apply OK" || fail "T19: первый apply FAILED"
OUT2=$(tf_apply "${D}")
echo "${OUT2}" | grep -qE "Apply complete.*0 added, 0 changed, 0 destroyed" \
&& pass "T19: второй apply — 0 changes (idempotent)" \
|| fail "T19: второй apply не idempotent: $(echo "${OUT2}" | grep -E 'added|changed|destroyed' | tail -2)"
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T19: destroy OK" || fail "T19: destroy FAILED"
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T20: destroy без trigger (url не задан) — нет паники"
# ════════════════════════════════════════════════════════════════════════
T20="${RUN_ID}-notrig-del"
D=$(setup_workdir "t20")
write_provider "${D}"
write_python "${D}/code"
cat >> "${D}/main.tf" <<EOF
resource "fission_simple_function" "fn" {
name = "${T20}"
runtime = "python"
code_dir = "\${path.module}/code"
}
EOF
tf_apply "${D}" | grep -q "Apply complete" && pass "T20: apply OK" || fail "T20: apply FAILED"
OUT=$(tf_destroy "${D}")
echo "${OUT}" | grep -q "Destroy complete" \
&& pass "T20: destroy OK (нет паники без trigger)" \
|| fail "T20: destroy FAILED"
echo "${OUT}" | grep -qi "panic\|nil pointer" \
&& fail "T20: PANIC в destroy!" || pass "T20: нет паники при destroy"
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo "================================================================"
if [ "${FAIL}" -eq 0 ]; then
green " ИТОГ: PASS=${PASS} FAIL=${FAIL} SKIP=${SKIP} — ВСЕ ТЕСТЫ ПРОШЛИ"
else
red " ИТОГ: PASS=${PASS} FAIL=${FAIL} SKIP=${SKIP} — ЕСТЬ ПРОВАЛЫ"
fi
echo "================================================================"
echo ""
# Очистка temp dirs
rm -rf "${TESTDIR}"
[ "${FAIL}" -eq 0 ]