Files
2026-06-27 16:43:36 +00:00

190 lines
6.0 KiB
Bash
Executable File

#!/bin/bash
# test-deploy.sh — Smoke test for the portainer-deploy-action deploy.sh
# Mocks Portainer's HTTP responses via a wrapper script on PATH.
set -e
TEST_DIR="$(mktemp -d)"
cd "$TEST_DIR"
export PATH="$TEST_DIR/bin:$PATH"
mkdir -p bin
cat > bin/curl <<'CURL_EOF'
#!/bin/bash
# Mock curl. Routes Portainer API calls to canned responses.
# Returns body on stdout, status code on fd 3, and honors -w "\n%{http_code}".
# Args are inspected; -X METHOD, url, -d BODY are the key ones.
METHOD=""
URL=""
BODY=""
DUMP_STATUS=0
STATUS=200
while [ $# -gt 0 ]; do
case "$1" in
-X) shift 2;;
-w) DUMP_STATUS=1; shift 2;; # -w takes a value
-H) shift 2;; # -H takes a value
-d) shift 2;; # -d takes a value
-s|--max-time|--output|-o|-sf) shift 1;; # these don't take a value
-*) shift 1;;
*) URL="$1"; shift;;
esac
done
case "$URL" in
*api/stacks\?endpointId=*containers*)
# Healthcheck against external URL — real curl behavior
exec /usr/bin/curl "$@"
;;
*containers/json*)
# Return two containers: the "current" and the "previous" one
# to simulate rollback history.
echo '[
{"Names":["/iron-requiem"],"Labels":{"gitea-deploy-orchestrator.image-digest":"git.homelab.local:8443/kaykayyali/iron-requiem:current99","gitea-deploy-orchestrator.stack":"iron-requiem"}},
{"Names":["/iron-requiem-prev"],"Labels":{"gitea-deploy-orchestrator.image-digest":"git.homelab.local:8443/kaykayyali/iron-requiem:abc123d","gitea-deploy-orchestrator.stack":"iron-requiem"}}
]'
STATUS=200
;;
*api/stacks/create*)
# create new — must come before *api/stacks so it doesn't match prefix
echo '{"Id":42,"Name":"new-stack"}'
STATUS=200
;;
*api/stacks/7*)
# update existing
echo '{"Id":7,"Name":"iron-requiem"}'
STATUS=200
;;
*api/stacks*)
# list stacks — matches both with and without query string
if [ "${MOCK_STACK_EXISTS:-true}" = "true" ]; then
echo '[{"Id":7,"Name":"iron-requiem","EndpointId":1}]'
else
echo '[]'
fi
STATUS=200
;;
*)
echo '{"message":"unknown mock URL: '"$URL"'"}'
STATUS=500
;;
esac
if [ "$DUMP_STATUS" = "1" ]; then
echo ""
echo "$STATUS"
fi
CURL_EOF
chmod +x bin/curl
# Set up a sample compose file
cat > docker-compose.yml <<'COMPOSE'
services:
iron-requiem:
image: git.homelab.local:8443/kaykayyali/iron-requiem:latest
container_name: iron-requiem
networks:
- hermes-net
networks:
hermes-net:
external: true
COMPOSE
# Disable healthcheck by setting URL empty
export HEALTHCHECK_URL=""
export STACK_NAME="iron-requiem"
export PORTAINER_URL="http://mock:9000"
export PORTAINER_TOKEN="fake-token"
export ENDPOINT_ID="1"
export COMPOSE_FILE="$TEST_DIR/docker-compose.yml"
export OUTPUT_FILE="$TEST_DIR/output.json"
export IMAGE=""
export IMAGE_DIGEST=""
export ROLLBACK="false"
export FORCE_PULL="true"
export PRUNE="true"
export MOCK_STACK_EXISTS="true"
echo "=== Test 1: update existing stack ==="
/tmp/portainer-deploy-action/deploy.sh
echo "Output:"
cat "$OUTPUT_FILE"
echo ""
jq -e '.status == "success" and .action == "update" and .stack_id == "7"' "$OUTPUT_FILE" >/dev/null && echo "✓ Test 1 passed" || { echo "✗ Test 1 FAILED"; exit 1; }
echo ""
echo "=== Test 2: create new stack ==="
unset STACK_ID # clear
export MOCK_STACK_EXISTS="false"
export STACK_NAME="new-stack"
/tmp/portainer-deploy-action/deploy.sh
cat "$OUTPUT_FILE"
echo ""
jq -e '.status == "success" and .action == "create" and .stack_id == "42"' "$OUTPUT_FILE" >/dev/null && echo "✓ Test 2 passed" || { echo "✗ Test 2 FAILED"; exit 1; }
echo ""
echo "=== Test 3: image pin rewrites compose ==="
export STACK_NAME="iron-requiem"
export MOCK_STACK_EXISTS="true"
export IMAGE="git.homelab.local:8443/kaykayyali/iron-requiem:abc123d"
/tmp/portainer-deploy-action/deploy.sh
# Verify the request body sent to Portainer included the pinned image
# (We can't easily check that with this mock, but we can verify the script ran)
/tmp/portainer-deploy-action/deploy.sh 2>&1 | grep -q "Pinned stack to image" && echo "✓ Test 3 (image pin path) passed" || { echo "✗ Test 3 FAILED"; exit 1; }
unset IMAGE
echo ""
echo "=== Test 4: rollback fetches previous image ==="
export ROLLBACK="true"
/tmp/portainer-deploy-action/deploy.sh
cat "$OUTPUT_FILE"
echo ""
jq -e '.status == "success" and .action == "update" and .image == "git.homelab.local:8443/kaykayyali/iron-requiem:abc123d"' "$OUTPUT_FILE" >/dev/null && echo "✓ Test 4 passed" || { echo "✗ Test 4 FAILED"; exit 1; }
unset ROLLBACK
echo ""
echo "=== Test 5: missing stack_name fails fast ==="
unset STACK_NAME
set +e
/tmp/portainer-deploy-action/deploy.sh 2>&1 | grep -q "STACK_NAME is required"
RC=$?
set -e
[ $RC -eq 0 ] && echo "✓ Test 5 passed" || { echo "✗ Test 5 FAILED"; exit 1; }
export STACK_NAME="iron-requiem"
echo ""
echo "=== Test 6: image + image_digest mutual exclusion ==="
export IMAGE="foo:bar"
export IMAGE_DIGEST="foo@sha256:abc"
set +e
/tmp/portainer-deploy-action/deploy.sh 2>&1 | grep -q "mutually exclusive"
RC=$?
set -e
[ $RC -eq 0 ] && echo "✓ Test 6 passed" || { echo "✗ Test 6 FAILED"; exit 1; }
unset IMAGE IMAGE_DIGEST
echo ""
echo "=== Test 7: env_file merges into stack env ==="
export STACK_NAME="env-test"
export MOCK_STACK_EXISTS="true"
export IMAGE=""
export IMAGE_DIGEST=""
export ROLLBACK="false"
cat > "$TEST_DIR/.env" <<'ENVFILE'
DATABASE_URL=postgres://localhost/db
LOG_LEVEL=info
ENVFILE
export ENV_FILE="$TEST_DIR/.env"
/tmp/portainer-deploy-action/deploy.sh 2>&1 | grep -q "Loaded env from" && echo "✓ Test 7 (env_file path) passed" || { echo "✗ Test 7 FAILED"; exit 1; }
unset ENV_FILE
echo ""
echo "=== Test 8: image_digest pin ==="
export IMAGE_DIGEST="git.homelab.local:8443/kaykayyali/iron-requiem@sha256:abc123"
/tmp/portainer-deploy-action/deploy.sh 2>&1 | grep -q "Pinned stack to image: git.homelab.local:8443/kaykayyali/iron-requiem@sha256:abc123" && echo "✓ Test 8 (image_digest path) passed" || { echo "✗ Test 8 FAILED"; exit 1; }
unset IMAGE_DIGEST
echo ""
echo "=== All tests passed ==="