#!/usr/bin/env bash # Issue 0128 — smoke test of card file attachments. # Builds kanban (assumes ./kanban present or builds it), boots on ephemeral port, # exercises POST upload, GET list, GET serve, DELETE, GET list-after-delete. # Exits 0 on success, non-zero on any failure. set -euo pipefail PORT=${PORT:-18095} BASE="http://127.0.0.1:${PORT}" DB=$(mktemp /tmp/kanban_files_smoke.XXXXXX.db) COOKIE=$(mktemp /tmp/kanban_files_smoke.cookie.XXXXXX) UPLOAD_DIR=$(dirname "$DB")/uploads PNG=$(mktemp /tmp/kanban_files_smoke.XXXXXX.png) PID_FILE=$(mktemp /tmp/kanban_files_smoke.pid.XXXXXX) cleanup() { if [ -s "$PID_FILE" ]; then kill "$(cat "$PID_FILE")" 2>/dev/null || true fi rm -f "$DB" "$DB-shm" "$DB-wal" "$COOKIE" "$PNG" "$PID_FILE" rm -rf "$UPLOAD_DIR" } trap cleanup EXIT # Build if missing. if [ ! -x ./kanban ]; then echo "[smoke] building kanban binary..." (cd backend && CGO_ENABLED=1 go build -tags fts5 -o ../kanban .) fi # Boot. ./kanban --port "$PORT" --db "$DB" --initial-admin admin:adminpw \ > /tmp/kanban_files_smoke.log 2>&1 & echo $! > "$PID_FILE" # Wait for /api/board. for _ in $(seq 1 30); do if curl -sf -o /dev/null "$BASE/api/board"; then break; fi sleep 0.2 done # Login. curl -sf -c "$COOKIE" -X POST "$BASE/api/auth/login" \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"adminpw"}' > /dev/null # Column + card. COL=$(curl -sf -b "$COOKIE" -X POST "$BASE/api/columns" \ -H 'Content-Type: application/json' \ -d '{"name":"To Do"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') CARD=$(curl -sf -b "$COOKIE" -X POST "$BASE/api/cards" \ -H 'Content-Type: application/json' \ -d "{\"column_id\":\"$COL\",\"title\":\"smoke\",\"requester\":\"r\"}" \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') # Minimal PNG. printf '\x89PNG\r\n\x1a\n' > "$PNG" # Upload. UP=$(curl -sf -b "$COOKIE" -X POST "$BASE/api/cards/$CARD/files" \ -F "file=@$PNG;type=image/png") FID=$(echo "$UP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])') [ -n "$FID" ] || { echo "[smoke] upload missing id"; exit 1; } # List active. N=$(curl -sf -b "$COOKIE" "$BASE/api/cards/$CARD/files" | python3 -c 'import sys,json;print(len(json.load(sys.stdin)))') [ "$N" = "1" ] || { echo "[smoke] expected 1 file, got $N"; exit 1; } # Serve. CT=$(curl -sf -b "$COOKIE" -I "$BASE/api/files/$FID" | awk '/^[Cc]ontent-[Tt]ype/ {print $2}' | tr -d '\r\n') echo "$CT" | grep -q image/png || { echo "[smoke] wrong content-type: $CT"; exit 1; } # Delete. HTTP=$(curl -sb "$COOKIE" -X DELETE -o /dev/null -w "%{http_code}" "$BASE/api/files/$FID") [ "$HTTP" = "204" ] || { echo "[smoke] expected 204 on delete, got $HTTP"; exit 1; } # List after delete. N=$(curl -sf -b "$COOKIE" "$BASE/api/cards/$CARD/files" | python3 -c 'import sys,json;print(len(json.load(sys.stdin)))') [ "$N" = "0" ] || { echo "[smoke] expected 0 after delete, got $N"; exit 1; } echo "[smoke] OK"