#!/bin/bash # Test suite for post.sh, run against tests/mock_wp_server.py so no real # WordPress install is needed. Requires curl, jq, python3, python3-markdown2. set -u cd "$(dirname "$0")/.." for bin in curl jq python3; do command -v "$bin" >/dev/null || { echo "missing dependency: $bin"; exit 1; } done python3 -c "import markdown2" 2>/dev/null || { echo "missing dependency: python3-markdown2"; exit 1; } PORT=8899 WORKDIR=$(mktemp -d) trap 'kill $SERVER_PID 2>/dev/null; rm -rf "$WORKDIR"' EXIT export MOCK_WP_PORT=$PORT export MOCK_WP_CONTROL_FILE="$WORKDIR/control.json" export MOCK_WP_REQUEST_FILE="$WORKDIR/request.json" export MOCK_WP_TERMS_FILE="$WORKDIR/terms.json" python3 -u tests/mock_wp_server.py & SERVER_PID=$! for i in $(seq 1 20); do curl -s -o /dev/null "http://127.0.0.1:$PORT/" && break sleep 0.2 done export HOME="$WORKDIR/home" mkdir -p "$HOME/.config/wordpress-rest-curl" cat > "$HOME/.config/wordpress-rest-curl/config.sh" < "$HOME/.config/wordpress-rest-curl/othersite.sh" < "$POST_FILE" <<'EOF2' # My Test Title Some **markdown** content here. EOF2 run_post_sh() { printf "" | "$PWD/post.sh" "$@" } run_terms_sh() { "$PWD/terms.sh" "$@" } FAIL=0 assert_eq() { local desc="$1" expected="$2" actual="$3" if [[ "$expected" != "$actual" ]]; then echo "FAIL: $desc (expected [$expected], got [$actual])" FAIL=1 else echo "PASS: $desc" fi } # --- create + publish --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" --publish >/dev/null assert_eq "create: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "create: path" "/wp-json/wp/v2/posts/" "$(jq -r '.path' <<<"$REQ")" assert_eq "create: title extracted from heading" "My Test Title" "$(jq -r '.payload.title' <<<"$REQ")" assert_eq "create: status" "publish" "$(jq -r '.payload.status' <<<"$REQ")" assert_eq "create: categories sent as array" "[3,7]" "$(jq -c '.payload.categories' <<<"$REQ")" assert_eq "create: tags sent as array" "[10]" "$(jq -c '.payload.tags' <<<"$REQ")" assert_eq "create: content rendered as markdown" "true" "$(jq -r '.payload.content | test("markdown")' <<<"$REQ")" assert_eq "create: sends a non-default user agent" "true" "$(jq -r '.user_agent != "" and (.user_agent | test("^curl/") | not)' <<<"$REQ")" # --- USER_AGENT is configurable, e.g. to dodge a host's DDoS protection --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" cat >> "$HOME/.config/wordpress-rest-curl/config.sh" <<'EOF3' USER_AGENT="Mozilla/5.0 (test override)" EOF3 run_post_sh --file "$POST_FILE" >/dev/null assert_eq "user agent: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "user agent: override from config is sent" "Mozilla/5.0 (test override)" "$(jq -r '.user_agent' <<<"$REQ")" # --- trailing slash on SERVER doesn't produce a double slash in the URL --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" cat >> "$HOME/.config/wordpress-rest-curl/config.sh" </dev/null assert_eq "trailing slash: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "trailing slash: path has no double slash" "/wp-json/wp/v2/posts/" "$(jq -r '.path' <<<"$REQ")" # --- --date backdates the post via date_gmt --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" --date "2026-07-27T10:00:00Z" >/dev/null assert_eq "date: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "date: date_gmt sent, trailing Z stripped" "2026-07-27T10:00:00" "$(jq -r '.payload.date_gmt' <<<"$REQ")" rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" >/dev/null REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "no date: date_gmt omitted" "true" "$(jq -r '.payload | has("date_gmt") | not' <<<"$REQ")" # --- edit an existing post --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" --edit 99 >/dev/null assert_eq "edit: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "edit: path includes post ID" "/wp-json/wp/v2/posts/99" "$(jq -r '.path' <<<"$REQ")" assert_eq "edit: status defaults to draft" "draft" "$(jq -r '.payload.status' <<<"$REQ")" # --- --site layers on top of config.sh: overrides what it sets, inherits the rest --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --site othersite --file "$POST_FILE" >/dev/null assert_eq "site: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "site: categories overridden by site config" "[1]" "$(jq -c '.payload.categories' <<<"$REQ")" assert_eq "site: tags inherited from default config" "[10]" "$(jq -c '.payload.tags' <<<"$REQ")" assert_eq "site: title transform inherited from default config" "My Test Title" "$(jq -r '.payload.title' <<<"$REQ")" rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" OUT=$(run_post_sh --site does-not-exist --file "$POST_FILE" 2>&1) assert_eq "site: unknown site exits non-zero" "1" "$?" assert_eq "site: unknown site error message" "true" "$(grep -q "Config file not found" <<<"$OUT" && echo true || echo false)" # --- no --site and no default config.sh: clean error, not a crash --- mv "$HOME/.config/wordpress-rest-curl/config.sh" "$WORKDIR/config.sh.bak" OUT=$(run_post_sh --file "$POST_FILE" 2>&1) assert_eq "no config: exits non-zero" "1" "$?" assert_eq "no config: error message" "true" "$(grep -q "No site specified and no default config found" <<<"$OUT" && echo true || echo false)" mv "$WORKDIR/config.sh.bak" "$HOME/.config/wordpress-rest-curl/config.sh" # --- non-2xx response is surfaced and script exits non-zero --- rm -f "$MOCK_WP_REQUEST_FILE" echo '{"status": 401, "body": {"code": "rest_forbidden", "message": "Sorry, you are not allowed to do that."}}' > "$MOCK_WP_CONTROL_FILE" OUT=$(run_post_sh --file "$POST_FILE" 2>&1) assert_eq "error: exit code" "1" "$?" assert_eq "error: HTTP status surfaced" "true" "$(grep -q "HTTP 401" <<<"$OUT" && echo true || echo false)" rm -f "$MOCK_WP_CONTROL_FILE" # --- terms.sh: list starts empty --- rm -f "$MOCK_WP_TERMS_FILE" OUT=$(run_terms_sh --list) assert_eq "terms list: exit code" "0" "$?" assert_eq "terms list: empty when no categories exist" "" "$OUT" # --- terms.sh: create a category, then find and list it --- OUT=$(run_terms_sh --create "Hardware") assert_eq "terms create: exit code" "0" "$?" assert_eq "terms create: prints the new id" "1" "$OUT" OUT=$(run_terms_sh --find "Hardware") assert_eq "terms find: exit code" "0" "$?" assert_eq "terms find: resolves name to id" "1" "$OUT" OUT=$(run_terms_sh --list) assert_eq "terms list: shows created category" "1 Hardware hardware 0" "$OUT" # --- terms.sh: categories and tags are separate namespaces --- OUT=$(run_terms_sh --type tag --create "arduino") assert_eq "terms create tag: exit code" "0" "$?" assert_eq "terms create tag: gets its own id sequence" "1" "$OUT" OUT=$(run_terms_sh --type tag --list) assert_eq "terms list tags: shows only the tag, not the category" "1 arduino arduino 0" "$OUT" # --- terms.sh: --find on a name that doesn't exist --- OUT=$(run_terms_sh --find "Does Not Exist" 2>&1) assert_eq "terms find missing: exits non-zero" "1" "$?" assert_eq "terms find missing: error message" "true" "$(grep -q "No category named" <<<"$OUT" && echo true || echo false)" # --- post.sh: --category/--tag by name resolve to an existing term, merged with config IDs --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" --category "Hardware" >/dev/null assert_eq "post category resolve: exit code" "0" "$?" REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "post category resolve: merges existing id with config CATEGORIES" "[1,3,7]" "$(jq -c '.payload.categories | sort' <<<"$REQ")" assert_eq "post category resolve: does not create a duplicate" "1" "$(run_terms_sh --list | wc -l)" # --- post.sh: --category/--tag by a new name creates it, then attaches it --- rm -f "$MOCK_WP_REQUEST_FILE" "$MOCK_WP_CONTROL_FILE" run_post_sh --file "$POST_FILE" --category "Software" --tag "midi" >/dev/null assert_eq "post category create: exit code" "0" "$?" NEW_CAT_ID=$(run_terms_sh --find "Software") NEW_TAG_ID=$(run_terms_sh --type tag --find "midi") REQ=$(cat "$MOCK_WP_REQUEST_FILE") assert_eq "post category create: includes the newly created category" "true" "$(jq --argjson id "$NEW_CAT_ID" '.payload.categories | index($id) != null' <<<"$REQ")" assert_eq "post tag create: includes the newly created tag" "true" "$(jq --argjson id "$NEW_TAG_ID" '.payload.tags | index($id) != null' <<<"$REQ")" echo if [[ $FAIL -eq 0 ]]; then echo "All tests passed." else echo "Some tests FAILED." fi exit $FAIL