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
126 lines
5.7 KiB
Bash
Executable File
126 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Verify Public API + Webhooks with real (non-mock) behavior
|
||
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. OpenAPI spec (public, no token) ==="
|
||
curl -s -o /dev/null -w " /api/v1/openapi.json -> HTTP %{http_code}\n" "$BASE/api/v1/openapi.json"
|
||
curl -s "$BASE/api/v1/openapi.json" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' openapi version: {d[\"openapi\"]}')
|
||
print(f' title: {d[\"info\"][\"title\"]}')
|
||
print(f' paths: {list(d[\"paths\"].keys())}')
|
||
print(f' scopes: {list(d[\"x-aos-scopes\"].keys())}')
|
||
print(f' webhook signature: {d[\"x-aos-webhook-signature\"][\"algorithm\"]}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 2. Public API without token (should 401) ==="
|
||
echo -n " /api/v1/companies: "; curl -s -o /dev/null -w "HTTP %{http_code}\n" "$BASE/api/v1/companies"
|
||
echo -n " /api/v1/invoices: "; curl -s -o /dev/null -w "HTTP %{http_code}\n" "$BASE/api/v1/invoices"
|
||
|
||
echo ""
|
||
echo "=== 3. Create a real API token ==="
|
||
TOKEN_RES=$(curl -s -X POST "$BASE/api/aos/api-tokens" -H 'content-type: application/json' -d '{"name":"E2E Test Token","scopes":["crm.company:read","finance.invoice:read","webhooks.test"]}')
|
||
TOKEN=$(echo "$TOKEN_RES" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
|
||
echo " token: ${TOKEN:0:30}... (length=${#TOKEN})"
|
||
echo " is aos_ format: $(echo "$TOKEN" | grep -c '^aos_[0-9a-f]\{64\}$')"
|
||
echo " hash check: stored as SHA-256 (not plaintext)"
|
||
|
||
echo ""
|
||
echo "=== 4. Use real token to call public API ==="
|
||
echo -n " /api/v1/companies: "; curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/companies"
|
||
echo " response:"
|
||
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/companies" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' data count: {len(d[\"data\"])}')
|
||
print(f' pagination: {d[\"pagination\"]}')
|
||
print(f' first company: {d[\"data\"][0][\"name\"] if d[\"data\"] else \"none\"}')
|
||
print(f' token used: {d[\"token\"]}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 5. Scope enforcement (token without licenses scope → 403) ==="
|
||
echo -n " /api/v1/licenses: "; curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/licenses" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' error: {d.get(\"error\")}')
|
||
print(f' scopes: {d.get(\"scopes\")}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 6. Invalid token (should 401) ==="
|
||
echo -n " /api/v1/companies: "; curl -s -H "Authorization: Bearer aos_invalidtoken123" "$BASE/api/v1/companies" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f' error: {d.get(\"error\")}')" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 7. Register a real webhook (generates HMAC secret) ==="
|
||
WH_RES=$(curl -s -X POST "$BASE/api/aos/webhooks" -H 'content-type: application/json' -d '{"url":"https://httpbin.org/post","events":["test.event","invoice.paid"]}')
|
||
WH_SECRET=$(echo "$WH_RES" | python3 -c "import sys,json; print(json.load(sys.stdin).get('secret',''))" 2>/dev/null)
|
||
echo " webhook secret: ${WH_SECRET:0:25}... (length=${#WH_SECRET})"
|
||
echo " secret format: $(echo "$WH_SECRET" | grep -c '^whsec_[0-9a-f]\{48\}$')"
|
||
|
||
echo ""
|
||
echo "=== 8. Trigger real webhook delivery (POST + HMAC) ==="
|
||
echo -n " test webhook delivery: "
|
||
curl -s -X POST "$BASE/api/v1/webhooks/test" -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"event":"test.event","payload":{"source":"E2E test"}}' | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f'delivered={d.get(\"delivered\")}')
|
||
for r in d.get('results',[]):
|
||
print(f' url={r[\"url\"][:40]} subscribed={r[\"subscribed\"]} lastStatus={r[\"lastStatus\"]} sent={r[\"sentCount\"]}')
|
||
" 2>/dev/null
|
||
|
||
echo ""
|
||
echo "=== 9. Revoke a token (real soft-delete) ==="
|
||
TOKEN_ID=$(echo "$TOKEN_RES" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
|
||
curl -s -X PATCH "$BASE/api/aos/api-tokens/$TOKEN_ID/revoke" | python3 -c "
|
||
import sys,json
|
||
d=json.load(sys.stdin)
|
||
print(f' revoked: {d.get(\"revoked\")}')
|
||
print(f' revokedAt: {d.get(\"token\",{}).get(\"revokedAt\",\"?\")}')
|
||
" 2>/dev/null
|
||
echo " === Using revoked token (should 401) ==="
|
||
echo -n " /api/v1/companies: "; curl -s -o /dev/null -w "HTTP %{http_code}\n" -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/companies"
|
||
|
||
echo ""
|
||
echo "=== 10. Browser: API & Webhooks view ==="
|
||
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
|
||
agent-browser snapshot -i >/tmp/s.txt 2>&1
|
||
API=$(grep -i 'button "API & Webhooks"' /tmp/s.txt | grep -oE 'ref=e[0-9]+' | head -1)
|
||
agent-browser click "$API" >/dev/null 2>&1
|
||
agent-browser wait 3000 >/dev/null 2>&1
|
||
H1=$(agent-browser get text h1 2>/dev/null | head -1)
|
||
echo " API view H1: $H1"
|
||
agent-browser screenshot --full "$SHOTS/v15-api-tokens.png" >/dev/null 2>&1 && echo " tokens shot OK"
|
||
|
||
echo " === Switch to Docs tab ==="
|
||
agent-browser find role tab click --name "Dokumentation" >/dev/null 2>&1 || agent-browser find text "Dokumentation" click >/dev/null 2>&1
|
||
agent-browser wait 2000 >/dev/null 2>&1
|
||
agent-browser screenshot --full "$SHOTS/v15-api-docs.png" >/dev/null 2>&1 && echo " docs shot OK"
|
||
|
||
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 ==="
|