The fake smoke Discourse now serves /search.json (title substring matches over created topics), accepts private-message creation on POST /posts.json (targets comma-joined on the wire, deliveries recorded and exposed via /__pms.json), and the smoke script drives the real CLI through it: search hits, PM send + recipient assertion, webhook verify with a python-computed HMAC (good sig exits 0, bad sig exits 2 with the typed message), and sso verify decoding a signed Discourse Connect payload (tamper case exits 2). No live forum is contacted. Part of Redmine 507 (Discourse Go client).
146 lines
6.5 KiB
Bash
Executable File
146 lines
6.5 KiB
Bash
Executable File
# End-to-end smoke for discourse-go: builds the CLI in the Docker
|
|
# builder, boots the FAKE Discourse in a container on 127.0.0.1:8610,
|
|
# drives the real binary from the host through env vars (0600 temp env
|
|
# file), asserts the happy paths + failure classes + redaction, and tears
|
|
# everything down. No live forum is ever contacted. Only exact container
|
|
# IDs spawned here are killed.
|
|
set -e
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
IMAGE="golang@sha256:e8c859f5632dcfde7b32d2012b4351728f6437930887c2f6a91ea242459e5514"
|
|
PORT=8610
|
|
SMOKE_KEY="smoke-key-do-not-use"
|
|
CID=""
|
|
|
|
cleanup() {
|
|
if [ -n "$CID" ]; then
|
|
docker rm -f "$CID" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -rf .smoke
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
mkdir -p .smoke
|
|
umask 077
|
|
|
|
echo "--- build CLI (docker builder)"
|
|
docker run --rm -v "$PWD:/h" -w /h \
|
|
-u "$(id -u):$(id -g)" -e HOME=/tmp -e GOFLAGS=-buildvcs=false \
|
|
"$IMAGE" go build -o bin/discourse-go ./cmd/discourse-go
|
|
|
|
echo "--- boot fake Discourse (container, port $PORT)"
|
|
CID=$(docker run -d --rm \
|
|
-v "$PWD:/h" -w /h \
|
|
-u "$(id -u):$(id -g)" -e HOME=/tmp -e GOFLAGS=-buildvcs=false \
|
|
-p 127.0.0.1:$PORT:8610 \
|
|
"$IMAGE" go run ./smoke/fakediscourse -addr :8610)
|
|
|
|
# wait for the fake to answer (any HTTP response proves it is up)
|
|
wait_up() {
|
|
python3 - "$PORT" <<'PYEOF'
|
|
import sys, urllib.request, urllib.error
|
|
port = sys.argv[1]
|
|
req = urllib.request.Request('http://127.0.0.1:%s/session/current.json' % port,
|
|
headers={'Api-Key': 'probe', 'Api-Username': 'probe'})
|
|
try:
|
|
urllib.request.urlopen(req, timeout=2)
|
|
except urllib.error.HTTPError:
|
|
sys.exit(0) # got an HTTP answer: server is up
|
|
except Exception:
|
|
sys.exit(1) # not yet
|
|
sys.exit(0)
|
|
PYEOF
|
|
}
|
|
i=0
|
|
until [ -n "$CID" ] && [ "$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null)" = "true" ] && wait_up; do
|
|
i=$((i+1))
|
|
if [ "$i" -ge 60 ]; then
|
|
echo "smoke: fake server did not come up; logs:" >&2
|
|
docker logs "$CID" >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# 0600 env file = the credential path the docs prescribe
|
|
ENVF=".smoke/env"
|
|
printf 'DISCOURSE_URL=http://127.0.0.1:%s\nDISCOURSE_API_KEY=%s\nDISCOURSE_API_USERNAME=smoker\n' \
|
|
"$PORT" "$SMOKE_KEY" > "$ENVF"
|
|
|
|
CLI() {
|
|
( set -a; . "$ENVF"; set +a; exec ./bin/discourse-go "$@" )
|
|
}
|
|
|
|
capture="$PWD/.smoke/out"
|
|
touch "$capture"
|
|
|
|
echo "--- whoami"
|
|
CLI whoami | tee -a "$capture" | grep -q '"username": "smoker"' || { echo "smoke: whoami failed" >&2; exit 1; }
|
|
|
|
echo "--- categories list"
|
|
CLI categories list | tee -a "$capture" | grep -q '"mopac-briefings"' || { echo "smoke: categories list failed" >&2; exit 1; }
|
|
|
|
echo "--- categories create + typed 404 check"
|
|
CLI categories create "Smoke Cat" -color 25AAE2 | tee -a "$capture" | grep -q '"slug": "smoke-cat"' || { echo "smoke: category create failed" >&2; exit 1; }
|
|
CLI raw POST /definitely/not/there -data '{}' 2>>"$capture" >>"$capture" || true
|
|
grep -q "not found" "$capture" || { echo "smoke: 404 class missing" >&2; exit 1; }
|
|
|
|
echo "--- topics create / list / post reply / update"
|
|
CLI topics create -title "Smoke Topic" -category 2 -raw "first body" | tee -a "$capture" | grep -q '"topic_id"' || { echo "smoke: topic create failed" >&2; exit 1; }
|
|
CLI topics list -category 2 | tee -a "$capture" | grep -q '"Smoke Topic"' || { echo "smoke: topics list failed" >&2; exit 1; }
|
|
CLI topics list -slug mopac-briefings | tee -a "$capture" | grep -q '"Smoke Topic"' || { echo "smoke: topics list by slug failed" >&2; exit 1; }
|
|
POST_ID=$(CLI posts create -topic 10 -raw "reply body" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
|
|
CLI posts update "$POST_ID" -raw "edited body" -reason smoke | tee -a "$capture" | grep -q '"raw": "edited body"' || { echo "smoke: post update failed" >&2; exit 1; }
|
|
|
|
echo "--- raw passthrough"
|
|
CLI raw GET /latest.json | tee -a "$capture" | grep -q '"topic_list"' || { echo "smoke: raw GET failed" >&2; exit 1; }
|
|
|
|
echo "--- search"
|
|
CLI search "Smoke Topic" | tee -a "$capture" | grep -q '"Smoke Topic"' || { echo "smoke: search failed" >&2; exit 1; }
|
|
|
|
echo "--- private message send + delivery record"
|
|
CLI messages send -title "Smoke PM" -to ops,reachableceo -raw "pm body" | tee -a "$capture" | grep -q '"topic_slug": "smoke-pm"' || { echo "smoke: PM send failed" >&2; exit 1; }
|
|
CLI raw GET /__pms.json | tee -a "$capture" | grep -q '"target_usernames": "ops,reachableceo"' || { echo "smoke: PM recipients not recorded" >&2; exit 1; }
|
|
|
|
echo "--- webhook verify: good sig exits 0, bad sig exits 2"
|
|
WHSEC="smoke-webhook-secret"
|
|
printf '{"post":{"id":101}}' > .smoke/hook-body
|
|
SIG=$(python3 -c 'import hmac,hashlib,sys; print("sha256="+hmac.new(sys.argv[1].encode(),open(sys.argv[2],"rb").read(),hashlib.sha256).hexdigest())' "$WHSEC" .smoke/hook-body)
|
|
( set -a; DISCOURSE_WEBHOOK_SECRET="$WHSEC"; set +a; exec ./bin/discourse-go webhook verify "$SIG" ) < .smoke/hook-body >>"$capture" 2>&1 \
|
|
|| { echo "smoke: webhook verify (good sig) failed" >&2; exit 1; }
|
|
set +e
|
|
( set -a; DISCOURSE_WEBHOOK_SECRET="$WHSEC"; set +a; exec ./bin/discourse-go webhook verify "sha256=deadbeef" ) < .smoke/hook-body >"$PWD/.smoke/badsig" 2>&1
|
|
code=$?
|
|
set -e
|
|
if [ "$code" -ne 2 ]; then echo "smoke: bad webhook sig exit=$code want 2" >&2; exit 1; fi
|
|
grep -q "bad signature" "$PWD/.smoke/badsig" || { echo "smoke: bad-signature message missing" >&2; exit 1; }
|
|
|
|
echo "--- sso verify: decoded payload + tamper rejection"
|
|
SSOSEC="smoke-sso-secret"
|
|
SSO=$(python3 -c 'import base64; print(base64.b64encode(b"nonce=smoke123&return_sso_url=https%3A%2F%2Fapp.test%2Fcb").decode())')
|
|
SSIG=$(python3 -c 'import hmac,hashlib,sys; print(hmac.new(sys.argv[1].encode(),sys.argv[2].encode(),hashlib.sha256).hexdigest())' "$SSOSEC" "$SSO")
|
|
( set -a; DISCOURSE_SSO_SECRET="$SSOSEC"; set +a; exec ./bin/discourse-go sso verify "$SSO" "$SSIG" ) | tee -a "$capture" | grep -q '"nonce": "smoke123"' \
|
|
|| { echo "smoke: sso verify failed" >&2; exit 1; }
|
|
set +e
|
|
( set -a; DISCOURSE_SSO_SECRET="$SSOSEC"; set +a; exec ./bin/discourse-go sso verify "$SSO" "deadbeef" ) >/dev/null 2>&1
|
|
code=$?
|
|
set -e
|
|
if [ "$code" -ne 2 ]; then echo "smoke: bad sso sig exit=$code want 2" >&2; exit 1; fi
|
|
|
|
echo "--- redaction: no key material in any captured output"
|
|
if grep -q "$SMOKE_KEY" "$capture"; then
|
|
echo "smoke: KEY LEAKED in output" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "--- bad key: exit code 2 + typed error"
|
|
set +e
|
|
( set -a; . "$ENVF"; set +a; DISCOURSE_API_KEY=wrong-key exec ./bin/discourse-go whoami ) >"$PWD/.smoke/bad" 2>&1
|
|
code=$?
|
|
set -e
|
|
if [ "$code" -ne 2 ]; then echo "smoke: bad key exit=$code want 2" >&2; exit 1; fi
|
|
grep -q "HTTP 403" "$PWD/.smoke/bad" || { echo "smoke: typed error missing" >&2; exit 1; }
|
|
|
|
echo "smoke: OK"
|