#!/usr/bin/env bash # Durchlauf durch die Phase-2-Endpunkte. Legt eine Testliste an, füllt # sie und prüft die gruppierte Ansicht. # # ./smoke-phase2.sh http://localhost:46600 admin@example.de # # Das Passwort wird abgefragt, nicht als Argument übergeben. Damit landet # es nicht in der Shell-History, und Sonderzeichen wie ! machen keinen # Ärger: die History-Expansion der Bash greift nur bei interaktiver # Eingabe auf der Kommandozeile, nicht bei "read". # # Alternativ per Umgebungsvariable, z.B. aus einem Passwortmanager: # EA_PASSWORD="$(pass show einkaufsapp)" ./smoke-phase2.sh URL MAIL # # Benötigt curl sowie jq oder python3. set -euo pipefail BASE="${1:-http://localhost:46600}" EMAIL="${2:?E-Mail angeben}" if [ -n "${EA_PASSWORD:-}" ]; then PASSWORD="$EA_PASSWORD" else printf 'Passwort für %s: ' "$EMAIL" >&2 IFS= read -rs PASSWORD printf '\n' >&2 fi [ -n "$PASSWORD" ] || { echo "Kein Passwort angegeben." >&2; exit 1; } # jq ist bequem, aber nicht zwingend. Ohne jq springt python3 ein - # beides kann JSON korrekt erzeugen und lesen, was hier der Punkt ist: # Passwoerter und Umlaute duerfen nicht per String-Bastelei in die # Nutzlast wandern. if command -v jq >/dev/null 2>&1; then JSON_TOOL=jq elif command -v python3 >/dev/null 2>&1; then JSON_TOOL=python3 else echo "Benötigt jq oder python3. Installieren mit: apt install jq" >&2 exit 1 fi # json_obj key value [key value ...] -> JSON-Objekt auf stdout json_obj() { if [ "$JSON_TOOL" = jq ]; then local args=() filter="{" first=1 i=1 while [ $# -gt 0 ]; do args+=(--arg "k$i" "$1" --arg "v$i" "$2") [ $first -eq 1 ] || filter="$filter," filter="$filter(\$k$i): \$v$i" first=0; i=$((i+1)); shift 2 done jq -n "${args[@]}" "$filter}" else python3 -c 'import json,sys; a=sys.argv[1:]; print(json.dumps(dict(zip(a[::2], a[1::2]))))' "$@" fi } # json_get - liest einen Wert aus JSON auf stdin json_get() { if [ "$JSON_TOOL" = jq ]; then jq -r ".$1 // empty" else python3 -c 'import json,sys try: d = json.load(sys.stdin) except Exception: sys.exit(0) v = d.get(sys.argv[1]) if isinstance(d, dict) else None print("" if v is None else v)' "$1" fi } JAR="$(mktemp)" chmod 600 "$JAR" trap 'rm -f "$JAR"' EXIT say() { printf '\n\033[1m== %s\033[0m\n' "$1"; } api() { local method="$1" path="$2" body="${3:-}" local csrf csrf="$(awk '/ea_csrf/ {print $7}' "$JAR")" if [ -n "$body" ]; then curl -sS -b "$JAR" -c "$JAR" -X "$method" "$BASE$path" \ -H 'Content-Type: application/json' -H "X-CSRF-Token: $csrf" \ --data-binary "$body" else curl -sS -b "$JAR" -c "$JAR" -X "$method" "$BASE$path" \ -H "X-CSRF-Token: $csrf" fi } say "Anmelden" # JSON strukturiert erzeugen, nicht per String-Interpolation: sonst # zerbricht jedes " oder \ im Passwort die Nutzlast. LOGIN_JSON="$(json_obj email "$EMAIL" password "$PASSWORD")" curl -sS -c "$JAR" -X POST "$BASE/api/auth/login" \ -H 'Content-Type: application/json' \ --data-binary "$LOGIN_JSON" > /tmp/ea_login.$$ 2>&1 WHO="$(json_get email < /tmp/ea_login.$$)" if [ -z "$WHO" ]; then echo " Anmeldung fehlgeschlagen: $(json_get detail < /tmp/ea_login.$$)" >&2 rm -f /tmp/ea_login.$$ exit 1 fi echo " angemeldet als $WHO" rm -f /tmp/ea_login.$$ unset PASSWORD LOGIN_JSON say "CSRF-Schutz muss greifen (erwartet 403)" code=$(curl -sS -o /dev/null -w '%{http_code}' -b "$JAR" -X POST "$BASE/api/lists" \ -H 'Content-Type: application/json' --data-binary '{"name":"Ohne CSRF"}') [ "$code" = "403" ] && echo " ok: $code" || { echo " FEHLER: $code statt 403"; exit 1; } say "Liste anlegen" LIST=$(api POST /api/lists "$(json_obj name Wocheneinkauf)" | json_get id) echo " list_id=$LIST" say "Märkte anlegen" EDEKA=$(api POST "/api/lists/$LIST/markets" '{"name":"Edeka","sort_order":1}' | json_get id) DM=$(api POST "/api/lists/$LIST/markets" '{"name":"dm","sort_order":2}' | json_get id) echo " Edeka=$EDEKA dm=$DM" say "Doppelter Marktname muss abgelehnt werden (erwartet 409)" code=$(curl -sS -o /dev/null -w '%{http_code}' -b "$JAR" -X POST "$BASE/api/lists/$LIST/markets" \ -H 'Content-Type: application/json' \ -H "X-CSRF-Token: $(awk '/ea_csrf/ {print $7}' "$JAR")" \ --data-binary '{"name":"Edeka"}') [ "$code" = "409" ] && echo " ok: $code" || { echo " FEHLER: $code statt 409"; exit 1; } say "Warengruppen anlegen" MOLK=$(api POST "/api/lists/$LIST/categories" '{"name":"Molkerei","sort_order":1}' | json_get id) OBST=$(api POST "/api/lists/$LIST/categories" '{"name":"Obst und Gemüse","sort_order":2}' | json_get id) say "Artikel mit Attributen anlegen" api POST "/api/lists/$LIST/articles" "$(cat < /dev/null done MILCH=$(api POST "/api/lists/$LIST/items" \ '{"article_name":"Vollmilch","quantity":2,"unit":"l"}' | json_get id) say "Zuordnungen setzen" api PATCH "/api/lists/$LIST/items/$MILCH" \ "{\"market_id\":\"$EDEKA\",\"category_id\":\"$MOLK\",\"price_cents\":129}" \ | json_get article_name | sed 's/^/ Preis gesetzt für: /' say "Barcode-Suche" api GET "/api/lists/$LIST/articles/by-barcode/4001234567890" | json_get name | sed 's/^/ gefunden: /' say "Gruppierte Ansicht" api GET "/api/lists/$LIST/view" | python3 "$(dirname "$0")/render_view.py" say "Abhaken und aufräumen" api PATCH "/api/lists/$LIST/items/$MILCH" '{"status":"bought"}' | json_get status | sed 's/^/ neuer Status: /' api POST "/api/lists/$LIST/items/clear-bought" | json_get removed | sed 's/^/ entfernte Einträge: /' say "Fremde Liste darf nicht sichtbar sein (erwartet 404)" code=$(curl -sS -o /dev/null -w '%{http_code}' -b "$JAR" \ "$BASE/api/lists/00000000-0000-0000-0000-000000000000") [ "$code" = "404" ] && echo " ok: $code" || { echo " FEHLER: $code statt 404"; exit 1; } say "Fertig" echo "Testliste $LIST bleibt bestehen. Löschen mit:" echo " curl -b -X DELETE $BASE/api/lists/$LIST -H 'X-CSRF-Token: ...'"