- Add POST /admin/users endpoint with password hashing and role support - Add user creation dialog to admin users page - Migrate web from Next.js to Vite + TanStack Router - Fix Dialog component positioning for Tailwind CSS v4 - Add @source directive for @sase/ui package scanning - Add pl24 integration parsers and vehicle decode flow - Backup old Next.js app to apps/web-nj Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
221 lines
7.4 KiB
Bash
Executable File
221 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# PL24 Integration E2E Test Script
|
|
# Tests: Login → VIN Decode → Category Tree → Subgroups → Parts
|
|
#
|
|
set -euo pipefail
|
|
|
|
API="http://localhost:4000/api"
|
|
EMAIL="admin@sase.tr"
|
|
PASSWORD="Sase2026"
|
|
VIN="WVWZZZ1JZ3W597935"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
pass() { echo -e "${GREEN}✓ $1${NC}"; }
|
|
fail() { echo -e "${RED}✗ $1${NC}"; echo " $2"; exit 1; }
|
|
info() { echo -e "${CYAN}→ $1${NC}"; }
|
|
warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
|
|
|
|
# ─── Step 0: Check API health ───
|
|
info "Checking API health..."
|
|
HEALTH=$(curl -sf "$API/health" 2>/dev/null || echo "")
|
|
if [ -z "$HEALTH" ]; then
|
|
fail "API not running" "Start with: pnpm --filter api dev"
|
|
fi
|
|
pass "API is running"
|
|
|
|
# ─── Step 1: Login ───
|
|
info "Logging in as $EMAIL..."
|
|
LOGIN_RESPONSE=$(curl -s -D - -X POST "$API/auth/sign-in/email" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" 2>&1)
|
|
|
|
COOKIE=$(echo "$LOGIN_RESPONSE" | grep -i 'set-cookie:.*session_token=' | head -1 | sed 's/.*set-cookie: //i' | cut -d';' -f1)
|
|
|
|
if [ -z "$COOKIE" ]; then
|
|
fail "Login failed" "$(echo "$LOGIN_RESPONSE" | tail -1)"
|
|
fi
|
|
pass "Logged in — cookie: ${COOKIE:0:40}..."
|
|
|
|
# ─── Step 2: Decode VIN ───
|
|
info "Decoding VIN: $VIN ..."
|
|
DECODE=$(curl -s -X POST "$API/vehicles/decode" \
|
|
-H "Cookie: $COOKIE" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"vin\":\"$VIN\"}")
|
|
|
|
VEHICLE_ID=$(echo "$DECODE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('data',d).get('id','') if isinstance(d.get('data',d),dict) else '')" 2>/dev/null || echo "")
|
|
|
|
# Try flat response too
|
|
if [ -z "$VEHICLE_ID" ]; then
|
|
VEHICLE_ID=$(echo "$DECODE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
if [ -z "$VEHICLE_ID" ]; then
|
|
fail "VIN decode failed" "$DECODE"
|
|
fi
|
|
|
|
BRAND=$(echo "$DECODE" | python3 -c "import json,sys; d=json.load(sys.stdin); v=d.get('data',d) if isinstance(d.get('data',d),dict) else d; print(f\"{v.get('brandName','')} {v.get('model','')} ({v.get('year','')})\")" 2>/dev/null || echo "unknown")
|
|
pass "VIN decoded → $BRAND (vehicle: $VEHICLE_ID)"
|
|
|
|
# ─── Step 3: Get Category Tree ───
|
|
info "Fetching category tree for vehicle $VEHICLE_ID ..."
|
|
TREE=$(curl -s "$API/categories/tree/$VEHICLE_ID" \
|
|
-H "Cookie: $COOKIE")
|
|
|
|
TREE_COUNT=$(echo "$TREE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
print(len(items))
|
|
" 2>/dev/null || echo "0")
|
|
|
|
if [ "$TREE_COUNT" = "0" ]; then
|
|
fail "Category tree empty" "$TREE"
|
|
fi
|
|
pass "Category tree has $TREE_COUNT main groups"
|
|
|
|
# Print category names
|
|
echo "$TREE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
for i, cat in enumerate(items[:5]):
|
|
print(f' {i+1}. {cat.get(\"name\",\"?\")} (id: {cat.get(\"id\",\"?\")[:8]}...)')
|
|
if len(items) > 5:
|
|
print(f' ... and {len(items)-5} more')
|
|
" 2>/dev/null
|
|
|
|
# ─── Step 4: Get Children (subgroups) of first category ───
|
|
FIRST_CAT_ID=$(echo "$TREE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
print(items[0]['id'] if items else '')
|
|
" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$FIRST_CAT_ID" ]; then
|
|
fail "No category ID found" ""
|
|
fi
|
|
|
|
info "Fetching children for category $FIRST_CAT_ID ..."
|
|
CHILDREN=$(curl -s "$API/categories/$FIRST_CAT_ID/children" \
|
|
-H "Cookie: $COOKIE")
|
|
|
|
CHILDREN_COUNT=$(echo "$CHILDREN" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
print(len(items))
|
|
" 2>/dev/null || echo "0")
|
|
|
|
if [ "$CHILDREN_COUNT" = "0" ]; then
|
|
warn "No subgroups returned for first category (may be a leaf)"
|
|
else
|
|
pass "Got $CHILDREN_COUNT subgroups"
|
|
|
|
# Check unique IDs
|
|
UNIQUE_IDS=$(echo "$CHILDREN" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
ids = [c['id'] for c in items]
|
|
print(f'{len(set(ids))}/{len(ids)}')
|
|
" 2>/dev/null || echo "?")
|
|
pass "Unique subgroup IDs: $UNIQUE_IDS"
|
|
|
|
# Print subgroups
|
|
echo "$CHILDREN" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
for i, sg in enumerate(items[:5]):
|
|
print(f' {i+1}. {sg.get(\"name\",\"?\")} (id: {sg.get(\"id\",\"?\")[:8]}...)')
|
|
if len(items) > 5:
|
|
print(f' ... and {len(items)-5} more')
|
|
" 2>/dev/null
|
|
fi
|
|
|
|
# ─── Step 5: Get category with parts (new endpoint) ───
|
|
# Use the first child (subgroup) if available, otherwise first main category
|
|
if [ "$CHILDREN_COUNT" != "0" ]; then
|
|
PARTS_CAT_ID=$(echo "$CHILDREN" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
items = d.get('data', d) if isinstance(d.get('data'), list) else (d if isinstance(d, list) else [])
|
|
print(items[0]['id'] if items else '')
|
|
" 2>/dev/null || echo "")
|
|
info "Fetching parts for subgroup $PARTS_CAT_ID ..."
|
|
else
|
|
PARTS_CAT_ID="$FIRST_CAT_ID"
|
|
info "Fetching parts for main category $PARTS_CAT_ID ..."
|
|
fi
|
|
|
|
PARTS_RESPONSE=$(curl -s "$API/vehicles/$VEHICLE_ID/categories/$PARTS_CAT_ID" \
|
|
-H "Cookie: $COOKIE")
|
|
|
|
echo "$PARTS_RESPONSE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
data = d.get('data', d) if isinstance(d.get('data'), dict) else d
|
|
|
|
parts_count = len(data.get('parts', []))
|
|
pics_count = len(data.get('schemaPics', []))
|
|
hotspots_count = len(data.get('hotspots', []))
|
|
children_count = len(data.get('children', []))
|
|
name = data.get('name', '?')
|
|
|
|
print(f' Name: {name}')
|
|
print(f' Parts: {parts_count}')
|
|
print(f' Schema pics: {pics_count}')
|
|
print(f' Hotspots: {hotspots_count}')
|
|
print(f' Children: {children_count}')
|
|
|
|
if parts_count > 0:
|
|
first = data['parts'][0]
|
|
print(f' First part: {first.get(\"oemCode\",\"?\")} — {first.get(\"name\",\"?\")}')
|
|
" 2>/dev/null
|
|
|
|
PARTS_COUNT=$(echo "$PARTS_RESPONSE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
data = d.get('data', d) if isinstance(d.get('data'), dict) else d
|
|
print(len(data.get('parts', [])))
|
|
" 2>/dev/null || echo "0")
|
|
|
|
CHILDREN_IN_RESPONSE=$(echo "$PARTS_RESPONSE" | python3 -c "
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
data = d.get('data', d) if isinstance(d.get('data'), dict) else d
|
|
print(len(data.get('children', [])))
|
|
" 2>/dev/null || echo "0")
|
|
|
|
if [ "$PARTS_COUNT" != "0" ]; then
|
|
pass "Got $PARTS_COUNT parts from category endpoint"
|
|
elif [ "$CHILDREN_IN_RESPONSE" != "0" ]; then
|
|
pass "Category is a parent with $CHILDREN_IN_RESPONSE children (no parts expected)"
|
|
else
|
|
warn "No parts and no children returned (may need deeper navigation)"
|
|
fi
|
|
|
|
# ─── Step 6: Frontend route check ───
|
|
info "Checking frontend routes..."
|
|
WEB_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null || echo "000")
|
|
if [ "$WEB_STATUS" = "200" ]; then
|
|
pass "Web frontend is running"
|
|
echo " Dashboard: http://localhost:3000/dashboard/search"
|
|
echo " Vehicle: http://localhost:3000/dashboard/vehicles/$VEHICLE_ID"
|
|
else
|
|
warn "Web frontend not running (status: $WEB_STATUS). Start with: pnpm --filter web dev"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}═══ Test complete ═══${NC}"
|
|
echo "Vehicle ID: $VEHICLE_ID"
|
|
echo "Test in browser: http://localhost:3000/dashboard/vehicles/$VEHICLE_ID"
|