#!/usr/bin/env zsh SESSION_NAME="crussell-dev" SEED_SCRIPT="/tmp/seed_data.sh" setopt NO_UNSET setopt PIPE_FAIL setopt ERR_EXIT # --- UI Helpers --- C_RESET=$'\033[0m' C_GREEN=$'\033[32m' C_RED=$'\033[31m' C_BLUE=$'\033[34m' C_YELLOW=$'\033[33m' log_info() { echo "๐Ÿ”น $1" } log_success() { echo "โœ… $1" } log_error() { echo "โŒ $1" } log_step() { echo "โ–ถ๏ธ $1" } # --- 1. Environment Setup --- if [ -f .env ]; then set -a source .env set +a log_success "Loaded environment variables" else log_error ".env file not found!" exit 1 fi # --- 2. Docker Checks --- if ! docker info > /dev/null 2>&1; then log_info "Docker daemon not running. Starting..." sudo systemctl start docker sleep 2 if ! docker info > /dev/null 2>&1; then log_error "Failed to start Docker." exit 1 fi log_success "Docker started" fi # --- 3. Database Reset --- log_step "Resetting PostgreSQL..." docker compose down -v postgres > /dev/null 2>&1 docker compose up postgres -d > /dev/null 2>&1 log_success "PostgreSQL reset complete" # --- 3a. Wait for PostgreSQL to be ready --- log_step "Waiting for PostgreSQL to initialize..." MAX_STARTUP_ATTEMPTS=30 STARTUP_ATTEMPT=0 while [ $STARTUP_ATTEMPT -lt $MAX_STARTUP_ATTEMPTS ]; do if docker exec postgres psql -U myuser -d mydb -c "SELECT 1;" > /dev/null 2>&1; then log_success "PostgreSQL is ready" break fi STARTUP_ATTEMPT=$((STARTUP_ATTEMPT+1)) sleep 1 done if [ $STARTUP_ATTEMPT -eq $MAX_STARTUP_ATTEMPTS ]; then log_error "PostgreSQL failed to start after $MAX_STARTUP_ATTEMPTS attempts" exit 1 fi # --- 3a-extra. Additional wait for full PostgreSQL initialization --- log_step "Allowing PostgreSQL to fully initialize..." sleep 5 # --- 3b. Create Test Database --- log_step "Setting up test database (crussell_test)..." docker exec postgres psql -U myuser -d mydb -c "CREATE DATABASE crussell_test;" 2>&1 || log_info "Test database may already exist" sleep 2 # --- 3c. Seed Test Database Schema --- log_step "Seeding test database schema..." docker exec -i postgres psql -U myuser -d crussell_test < init-scripts/init-script.sql 2>&1 | head -5 || true log_success "Test database schema seeded" # --- 3d. Verify test database is ready --- log_step "Verifying test database readiness..." MAX_ATTEMPTS=10 ATTEMPT=0 while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do if docker exec postgres psql -U myuser -d crussell_test -c "SELECT 1;" > /dev/null 2>&1; then log_success "Test database ready" break fi ATTEMPT=$((ATTEMPT+1)) sleep 1 done if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then log_error "Test database failed to initialize after $MAX_ATTEMPTS attempts" exit 1 fi # --- 3e. Rustfs (wipe data for fresh start) --- log_step "Wiping Rustfs (S3 storage)..." docker compose stop rustfs > /dev/null 2>&1 || true docker compose rm -f rustfs > /dev/null 2>&1 || true docker volume rm crussell_rustfs_data > /dev/null 2>&1 || true docker compose up rustfs -d > /dev/null 2>&1 log_success "Rustfs wiped and restarted" sleep 2 # --- 3f. SabreDAV (start early to create tables) --- log_step "Starting SabreDAV (CardDAV/CalDAV)..." docker compose up sabredav -d > /dev/null 2>&1 log_success "SabreDAV started" sleep 3 # --- 4. Tmux Session Setup --- if tmux has-session -t $SESSION_NAME 2>/dev/null; then log_info "Killing existing tmux session..." tmux kill-session -t $SESSION_NAME fi log_step "Starting tmux session '$SESSION_NAME'..." tmux new-session -d -s $SESSION_NAME -n "Workspace" # Pass env vars to tmux session (must be after session creation) tmux set-environment -t $SESSION_NAME POSTGRES_USER "$POSTGRES_USER" tmux set-environment -t $SESSION_NAME POSTGRES_PASSWORD "$POSTGRES_PASSWORD" tmux set-environment -t $SESSION_NAME POSTGRES_DB "$POSTGRES_DB" tmux set-environment -t $SESSION_NAME POSTGRES_HOST "$POSTGRES_HOST" tmux set-environment -t $SESSION_NAME POSTGRES_PORT "$POSTGRES_PORT" tmux set-environment -t $SESSION_NAME JWT_SECRET_KEY "$JWT_SECRET_KEY" tmux set-environment -t $SESSION_NAME S3_ENDPOINT "$S3_ENDPOINT" tmux set-environment -t $SESSION_NAME S3_PUBLIC_URL "$S3_PUBLIC_URL" tmux set-environment -t $SESSION_NAME S3_ACCESS_KEY "$S3_ACCESS_KEY" tmux set-environment -t $SESSION_NAME S3_SECRET_KEY "$S3_SECRET_KEY" tmux set-environment -t $SESSION_NAME S3_BUCKET "$S3_BUCKET" tmux set-environment -t $SESSION_NAME AWS_REGION "$AWS_REGION" tmux set-environment -t $SESSION_NAME VITE_BACKEND_URL "$VITE_BACKEND_URL" # Pane 0: Database tmux send-keys -t $SESSION_NAME 'docker exec -it postgres psql -U myuser -d mydb -c "SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY table_name;"' tmux select-pane -t $SESSION_NAME:0.0 -T "DB" # Pane 1: Backend (Split Horizontally) tmux split-window -v -t $SESSION_NAME tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd backend && go run -tags dev ./main.go" Enter tmux select-pane -t $SESSION_NAME:0.1 -T "Backend" # Pane 2: Frontend (Split Vertically from Backend) tmux split-window -h -t $SESSION_NAME:0.1 tmux send-keys -t $SESSION_NAME "set -a; source .env > /dev/null 2>&1; cd frontend && npm run dev -- --host" Enter tmux select-pane -t $SESSION_NAME:0.2 -T "Frontend" # Pane 3: Rustfs (S3-compatible storage) - Split from Frontend tmux split-window -v -t $SESSION_NAME:0.2 tmux send-keys -t $SESSION_NAME "docker logs -f rustfs" Enter tmux select-pane -t $SESSION_NAME:0.3 -T "Rustfs" # Layout configuration tmux select-layout -t $SESSION_NAME even-vertical tmux select-pane -t $SESSION_NAME:0.0 # --- 5. Seed Script Generation --- log_step "Generating seed script..." cat > $SEED_SCRIPT << 'SEED_EOF' #!/bin/bash # --- Config --- ADMIN_EMAIL="admin@example.com" ADMIN_PASS="password" USER_EMAIL="user@example.com" USER_PASS="password" BASE_URL="http://localhost:8080/api" # Safe booking times โ€” mid-morning to early afternoon to avoid closing-hour conflicts. # Adjust these if your business hours differ. SLOT_A="10:00:00" SLOT_B="11:30:00" SLOT_C="13:00:00" SLOT_D="14:30:00" SLOT_E="12:15:00" # Used only for pending demo bookings โ€” avoids conflicts with A-D rotation # --- Formatting --- C_RESET=$'\033[0m' C_GREEN=$'\033[32m' C_RED=$'\033[31m' C_BLUE=$'\033[34m' C_YELLOW=$'\033[33m' # --- Global for ID Capture --- LAST_BOOKING_ID="" # --- Helper: API Request --- # Returns the root-level "id" from the response body on success. api_post() { local url="$1" local data="$2" local desc="$3" local token="$4" local curl_opts=(-s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json') [[ -n "$token" ]] && curl_opts+=(-H "Authorization: Bearer $token") [[ -n "$data" ]] && curl_opts+=(-d "$data") local response=$(curl "${curl_opts[@]}" "$url") local http_code=$(echo "$response" | tail -n1) local body=$(echo "$response" | sed '$d') if [[ "$http_code" =~ ^2 ]]; then # Strip nested "user" object so we reliably get the root "id" (UUID) local extracted extracted=$(echo "$body" | tr -d '\n' | sed 's/"user":{[^}]*}//' | grep -o '"id":"[^"]*' | cut -d'"' -f4 | tr -d '\r\n ') # Only emit the value if it looks like a UUID (hex + hyphens, 8+ chars) if [[ "$extracted" =~ ^[0-9a-f-]{8,}$ ]]; then echo "$extracted" fi return 0 else printf "${C_RED}โŒ Failed: %s (HTTP %s)${C_RESET}\n" "$desc" "$http_code" printf " Request: %s\n" "$data" printf " Response: %s\n" "$body" return 1 fi } # --- Helper: Login and return token --- login() { local email="$1" pass="$2" curl -s -X POST -H 'Content-Type: application/json' \ -d "{\"email\":\"$email\",\"password\":\"$pass\"}" \ "$BASE_URL/login" \ | tr -d '\r\n\t ' \ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p' } wait_for_backend() { echo -ne "โณ Waiting for backend..." for ((i=1; i<=60; i++)); do if curl -s --connect-timeout 2 http://localhost:8080/api/register > /dev/null 2>&1; then echo -e "\rโณ Waiting for backend... ${C_GREEN}Ready!${C_RESET}" return 0 fi echo -n "." sleep 1 done echo -e "\rโณ Waiting for backend... ${C_RED}Timed out${C_RESET}" exit 1 } # --- Main Execution --- wait_for_backend # =========================================================================== # 1. REGISTER USERS # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ‘ค Registering Users...${C_RESET}" success=0 total=20 # Admin if api_post "$BASE_URL/register" '{"firstName":"Chelsea","lastName":"Russell","email":"admin@example.com","password":"password","phone":"+447000000000","dateOfBirth":"1985-01-01","agreedToPolicy":true}' "Register Admin" "" > /dev/null; then success=$((success+1)); fi # Primary test user (no-deposit, easy to book with) if api_post "$BASE_URL/register" '{"firstName":"Regular","lastName":"User","email":"user@example.com","password":"password","phone":"+447000000001","dateOfBirth":"1990-05-15","agreedToPolicy":true}' "Register User" "" > /dev/null; then success=$((success+1)); fi # Loyal regulars if api_post "$BASE_URL/register" '{"firstName":"Emma","lastName":"Johnson","email":"emma.johnson@example.com","password":"password","phone":"+447000000002","dateOfBirth":"1988-03-22","agreedToPolicy":true}' "Register Emma Johnson" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Sophie","lastName":"Williams","email":"sophie.williams@example.com","password":"password","phone":"+447000000003","dateOfBirth":"1995-11-08","agreedToPolicy":true}' "Register Sophie Williams" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Amelia","lastName":"Jones","email":"amelia.jones@example.com","password":"password","phone":"+447000000004","dateOfBirth":"1993-09-30","agreedToPolicy":true}' "Register Amelia Jones" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Isla","lastName":"Davies","email":"isla.davies@example.com","password":"password","phone":"+447000000005","dateOfBirth":"1989-12-25","agreedToPolicy":true}' "Register Isla Davies" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Lily","lastName":"Wilson","email":"lily.wilson@example.com","password":"password","phone":"+447000000006","dateOfBirth":"1996-08-03","agreedToPolicy":true}' "Register Lily Wilson" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Ava","lastName":"Walker","email":"ava.walker@example.com","password":"password","phone":"+447000000007","dateOfBirth":"1993-03-07","agreedToPolicy":true}' "Register Ava Walker" "" > /dev/null; then success=$((success+1)); fi # Occasional clients if api_post "$BASE_URL/register" '{"firstName":"Oliver","lastName":"Smith","email":"oliver.smith@example.com","password":"password","phone":"+447000000008","dateOfBirth":"1992-07-14","agreedToPolicy":true}' "Register Oliver Smith" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Harry","lastName":"Brown","email":"harry.brown@example.com","password":"password","phone":"+447000000009","dateOfBirth":"1987-02-19","agreedToPolicy":true}' "Register Harry Brown" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"George","lastName":"Roberts","email":"george.roberts@example.com","password":"password","phone":"+447000000010","dateOfBirth":"1986-01-29","agreedToPolicy":true}' "Register George Roberts" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Charlie","lastName":"Wright","email":"charlie.wright@example.com","password":"password","phone":"+447000000011","dateOfBirth":"1990-10-11","agreedToPolicy":true}' "Register Charlie Wright" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Noah","lastName":"Robinson","email":"noah.robinson@example.com","password":"password","phone":"+447000000012","dateOfBirth":"1988-11-16","agreedToPolicy":true}' "Register Noah Robinson" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Thomas","lastName":"Evans","email":"thomas.evans@example.com","password":"password","phone":"+447000000013","dateOfBirth":"1994-04-17","agreedToPolicy":true}' "Register Thomas Evans" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Oscar","lastName":"Hughes","email":"oscar.hughes@example.com","password":"password","phone":"+447000000014","dateOfBirth":"1991-02-04","agreedToPolicy":true}' "Register Oscar Hughes" "" > /dev/null; then success=$((success+1)); fi # Deposit-required users (demonstrate deposit snapshot behaviour) if api_post "$BASE_URL/register" '{"firstName":"Poppy","lastName":"Thompson","email":"poppy.thompson@example.com","password":"password","phone":"+447000000015","dateOfBirth":"1997-06-21","agreedToPolicy":true}' "Register Poppy Thompson" "" > /dev/null; then success=$((success+1)); fi if api_post "$BASE_URL/register" '{"firstName":"Mia","lastName":"White","email":"mia.white@example.com","password":"password","phone":"+447000000016","dateOfBirth":"1995-07-28","agreedToPolicy":true}' "Register Mia White" "" > /dev/null; then success=$((success+1)); fi # Young client (for age-restricted service testing) if api_post "$BASE_URL/register" '{"firstName":"Chloe","lastName":"Park","email":"chloe.park@example.com","password":"password","phone":"+447000000017","dateOfBirth":"2009-04-12","agreedToPolicy":true}' "Register Chloe Park" "" > /dev/null; then success=$((success+1)); fi # Patch-test-complete user (will have gel allergy test recorded) if api_post "$BASE_URL/register" '{"firstName":"Grace","lastName":"Fletcher","email":"grace.fletcher@example.com","password":"password","phone":"+447000000018","dateOfBirth":"1991-08-30","agreedToPolicy":true}' "Register Grace Fletcher" "" > /dev/null; then success=$((success+1)); fi # No-show history user (to demonstrate 48h booking restriction) if api_post "$BASE_URL/register" '{"firstName":"Liam","lastName":"Caldwell","email":"liam.caldwell@example.com","password":"password","phone":"+447000000019","dateOfBirth":"1989-03-14","agreedToPolicy":true}' "Register Liam Caldwell" "" > /dev/null; then success=$((success+1)); fi # --- Promote admin and set deposit flags via DB --- docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET account_role = 'admin' WHERE email = 'admin@example.com'" > /dev/null 2>&1 # Primary test user: zero deposit requirement for easy booking docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 0 WHERE email = 'user@example.com'" > /dev/null 2>&1 # Loyal regulars: zero deposit requirement docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 0 WHERE email IN ('emma.johnson@example.com','sophie.williams@example.com','amelia.jones@example.com','isla.davies@example.com','lily.wilson@example.com','ava.walker@example.com','grace.fletcher@example.com')" > /dev/null 2>&1 # Deposit-required users: 3 no-shows on record docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 3 WHERE email IN ('poppy.thompson@example.com','mia.white@example.com')" > /dev/null 2>&1 # Liam: 1 no-show, in 48h restriction window docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 1 WHERE email = 'liam.caldwell@example.com'" > /dev/null 2>&1 echo "${C_GREEN}โœ… Registered $success/$total Users${C_RESET}" user_success=$success user_total=$total # =========================================================================== # 2. LOGIN # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ”‘ Authenticating...${C_RESET}" ADMIN_TOKEN=$(login "$ADMIN_EMAIL" "$ADMIN_PASS") if [ -z "$ADMIN_TOKEN" ]; then echo "โŒ Admin auth failed"; exit 1; fi USER_TOKEN=$(login "$USER_EMAIL" "$USER_PASS") if [ -z "$USER_TOKEN" ]; then echo "โŒ User auth failed"; exit 1; fi EMMA_TOKEN=$(login "emma.johnson@example.com" "password") SOPHIE_TOKEN=$(login "sophie.williams@example.com" "password") AMELIA_TOKEN=$(login "amelia.jones@example.com" "password") ISLA_TOKEN=$(login "isla.davies@example.com" "password") LILY_TOKEN=$(login "lily.wilson@example.com" "password") AVA_TOKEN=$(login "ava.walker@example.com" "password") POPPY_TOKEN=$(login "poppy.thompson@example.com" "password") MIA_TOKEN=$(login "mia.white@example.com" "password") GRACE_TOKEN=$(login "grace.fletcher@example.com" "password") # Look up user IDs for admin booking creation get_user_id() { docker exec postgres psql -U myuser -d mydb -tAc "SELECT id FROM users WHERE email='$1';" 2>/dev/null | tr -d '\r\n\t ' } ADMIN_USER_ID=$(get_user_id "admin@example.com") USER_USER_ID=$(get_user_id "user@example.com") EMMA_ID=$(get_user_id "emma.johnson@example.com") SOPHIE_ID=$(get_user_id "sophie.williams@example.com") AMELIA_ID=$(get_user_id "amelia.jones@example.com") ISLA_ID=$(get_user_id "isla.davies@example.com") LILY_ID=$(get_user_id "lily.wilson@example.com") AVA_ID=$(get_user_id "ava.walker@example.com") OLIVER_ID=$(get_user_id "oliver.smith@example.com") HARRY_ID=$(get_user_id "harry.brown@example.com") CHARLIE_ID=$(get_user_id "charlie.wright@example.com") NOAH_ID=$(get_user_id "noah.robinson@example.com") POPPY_ID=$(get_user_id "poppy.thompson@example.com") MIA_ID=$(get_user_id "mia.white@example.com") GRACE_ID=$(get_user_id "grace.fletcher@example.com") LIAM_ID=$(get_user_id "liam.caldwell@example.com") echo "${C_GREEN}โœ… Authentication successful${C_RESET}" sleep 1 # =========================================================================== # 3. CREATE SERVICES # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ’… Creating Services...${C_RESET}" SERVICES=( '{"name":"Classic Manicure","description":"Nail shaping, cuticle care, hand massage, and polish.","price":25.00,"duration_minutes":45,"minimum_age_required":0}' '{"name":"Gel Manicure (BIAB)","description":"Hard-wearing gel polish with Builder In A Bottle base. Long-lasting with added nail strength.","price":35.00,"duration_minutes":60,"minimum_age_required":0}' '{"name":"Luxury Pedicure","description":"Foot soak, scrub, mask, extended massage, and polish.","price":45.00,"duration_minutes":75,"minimum_age_required":0}' '{"name":"Express Mani & Pedi","description":"Quick file, shape, and polish for both hands and feet.","price":40.00,"duration_minutes":60,"minimum_age_required":0}' '{"name":"Gel Polish Removal","description":"Safe soak-off removal of existing gel polish, including aftercare oil treatment.","price":10.00,"duration_minutes":20,"minimum_age_required":0}' '{"name":"Nail Art Add-on","description":"Custom nail art per two fingers โ€” French tips, florals, gems, and more.","price":5.00,"duration_minutes":15,"minimum_age_required":0}' '{"name":"Gel Polish Full Set","description":"Full gel polish application over natural nails. Requires patch test 24h before first appointment.","price":45.00,"duration_minutes":60,"minimum_age_required":0}' '{"name":"Luxury Gel Manicure","description":"Premium gel polish with extended hand massage and cuticle treatment. Requires patch test 24h before first appointment.","price":55.00,"duration_minutes":75,"minimum_age_required":0}' '{"name":"Acrylic Full Set","description":"Full set of acrylic extensions, shaped and polished to your preference.","price":55.00,"duration_minutes":90,"minimum_age_required":16}' '{"name":"Acrylic Infill","description":"Maintenance infill for existing acrylic extensions.","price":35.00,"duration_minutes":60,"minimum_age_required":16}' '{"name":"Paraffin Wax Treatment","description":"Deeply moisturising paraffin wax hand or foot treatment, great as an add-on.","price":12.00,"duration_minutes":20,"minimum_age_required":0}' '{"name":"Bridal Nail Package","description":"Luxury manicure and pedicure with nail art, paraffin wax, and extended massage for the big day.","price":120.00,"duration_minutes":150,"minimum_age_required":0}' ) SERVICE_IDS=() success=0 total=${#SERVICES[@]} for svc in "${SERVICES[@]}"; do NAME=$(echo "$svc" | grep -o '"name":"[^"]*' | cut -d'"' -f4) ID=$(api_post "$BASE_URL/admin/services" "$svc" "Create $NAME" "$ADMIN_TOKEN") if [[ -n "$ID" && "$ID" =~ ^[0-9a-f-]{8,}$ ]]; then SERVICE_IDS+=("$ID") success=$((success+1)) fi done echo "${C_GREEN}โœ… Created $success/$total Services${C_RESET}" get_svc() { echo "${SERVICE_IDS[$1]}"; } # =========================================================================== # 3b. CREATE PATCH TESTS (seeded directly via SQL โ€” no creation API endpoint) # =========================================================================== echo -e "\n${C_BLUE}๐Ÿงช Creating Patch Tests...${C_RESET}" # Gel Allergy Test covers: Gel Polish Full Set (idx 6), Luxury Gel Manicure (idx 7) GEL_SVC_1="${SERVICE_IDS[6]}" GEL_SVC_2="${SERVICE_IDS[7]}" # Insert patch test directly via SQL (no creation API endpoint exists) # service_ids cast explicitly to uuid[] to handle typed columns PATCH_TEST_RESULT=$(docker exec postgres psql -U myuser -d mydb -tAc \ "INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids) VALUES ('Gel Allergy Test', 'Mandatory patch test for all gel polish services. Must be completed at least 24 hours before your first gel appointment.', 24, 6, ARRAY['${GEL_SVC_1}','${GEL_SVC_2}']) RETURNING id;" 2>&1) PATCH_TEST_ID=$(echo "$PATCH_TEST_RESULT" | head -1 | tr -d '\r\t ') # Validate it looks like a UUID; print raw output if not to help diagnose if [[ -z "$PATCH_TEST_ID" || ! "$PATCH_TEST_ID" =~ ^[0-9a-f-]{8,}$ ]]; then echo "${C_YELLOW}โš ๏ธ Patch test SQL failed. Raw output: $PATCH_TEST_RESULT${C_RESET}" PATCH_TEST_ID="" fi # Record that Grace has already completed her patch test via the API if [[ -n "$PATCH_TEST_ID" && -n "$GRACE_ID" ]]; then api_post "$BASE_URL/admin/users/$GRACE_ID/patch-tests" \ "{\"patch_test_id\":\"$PATCH_TEST_ID\"}" \ "Record patch test for Grace" "$ADMIN_TOKEN" > /dev/null echo "${C_GREEN}โœ… Patch test seeded (SQL) and recorded for Grace Fletcher${C_RESET}" else echo "${C_YELLOW}โš ๏ธ Patch test seeding failed (check DB connection or patch_tests table)${C_RESET}" fi # =========================================================================== # 4. BOOKINGS # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ“… Creating Bookings...${C_RESET}" format_london_time() { TZ=Europe/London date -d "$1 $2" +"%Y-%m-%dT%H:%M:%S%:z" } # Create a booking as a regular user, capture LAST_BOOKING_ID create_booking() { local token=$1 time=$2 services=$3 notes=$4 name=$5 local json="{\"start_time\":\"$time\",\"service_ids\":$services" [[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\"" json="$json}" local id=$(api_post "$BASE_URL/bookings" "$json" "Book: $name" "$token") if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then LAST_BOOKING_ID="$id" return 0 else LAST_BOOKING_ID="" return 1 fi } # Create a booking as admin (no advance restriction, always confirmed) create_admin_booking() { local user_id=$1 time=$2 services=$3 notes=$4 name=$5 local json="{\"user_id\":\"$user_id\",\"start_time\":\"$time\",\"service_ids\":$services,\"service_overrides\":[],\"enforce_deposits\":false" [[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\"" json="$json}" local id=$(api_post "$BASE_URL/admin/bookings" "$json" "Admin Book: $name" "$ADMIN_TOKEN") if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then LAST_BOOKING_ID="$id" return 0 else LAST_BOOKING_ID="" return 1 fi } # Returns the nearest OPEN business day at or after the given date. # Schema: Monday=0 closed, Sunday=6 closed. Tue-Sat open. # dow: date's day-of-week as 0=Sun,1=Mon,...,6=Sat (GNU date %w) open_day() { local d="$1" local max=7 for ((i=0; i /dev/null 2>&1 PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Isla - Gel + Nail Art (pending)") fi fi if create_admin_booking "$AVA_ID" "$(format_london_time "$TOMORROW" "$SLOT_E")" "[\"$(get_svc 0)\"]" "Could I get a specific nail shape โ€” stiletto if possible?" "Ava - Classic Manicure (tomorrow, pending)"; then count_tomorrow=$((count_tomorrow+1)) if [[ -n "$LAST_BOOKING_ID" ]]; then docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1 PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Ava - Classic Manicure (pending)") fi fi echo "${C_GREEN}โœ… Created $count_tomorrow Tomorrow's Bookings${C_RESET}" # --------------------------------------------------------------------------- # UPCOMING โ€” next 14 days, all via admin to avoid booking_status enum bug. # Bridal package uses user token (notes โ†’ pending) as the one exception. # --------------------------------------------------------------------------- echo -e "\n${C_YELLOW}๐Ÿ“… Creating Upcoming Bookings (next 14 days)...${C_RESET}" count_future=0 UP_IDS=( "$EMMA_ID" "$SOPHIE_ID" "$AMELIA_ID" "$ISLA_ID" "$LILY_ID" "$AVA_ID" "$GRACE_ID" "$USER_USER_ID" ) UP_SVCS=( "1" "2" "0" "3" "1" "2" "7" "0" ) UP_SLOTS=( "$SLOT_A" "$SLOT_C" "$SLOT_D" "$SLOT_B" "$SLOT_C" "$SLOT_A" "$SLOT_D" "$SLOT_B" ) UP_NAMES=( "Gel Manicure" "Luxury Pedicure" "Classic Manicure" "Express Mani & Pedi" "Gel Manicure" "Luxury Pedicure" "Luxury Gel Manicure" "Classic Manicure" ) for day_offset in {2..15}; do RAW_DATE=$(TZ=Europe/London date -d "$TODAY +$day_offset days" +%Y-%m-%d) FUTURE_DATE=$(open_day "$RAW_DATE") idx=$(( (day_offset - 2) % ${#UP_IDS[@]} )) uid="${UP_IDS[$idx]}" svc_idx="${UP_SVCS[$idx]}" slot="${UP_SLOTS[$idx]}" label="${UP_NAMES[$idx]}" if create_admin_booking "$uid" "$(format_london_time "$FUTURE_DATE" "$slot")" "[\"$(get_svc $svc_idx)\"]" "" "$label ($FUTURE_DATE)"; then count_future=$((count_future+1)) # NOTE: do NOT add admin bookings to PENDING_BOOKING_IDS โ€” they're already confirmed fi done # Multi-service admin bookings for variety D2=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)") if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D2" "$SLOT_B")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "User - Classic + Nail Art (+3 days)"; then count_future=$((count_future+1)) fi D3=$(open_day "$(TZ=Europe/London date -d "$TODAY +6 days" +%Y-%m-%d)") if create_admin_booking "$EMMA_ID" "$(format_london_time "$D3" "$SLOT_A")" "[\"$(get_svc 1)\",\"$(get_svc 4)\"]" "" "Emma - Gel Manicure + Removal (+6 days)"; then count_future=$((count_future+1)) fi # Bridal package โ€” admin-created with notes, then forced to pending via DB. D4=$(open_day "$(TZ=Europe/London date -d "$TODAY +12 days" +%Y-%m-%d)") if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$D4" "$SLOT_D")" "[\"$(get_svc 11)\"]" "Bride-to-be โ€” please can we discuss nail art options beforehand?" "Sophie - Bridal Package (+12 days, pending)"; then count_future=$((count_future+1)) if [[ -n "$LAST_BOOKING_ID" ]]; then docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1 PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Sophie - Bridal Package (pending)") fi fi # Deposit-required user โ€” admin-created to bypass deposit check D5=$(open_day "$(TZ=Europe/London date -d "$TODAY +4 days" +%Y-%m-%d)") if create_admin_booking "$POPPY_ID" "$(format_london_time "$D5" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Poppy - Future (deposit snapshotted)"; then count_future=$((count_future+1)) fi echo "${C_GREEN}โœ… Created $count_future Upcoming Bookings${C_RESET}" # --------------------------------------------------------------------------- # CONFIRM roughly 2/3 of pending bookings, leave ~1/3 for admin review # --------------------------------------------------------------------------- echo -e "\n${C_BLUE}๐Ÿ”’ Confirming Upcoming Bookings (approx. 2/3)...${C_RESET}" confirmed_count=0 skipped_count=0 sleep 1 for ((i=0; i<${#PENDING_BOOKING_IDS[@]}; i++)); do id="${PENDING_BOOKING_IDS[$i]}" [[ -z "$id" ]] && continue [[ ! "$id" =~ ^[0-9a-f-]{8,}$ ]] && continue if [ $((RANDOM % 3)) -ne 0 ]; then RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -d '{"service_overrides":[],"notes":""}' \ "$BASE_URL/admin/bookings/$id/confirm") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) [ "$HTTP_CODE" = "200" ] && confirmed_count=$((confirmed_count+1)) sleep 0.05 else skipped_count=$((skipped_count+1)) fi done echo "${C_GREEN}โœ… Confirmed $confirmed_count bookings, left $skipped_count pending for admin review${C_RESET}" # =========================================================================== # 6. GUEST BOOKINGS # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ‘ค Creating Guest Accounts & Bookings...${C_RESET}" # Create guest users via the public guest endpoint create_guest() { local name="$1" email="$2" phone="$3" curl -s -X POST -H 'Content-Type: application/json' \ -d "{\"firstName\":\"$name\",\"lastName\":\"Guest\",\"email\":\"$email\",\"phone\":\"$phone\"}" \ "$BASE_URL/users/guest" \ | tr -d '\r\n\t ' \ | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' } GUEST1_ID=$(create_guest "Alice" "alice.guest@example.com" "+447000000020") GUEST2_ID=$(create_guest "Bob" "bob.guest@example.com" "+447000000021") GUEST3_ID=$(create_guest "Carol" "carol.guest@example.com" "+447000000022") # Same email as GUEST1 โ€” should create a separate account (duplicate emails allowed for guests) GUEST4_ID=$(create_guest "Diana" "alice.guest@example.com" "+447000000023") # Same email as registered user โ€” should fail (blocked) GUEST5_ID=$(create_guest "Evil" "$USER_EMAIL" "+447000000024") count_guest=0 if [[ -n "$GUEST1_ID" ]]; then echo "${C_GREEN}โœ… Created guest: Alice ($GUEST1_ID)${C_RESET}" # Book tomorrow at slot B GUEST1_TIME=$(format_london_time "$TOMORROW" "$SLOT_B") GUEST1_JSON="{\"user_id\":\"$GUEST1_ID\",\"start_time\":\"$GUEST1_TIME\",\"service_ids\":[\"$(get_svc 0)\"]}" G1_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$GUEST1_JSON" "$BASE_URL/bookings") G1_CODE=$(echo "$G1_RESP" | tail -n1) if [[ "$G1_CODE" =~ ^2 ]]; then count_guest=$((count_guest+1)); echo "${C_GREEN}โœ… Guest booking: Alice - Classic Manicure${C_RESET}"; fi fi if [[ -n "$GUEST2_ID" ]]; then echo "${C_GREEN}โœ… Created guest: Bob ($GUEST2_ID)${C_RESET}" D_G2=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)") GUEST2_TIME=$(format_london_time "$D_G2" "$SLOT_C") GUEST2_JSON="{\"user_id\":\"$GUEST2_ID\",\"start_time\":\"$GUEST2_TIME\",\"service_ids\":[\"$(get_svc 3)\"]}" G2_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$GUEST2_JSON" "$BASE_URL/bookings") G2_CODE=$(echo "$G2_RESP" | tail -n1) if [[ "$G2_CODE" =~ ^2 ]]; then count_guest=$((count_guest+1)); echo "${C_GREEN}โœ… Guest booking: Bob - Express Mani & Pedi${C_RESET}"; fi fi if [[ -n "$GUEST3_ID" ]]; then echo "${C_GREEN}โœ… Created guest: Carol ($GUEST3_ID)${C_RESET}" D_G3=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)") GUEST3_TIME=$(format_london_time "$D_G3" "$SLOT_D") GUEST3_JSON="{\"user_id\":\"$GUEST3_ID\",\"start_time\":\"$GUEST3_TIME\",\"service_ids\":[\"$(get_svc 1)\"]}" G3_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$GUEST3_JSON" "$BASE_URL/bookings") G3_CODE=$(echo "$G3_RESP" | tail -n1) if [[ "$G3_CODE" =~ ^2 ]]; then count_guest=$((count_guest+1)); echo "${C_GREEN}โœ… Guest booking: Carol - Gel Manicure${C_RESET}"; fi fi if [[ -n "$GUEST4_ID" && "$GUEST4_ID" != "$GUEST1_ID" ]]; then echo "${C_GREEN}โœ… Created guest: Diana ($GUEST4_ID) โ€” shares email with Alice (separate account)${C_RESET}" else echo "${C_YELLOW}โš ๏ธ Diana guest creation โ€” should be separate from Alice${C_RESET}" fi if [[ -z "$GUEST5_ID" ]]; then echo "${C_GREEN}โœ… Blocked guest booking with registered email ($USER_EMAIL) โ€” correct behaviour${C_RESET}" else echo "${C_YELLOW}โš ๏ธ Guest booking with registered email should have been blocked${C_RESET}" fi echo "${C_GREEN}โœ… Created $count_guest Guest Bookings${C_RESET}" # =========================================================================== # 7. TIME BLOCKERS (admin-blocked time) # =========================================================================== echo -e "\n${C_BLUE}๐Ÿšซ Creating Time Blockers...${C_RESET}" count_blockers=0 # Staff meeting block โ€” tomorrow 14:00-15:00 TB1_TIME=$(format_london_time "$TOMORROW" "14:00:00") TB1_JSON="{\"start_time\":\"$TB1_TIME\",\"duration_minutes\":60,\"description\":\"Staff meeting\"}" TB1_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$TB1_JSON" "$BASE_URL/admin/time-blockers") TB1_CODE=$(echo "$TB1_RESP" | tail -n1) if [[ "$TB1_CODE" =~ ^2 ]]; then count_blockers=$((count_blockers+1)); echo "${C_GREEN}โœ… Time blocker: Staff meeting (tomorrow 14:00)${C_RESET}"; fi # Holiday block โ€” 7 days out, all day (just block a slot to demonstrate) D_TB2=$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)") TB2_TIME=$(format_london_time "$D_TB2" "$SLOT_A") TB2_JSON="{\"start_time\":\"$TB2_TIME\",\"duration_minutes\":120,\"description\":\"Holiday โ€” closed morning\"}" TB2_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$TB2_JSON" "$BASE_URL/admin/time-blockers") TB2_CODE=$(echo "$TB2_RESP" | tail -n1) if [[ "$TB2_CODE" =~ ^2 ]]; then count_blockers=$((count_blockers+1)); echo "${C_GREEN}โœ… Time blocker: Holiday ($D_TB2 morning)${C_RESET}"; fi # Late start block โ€” block tomorrow morning until 11am TB3_TIME=$(format_london_time "$TOMORROW" "09:00:00") TB3_JSON="{\"start_time\":\"$TB3_TIME\",\"duration_minutes\":120,\"description\":\"Late start โ€” closed until 11am\"}" TB3_RESP=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$TB3_JSON" "$BASE_URL/admin/time-blockers") TB3_CODE=$(echo "$TB3_RESP" | tail -n1) if [[ "$TB3_CODE" =~ ^2 ]]; then count_blockers=$((count_blockers+1)); echo "${C_GREEN}โœ… Time blocker: Late start (tomorrow until 11am)${C_RESET}"; fi echo "${C_GREEN}โœ… Created $count_blockers Time Blockers${C_RESET}" # --------------------------------------------------------------------------- # CANCELLATIONS # Both POST /admin/bookings/{id}/cancel and DELETE /bookings/{id} have backend # bugs (enum value mismatch + FK violation). Patching status directly in DB. # Auto-detects the actual enum values from pg_enum to avoid hardcoding. # --------------------------------------------------------------------------- echo -e "\n${C_BLUE}๐Ÿšซ Simulating Cancellations...${C_RESET}" cancel_count=0 # Discover actual booking_status enum values ENUM_VALUES=$(docker exec postgres psql -U myuser -d mydb -tAc \ "SELECT string_agg(enumlabel, ',' ORDER BY enumsortorder) FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid WHERE t.typname = 'booking_status';" 2>/dev/null | tr -d '\r\n\t ') echo " โ„น๏ธ booking_status enum: $ENUM_VALUES" CLIENT_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -i 'client' | head -1) ADMIN_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -iE 'we_|admin' | head -1) # If no distinct admin status, fall back to the first cancel-looking value [[ -z "$CLIENT_CANCEL_STATUS" ]] && CLIENT_CANCEL_STATUS=$(echo "$ENUM_VALUES" | tr ',' '\n' | grep -i 'cancel' | head -1) [[ -z "$ADMIN_CANCEL_STATUS" ]] && ADMIN_CANCEL_STATUS="$CLIENT_CANCEL_STATUS" echo " โ„น๏ธ Cancel statuses โ€” client: '$CLIENT_CANCEL_STATUS' admin: '$ADMIN_CANCEL_STATUS'" db_cancel() { local booking_id="$1" status="$2" reason="$3" [[ -z "$booking_id" || -z "$status" ]] && return 1 # Discover the cancel reason column name (varies by schema) local reason_col reason_col=$(docker exec postgres psql -U myuser -d mydb -tAc \ "SELECT column_name FROM information_schema.columns WHERE table_name='bookings' AND column_name IN ('cancel_reason','cancellation_reason','reason') LIMIT 1;" 2>/dev/null | head -1 | tr -d '\r\t ') local sql if [[ -n "$reason_col" ]]; then sql="UPDATE bookings SET status = '$status', $reason_col = '$reason' WHERE id = '$booking_id' RETURNING id;" else sql="UPDATE bookings SET status = '$status' WHERE id = '$booking_id' RETURNING id;" fi local result result=$(docker exec postgres psql -U myuser -d mydb -tAc "$sql" 2>&1) local rows rows=$(echo "$result" | head -1 | tr -d '\r\t ') if [[ -n "$rows" && "$rows" =~ ^[0-9a-f-]{8,}$ ]]; then return 0 else echo "${C_RED} SQL: $sql${C_RESET}" echo "${C_RED} Result: $result${C_RESET}" return 1 fi } D_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)") if create_admin_booking "$AVA_ID" "$(format_london_time "$D_CANCEL" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "Ava - to be cancelled"; then if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Something came up, really sorry!"; then cancel_count=$((cancel_count+1)) else echo "${C_RED}โŒ DB cancel failed for Ava${C_RESET}" fi fi D_CANCEL2=$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)") if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D_CANCEL2" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "User - to be cancelled"; then if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Plans changed, apologies for the late notice."; then cancel_count=$((cancel_count+1)) else echo "${C_RED}โŒ DB cancel failed for User${C_RESET}" fi fi D_ADMIN_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +9 days" +%Y-%m-%d)") if create_admin_booking "$NOAH_ID" "$(format_london_time "$D_ADMIN_CANCEL" "$SLOT_B")" "[\"$(get_svc 1)\"]" "" "Noah - to be admin-cancelled"; then if db_cancel "$LAST_BOOKING_ID" "$ADMIN_CANCEL_STATUS" "Slot no longer available due to schedule change."; then cancel_count=$((cancel_count+1)) else echo "${C_RED}โŒ DB cancel failed for Noah${C_RESET}" fi fi echo "${C_GREEN}โœ… Simulated $cancel_count cancellations${C_RESET}" # =========================================================================== # 5. EXCEPTIONAL SCHEDULING GROUPS # =========================================================================== echo -e "\n${C_BLUE}๐Ÿ—“๏ธ Creating Exceptional Schedule Groups...${C_RESET}" sched_success=0 # Easter break โ€” fully closed EASTER_BREAK='{ "name":"Easter Break", "description":"Closed for Easter weekend", "hours":[ {"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":1,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":2,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":3,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":4,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":5,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false} ], "weekStarts":["2026-04-06"] }' # Summer half-term โ€” reduced hours (mornings only, Tue-Sat) HALF_TERM='{ "name":"Spring Half-Term", "description":"Reduced hours during half-term week โ€” mornings only", "hours":[ {"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":1,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true}, {"weekday":2,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true}, {"weekday":3,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true}, {"weekday":4,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true}, {"weekday":5,"startTime":"09:30:00","endTime":"13:00:00","isOpen":true}, {"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false} ], "weekStarts":["2026-06-01"] }' # Christmas holiday โ€” fully closed across two weeks XMAS_BREAK='{ "name":"Christmas Holiday Period", "description":"Closed for Christmas and New Year", "hours":[ {"weekday":0,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":1,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":2,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":3,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":4,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":5,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false}, {"weekday":6,"startTime":"00:00:00","endTime":"00:00:00","isOpen":false} ], "weekStarts":["2026-12-21","2026-12-28"] }' if api_post "$BASE_URL/scheduling/exceptional-groups" "$EASTER_BREAK" "Easter Break" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi if api_post "$BASE_URL/scheduling/exceptional-groups" "$HALF_TERM" "Spring Half-Term" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi if api_post "$BASE_URL/scheduling/exceptional-groups" "$XMAS_BREAK" "Christmas Break" "$ADMIN_TOKEN" > /dev/null; then sched_success=$((sched_success+1)); fi echo "${C_GREEN}โœ… Created $sched_success/3 Exceptional Schedule Groups${C_RESET}" # =========================================================================== # SUMMARY # =========================================================================== TOTAL_BOOKINGS=$((count_past + count_today + count_tomorrow + count_future)) echo "" echo -e "${C_GREEN}โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”${C_RESET}" echo -e "${C_GREEN}๐ŸŽ‰ Seeding Complete!${C_RESET}" echo -e "${C_GREEN}โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”${C_RESET}" echo -e " Users registered : $user_success/$user_total" echo -e " Services created : ${#SERVICE_IDS[@]}/${#SERVICES[@]}" if [[ -n "$PATCH_TEST_ID" ]]; then echo -e " Patch tests : 1 (Gel Allergy Test, recorded for Grace)" else echo -e " Patch tests : โš ๏ธ failed โ€” see output above" fi echo -e " Bookings โ€” past : $count_past" echo -e " Bookings โ€” today : $count_today" echo -e " Bookings โ€” tomorrow: $count_tomorrow" echo -e " Bookings โ€” future : $count_future" echo -e " Bookings โ€” total : $TOTAL_BOOKINGS" echo -e " Cancellations : $cancel_count" echo -e " Confirmed : $confirmed_count | Still pending: $skipped_count" echo -e " Bookings โ€” guest : $count_guest" echo -e " Time blockers : $count_blockers" echo -e " Schedule groups : $sched_success/3" echo "" echo -e " Quick login creds (all pass: ${C_YELLOW}password${C_RESET})" echo -e " Admin : ${C_YELLOW}admin@example.com${C_RESET}" echo -e " User : ${C_YELLOW}user@example.com${C_RESET}" echo -e " Deposit: ${C_YELLOW}poppy.thompson@example.com${C_RESET}" echo -e " Gel โœ“ : ${C_YELLOW}grace.fletcher@example.com${C_RESET} (patch test done)" echo -e "${C_GREEN}โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”${C_RESET}" echo -e "\n${C_YELLOW}Press ENTER to run tests...${C_RESET}" read -r echo -e "${C_GREEN}โณ Running tests...${C_RESET}" cd /home/popertots/Crussell/backend export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_DB GO_TESTING=1 TEST_OUTPUT=$(go test -tags test -v -p 1 -count=1 ./... 2>&1 || true) cd .. TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep "^=== RUN" | grep -cv "/" || echo "0") FAILED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- FAIL" || echo "0") SKIPPED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- SKIP" || echo "0") PASSED_TESTS=$(echo "$TEST_OUTPUT" | grep -c "^--- PASS" || echo "0") TOTAL_TESTS=${TOTAL_TESTS:-0} PASSED_TESTS=${PASSED_TESTS:-0} FAILED_TESTS=${FAILED_TESTS:-0} SKIPPED_TESTS=${SKIPPED_TESTS:-0} echo "" if [ "$SKIPPED_TESTS" -gt 0 ] 2>/dev/null; then echo -e "${C_YELLOW}โš ๏ธ Tests Skipped: $SKIPPED_TESTS${C_RESET}" fi if [ "$FAILED_TESTS" -gt 0 ] 2>/dev/null; then echo -e "${C_RED}โŒ Tests Failed: $FAILED_TESTS/$TOTAL_TESTS failed${C_RESET}" echo "" echo -e "${C_RED}--- Failed Tests ---${C_RESET}" echo "$TEST_OUTPUT" | grep "^--- FAIL" | head -20 else echo -e "${C_GREEN}โœ… All Tests Passed: $PASSED_TESTS/$TOTAL_TESTS${C_RESET}" fi echo "" echo -e "${C_YELLOW}Press ENTER to restore 4-pane layout...${C_RESET}" read -r tmux select-window -t $SESSION_NAME:0 tmux select-pane -t $SESSION_NAME:0.0 SEED_EOF chmod +x $SEED_SCRIPT # --- 6. Execute Seed Script in Tmux --- log_step "Starting seeding process in new window..." tmux new-window -t $SESSION_NAME -n "Seeding" "SESSION_NAME=$SESSION_NAME $SEED_SCRIPT" # --- 7. Finalize --- trap 'rm -f $SEED_SCRIPT' EXIT log_success "Environment ready. Attaching to session..." tmux attach-session -t $SESSION_NAME