805 lines
35 KiB
Bash
Executable File
805 lines
35 KiB
Bash
Executable File
#!/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
|
||
}
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
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 ]
|