Co-authored-by: Kay Kayyali <kaykayyali@gmail.com> Co-committed-by: Kay Kayyali <kaykayyali@gmail.com>
190 lines
6.0 KiB
Bash
Executable File
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 ==="
|