From fb28f09e0c797657baa0e79fe1890338a910a6df Mon Sep 17 00:00:00 2001 From: Mischa Date: Thu, 18 Jun 2026 19:57:49 +0200 Subject: [PATCH] test: add form config validator and HTTP integration test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test-config: static YAML validator for post-form.md — checks that the add-page-by-form action name, pageconfig/pagefrontmatter blocks, and all required fields are correctly wired. Fast, no server needed. Catches the class of bug that caused silent post failures. test-post: end-to-end HTTP test — logs in, submits the form, verifies an entry.md was created on disk, then cleans up. Requires GRAV_TEST_USER and GRAV_TEST_PASS in .env (see .env.example). make test-config # fast, no credentials needed make test-post # full e2e, needs .env credentials make test # both Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 5 ++ Makefile | 10 +++ scripts/test-form-config.sh | 56 +++++++++++++++++ scripts/test-post.sh | 121 ++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100755 scripts/test-form-config.sh create mode 100755 scripts/test-post.sh diff --git a/.env.example b/.env.example index b42d863..be6fb7b 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,8 @@ MAIN_REPO=https://gitea.example.com/org/travel-blog-intotheeast.git GITEA_HOST=gitea.example.com GITEA_USER=deploy-user GITEA_TOKEN=your-gitea-personal-access-token + +# Test credentials — used by 'make test-post' (must be a valid Grav site login user) +GRAV_TEST_USER=mischa +GRAV_TEST_PASS=your-grav-password +GRAV_BASE_URL=http://localhost:8081 diff --git a/Makefile b/Makefile index dd80951..0b66d8e 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,16 @@ SSH := ssh -p $(REMOTE_PORT) $(REMOTE_USER)@$(REMOTE_HOST) WEBROOT ?= $(REMOTE_HOME)/public_html SITE_CONFIG_DIR ?= $(REMOTE_HOME)/site-config +# ── Tests ───────────────────────────────────────────────────────────────────── + +test-config: + @bash scripts/test-form-config.sh + +test-post: + @bash scripts/test-post.sh + +test: test-config test-post + # ── Local dev ────────────────────────────────────────────────────────────────── start: diff --git a/scripts/test-form-config.sh b/scripts/test-form-config.sh new file mode 100755 index 0000000..ca7f713 --- /dev/null +++ b/scripts/test-form-config.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Validates that post-form.md is wired correctly for the add-page-by-form plugin. +# Fast, no server needed. Catches the class of bug that caused silent post failures. +set -euo pipefail + +FORM="user/pages/02.post/post-form.md" +PASS=0 +FAIL=0 +ERRORS=() + +ok() { echo " ✓ $1"; PASS=$((PASS+1)); } +fail() { echo " ✗ $1"; FAIL=$((FAIL+1)); ERRORS+=("$1"); } + +check_grep() { + local desc="$1"; local pattern="$2" + if grep -q "$pattern" "$FORM"; then ok "$desc"; else fail "$desc"; fi +} + +echo "" +echo "Form config validator — $FORM" +echo "────────────────────────────────────────" + +# Plugin trigger: must use add_page or addpage — NOT add-page-by-form +grep -q "add_page:\|addpage:" "$FORM" && ok "Process action is 'add_page' (plugin trigger)" \ + || fail "Process action must be 'add_page: true' — 'add-page-by-form' is not handled by the plugin" + +# Config must be in frontmatter, not in the process block +check_grep "pageconfig block exists in frontmatter" "^pageconfig:" +check_grep "parent set to /tracker" "parent: '/tracker'" +check_grep "slug_field set (determines entry folder name)" "slug_field:" +check_grep "pagefrontmatter block exists in frontmatter" "^pagefrontmatter:" +check_grep "template: entry (creates entry.md filename)" "template: entry" + +# Form name must stay 'new-entry' — cache-on-save plugin checks this exact string +check_grep "form name is 'new-entry' (required by cache-on-save plugin)" "name: new-entry" + +# Required form fields +check_grep "title field present" "name: title" +check_grep "date field present" "name: date" +check_grep "content field present" "name: content" +check_grep "lat field present" "name: lat" +check_grep "lng field present" "name: lng" +check_grep "location_city field present" "name: location_city" +check_grep "location_country field present" "name: location_country" + +echo "────────────────────────────────────────" +echo " $PASS passed, $FAIL failed" + +if [ ${#ERRORS[@]} -gt 0 ]; then + echo "" + echo "Failed checks:" + for e in "${ERRORS[@]}"; do echo " → $e"; done + echo "" + exit 1 +fi +echo "" diff --git a/scripts/test-post.sh b/scripts/test-post.sh new file mode 100755 index 0000000..7b93dbc --- /dev/null +++ b/scripts/test-post.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# End-to-end test: logs in, submits the post form, verifies entry is created on disk. +# Requires GRAV_TEST_USER and GRAV_TEST_PASS (set in .env or environment). +# Cleans up the test entry after the test. +set -euo pipefail + +BASE_URL="${GRAV_BASE_URL:-http://localhost:8081}" +USER="${GRAV_TEST_USER:-}" +PASS="${GRAV_TEST_PASS:-}" +TRACKER="user/pages/01.tracker" +COOKIE_JAR="$(mktemp /tmp/grav-test-cookies.XXXXXX)" +PASS_COUNT=0 +FAIL_COUNT=0 +TEST_SLUG="" + +cleanup() { + rm -f "$COOKIE_JAR" + if [ -n "$TEST_SLUG" ] && [ -d "$TRACKER/$TEST_SLUG" ]; then + rm -rf "$TRACKER/$TEST_SLUG" + echo " [cleanup] Removed test entry: $TEST_SLUG" + fi +} +trap cleanup EXIT + +ok() { echo " ✓ $1"; PASS_COUNT=$((PASS_COUNT+1)); } +fail() { echo " ✗ $1"; FAIL_COUNT=$((FAIL_COUNT+1)); } +die() { echo ""; echo "FATAL: $1"; exit 1; } + +echo "" +echo "Post form integration test — $BASE_URL" +echo "────────────────────────────────────────" + +[ -n "$USER" ] || die "GRAV_TEST_USER not set. Add it to .env" +[ -n "$PASS" ] || die "GRAV_TEST_PASS not set. Add it to .env" + +# ── Step 1: get login page + nonce ─────────────────────────────────────────── +LOGIN_HTML=$(curl -sf -c "$COOKIE_JAR" -b "$COOKIE_JAR" "$BASE_URL/login") \ + || die "Could not reach $BASE_URL/login" + +LOGIN_NONCE=$(echo "$LOGIN_HTML" | grep -o 'name="form-nonce" value="[^"]*"' | head -1 | sed 's/.*value="\([^"]*\)".*/\1/') +[ -n "$LOGIN_NONCE" ] || die "Could not extract login form nonce — is the site running?" + +# ── Step 2: log in ─────────────────────────────────────────────────────────── +LOGIN_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -L \ + -d "username=${USER}&password=${PASS}&form-nonce=${LOGIN_NONCE}&task=login" \ + "$BASE_URL/login") + +# After login, check we can access /post (302 → 200 means logged in) +POST_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + "$BASE_URL/post") + +[ "$POST_STATUS" = "200" ] && ok "Login succeeded and /post is accessible" \ + || die "Login failed or /post returned $POST_STATUS — check GRAV_TEST_USER / GRAV_TEST_PASS" + +# ── Step 3: get post form + nonce ──────────────────────────────────────────── +POST_HTML=$(curl -sf -c "$COOKIE_JAR" -b "$COOKIE_JAR" "$BASE_URL/post") \ + || die "Could not fetch post form" + +POST_NONCE=$(echo "$POST_HTML" | grep -o 'name="form-nonce" value="[^"]*"' | head -1 | sed 's/.*value="\([^"]*\)".*/\1/') +[ -n "$POST_NONCE" ] || die "Could not extract post form nonce" +ok "Post form loaded and nonce extracted" + +# ── Step 4: submit test entry ──────────────────────────────────────────────── +TEST_TITLE="Automated Test Entry" +TEST_DATE=$(date "+%Y-%m-%d %H:%M") +TEST_SLUG_EXPECTED=$(date "+%Y-%m-%d-%H%M")-automated-test-entry + +SUBMIT_BODY=$(curl -sf \ + -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -d "data[title]=${TEST_TITLE}" \ + -d "data[date]=${TEST_DATE}" \ + -d "data[content]=This+is+an+automated+test+entry.+Safe+to+delete." \ + -d "data[location_city]=Test+City" \ + -d "data[location_country]=Test+Country" \ + -d "form-nonce=${POST_NONCE}" \ + -d "task=process" \ + "$BASE_URL/post") + +ok "Form submitted" + +# ── Step 5: verify entry exists on disk ───────────────────────────────────── +sleep 1 # give Grav a moment to write the file + +# Look for the entry — slug might have slight timestamp variation +FOUND=$(find "$TRACKER" -name "entry.md" -newer "$TRACKER/2026-06-17.entry/entry.md" \ + -not -path "*/2026-*" 2>/dev/null | head -1) + +# Also look for today's dated entries +FOUND_TODAY=$(find "$TRACKER" -maxdepth 1 -type d -name "$(date '+%Y-%m-%d')*" 2>/dev/null | head -1) + +if [ -n "$FOUND_TODAY" ]; then + TEST_SLUG=$(basename "$FOUND_TODAY") + ok "Entry created on disk: $TEST_SLUG" + + # Verify it has an entry.md inside + if [ -f "$TRACKER/$TEST_SLUG/entry.md" ]; then + ok "entry.md exists inside the entry folder" + else + fail "Entry folder exists but entry.md is missing" + fi + + # Verify the title is in the frontmatter + if grep -q "$TEST_TITLE" "$TRACKER/$TEST_SLUG/entry.md"; then + ok "Title appears in entry frontmatter" + else + fail "Title not found in entry.md — frontmatter may be malformed" + fi +else + fail "No entry created on disk — form processing failed silently" + echo " Expected a folder matching: $TRACKER/$(date '+%Y-%m-%d')-*/" +fi + +# ── Result ─────────────────────────────────────────────────────────────────── +echo "────────────────────────────────────────" +echo " $PASS_COUNT passed, $FAIL_COUNT failed" +echo "" + +[ $FAIL_COUNT -eq 0 ]