Erste Produktivversion

This commit is contained in:
2026-08-08 20:31:58 +02:00
parent d8ded2816d
commit 7b21e2d1b4
110 changed files with 18170 additions and 644 deletions

176
smoke-phase2.sh Normal file
View File

@@ -0,0 +1,176 @@
#!/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 <schluessel> - 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 <<JSON
{"name":"Vollmilch","barcode":"4001234567890",
"default_market_id":"$EDEKA","default_category_id":"$MOLK",
"attributes":[{"name":"Verpackungseinheit","value":"1 Liter"},
{"name":"Fettgehalt","value":"3,5 %"}],
"available_market_ids":["$EDEKA"]}
JSON
)" | json_get name | sed 's/^/ Artikel angelegt: /'
say "Einträge anlegen (Artikel wird bei Bedarf erzeugt)"
for a in Butter Äpfel Bananen Zahnpasta; do
api POST "/api/lists/$LIST/items" "$(json_obj article_name "$a")" > /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 <cookiejar> -X DELETE $BASE/api/lists/$LIST -H 'X-CSRF-Token: ...'"