D2 Demo Script - AY2627S1-CS3219-P23/foc GitHub Wiki

D2 Demo Script — curl walkthrough

Proves the backends work without the UI (rubric P2.3) and that roles gate access (P1.3, P1.4, P2.4). Run top to bottom; every call prints its HTTP status. Stop the web container first for the UI-independence point:

docker compose stop web        # backends keep running

Prereqs: stack up (docker compose up -d), .env has OWNER_SETUP_TOKEN set. OTP codes arrive in Mailpit — inbox UI at http://localhost:8025 (the script also pulls the code from Mailpit's API so it can run hands-free).

#!/usr/bin/env bash
set -euo pipefail

USERS=http://localhost:8087
SUPPLIERS=http://localhost:8086
MAILPIT=http://localhost:8025
PASS='Demo-Pass-123'            # 10-50 chars, upper+lower+digit

j() { curl -s -w '\n%{http_code}\n' -H 'Content-Type: application/json' "$@"; }

### 1. Owner via the setup endpoint (P1.6) — 201; rerun-safe (400 if it exists)
j -X POST "$USERS/auth/setup-owner" \
  -H "X-Setup-Token: ${OWNER_SETUP_TOKEN:?export it from .env first}" \
  -d '{"email":"[email protected]","username":"demo_owner","password":"'"$PASS"'"}'

### 2. Wrong setup token → 403 (controlled + secured)
j -X POST "$USERS/auth/setup-owner" -H "X-Setup-Token: wrong" \
  -d '{"email":"[email protected]","username":"x","password":"'"$PASS"'"}'

### 3. Sign-up with OTP (P1.3, F1.1) — 202, then verify → 201
j -X POST "$USERS/auth/signup" \
  -d '{"email":"[email protected]","username":"demo_user","password":"'"$PASS"'"}'
sleep 1
CODE=$(curl -s "$MAILPIT/api/v1/messages" | grep -oE '[0-9]{6}' | head -1)
echo "OTP from Mailpit: $CODE"
j -X POST "$USERS/auth/signup/verify" \
  -d '{"email":"[email protected]","code":"'"$CODE"'"}'

### 4. Bad password policy → 400 with exact reasons (F1.1.5-6)
j -X POST "$USERS/auth/signup" \
  -d '{"email":"[email protected]","username":"demo_weak","password":"short"}'

### 5. Login → JWTs (P1.3)
token() { curl -s -H 'Content-Type: application/json' -X POST "$USERS/auth/login" \
  -d '{"usernameOrEmail":"'"$1"'","password":"'"$PASS"'"}' \
  | sed -E 's/.*"accessToken":"([^"]+)".*/\1/'; }
OWNER_T=$(token demo_owner)
USER_T=$(token demo_user)
echo "decoded payload:"; echo "$USER_T" | cut -d. -f2 | base64 -d 2>/dev/null; echo

### 6. Owner promotes the first admin (P1.6) — needs the user id from step 3's 201
UID3=$(curl -s -H "Authorization: Bearer $USER_T" "$USERS/users/me" | sed -E 's/.*"id":([0-9]+).*/\1/')
j -X PATCH "$USERS/users/$UID3" -H "Authorization: Bearer $OWNER_T" -d '{"role":"ADMIN"}'
ADMIN_T=$(token demo_user)     # fresh token now carries role: ADMIN

### 7. RBAC on user-service (P1.3): admin list — 200 as admin, 403 as plain user, 401 bare
curl -s -o /dev/null -w 'admin list as ADMIN: %{http_code}\n' -H "Authorization: Bearer $ADMIN_T" "$USERS/users"
curl -s -o /dev/null -w 'admin list, no token: %{http_code}\n' "$USERS/users"

### 8. Protected fields (P1.5): role in the signup body is ignored
j -X POST "$USERS/auth/signup" \
  -d '{"email":"[email protected]","username":"demo_sneaky","password":"'"$PASS"'","role":"ADMIN"}'
# → 202; after verify the account is role USER

### 9. Supplier reads (P2.2, after #143: Bearer required)
curl -s -o /dev/null -w 'list page 0:        %{http_code}\n' -H "Authorization: Bearer $USER_T" \
  "$SUPPLIERS/suppliers?page=0&size=5"
curl -s -o /dev/null -w 'search+filter:      %{http_code}\n' -H "Authorization: Bearer $USER_T" \
  "$SUPPLIERS/suppliers?search=coffee&category=Coffee&sort=name,desc"
curl -s -o /dev/null -w 'nearest to me:      %{http_code}\n' -H "Authorization: Bearer $USER_T" \
  "$SUPPLIERS/suppliers?lat=1.2966&lng=103.7764"
curl -s -o /dev/null -w 'empty search (200): %{http_code}\n' -H "Authorization: Bearer $USER_T" \
  "$SUPPLIERS/suppliers?search=zzz-no-match"
curl -s -o /dev/null -w 'no token (401):     %{http_code}\n' "$SUPPLIERS/suppliers"

### 10. Supplier writes (P2.3/P2.4, once implemented): ADMIN 201 vs USER 403
j -X POST "$SUPPLIERS/suppliers" -H "Authorization: Bearer $ADMIN_T" \
  -d '{"name":"Demo Kopi","building":"COM1","categories":["Coffee"],"openingTime":"09:00","closingTime":"18:00"}'
j -X POST "$SUPPLIERS/suppliers" -H "Authorization: Bearer $USER_T" \
  -d '{"name":"Should Fail"}'    # → 403 problem+json
j -X POST "$SUPPLIERS/suppliers" -H "Authorization: Bearer $ADMIN_T" \
  -d '{"building":"no name"}'    # → 400, detail names the missing field

Afterwards: docker compose start web and repeat the browse/manage flows in the UI (P2.5) at desktop and mobile widths.

Notes

  • Step 10's request body is the proposed create contract — align with the implementation when the write endpoints land.
  • The lockout demo (5 wrong passwords → 429 + Retry-After) is worth one live call: repeat token demo_user with a bad password five times.
  • Mailpit keeps only demo mail in memory — restarting the container clears the inbox.