Skip to content

fix(docker): complete the Node and Python image builds #4

fix(docker): complete the Node and Python image builds

fix(docker): complete the Node and Python image builds #4

Workflow file for this run

name: Docker image smoke test
# Builds every image the repository ships and checks each actually serves what
# it claims. Reading a Dockerfile is not enough — both failures below were
# invisible in review and obvious the moment anything ran the container:
#
# - the published image (#23) copied the widget to /app/static/fcaptcha.js
# while the server only looked in client/, so /fcaptcha.js returned 404 for
# three months while the file sat in the image untouched.
# - server-node/ and server-python/ copied only server.js / server.py without
# the modules they import, so both crashed on startup. Broken in the initial
# release commit and never once built in the seven months since.
#
# Neither is catchable by a unit test: the mismatch lives between the Dockerfile
# and the source, not inside either.
on:
push:
branches: [main]
paths: ['docker/**', 'server-go/**', 'server-node/**', 'server-python/**', 'client/**', '.github/workflows/docker-smoke.yml']
pull_request:
paths: ['docker/**', 'server-go/**', 'server-node/**', 'server-python/**', 'client/**', '.github/workflows/docker-smoke.yml']
jobs:
smoke:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: published (Go)
dockerfile: docker/Dockerfile
demo: true
- name: node
dockerfile: server-node/Dockerfile
demo: false
- name: python
dockerfile: server-python/Dockerfile
demo: false
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v4
# Always from the repository root: every image needs client/fcaptcha.js,
# which sits outside the per-server directories.
- name: Build
run: docker build -f ${{ matrix.dockerfile }} -t smoke-img .
- name: Start
run: |
docker run -d --name smoke -p 3000:3000 smoke-img
for _ in $(seq 1 60); do
curl -sf http://localhost:3000/health >/dev/null && exit 0
sleep 0.5
done
echo "::error::container never became healthy — it may have crashed on startup"
docker logs smoke
exit 1
- name: Serves the widget
run: |
code=$(curl -s -o /tmp/w.js -w '%{http_code}' http://localhost:3000/fcaptcha.js)
[ "$code" = "200" ] || { echo "::error::/fcaptcha.js returned $code — the image ships the widget but does not serve it"; docker logs smoke; exit 1; }
grep -q "FCaptcha" /tmp/w.js || { echo "::error::/fcaptcha.js served something that is not the widget"; exit 1; }
echo "widget served, $(wc -c < /tmp/w.js) bytes"
- name: Issues a proof-of-work challenge
run: |
curl -sf "http://localhost:3000/api/pow/challenge?siteKey=smoke" -o /tmp/c.json \
|| { echo "::error::the challenge endpoint did not respond"; docker logs smoke; exit 1; }
grep -q challengeId /tmp/c.json || { echo "::error::challenge response has no challengeId"; cat /tmp/c.json; exit 1; }
- name: No missing-widget warning in the startup log
run: |
if docker logs smoke 2>&1 | grep -q "will return 404"; then
echo "::error::server logged that it could not find the widget"
docker logs smoke; exit 1
fi
# The demo page loads the widget from /fcaptcha.js, so it was collateral
# damage in #23 — 200 with a widget that never initialised.
- name: Demo page loads and its widget reference resolves
if: matrix.demo
run: |
curl -sf http://localhost:3000/demo/ -o /tmp/demo.html || { echo "::error::/demo/ did not load"; exit 1; }
src=$(grep -o 'src="[^"]*fcaptcha[^"]*"' /tmp/demo.html | head -1 | sed 's/src="//;s/"//')
echo "demo loads the widget from: $src"
curl -sf "http://localhost:3000${src}" >/dev/null || { echo "::error::the demo's widget URL $src does not resolve"; exit 1; }
- name: End-to-end detection suite against the container
run: node test/test-detection.js || true # Go and Python have documented divergences; startup and routing are what this job guards
- name: Cleanup
if: always()
run: docker rm -f smoke || true