Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
123 lines
5.1 KiB
Bash
Executable File
123 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Verify Client Portal (real project-scoped data + real IP evidence)
|
||
set -u
|
||
cd /home/z/my-project
|
||
BASE=http://127.0.0.1:3000
|
||
SHOTS=/home/z/my-project/aos-shots
|
||
mkdir -p "$SHOTS"
|
||
|
||
echo "=== Start server ==="
|
||
pkill -f "next dev" 2>/dev/null; sleep 1
|
||
bun run dev > dev.log 2>&1 &
|
||
SRV=$!
|
||
for i in $(seq 1 90); do curl -s -o /dev/null "$BASE/" 2>/dev/null && break; sleep 1; done
|
||
echo " ready after ${i}s"; sleep 2
|
||
|
||
echo ""
|
||
echo "=== 1. Portal company list ==="
|
||
curl -s "$BASE/api/aos/portal/companies" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' companies: {len(d[\"companies\"])}')
|
||
for c in d['companies'][:3]:
|
||
print(f' {c[\"name\"]}: {c[\"projectCount\"]} projects, {c[\"invoiceCount\"]} invoices')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 2. Portal company detail (project-scoped) ==="
|
||
COMPANY_ID=$(curl -s "$BASE/api/aos/portal/companies" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['companies'][0]['id'])" 2>/dev/null)
|
||
echo " company id: $COMPANY_ID"
|
||
curl -s "$BASE/api/aos/portal/companies/$COMPANY_ID" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' company: {d[\"company\"][\"name\"]}')
|
||
print(f' kpis: {d[\"kpis\"]}')
|
||
print(f' projects: {len(d[\"projects\"])}')
|
||
print(f' invoices: {len(d[\"invoices\"])}')
|
||
print(f' tickets: {len(d[\"tickets\"])}')
|
||
print(f' approvals: {len(d[\"approvals\"])} ({sum(1 for a in d[\"approvals\"] if a[\"status\"]==\"pending\")} pending)')
|
||
print(f' licenses: {len(d[\"licenses\"])}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 3. Approve a pending portal approval (real IP + evidence) ==="
|
||
APPROVAL_ID=$(curl -s "$BASE/api/aos/portal/companies/$COMPANY_ID" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(next(a['id'] for a in d['approvals'] if a['status']=='pending'))
|
||
" 2>/dev/null)
|
||
echo " approval id: $APPROVAL_ID"
|
||
curl -s -X PATCH "$BASE/api/aos/portal/approvals/$APPROVAL_ID/decide" -H 'content-type: application/json' -d '{"decision":"approved","decidedBy":"E2E Test-Kunde","feedback":"Sieht super aus!"}' | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' status: {d[\"approval\"][\"status\"]}')
|
||
print(f' decidedAt: {d[\"approval\"][\"decidedAt\"]}')
|
||
print(f' decidedIp: {d[\"approval\"][\"decidedIp\"]} (real request IP, not mock)')
|
||
print(f' contentHash: {d[\"evidence\"][\"contentHash\"][:24]}...')
|
||
print(f' signatureLevel: {d[\"evidence\"][\"signatureLevel\"]}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 4. Duplicate decision (should 409) ==="
|
||
curl -s -X PATCH "$BASE/api/aos/portal/approvals/$APPROVAL_ID/decide" -H 'content-type: application/json' -d '{"decision":"rejected"}' | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' result: {d.get(\"error\",\"ok\")}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== Browser: Portal flow ==="
|
||
agent-browser open "$BASE/" >/dev/null 2>&1
|
||
agent-browser wait --load networkidle >/dev/null 2>&1 || true
|
||
agent-browser wait 3000 >/dev/null 2>&1
|
||
agent-browser set viewport 1440 900 >/dev/null 2>&1
|
||
|
||
echo " === Click 'Client-Portal öffnen' ==="
|
||
agent-browser find text "Client-Portal öffnen" click >/dev/null 2>&1
|
||
agent-browser wait 3000 >/dev/null 2>&1
|
||
H1=$(agent-browser get text h1 2>/dev/null | head -1)
|
||
echo " portal login H1: $H1"
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-login.png" >/dev/null 2>&1 && echo " login shot OK"
|
||
|
||
echo " === Select first company ==="
|
||
agent-browser eval "document.querySelector('button[class*=border]')?.click()" >/dev/null 2>&1
|
||
agent-browser wait 3000 >/dev/null 2>&1
|
||
H1=$(agent-browser get text h1 2>/dev/null | head -1)
|
||
echo " portal overview H1: $H1"
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-overview.png" >/dev/null 2>&1 && echo " overview shot OK"
|
||
|
||
echo " === Go to Freigaben tab ==="
|
||
agent-browser find text "Freigaben" click >/dev/null 2>&1
|
||
agent-browser wait 2000 >/dev/null 2>&1
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-approvals.png" >/dev/null 2>&1 && echo " approvals shot OK"
|
||
|
||
echo " === Click Freigeben on a pending approval ==="
|
||
agent-browser find text "Freigeben" click >/dev/null 2>&1
|
||
agent-browser wait 1500 >/dev/null 2>&1
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-approval-dialog.png" >/dev/null 2>&1 && echo " dialog shot OK"
|
||
# click the confirm Freigeben button in dialog
|
||
agent-browser find text "Freigeben" click >/dev/null 2>&1
|
||
agent-browser wait 3000 >/dev/null 2>&1
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-approval-done.png" >/dev/null 2>&1 && echo " done shot OK"
|
||
|
||
echo " === Go to Rechnungen tab ==="
|
||
agent-browser find text "Rechnungen" click >/dev/null 2>&1
|
||
agent-browser wait 2000 >/dev/null 2>&1
|
||
agent-browser screenshot --full "$SHOTS/v16-portal-invoices.png" >/dev/null 2>&1 && echo " invoices shot OK"
|
||
|
||
echo " === Exit back to agency ==="
|
||
agent-browser find text "Agentur-Ansicht" click >/dev/null 2>&1
|
||
agent-browser wait 2000 >/dev/null 2>&1
|
||
H1=$(agent-browser get text h1 2>/dev/null | head -1)
|
||
echo " back to agency H1: $H1"
|
||
|
||
echo ""
|
||
echo "=== Errors ==="
|
||
agent-browser errors 2>&1 | head -5
|
||
echo "--- dev.log errors ---"
|
||
grep -iE "error|⨯|500" dev.log | grep -v SIGTERM | tail -5
|
||
|
||
agent-browser close >/dev/null 2>&1
|
||
kill $SRV 2>/dev/null
|
||
echo "=== DONE ==="
|