#!/usr/bin/env bash
# End-to-end test of the local_extenddeadline web services over real HTTP.
#
# PHPUnit calls execute() directly, so it never exercises the REST layer, token
# auth, service registration, JSON serialisation, or execute_returns() against
# real output. All of those can be broken while every unit test passes, and the
# React portal only ever talks to this path.
#
# Usage:
#   eval "$(/opt/homebrew/opt/php@8.3/bin/php local/extenddeadline/cli/seed_e2e_fixture.php | grep '^export')"
#   bash local/extenddeadline/cli/e2e_curl_test.sh
#
# Exits non-zero if any assertion fails.

set -uo pipefail

: "${WWWROOT:?run the seed script and eval its export lines first}"
: "${TOKEN:?run the seed script and eval its export lines first}"
: "${CMID:?run the seed script and eval its export lines first}"
: "${PROGRAMID:?run the seed script and eval its export lines first}"

ENDPOINT="${WWWROOT}/webservice/rest/server.php"
PASS=0
FAIL=0

# Call a web service function. $1 = function name, remaining args = form fields.
call() {
  local fn="$1"; shift
  local args=()
  for kv in "$@"; do args+=(--data-urlencode "$kv"); done
  curl -sS -X POST "$ENDPOINT" \
    --data-urlencode "wstoken=${TOKEN}" \
    --data-urlencode "wsfunction=${fn}" \
    --data-urlencode "moodlewsrestformat=json" \
    "${args[@]}"
}

# $1 = label, $2 = expected substring, $3 = payload
assert_contains() {
  if printf '%s' "$3" | grep -q -- "$2"; then
    echo "  PASS  $1"
    PASS=$((PASS + 1))
  else
    echo "  FAIL  $1"
    echo "        expected to contain: $2"
    echo "        got: $3"
    FAIL=$((FAIL + 1))
  fi
}

# $1 = label, $2 = unexpected substring, $3 = payload
assert_not_contains() {
  if printf '%s' "$3" | grep -q -- "$2"; then
    echo "  FAIL  $1"
    echo "        did not expect: $2"
    echo "        got: $3"
    FAIL=$((FAIL + 1))
  else
    echo "  PASS  $1"
    PASS=$((PASS + 1))
  fi
}

echo "1. get_options reports the activity as eligible"
OUT=$(call local_extenddeadline_get_options "cmids[0]=${CMID}" "programid=${PROGRAMID}")
assert_not_contains "no exception returned" '"exception"' "$OUT"
assert_contains "eligible is true" '"eligible":true' "$OUT"
assert_contains "all five durations offered" '"alloweddurations":\[1,3,5,7,10\]' "$OUT"
assert_contains "quota starts at zero" '"grantsused":0' "$OUT"
assert_contains "grant limit is three" '"grantslimit":3' "$OUT"
assert_contains "reason buckets returned" '"reasonbuckets"' "$OUT"
# existinggrant is OMITTED, not null -- Moodle 4.1 has no NULL_ALLOWED on a
# structure, so VALUE_OPTIONAL only works by leaving the key out entirely.
assert_not_contains "existinggrant absent before any grant" '"existinggrant"' "$OUT"

echo "2. request_extension grants three days"
KEY="e2e$(date +%s)a"
OUT=$(call local_extenddeadline_request_extension \
  "cmid=${CMID}" "programid=${PROGRAMID}" "days=3" \
  "reasonbucket=medical" "reasontext=" "idempotencykey=${KEY}")
assert_not_contains "no exception returned" '"exception"' "$OUT"
assert_contains "three days granted" '"daysgranted":3' "$OUT"
assert_contains "two grants remaining" '"grantsremaining":2' "$OUT"
GRANTID=$(printf '%s' "$OUT" | sed -n 's/.*"grantid":\([0-9]*\).*/\1/p')
echo "        (grantid=${GRANTID})"

echo "3. replaying the same idempotency key returns the same grant"
OUT=$(call local_extenddeadline_request_extension \
  "cmid=${CMID}" "programid=${PROGRAMID}" "days=7" \
  "reasonbucket=travel" "reasontext=" "idempotencykey=${KEY}")
assert_contains "same grantid returned" "\"grantid\":${GRANTID}" "$OUT"
assert_contains "original duration preserved, not re-granted" '"daysgranted":3' "$OUT"

echo "4. a fresh key on the same activity returns the existing grant"
OUT=$(call local_extenddeadline_request_extension \
  "cmid=${CMID}" "programid=${PROGRAMID}" "days=10" \
  "reasonbucket=work" "reasontext=" "idempotencykey=e2e$(date +%s)b")
assert_contains "same grantid returned" "\"grantid\":${GRANTID}" "$OUT"

echo "5. get_options now reports already_extended"
OUT=$(call local_extenddeadline_get_options "cmids[0]=${CMID}" "programid=${PROGRAMID}")
assert_contains "eligible is false" '"eligible":false' "$OUT"
assert_contains "reason is already_extended" '"reason":"already_extended"' "$OUT"
assert_contains "existing grant is now reported" '"existinggrant"' "$OUT"
assert_contains "quota now shows one used" '"grantsused":1' "$OUT"

echo "6. an unknown program is refused"
OUT=$(call local_extenddeadline_get_options "cmids[0]=${CMID}" "programid=99999999")
assert_contains "reason is not_in_program" '"reason":"not_in_program"' "$OUT"

echo "7. a bad token is rejected by the REST layer"
OUT=$(curl -sS -X POST "$ENDPOINT" \
  --data-urlencode "wstoken=definitelynotavalidtoken" \
  --data-urlencode "wsfunction=local_extenddeadline_get_options" \
  --data-urlencode "moodlewsrestformat=json" \
  --data-urlencode "cmids[0]=${CMID}" \
  --data-urlencode "programid=${PROGRAMID}")
assert_contains "invalid token exception" 'invalidtoken' "$OUT"

echo
echo "passed: ${PASS}  failed: ${FAIL}"
[ "$FAIL" -eq 0 ] || exit 1
