The log is a record of what was eaten, so there is nothing to write down for a dinner that has not happened, and a stray entry dated next year would sit at the top of the history forever. A future date is now clamped to today. The clamp lives in the one function every read and write already goes through, so ?pvm=, the date picker, saving and deleting are all covered. The picker also gets max=today, which avoids offering the dead end at all. Fixes a latent bug found alongside it: today() returned the current instant with its time of day, while dates parsed from ?pvm= are midnight, so the two never compared equal. After saving today's dinner the redirect landed on /?pvm=... and the card then read "la 5.9. kirjattu" instead of "Tänään kirjattu", and "Tänään" stopped highlighting whenever the date was spelled out. today() now truncates to midnight in the configured location.
212 lines
7.4 KiB
Bash
Executable File
212 lines
7.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# End-to-end check of a running Foodster: auth, static assets and the bundle
|
|
# import flow. Builds its own binary, uses a scratch database and a spare
|
|
# port, and cleans up after itself, so it never touches a real instance.
|
|
#
|
|
# Run it with `make smoke`.
|
|
|
|
set -eu
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
addr=127.0.0.1:8099
|
|
pass=smoke
|
|
tmp=$(mktemp -d)
|
|
trap 'kill ${srv:-0} 2>/dev/null || true; rm -rf "$tmp"' EXIT
|
|
|
|
go build -o "$tmp/foodster" ./cmd/foodster
|
|
|
|
FOODSTER_PASSWORD="$pass" FOODSTER_DB="$tmp/smoke.db" FOODSTER_ADDR="$addr" \
|
|
"$tmp/foodster" >"$tmp/server.log" 2>&1 &
|
|
srv=$!
|
|
|
|
i=0
|
|
while ! curl -sf "http://$addr/healthz" >/dev/null 2>&1; do
|
|
i=$((i + 1))
|
|
if [ "$i" -gt 50 ]; then
|
|
echo "server did not start:"
|
|
cat "$tmp/server.log"
|
|
exit 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
|
|
fail=0
|
|
|
|
# check <name> <haystack> <needle>
|
|
check() {
|
|
if printf '%s' "$2" | grep -qF -- "$3"; then
|
|
echo " ok $1"
|
|
else
|
|
echo " FAIL $1 (expected to find: $3)"
|
|
fail=1
|
|
fi
|
|
}
|
|
|
|
echo "smoke: http://$addr"
|
|
|
|
check "unauthenticated request is refused" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' "http://$addr/")" "401"
|
|
|
|
check "healthz needs no password" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' "http://$addr/healthz")" "200"
|
|
|
|
check "datastar client is served" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/datastar.js")" "200"
|
|
|
|
check "favicon is served" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/favicon.svg")" "200"
|
|
|
|
check "theme script is served" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/theme.js")" "200"
|
|
|
|
home=$(curl -s -u ":$pass" "http://$addr/")
|
|
check "the header carries the brand" "$home" "Foodster"
|
|
check "dark is the default without JavaScript" "$home" '<html lang="fi" data-theme="dark">'
|
|
check "the theme toggle is present" "$home" "data-theme-toggle"
|
|
check "both theme icons ship so CSS can pick one" "$home" 'class="i-moon"'
|
|
|
|
check "apple touch icon is served" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" "http://$addr/static/apple-touch-icon.png")" "200"
|
|
|
|
# A manifest served as octet-stream is silently ignored by the browser.
|
|
check "manifest has the right content type" \
|
|
"$(curl -s -o /dev/null -w '%{content_type}' -u ":$pass" "http://$addr/static/manifest.webmanifest")" \
|
|
"application/manifest+json"
|
|
|
|
check "catalog starts empty" \
|
|
"$(curl -s -u ":$pass" "http://$addr/ruuat")" "0 pääruokaa"
|
|
|
|
out=$(curl -s -u ":$pass" -F "tiedosto=@seeds/testi.json" "http://$addr/ruuat/tuonti")
|
|
check "file upload imports the seed bundle" "$out" "Lisätty 22, ohitettu 0"
|
|
check "counts update after import" "$out" "16 pääruokaa, 6 lisuketta"
|
|
|
|
check "re-import refuses duplicates" \
|
|
"$(curl -s -u ":$pass" -F "tiedosto=@seeds/testi.json" "http://$addr/ruuat/tuonti")" \
|
|
"jo listalla"
|
|
|
|
check "pasted JSON imports" \
|
|
"$(curl -s -u ":$pass" -F 'json={"mains":[],"sides":[{"name":"Perunasalaatti"}]}' \
|
|
"http://$addr/ruuat/tuonti")" "Lisätty 1"
|
|
|
|
check "unknown category is reported" \
|
|
"$(curl -s -u ":$pass" -F 'json={"mains":[{"name":"Rikki","categories":["kana"]}],"sides":[]}' \
|
|
"http://$addr/ruuat/tuonti")" "tuntematon kategoria"
|
|
|
|
check "empty submit is explained" \
|
|
"$(curl -s -u ":$pass" -F 'json=' "http://$addr/ruuat/tuonti")" "Ei tuotavaa"
|
|
|
|
check "malformed JSON is explained" \
|
|
"$(curl -s -u ":$pass" -F 'json={nope' "http://$addr/ruuat/tuonti")" "JSON ei kelpaa"
|
|
|
|
# ---- the log flow, against the dishes imported above --------------------
|
|
|
|
board=$(curl -s -u ":$pass" "http://$addr/")
|
|
check "board lists imported dishes" "$board" "Lihapullat"
|
|
|
|
# Pull a real dish id out of the board rather than assuming one.
|
|
ruoka=$(printf '%s' "$board" | grep -o 'ruoka=[0-9]*' | head -n1 | cut -d= -f2)
|
|
if [ -z "$ruoka" ]; then
|
|
echo " FAIL could not find a dish link on the board"
|
|
fail=1
|
|
ruoka=1
|
|
fi
|
|
|
|
check "picking a dish opens the sides step" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?ruoka=$ruoka")" "Tallenna"
|
|
|
|
check "saving redirects back to the day" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" \
|
|
-d "pvm=2026-09-05&ruoka=$ruoka" "http://$addr/kirjaa")" "303"
|
|
|
|
check "the saved day shows what was eaten" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05")" "kirjattu"
|
|
|
|
check "history is on the same page as the logger" \
|
|
"$(curl -s -u ":$pass" "http://$addr/")" "Aiemmin"
|
|
|
|
check "deleting redirects back" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" \
|
|
-d "pvm=2026-09-05" "http://$addr/poista")" "303"
|
|
|
|
check "the day is empty again" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05")" "Etsi"
|
|
|
|
check "search filters the board" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?haku=keitto")" "keitto"
|
|
|
|
# Nothing was eaten tomorrow. A future date is clamped rather than logged.
|
|
future=$(date -d '+30 days' +%Y-%m-%d)
|
|
check "a future date falls back to today" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?pvm=$future")" "$(date +%-d.%-m.%Y)"
|
|
|
|
check "saving a future date is clamped too" \
|
|
"$(curl -s -o /dev/null -w '%{redirect_url}' -u ":$pass" \
|
|
-d "pvm=$future&ruoka=$ruoka" "http://$addr/kirjaa")" "/"
|
|
|
|
check "tomorrow was not written to the log" \
|
|
"$(curl -s -u ":$pass" "http://$addr/?pvm=$future")" "$(date +%-d.%-m.%Y)"
|
|
|
|
# Clean up the entry that clamped onto today.
|
|
curl -s -o /dev/null -u ":$pass" -d "pvm=$(date +%Y-%m-%d)" "http://$addr/poista"
|
|
|
|
# ---- adding a dish without leaving Kirjaa --------------------------------
|
|
|
|
miss=$(curl -s -u ":$pass" "http://$addr/?haku=Poronkariste")
|
|
check "a search with no hits offers to add it" "$miss" "Ei osumia. Lisätäänkö?"
|
|
check "the add form is prefilled with the search" "$miss" 'value="Poronkariste"'
|
|
|
|
check "quick add goes straight to the sides step" \
|
|
"$(curl -s -o /dev/null -w '%{redirect_url}' -u ":$pass" \
|
|
-d 'nimi=Poronkariste&kategoria=meat&lisukkeita=1' "http://$addr/lisaa")" \
|
|
"ruoka="
|
|
|
|
check "quick add rejects a dish with no category" \
|
|
"$(curl -s -u ":$pass" -d 'nimi=Kategoriaton' "http://$addr/lisaa")" \
|
|
"Valitse vähintään yksi kategoria."
|
|
|
|
check "the quick-added dish is on the board" \
|
|
"$(curl -s -u ":$pass" "http://$addr/")" "Poronkariste"
|
|
|
|
# ---- catalog CRUD from the UI -------------------------------------------
|
|
|
|
check "adding a main redirects" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" \
|
|
-d 'nimi=uunikala&kategoria=fish&lisukkeita=1' "http://$addr/ruuat/paaruoka")" "303"
|
|
|
|
catalog=$(curl -s -u ":$pass" "http://$addr/ruuat")
|
|
check "the new main is listed, sentence-cased" "$catalog" "Uunikala"
|
|
|
|
check "a duplicate name is refused" \
|
|
"$(curl -s -u ":$pass" -d 'nimi=UUNIKALA&kategoria=fish' "http://$addr/ruuat/paaruoka")" \
|
|
"Nimi on jo listalla."
|
|
|
|
check "a main with no category is refused" \
|
|
"$(curl -s -u ":$pass" -d 'nimi=Kategoriaton' "http://$addr/ruuat/paaruoka")" \
|
|
"Valitse vähintään yksi kategoria."
|
|
|
|
check "a nameless dish is refused" \
|
|
"$(curl -s -u ":$pass" -d 'nimi=+++&kategoria=fish' "http://$addr/ruuat/paaruoka")" \
|
|
"Anna nimi."
|
|
|
|
check "adding a side redirects" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" \
|
|
-d 'nimi=lohkoperunat' "http://$addr/ruuat/lisuke")" "303"
|
|
|
|
check "the new side is listed" \
|
|
"$(curl -s -u ":$pass" "http://$addr/ruuat")" "Lohkoperunat"
|
|
|
|
uusi=$(printf '%s' "$catalog" | grep -o 'muokkaa=[0-9]*' | head -n1 | cut -d= -f2)
|
|
check "the edit form is prefilled" \
|
|
"$(curl -s -u ":$pass" "http://$addr/ruuat?muokkaa=$uusi")" "Muokkaa pääruokaa"
|
|
|
|
check "deleting a main redirects" \
|
|
"$(curl -s -o /dev/null -w '%{http_code}' -u ":$pass" \
|
|
-d "id=$uusi&tyyppi=paa" "http://$addr/ruuat/poista")" "303"
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
echo "smoke: FAILED"
|
|
exit 1
|
|
fi
|
|
echo "smoke: all passed"
|