this repo has no description
1#!/bin/bash
2# E2E tests for PDS - runs against local wrangler dev
3set -e
4
5BASE="http://localhost:8787"
6DID="did:plc:c6vxslynzebnlk5kw2orx37o"
7
8# Helper for colored output
9pass() { echo "✓ $1"; }
10fail() {
11 echo "✗ $1" >&2
12 cleanup
13 exit 1
14}
15
16# Cleanup function
17cleanup() {
18 if [ -n "$WRANGLER_PID" ]; then
19 echo "Shutting down wrangler..."
20 kill $WRANGLER_PID 2>/dev/null || true
21 wait $WRANGLER_PID 2>/dev/null || true
22 fi
23}
24trap cleanup EXIT
25
26# Start wrangler dev
27echo "Starting wrangler dev..."
28npx wrangler dev --port 8787 >/dev/null 2>&1 &
29WRANGLER_PID=$!
30
31# Wait for server to be ready
32for i in {1..30}; do
33 if curl -sf "$BASE/" >/dev/null 2>&1; then
34 break
35 fi
36 sleep 0.5
37done
38
39# Verify server is up
40curl -sf "$BASE/" >/dev/null || fail "Server failed to start"
41pass "Server started"
42
43# Initialize PDS
44PRIVKEY=$(openssl rand -hex 32)
45curl -sf -X POST "$BASE/init?did=$DID" \
46 -H "Content-Type: application/json" \
47 -d "{\"did\":\"$DID\",\"privateKey\":\"$PRIVKEY\",\"handle\":\"test.local\"}" >/dev/null &&
48 pass "PDS initialized" || fail "PDS init"
49
50echo
51echo "Running tests..."
52echo
53
54# 1. Root returns ASCII art
55curl -sf "$BASE/" | grep -q "PDS" && pass "Root returns ASCII art" || fail "Root"
56
57# 2. describeServer works
58curl -sf "$BASE/xrpc/com.atproto.server.describeServer" | jq -e '.did' >/dev/null &&
59 pass "describeServer" || fail "describeServer"
60
61# 3. resolveHandle works
62curl -sf "$BASE/xrpc/com.atproto.identity.resolveHandle?handle=test.local" |
63 jq -e '.did' >/dev/null && pass "resolveHandle" || fail "resolveHandle"
64
65# 4. createSession returns tokens
66SESSION=$(curl -sf -X POST "$BASE/xrpc/com.atproto.server.createSession" \
67 -H "Content-Type: application/json" \
68 -d "{\"identifier\":\"$DID\",\"password\":\"test-password\"}")
69TOKEN=$(echo "$SESSION" | jq -r '.accessJwt')
70[ "$TOKEN" != "null" ] && [ -n "$TOKEN" ] && pass "createSession returns token" || fail "createSession"
71
72# 5. getSession works with token
73curl -sf "$BASE/xrpc/com.atproto.server.getSession" \
74 -H "Authorization: Bearer $TOKEN" | jq -e '.did' >/dev/null &&
75 pass "getSession with valid token" || fail "getSession"
76
77# 6. Protected endpoint rejects without auth
78STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE/xrpc/com.atproto.repo.createRecord" \
79 -H "Content-Type: application/json" \
80 -d '{"repo":"x","collection":"x","record":{}}')
81[ "$STATUS" = "401" ] && pass "createRecord rejects without auth" || fail "createRecord should reject"
82
83# 7. getPreferences works (returns empty array initially)
84curl -sf "$BASE/xrpc/app.bsky.actor.getPreferences" \
85 -H "Authorization: Bearer $TOKEN" | jq -e '.preferences' >/dev/null &&
86 pass "getPreferences" || fail "getPreferences"
87
88# 8. putPreferences works
89curl -sf -X POST "$BASE/xrpc/app.bsky.actor.putPreferences" \
90 -H "Authorization: Bearer $TOKEN" \
91 -H "Content-Type: application/json" \
92 -d '{"preferences":[{"$type":"app.bsky.actor.defs#savedFeedsPrefV2"}]}' >/dev/null &&
93 pass "putPreferences" || fail "putPreferences"
94
95# 9. createRecord works with auth
96RECORD=$(curl -sf -X POST "$BASE/xrpc/com.atproto.repo.createRecord" \
97 -H "Authorization: Bearer $TOKEN" \
98 -H "Content-Type: application/json" \
99 -d "{\"repo\":\"$DID\",\"collection\":\"app.bsky.feed.post\",\"record\":{\"text\":\"test\",\"createdAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}")
100URI=$(echo "$RECORD" | jq -r '.uri')
101[ "$URI" != "null" ] && [ -n "$URI" ] && pass "createRecord with auth" || fail "createRecord"
102
103# 10. getRecord retrieves it
104RKEY=$(echo "$URI" | sed 's|.*/||')
105curl -sf "$BASE/xrpc/com.atproto.repo.getRecord?repo=$DID&collection=app.bsky.feed.post&rkey=$RKEY" |
106 jq -e '.value.text' >/dev/null && pass "getRecord" || fail "getRecord"
107
108# 11. putRecord updates the record
109curl -sf -X POST "$BASE/xrpc/com.atproto.repo.putRecord" \
110 -H "Authorization: Bearer $TOKEN" \
111 -H "Content-Type: application/json" \
112 -d "{\"repo\":\"$DID\",\"collection\":\"app.bsky.feed.post\",\"rkey\":\"$RKEY\",\"record\":{\"text\":\"updated\",\"createdAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}" |
113 jq -e '.uri' >/dev/null && pass "putRecord" || fail "putRecord"
114
115# 12. listRecords shows the record
116curl -sf "$BASE/xrpc/com.atproto.repo.listRecords?repo=$DID&collection=app.bsky.feed.post" |
117 jq -e '.records | length > 0' >/dev/null && pass "listRecords" || fail "listRecords"
118
119# 13. describeRepo returns repo info
120curl -sf "$BASE/xrpc/com.atproto.repo.describeRepo?repo=$DID" |
121 jq -e '.did' >/dev/null && pass "describeRepo" || fail "describeRepo"
122
123# 14. applyWrites batch operation (create then delete a record)
124APPLY_RESULT=$(curl -sf -X POST "$BASE/xrpc/com.atproto.repo.applyWrites" \
125 -H "Authorization: Bearer $TOKEN" \
126 -H "Content-Type: application/json" \
127 -d "{\"repo\":\"$DID\",\"writes\":[{\"\$type\":\"com.atproto.repo.applyWrites#create\",\"collection\":\"app.bsky.feed.post\",\"rkey\":\"applytest\",\"value\":{\"text\":\"batch\",\"createdAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}]}")
128echo "$APPLY_RESULT" | jq -e '.results' >/dev/null && pass "applyWrites create" || fail "applyWrites create"
129
130# 15. applyWrites delete
131curl -sf -X POST "$BASE/xrpc/com.atproto.repo.applyWrites" \
132 -H "Authorization: Bearer $TOKEN" \
133 -H "Content-Type: application/json" \
134 -d "{\"repo\":\"$DID\",\"writes\":[{\"\$type\":\"com.atproto.repo.applyWrites#delete\",\"collection\":\"app.bsky.feed.post\",\"rkey\":\"applytest\"}]}" |
135 jq -e '.results' >/dev/null && pass "applyWrites delete" || fail "applyWrites delete"
136
137# 16. sync.getLatestCommit returns head
138curl -sf "$BASE/xrpc/com.atproto.sync.getLatestCommit?did=$DID" |
139 jq -e '.cid' >/dev/null && pass "sync.getLatestCommit" || fail "sync.getLatestCommit"
140
141# 17. sync.getRepoStatus returns status
142curl -sf "$BASE/xrpc/com.atproto.sync.getRepoStatus?did=$DID" |
143 jq -e '.did' >/dev/null && pass "sync.getRepoStatus" || fail "sync.getRepoStatus"
144
145# 18. sync.getRepo returns CAR file
146REPO_SIZE=$(curl -sf "$BASE/xrpc/com.atproto.sync.getRepo?did=$DID" | wc -c)
147[ "$REPO_SIZE" -gt 100 ] && pass "sync.getRepo returns CAR" || fail "sync.getRepo"
148
149# 19. sync.getRecord returns record with proof (binary CAR data)
150RECORD_SIZE=$(curl -sf "$BASE/xrpc/com.atproto.sync.getRecord?did=$DID&collection=app.bsky.feed.post&rkey=$RKEY" | wc -c)
151[ "$RECORD_SIZE" -gt 50 ] && pass "sync.getRecord" || fail "sync.getRecord"
152
153# 20. sync.listRepos lists repos
154curl -sf "$BASE/xrpc/com.atproto.sync.listRepos" |
155 jq -e '.repos | length > 0' >/dev/null && pass "sync.listRepos" || fail "sync.listRepos"
156
157# Error handling tests
158echo
159echo "Testing error handling..."
160
161# 21. Invalid password rejected
162STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE/xrpc/com.atproto.server.createSession" \
163 -H "Content-Type: application/json" \
164 -d "{\"identifier\":\"$DID\",\"password\":\"wrong-password\"}")
165[ "$STATUS" = "401" ] && pass "Invalid password rejected (401)" || fail "Invalid password should return 401"
166
167# 22. Wrong repo rejected (can't modify another user's repo)
168STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE/xrpc/com.atproto.repo.createRecord" \
169 -H "Authorization: Bearer $TOKEN" \
170 -H "Content-Type: application/json" \
171 -d '{"repo":"did:plc:z72i7hdynmk6r22z27h6tvur","collection":"app.bsky.feed.post","record":{"text":"x","createdAt":"2024-01-01T00:00:00Z"}}')
172[ "$STATUS" = "403" ] && pass "Wrong repo rejected (403)" || fail "Wrong repo should return 403"
173
174# 23. Non-existent record returns 404
175STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/xrpc/com.atproto.repo.getRecord?repo=$DID&collection=app.bsky.feed.post&rkey=nonexistent")
176[ "$STATUS" = "400" ] || [ "$STATUS" = "404" ] && pass "Non-existent record error" || fail "Non-existent record should error"
177
178# Cleanup: delete the test record
179curl -sf -X POST "$BASE/xrpc/com.atproto.repo.deleteRecord" \
180 -H "Authorization: Bearer $TOKEN" \
181 -H "Content-Type: application/json" \
182 -d "{\"repo\":\"$DID\",\"collection\":\"app.bsky.feed.post\",\"rkey\":\"$RKEY\"}" >/dev/null &&
183 pass "deleteRecord (cleanup)" || fail "deleteRecord"
184
185echo
186echo "All tests passed!"