#!/usr/bin/env bash # COMPLETE APPLICATION WALKTHROUGH. Exercises the real authenticated API end to end, through the genuine login # and CSRF flow, and reports what each step actually returns. No greps, no inference. # # THIS EXISTS BECAUSE GREPPING LIED TWICE. A pattern that omitted A-Z hid every write route with a camelCase # path parameter, and I concluded the recruiting write path was missing when it has 42 routes. The directive's # verification method says reproduce against the live database, then verify end to end through the live # authenticated API. That is what this does. set -uo pipefail BASE="${BASE:-https://missionmatch-staging.thatwebhostingguy.com}" EMAIL="${EMAIL:?}" PW="${PW:?}" J=$(mktemp); trap 'rm -f "$J"' EXIT PASS=0; FAIL=0 ok() { PASS=$((PASS+1)); printf ' PASS %-46s %s\n' "$1" "$2"; } bad() { FAIL=$((FAIL+1)); printf ' FAIL %-46s %s\n' "$1" "$2"; } # jval pulls a scalar from a json body without jq, which is not installed here. jval() { printf '%s' "$1" | grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/'; } code() { printf '%s' "$1" | tail -1; } body() { printf '%s' "$1" | sed '$d'; } req() { # method path [json] -> sets R (body) and C (code) local m="$1" p="$2" d="${3:-}" out if [ -n "$d" ]; then out=$(curl -s -m 25 -b "$J" -c "$J" -X "$m" "$BASE$p" \ -H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" -H "Origin: $BASE" -H "Referer: $BASE/" -d "$d" -w $'\n%{http_code}') else out=$(curl -s -m 25 -b "$J" -c "$J" -X "$m" "$BASE$p" -H "X-CSRF-Token: $CSRF" -H "Origin: $BASE" -H "Referer: $BASE/" -w $'\n%{http_code}') fi R=$(body "$out"); C=$(code "$out") } echo "=== 0. CSRF and LOGIN through the real flow ===" CSRF="" out=$(curl -s -m 20 -c "$J" "$BASE/api/auth/csrf" -w $'\n%{http_code}') CSRF=$(jval "$(body "$out")" csrf_token); [ -z "$CSRF" ] && CSRF=$(jval "$(body "$out")" token) [ -n "$CSRF" ] && ok "GET /api/auth/csrf" "token issued, len ${#CSRF}" || bad "GET /api/auth/csrf" "no token in $(body "$out" | head -c 90)" req POST /api/auth/login "{\"email\":\"$EMAIL\",\"password\":\"$PW\"}" [ "$C" = "200" ] && ok "POST /api/auth/login" "200" || bad "POST /api/auth/login" "$C $(printf '%s' "$R" | head -c 120)" out=$(curl -s -m 20 -b "$J" -c "$J" "$BASE/api/auth/csrf" -w $'\n%{http_code}') T=$(jval "$(body "$out")" csrf_token); [ -z "$T" ] && T=$(jval "$(body "$out")" token) [ -n "$T" ] && CSRF="$T" || echo " WARN post login csrf refetch found no token, writes will 403" req GET /api/auth/session [ "$C" = "200" ] && ok "GET /api/auth/session" "role $(jval "$R" role), tools $(printf '%s' "$R" | grep -oE '"tools":\[[^]]*\]' | head -c 60)" \ || bad "GET /api/auth/session" "$C" echo "=== 1. CLIENT, the entity a desk cannot work without ===" CN="Walkthrough Client $(date +%s)" req POST /api/recruiting/clients "{\"name\":\"$CN\",\"status\":\"active\"}" CLIENT=$(jval "$R" id) [ "$C" = "200" -o "$C" = "201" ] && ok "POST /recruiting/clients" "$C id ${CLIENT:0:8}" || bad "POST /recruiting/clients" "$C $(printf '%s' "$R" | head -c 160)" if [ -n "$CLIENT" ]; then req POST "/api/recruiting/clients/$CLIENT/contacts" '{"full_name":"Dana Reyes","title":"Contracts","email":"dana.reyes@example.invalid"}' [ "$C" = "200" -o "$C" = "201" ] && ok "POST /clients/{id}/contacts" "$C" || bad "POST /clients/{id}/contacts" "$C $(printf '%s' "$R" | head -c 160)" fi echo "=== 2. REQUISITION ===" req POST /api/recruiting/requisitions "{\"client_id\":\"$CLIENT\",\"title\":\"Cleared Systems Engineer\",\"employment_type\":\"contract\",\"openings\":1}" REQ=$(jval "$R" id) [ -n "$REQ" ] && ok "POST /recruiting/requisitions" "$C id ${REQ:0:8}" || bad "POST /recruiting/requisitions" "$C $(printf '%s' "$R" | head -c 200)" echo "=== 3. CANDIDATE, created through the API so the walkthrough is self contained ===" # The fixture org holds ZERO candidates, which is RLS working rather than a defect, so the earlier empty list # was correct. Creating one here also exercises POST /candidates. req POST /api/candidates '{"full_name":"Walkthrough Candidate","clearance_level":"secret","clearance_status":"active","available":true}' CAND=$(jval "$R" id) [ -n "$CAND" ] && ok "POST /candidates" "$C id ${CAND:0:8}" || bad "POST /candidates" "$C $(printf '%s' "$R" | head -c 200)" req GET "/api/candidates?limit=5" N=$(printf '%s' "$R" | grep -oc '"id"' 2>/dev/null || printf '%s' "$R" | grep -o '"id"' | wc -l | tr -d ' ') [ "$C" = "200" ] && ok "GET /candidates reads the write back" "$C, $N id fields present" || bad "GET /candidates" "$C" echo "=== 4. SOURCE the candidate onto the requisition ===" if [ -n "$REQ" ] && [ -n "$CAND" ]; then req POST "/api/recruiting/requisitions/$REQ/applications" "{\"candidate_id\":\"$CAND\",\"source_touch\":\"walkthrough\"}" APP=$(jval "$R" id) [ -n "$APP" ] && ok "POST /requisitions/{id}/applications" "$C stage $(jval "$R" stage), id ${APP:0:8}" \ || bad "POST /requisitions/{id}/applications" "$C $(printf '%s' "$R" | head -c 200)" fi echo "=== 5. ADVANCE THE STAGES, each with expected_stage as the concurrency token ===" FROM=sourced for TO in screened submitted interviewing; do [ -z "${APP:-}" ] && break req POST "/api/recruiting/applications/$APP/stage" "{\"expected_stage\":\"$FROM\",\"to_stage\":\"$TO\"}" NOW=$(jval "$R" stage) [ "$NOW" = "$TO" ] && { ok "stage $FROM to $TO" "$C now $NOW"; FROM="$TO"; } \ || bad "stage $FROM to $TO" "$C got '$NOW' $(printf '%s' "$R" | head -c 140)" done echo "=== 6. STALE TOKEN CONTROL. A move from the wrong expected_stage MUST be refused ===" if [ -n "${APP:-}" ]; then req POST "/api/recruiting/applications/$APP/stage" '{"expected_stage":"sourced","to_stage":"offered"}' [ "$C" != "200" ] && ok "stale expected_stage refused" "$C, correctly refused" \ || bad "stale expected_stage refused" "200, a stale token was ACCEPTED" fi echo "=== 7. INTERVIEW, schedule then record the outcome ===" if [ -n "${APP:-}" ]; then req POST "/api/recruiting/applications/$APP/interviews" "{\"kind\":\"client\",\"scheduled_at\":\"$(date -u -d '-2 hours' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-2H +%Y-%m-%dT%H:%M:%SZ)\"}" IV=$(jval "$R" id) [ -n "$IV" ] && ok "POST /applications/{id}/interviews" "$C outcome $(jval "$R" outcome)" \ || bad "POST /applications/{id}/interviews" "$C $(printf '%s' "$R" | head -c 200)" if [ -n "$IV" ]; then IVU=$(jval "$R" updated_at) req PATCH "/api/recruiting/interviews/$IV" "{\"expected_updated_at\":\"$IVU\",\"outcome\":\"passed\",\"completed_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" [ "$C" = "200" ] && ok "PATCH /interviews/{id} outcome passed" "$C" || bad "PATCH /interviews/{id}" "$C $(printf '%s' "$R" | head -c 160)" fi fi echo "=== 8. OFFER. The schema refuses an extended contract offer with no economics, so this proves both ===" if [ -n "${APP:-}" ]; then req POST "/api/recruiting/applications/$APP/stage" "{\"expected_stage\":\"interviewing\",\"to_stage\":\"offered\"}" [ "$C" = "200" ] && ok "stage interviewing to offered" "$C" || bad "stage interviewing to offered" "$C" echo " --- CONTROL: an offer with NO rates must be refused by the schema constraint ---" req POST "/api/recruiting/applications/$APP/offers" '{"employment_type":"contract"}' [ "$C" != "200" ] && [ "$C" != "201" ] && ok "unpriced contract offer refused" "$C: $(printf %s "$R" | head -c 90)" \ || bad "unpriced contract offer refused" "$C, an offer with no economics was ACCEPTED" req POST "/api/recruiting/applications/$APP/offers" '{"employment_type":"contract","bill_rate":"145.00","pay_rate":"95.00"}' OF=$(jval "$R" id) [ -n "$OF" ] && ok "POST /applications/{id}/offers" "$C status $(jval "$R" status), id ${OF:0:8}" \ || bad "POST /applications/{id}/offers" "$C $(printf '%s' "$R" | head -c 200)" if [ -n "${OF:-}" ]; then req POST "/api/recruiting/offers/$OF/status" '{"expected_status":"extended","status":"accepted"}' [ "$C" = "200" ] && ok "POST /offers/{id}/status accepted" "$C" || bad "POST /offers/{id}/status" "$C $(printf '%s' "$R" | head -c 160)" fi fi echo "=== 9. PLACEMENT, the revenue event ===" if [ -n "${APP:-}" ]; then for attempt in 1 2 3; do req POST "/api/recruiting/applications/$APP/stage" "{\"expected_stage\":\"offered\",\"to_stage\":\"placed\"}" [ "$C" != "000" ] && break; sleep 2 done [ "$C" = "200" ] && ok "stage offered to placed" "$C" || bad "stage offered to placed" "$C after $attempt attempt(s)" req POST "/api/recruiting/applications/$APP/placement" "{\"offer_id\":\"$OF\",\"employment_type\":\"contract\",\"start_date\":\"$(date -u +%Y-%m-%d)\",\"bill_rate\":\"145.00\",\"pay_rate\":\"95.00\"}" PL=$(jval "$R" id) [ -n "$PL" ] && ok "POST /applications/{id}/placement" "$C id ${PL:0:8}" \ || bad "POST /applications/{id}/placement" "$C $(printf '%s' "$R" | head -c 200)" fi echo "=== 10. READ IT ALL BACK, so a write that reports success but stores nothing is caught ===" if [ -n "${APP:-}" ]; then req GET "/api/recruiting/applications/$APP" [ "$C" = "200" ] && ok "GET /applications/{id}" "stage $(jval "$R" stage), furthest $(printf '%s' "$R" | grep -oE '"furthest[a-z_]*":"?[a-z0-9]*' | head -1)" \ || bad "GET /applications/{id}" "$C" req GET "/api/recruiting/requisitions/$REQ/pipeline" [ "$C" = "200" ] && ok "GET /requisitions/{id}/pipeline" "$C" || bad "GET /requisitions/{id}/pipeline" "$C" req GET "/api/recruiting/placements" [ "$C" = "200" ] && ok "GET /placements" "$C" || bad "GET /placements" "$C" req GET "/api/recruiting/analytics" [ "$C" = "200" ] && ok "GET /analytics" "$C" || bad "GET /analytics" "$C" fi echo echo "WALKTHROUGH RESULT: $PASS passed, $FAIL failed" [ "$FAIL" -gt 0 ] && exit 1 || exit 0