289 lines
10 KiB
Bash
Executable File
289 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
ENV_FILE="${ROOT_DIR}/credentials/.env"
|
|
|
|
if [[ -f "${ENV_FILE}" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "${ENV_FILE}"
|
|
fi
|
|
|
|
BASE_URL="${BASE_URL:-${WP_URL:-}}"
|
|
AUTH_USER="${CAL_TEST_USER:-adrians@chezstephens.org.uk}"
|
|
AUTH_PASS="${CAL_TEST_PASSWORD:-brillig1}"
|
|
EXPECTED_TABLE_PREFIX="${CAL_TABLE_PREFIX_EXPECTED:-wp_cs_calendar}"
|
|
ENABLE_PREFIX_CHECK="${CAL_ENABLE_PREFIX_CHECK:-1}"
|
|
REMOTE_WP_PATH="${REMOTE_WP_PATH:-}"
|
|
if [[ -z "${REMOTE_WP_PATH}" ]] && [[ -n "${REMOTE_APP_DIR:-}" ]]; then
|
|
REMOTE_WP_PATH="$(dirname "$(dirname "${REMOTE_APP_DIR}")")"
|
|
fi
|
|
if [[ -z "${REMOTE_WP_PATH}" ]]; then
|
|
REMOTE_WP_PATH="/var/www/wordpress"
|
|
fi
|
|
FAILURES=0
|
|
CREATED_EVENT_ID=""
|
|
CREATED_REC_EVENT_ID=""
|
|
|
|
usage() {
|
|
cat <<'TXT'
|
|
Remote API+E2E smoke runner for calendar plugin.
|
|
|
|
Usage:
|
|
tests/run_remote_tests.sh [--base-url URL] [--user EMAIL] [--password PASS]
|
|
|
|
Env overrides:
|
|
BASE_URL
|
|
CAL_TEST_USER
|
|
CAL_TEST_PASSWORD
|
|
CAL_TABLE_PREFIX_EXPECTED
|
|
CAL_ENABLE_PREFIX_CHECK
|
|
|
|
Defaults:
|
|
BASE_URL -> credentials/.env:WP_URL
|
|
user/password -> seeded smoke account
|
|
TXT
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--base-url)
|
|
BASE_URL="${2:-}"
|
|
shift 2
|
|
;;
|
|
--user)
|
|
AUTH_USER="${2:-}"
|
|
shift 2
|
|
;;
|
|
--password)
|
|
AUTH_PASS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "[remote-tests] unknown argument: $1" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "${BASE_URL}" ]]; then
|
|
echo "[remote-tests] BASE_URL is required (set WP_URL in credentials/.env or pass --base-url)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
BASE_URL="${BASE_URL%/}"
|
|
|
|
step() {
|
|
printf '\n[remote-tests] %s\n' "$1"
|
|
}
|
|
|
|
record_fail() {
|
|
FAILURES=$((FAILURES + 1))
|
|
echo "[remote-tests] FAIL: $1" >&2
|
|
}
|
|
|
|
json_assert() {
|
|
local file="$1"
|
|
local expr="$2"
|
|
python3 - "$file" "$expr" <<'PY'
|
|
import json, sys
|
|
path, expr = sys.argv[1], sys.argv[2]
|
|
data = json.load(open(path))
|
|
safe = {"bool": bool, "isinstance": isinstance, "list": list, "dict": dict, "str": str, "int": int}
|
|
ok = eval(expr, {"__builtins__": safe}, {"data": data})
|
|
if not ok:
|
|
raise SystemExit(1)
|
|
PY
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "${CREATED_EVENT_ID}" ]]; then
|
|
curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X DELETE \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_EVENT_ID}" >/dev/null || true
|
|
fi
|
|
if [[ -n "${CREATED_REC_EVENT_ID}" ]]; then
|
|
curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X DELETE \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_REC_EVENT_ID}" >/dev/null || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
step "health"
|
|
if curl -fsS "${BASE_URL}/wp-json/calendar/v1/health" >/tmp/remote_test_health.json; then
|
|
if ! json_assert /tmp/remote_test_health.json "data.get('status') == 'ok'"; then
|
|
record_fail "health payload invalid"
|
|
fi
|
|
else
|
|
record_fail "health endpoint unreachable"
|
|
fi
|
|
|
|
if [[ "${ENABLE_PREFIX_CHECK}" == "1" ]] && [[ -n "${REMOTE_HOST:-}" ]] && [[ -n "${REMOTE_USER:-}" ]] && [[ -n "${REMOTE_SSH_KEY_PATH:-}" ]] && [[ -n "${REMOTE_PORT:-}" ]] && [[ -n "${REMOTE_WP_CLI:-}" ]]; then
|
|
step "table prefix configuration"
|
|
SSH_KEY_PATH="${REMOTE_SSH_KEY_PATH}"
|
|
if [[ "${SSH_KEY_PATH}" != /* ]]; then
|
|
SSH_KEY_PATH="${ROOT_DIR}/${SSH_KEY_PATH}"
|
|
fi
|
|
SSH_PREFIX=(ssh -F /dev/null -i "${SSH_KEY_PATH}" -p "${REMOTE_PORT}" -o StrictHostKeyChecking=accept-new "${REMOTE_USER}@${REMOTE_HOST}")
|
|
if ! "${SSH_PREFIX[@]}" "echo ok" >/dev/null 2>&1; then
|
|
echo "[remote-tests] WARN: SSH unavailable, skipping table prefix check"
|
|
else
|
|
STEM="$("${SSH_PREFIX[@]}" "cd ${REMOTE_WP_PATH} && ${REMOTE_WP_CLI} option get calendar_plugin_table_stem --allow-root 2>/dev/null || true" | tr -d '\r' | tail -n1)"
|
|
if [[ -n "${STEM}" ]] && [[ "${STEM}" != "cs_calendar" ]] && [[ "${STEM}" != "calendar" ]]; then
|
|
record_fail "table stem option expected cs_calendar/calendar got '${STEM}'"
|
|
fi
|
|
TABLE_LIST="$("${SSH_PREFIX[@]}" "cd ${REMOTE_WP_PATH} && ${REMOTE_WP_CLI} db query \"SHOW TABLES;\" --allow-root --silent --skip-column-names 2>/dev/null || true" | tr -d '\r')"
|
|
TABLE_EXISTS="$(printf '%s\n' "${TABLE_LIST}" | grep -Fx "${EXPECTED_TABLE_PREFIX}_events" | head -n1 || true)"
|
|
LEGACY_EXISTS="$(printf '%s\n' "${TABLE_LIST}" | grep -Fx "wp_calendar_events" | head -n1 || true)"
|
|
if [[ "${TABLE_EXISTS}" != "${EXPECTED_TABLE_PREFIX}_events" ]] && [[ "${LEGACY_EXISTS}" != "wp_calendar_events" ]]; then
|
|
record_fail "expected table prefix '${EXPECTED_TABLE_PREFIX}' (or legacy wp_calendar) not found"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
step "public read month"
|
|
if curl -fsS "${BASE_URL}/wp-json/calendar/v1/public/events?view=month&date=2026-04-01" >/tmp/remote_test_public.json; then
|
|
if ! json_assert /tmp/remote_test_public.json "isinstance(data.get('data'), list)"; then
|
|
record_fail "public events payload invalid"
|
|
fi
|
|
else
|
|
record_fail "public events endpoint failed"
|
|
fi
|
|
|
|
step "authenticated /users/me"
|
|
if curl -fsS -u "${AUTH_USER}:${AUTH_PASS}" "${BASE_URL}/wp-json/calendar/v1/users/me" >/tmp/remote_test_me.json; then
|
|
if ! json_assert /tmp/remote_test_me.json "bool(data.get('data', {}).get('email'))"; then
|
|
record_fail "auth user context invalid"
|
|
fi
|
|
else
|
|
record_fail "auth /users/me failed"
|
|
fi
|
|
|
|
step "event CRUD"
|
|
EVENT_UID="remote-api-e2e-$(date +%s)@calendar-plugin"
|
|
CREATE_JSON=$(cat <<JSON
|
|
{"uid":"${EVENT_UID}","title":"Remote API E2E","description":"created by remote runner","location":"Remote","category":"QA","start_datetime":"2026-04-20T10:00:00+01:00","end_datetime":"2026-04-20T11:00:00+01:00","repeat_type":"none","repeat_interval":1,"repeat_range_mode":"none"}
|
|
JSON
|
|
)
|
|
CREATE_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -H 'Content-Type: application/json' \
|
|
-d "${CREATE_JSON}" -o /tmp/remote_test_create.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events" || true)"
|
|
if [[ "${CREATE_HTTP}" != "200" ]]; then
|
|
record_fail "event create failed (http ${CREATE_HTTP})"
|
|
else
|
|
CREATED_EVENT_ID="$(python3 - <<'PY'
|
|
import json
|
|
p=json.load(open('/tmp/remote_test_create.json'))
|
|
print(p.get('data', {}).get('id', ''))
|
|
PY
|
|
)"
|
|
if [[ -z "${CREATED_EVENT_ID}" ]]; then
|
|
record_fail "event create returned no id"
|
|
else
|
|
PATCH_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X PATCH -H 'Content-Type: application/json' \
|
|
-d '{"title":"Remote API E2E Updated"}' -o /tmp/remote_test_patch.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_EVENT_ID}" || true)"
|
|
if [[ "${PATCH_HTTP}" != "200" ]]; then
|
|
record_fail "event update failed (http ${PATCH_HTTP})"
|
|
elif ! json_assert /tmp/remote_test_patch.json "data.get('data', {}).get('title') == 'Remote API E2E Updated'"; then
|
|
record_fail "event update payload invalid"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
step "recurrence exception behavior"
|
|
REC_UID="remote-rec-exc-$(date +%s)@calendar-plugin"
|
|
REC_JSON=$(cat <<JSON
|
|
{"uid":"${REC_UID}","title":"Remote Recurrence","description":"exception test","location":"Remote","category":"QA","start_datetime":"2026-04-02T10:00:00+01:00","end_datetime":"2026-04-02T11:00:00+01:00","repeat_type":"daily","repeat_interval":1,"repeat_range_mode":"until","repeat_until":"2026-04-10"}
|
|
JSON
|
|
)
|
|
REC_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -H 'Content-Type: application/json' \
|
|
-d "${REC_JSON}" -o /tmp/remote_test_rec_create.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events" || true)"
|
|
if [[ "${REC_HTTP}" != "200" ]]; then
|
|
record_fail "recurring create failed (http ${REC_HTTP})"
|
|
else
|
|
CREATED_REC_EVENT_ID="$(python3 - <<'PY'
|
|
import json
|
|
p=json.load(open('/tmp/remote_test_rec_create.json'))
|
|
print(p.get('data', {}).get('id', ''))
|
|
PY
|
|
)"
|
|
if [[ -z "${CREATED_REC_EVENT_ID}" ]]; then
|
|
record_fail "recurring create returned no id"
|
|
else
|
|
PRE_LIST_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" \
|
|
-o /tmp/remote_test_occ_pre_list.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_REC_EVENT_ID}/occurrences?from=2026-04-01&months=1" || true)"
|
|
if [[ "${PRE_LIST_HTTP}" != "200" ]]; then
|
|
record_fail "list occurrences before delete failed (http ${PRE_LIST_HTTP})"
|
|
OCC_KEY=""
|
|
else
|
|
OCC_KEY="$(python3 - <<'PY'
|
|
import json
|
|
p=json.load(open('/tmp/remote_test_occ_pre_list.json'))
|
|
items=p.get('data', [])
|
|
print(items[2].get('occurrence_start', '') if len(items) >= 3 else (items[0].get('occurrence_start', '') if items else ''))
|
|
PY
|
|
)"
|
|
fi
|
|
if [[ -z "${OCC_KEY}" ]]; then
|
|
record_fail "no occurrence key available for delete test"
|
|
fi
|
|
OCC_KEY_ENC="$(python3 - "${OCC_KEY}" <<'PY'
|
|
import sys, urllib.parse
|
|
print(urllib.parse.quote(sys.argv[1], safe=''))
|
|
PY
|
|
)"
|
|
OCC_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X DELETE \
|
|
-o /tmp/remote_test_occ_del.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_REC_EVENT_ID}/occurrences/${OCC_KEY_ENC}" || true)"
|
|
if [[ "${OCC_HTTP}" != "200" ]]; then
|
|
record_fail "delete single occurrence failed (http ${OCC_HTTP})"
|
|
else
|
|
LIST_HTTP="$(curl -sS -u "${AUTH_USER}:${AUTH_PASS}" \
|
|
-o /tmp/remote_test_occ_list.json -w '%{http_code}' \
|
|
"${BASE_URL}/wp-json/calendar/v1/events/${CREATED_REC_EVENT_ID}/occurrences?from=2026-04-01&months=1" || true)"
|
|
if [[ "${LIST_HTTP}" != "200" ]]; then
|
|
record_fail "list occurrences after exception failed (http ${LIST_HTTP})"
|
|
else
|
|
if ! python3 - "${OCC_KEY}" <<'PY'
|
|
import json
|
|
import sys
|
|
deleted_key=sys.argv[1][:10]
|
|
p=json.load(open('/tmp/remote_test_occ_list.json'))
|
|
starts=[x.get('occurrence_start','')[:10] for x in p.get('data',[])]
|
|
assert deleted_key not in starts
|
|
PY
|
|
then
|
|
record_fail "occurrence exception not preserved"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
step "caldav discovery compatibility"
|
|
CALDAV_UNAUTH="$(curl -s -o /tmp/remote_test_caldav_unauth.txt -w '%{http_code}' "${BASE_URL}/caldav/" || true)"
|
|
if [[ "${CALDAV_UNAUTH}" != "401" ]]; then
|
|
record_fail "caldav unauth challenge expected 401 got ${CALDAV_UNAUTH}"
|
|
fi
|
|
CALDAV_PROP="$(curl -s -u "${AUTH_USER}:${AUTH_PASS}" -X PROPFIND -H 'Depth: 0' \
|
|
-o /tmp/remote_test_caldav_propfind.xml -w '%{http_code}' \
|
|
"${BASE_URL}/caldav/" || true)"
|
|
if [[ "${CALDAV_PROP}" != "207" ]]; then
|
|
record_fail "caldav root PROPFIND expected 207 got ${CALDAV_PROP}"
|
|
elif ! grep -Eqi 'calendar-home-set|current-user-principal' /tmp/remote_test_caldav_propfind.xml; then
|
|
record_fail "caldav PROPFIND missing expected discovery properties"
|
|
fi
|
|
|
|
printf '\n[remote-tests] completed with %d failure(s)\n' "${FAILURES}"
|
|
if [[ "${FAILURES}" -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
echo "[remote-tests] PASS"
|