#!/usr/bin/env sh set -eu root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) work=$(mktemp -d "${TMPDIR:-/tmp}/zds-smoke.XXXXXX") port="${ZDS_SMOKE_PORT:-2585}" db="${work}/zds-smoke.sqlite3" blob_root="${work}/zds-smoke-blobs" log="${work}/zds-smoke.log" base="http://127.0.0.1:${port}" jwt_secret="smoke-jwt-secret" cleanup() { status=$? if [ -n "${server_pid:-}" ]; then kill "$server_pid" 2>/dev/null || true wait "$server_pid" 2>/dev/null || true fi if [ -n "${plc_stub_pid:-}" ]; then kill "$plc_stub_pid" 2>/dev/null || true wait "$plc_stub_pid" 2>/dev/null || true fi if [ "$status" -eq 0 ]; then rm -rf "$work" else echo "Smoke failed; diagnostic files: $work" >&2 fi } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM cd "$root" rm -f "$db" "$db-wal" "$db-shm" "$log" rm -rf "$blob_root" mkdir -p "$blob_root" if [ "${ZDS_SMOKE_PREBUILT:-0}" != 1 ]; then zig build smoke-build fi # stand-in PLC directory so createAccount genesis never reaches plc.directory; # it also hosts a public OAuth client's metadata so PAR can be exercised. plc_port="${ZDS_SMOKE_PLC_PORT:-2586}" client_id="http://127.0.0.1:${plc_port}/oauth-client-metadata.json" python3 -c " import json from http.server import BaseHTTPRequestHandler, HTTPServer metadata = json.dumps({ 'client_id': '$client_id', 'client_name': 'smoke', 'redirect_uris': ['http://127.0.0.1/callback'], 'scope': 'atproto repo:*', 'grant_types': ['authorization_code', 'refresh_token'], 'response_types': ['code'], 'token_endpoint_auth_method': 'none', 'application_type': 'native', 'dpop_bound_access_tokens': True, }).encode() class Stub(BaseHTTPRequestHandler): def do_GET(self): if self.path != '/oauth-client-metadata.json': self.send_response(404) self.end_headers() return self.send_response(200) self.send_header('content-type', 'application/json') self.send_header('content-length', str(len(metadata))) self.end_headers() self.wfile.write(metadata) def do_POST(self): self.rfile.read(int(self.headers.get('content-length', 0))) self.send_response(200) self.end_headers() def log_message(self, *args): pass HTTPServer(('127.0.0.1', $plc_port), Stub).serve_forever() " & plc_stub_pid=$! # run the installed binary directly so $! is the server itself; killing the # `zig build run` wrapper orphans zds and leaves the port taken for the next run ZDS_PLC_ROTATION_KEY=1111111111111111111111111111111111111111111111111111111111111111 \ ./zig-out/bin/zds \ --host 127.0.0.1 \ --jwt-secret "$jwt_secret" \ --port "$port" \ --db "$db" \ --blobstore-path "$blob_root" \ --public-url "$base" \ --server-did did:web:localhost \ --handle-domains .test \ --invite-required \ --admin-token smoke-admin-token \ --operator-handle smoke-operator.test \ --plc-directory "http://127.0.0.1:${plc_port}" \ >"$log" 2>&1 & server_pid=$! i=0 while [ "$i" -lt 80 ]; do if curl -fsS "$base/xrpc/_health" >/dev/null 2>&1; then break fi i=$((i + 1)) sleep 0.1 done curl -fsS "$base/xrpc/_health" >/dev/null # A normal build must never pick up cached operator drawings implicitly. account_page=$(curl -fsS "$base/account/manage") if printf '%s' "$account_page" | grep -q '/ui/icon?cid='; then echo 'unconfigured build unexpectedly includes custom drawings' >&2 exit 1 fi test "$(curl -sS -o /dev/null -w '%{http_code}' "$base/ui/icon?cid=bafkreidhkabyea5vkonzvfdjvgbinqt76kovuli7hrwqqffx5cx2nf4chi")" = 404 stats_page=$(curl -fsS "$base/stats") printf '%s' "$stats_page" | grep -q '

pds health

' printf '%s' "$stats_page" | grep -q '

writes

' curl -fsS "$base/xrpc/com.atproto.server.describeServer" >/dev/null sqlite3 "$db" "insert into accounts (did, handle, email, password_hash, activated_at, email_confirmed_at) values ('did:plc:smoketest', 'smoke.test', 'smoke@test.com', 'password', unixepoch(), unixepoch())" sqlite3 "$db" "insert into oauth_requests (request_id, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, login_hint, expires_at) values ('smoke-oauth', 'https://client.example/oauth-client.json', 'https://client.example/callback', 'repo:*?action=create blob:*/*', 'state', 'challenge', 'S256', 'smoke.test', unixepoch() + 600)" resolved=$(curl -fsS "$base/xrpc/com.atproto.identity.resolveHandle?handle=smoke.test") printf '%s' "$resolved" | grep -q '"did":"did:plc:smoketest"' well_known=$(curl -fsS -H 'host: smoke.test' "$base/.well-known/atproto-did") test "$well_known" = "did:plc:smoketest" unsupported_well_known_status=$(curl -sS -o "$work/zds-unsupported-handle.txt" -w '%{http_code}' \ -H 'host: smoke.example.com' "$base/.well-known/atproto-did") test "$unsupported_well_known_status" = "404" session=$(curl -fsS -X POST "$base/xrpc/com.atproto.server.createSession" \ -H 'content-type: application/json' \ --data '{"identifier":"smoke.test","password":"password"}') token=$(printf '%s' "$session" | sed -n 's/.*"accessJwt":"\([^"]*\)".*/\1/p') test -n "$token" printf '%s' "$session" | grep -q '"didDoc":' ! printf '%s' "$session" | grep -q '"status":' printf '%s' "$session" | grep -q '"serviceEndpoint":"http://127.0.0.1:' session_did=$(printf '%s' "$session" | jq -r .did) # Grouped account access uses the same password-session boundary and validates paging. access_page=$(curl -fsS -H "authorization: Bearer $token" "$base/xrpc/dev.zat.account.listAccess?kind=password&limit=1") printf '%s' "$access_page" | jq -e '.authorized >= 1 and (.groups | length) == 1 and (.connections | length) == 0' >/dev/null access_group=$(printf '%s' "$access_page" | jq -r '.groups[0].key') curl -fsS -G -H "authorization: Bearer $token" --data-urlencode "kind=password" --data-urlencode "group=$access_group" "$base/xrpc/dev.zat.account.listAccess" | jq -e --arg did "$session_did" '.connections | length > 0 and all(.[]; .did == $did)' >/dev/null for query in 'kind=unknown' 'state=unknown' 'cursor=-1' 'cursor=nope' 'limit=0' 'limit=101'; do test "$(curl -sS -o /dev/null -w '%{http_code}' -H "authorization: Bearer $token" "$base/xrpc/dev.zat.account.listAccess?$query")" = 400 done test "$(curl -sS -o /dev/null -w '%{http_code}' "$base/xrpc/dev.zat.account.listAccess")" = 401 # an access token this server signed whose exp has passed must answer 400 # ExpiredToken (the reference PDS's answer, which clients key on to refresh), # while a token that never verified stays 401 InvalidToken. expired_token=$(python3 -c " import base64, hashlib, hmac, json, time def b64(b): return base64.urlsafe_b64encode(b).rstrip(b'=').decode() header = b64(json.dumps({'alg': 'HS256', 'typ': 'JWT'}).encode()) now = int(time.time()) payload = b64(json.dumps({'sub': '$session_did', 'scope': 'com.atproto.access', 'iat': now - 7200, 'exp': now - 3600, 'jti': 'zds-smoke-expired'}).encode()) sig = hmac.new(b'$jwt_secret', f'{header}.{payload}'.encode(), hashlib.sha256).digest() print(f'{header}.{payload}.{b64(sig)}') ") expired_body="${work}/zds-smoke-expired.json" expired_status=$(curl -sS -o "$expired_body" -w '%{http_code}' \ -H "authorization: Bearer $expired_token" "$base/xrpc/com.atproto.server.getSession") test "$expired_status" = "400" grep -q '"error":"ExpiredToken"' "$expired_body" garbage_status=$(curl -sS -o "$expired_body" -w '%{http_code}' \ -H "authorization: Bearer ${expired_token}x" "$base/xrpc/com.atproto.server.getSession") test "$garbage_status" = "401" grep -q '"error":"InvalidToken"' "$expired_body" invalid_auth_headers="${work}/zds-smoke-invalid-auth.headers" invalid_auth_body="${work}/zds-smoke-invalid-auth.json" invalid_auth_status=$(curl -sS -D "$invalid_auth_headers" -o "$invalid_auth_body" -w '%{http_code}' \ -H 'authorization: Bearer invalid-token' \ "$base/xrpc/com.atproto.server.getSession") test "$invalid_auth_status" = "401" grep -qi '^www-authenticate: Bearer error="invalid_token", error_description="Token is invalid"' "$invalid_auth_headers" invalid_dpop_status=$(curl -sS -D "$invalid_auth_headers" -o "$invalid_auth_body" -w '%{http_code}' \ -H 'authorization: DPoP invalid-token' \ "$base/xrpc/com.atproto.server.getSession") test "$invalid_dpop_status" = "401" grep -qi '^www-authenticate: DPoP error="invalid_token", error_description="Token is invalid"' "$invalid_auth_headers" grep -qi '^dpop-nonce:' "$invalid_auth_headers" # PAR scope validation must terminate the request: an unregistered scope is a # 400 invalid_scope, never a 201 (a helper once wrote the error and returned # success, and the handler overwrote it with a request_uri). par_body="${work}/zds-smoke-par.json" # the client is native and registers a port-less loopback redirect, so PAR # sending the port it actually bound is the RFC 8252 exception at work; the # path still has to match exactly. par() { par_redirect="${par_redirect:-http://127.0.0.1:${plc_port}/callback}" curl -sS -o "$par_body" -w '%{http_code}' -X POST "$base/oauth/par" \ --data-urlencode "client_id=${par_client:-$client_id}" \ --data-urlencode "redirect_uri=$par_redirect" \ --data-urlencode 'response_type=code' \ --data-urlencode 'code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM' \ --data-urlencode 'code_challenge_method=S256' \ --data-urlencode 'state=x' \ --data-urlencode "scope=$1" } test "$(par_redirect="http://127.0.0.1:${plc_port}/elsewhere" par 'atproto repo:*')" = "400" grep -q 'redirect_uri not registered' "$par_body" test "$(par 'atproto garbage:thing')" = "400" grep -q '"error":"invalid_scope"' "$par_body" test "$(par 'atproto include:com.example.nope')" = "400" grep -q '"error":"invalid_scope"' "$par_body" test "$(par 'atproto transition:generic repo:*')" = "400" grep -q '"error":"invalid_scope"' "$par_body" # a valid scope gets past validation to the DPoP check (no proof was sent) test "$(par 'atproto repo:*')" = "400" grep -q '"error":"use_dpop_nonce"' "$par_body" # the spec's localhost client development exception: an `http://localhost` # client_id (no port) is virtual, so nothing is fetched and its metadata comes # from the query string. Reaching the DPoP check proves metadata, redirect, and # scope all resolved. test "$(par_client='http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A4444%2Fcb&scope=atproto%20repo%3A*' par_redirect='http://127.0.0.1:4444/cb' par 'atproto repo:*')" = "400" grep -q '"error":"use_dpop_nonce"' "$par_body" test "$(par_client='http://localhost' par_redirect='http://127.0.0.1:5199/' par 'atproto')" = "400" grep -q '"error":"use_dpop_nonce"' "$par_body" test "$(par_client='http://localhost?scope=atproto' par_redirect='http://[::1]:5199/' par 'atproto repo:*')" = "400" grep -q '"error":"invalid_scope"' "$par_body" test "$(par_client='http://localhost?redirect_uri=https%3A%2F%2Fevil.example%2F' par_redirect='https://evil.example/' par 'atproto')" = "400" grep -q 'Invalid loopback client_id' "$par_body" test "$(par_client='http://localhost?redirect_uri=http%3A%2F%2Flocalhost%3A4444%2Fcb' par_redirect='http://localhost:4444/cb' par 'atproto')" = "400" grep -q 'Invalid loopback client_id' "$par_body" # a port makes it an ordinary URL client, which has to be fetched test "$(par_client='http://localhost:1' par_redirect='http://127.0.0.1:5199/' par 'atproto')" = "400" grep -q 'Could not fetch client metadata' "$par_body" describe=$(curl -fsS "$base/xrpc/com.atproto.server.describeServer") printf '%s' "$describe" | grep -q '"inviteCodeRequired":true' missing_invite_status=$(curl -sS -o "$work/zds-missing-invite.json" -w '%{http_code}' -X POST "$base/xrpc/com.atproto.server.createAccount" \ -H 'content-type: application/json' \ --data '{"handle":"missing-invite.test","email":"missing-invite@test.com","password":"password"}') test "$missing_invite_status" = "400" grep -q '"error":"InvalidInviteCode"' "$work/zds-missing-invite.json" invite=$(curl -fsS -X POST "$base/xrpc/com.atproto.server.createInviteCode" \ -H "authorization: Bearer smoke-admin-token" \ -H 'content-type: application/json' \ --data '{"useCount":1,"forAccount":"did:plc:smoketest"}') printf '%s' "$invite" | grep -q '"code":"127-0-0-1-' invites=$(curl -fsS -H "authorization: Bearer $token" "$base/xrpc/com.atproto.server.getAccountInviteCodes") printf '%s' "$invites" | grep -q '"available":1' signup_page=$(curl -fsS "$base/signup") printf '%s' "$signup_page" | grep -q 'join zds' printf '%s' "$signup_page" | grep -q 'com.atproto.server.describeServer' printf '%s' "$signup_page" | grep -q 'com.atproto.server.createAccount' printf '%s' "$signup_page" | grep -q 'DM /dev/null test "$(refresh_session "$app_refresh" "$refresh_body")" = "400" grep -q '"error":"ExpiredToken"' "$refresh_body" # a new account must be announced on the firehose immediately: identity, # account, genesis commit, and sync events plus a servable empty repo genesis=$(curl -fsS "$base/xrpc/com.atproto.sync.getLatestCommit?did=$signup_did") printf '%s' "$genesis" | grep -q '"cid":' printf '%s' "$genesis" | grep -q '"rev":' curl -fsS "$base/xrpc/com.atproto.sync.getRepo?did=$signup_did" -o "$work/zds-signup-genesis.car" test -s "$work/zds-signup-genesis.car" signup_events=$(sqlite3 "$db" "select count(*) from seq_events where did = '$signup_did'") test "$signup_events" = "4" signup_taken_invite=$(curl -fsS -X POST "$base/xrpc/com.atproto.server.createInviteCode" \ -H "authorization: Bearer smoke-admin-token" \ -H 'content-type: application/json' \ --data '{"useCount":1}' | jq -r .code) signup_taken_status=$(curl -sS -o "$work/zds-signup-taken.json" -w '%{http_code}' -X POST "$base/xrpc/com.atproto.server.createAccount" \ -H 'content-type: application/json' \ --data "$(printf '{"handle":"signup-smoke.test","email":"signup-smoke-2@test.com","password":"signup-password","inviteCode":"%s"}' "$signup_taken_invite")") test "$signup_taken_status" = "400" ! grep -q '"error":"InvalidInviteCode"' "$work/zds-signup-taken.json" grep -q '"error":' "$work/zds-signup-taken.json" curl -fsS -X POST "$base/xrpc/app.bsky.actor.putPreferences" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data '{"preferences":[{"$type":"app.bsky.actor.defs#contentLabelPref","label":"dogs","visibility":"show"},{"$type":"app.bsky.actor.defs#contentLabelPref","label":"cats","visibility":"warn"},{"$type":"app.bsky.actor.defs#personalDetailsPref","birthDate":"1970-01-01"},{"$type":"app.bsky.actor.defs#declaredAgePref","isOverAge13":false,"isOverAge16":false,"isOverAge18":false}]}' >/dev/null prefs=$(curl -fsS -H "authorization: Bearer $token" "$base/xrpc/app.bsky.actor.getPreferences") printf '%s' "$prefs" | grep -q '"label":"dogs"' printf '%s' "$prefs" | grep -q '"label":"cats"' printf '%s' "$prefs" | grep -q '"birthDate":"1970-01-01"' printf '%s' "$prefs" | grep -q '"isOverAge13":true' printf '%s' "$prefs" | grep -q '"isOverAge16":true' printf '%s' "$prefs" | grep -q '"isOverAge18":true' test "$(printf '%s' "$prefs" | grep -o 'declaredAgePref' | wc -l | tr -d ' ')" = "1" create=$(curl -fsS -X POST "$base/xrpc/com.atproto.repo.createRecord" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data '{"repo":"did:plc:smoketest","collection":"app.bsky.feed.post","record":{"$type":"app.bsky.feed.post","text":"smoke","createdAt":"2026-05-22T00:00:00.000Z"}}') printf '%s' "$create" | grep -q '"uri":"at://did:plc:smoketest/app.bsky.feed.post/' printf '%s' "$create" | grep -q '"validationStatus":"valid"' post_uri=$(printf '%s' "$create" | jq -r .uri) post_cid=$(printf '%s' "$create" | sed -n 's/.*"cid":"\([^"]*\)".*/\1/p') test -n "$post_cid" # sync.getRecord serves a proof CAR (commit + mst path + record); strict OAuth # lexicon resolvers depend on it and previously got 404 UnknownMethod post_rkey=$(printf '%s' "$create" | jq -r '.uri | split("/")[-1]') proof_car="$work/zds-record-proof.car" proof_status=$(curl -sS -o "$proof_car" -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRecord?did=did:plc:smoketest&collection=app.bsky.feed.post&rkey=$post_rkey") test "$proof_status" = "200" test -s "$proof_car" proof_type=$(curl -sS -o /dev/null -w '%{content_type}' "$base/xrpc/com.atproto.sync.getRecord?did=did:plc:smoketest&collection=app.bsky.feed.post&rkey=$post_rkey") test "$proof_type" = "application/vnd.ipld.car" proof_head=$(curl -sSI -o /dev/null -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRecord?did=did:plc:smoketest&collection=app.bsky.feed.post&rkey=$post_rkey") test "$proof_head" = "200" # exclusion proof for an absent rkey is still a 200 CAR exclusion_status=$(curl -sS -o "$proof_car" -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRecord?did=did:plc:smoketest&collection=app.bsky.feed.post&rkey=3zdsnosuchrkey") test "$exclusion_status" = "200" test -s "$proof_car" proof_missing_repo=$(curl -sS -o /dev/null -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRecord?did=did:plc:nobody&collection=app.bsky.feed.post&rkey=$post_rkey") test "$proof_missing_repo" = "404" like=$(curl -fsS -X POST "$base/xrpc/com.atproto.repo.createRecord" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data "$(printf '{"repo":"did:plc:smoketest","collection":"app.bsky.feed.like","record":{"$type":"app.bsky.feed.like","subject":{"uri":"%s","cid":"%s"},"createdAt":"2026-05-22T00:00:01.000Z"}}' "$post_uri" "$post_cid")") printf '%s' "$like" | grep -q '"uri":"at://did:plc:smoketest/app.bsky.feed.like/' printf '%s' "$like" | grep -q '"validationStatus":"valid"' like_rkey=$(printf '%s' "$like" | jq -r '.uri | split("/")[-1]') test "$(printf '%s' "$like_rkey" | wc -c | tr -d ' ')" = "13" case "$like_rkey" in 3zds*) echo "generated app.bsky.feed.like rkey is not a TID: $like_rkey" >&2 exit 1 ;; esac invalid_like_status=$(curl -sS -o "$work/zds-invalid-like.json" -w '%{http_code}' -X POST "$base/xrpc/com.atproto.repo.createRecord" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data "$(printf '{"repo":"did:plc:smoketest","collection":"app.bsky.feed.like","rkey":"3zdsbad","record":{"$type":"app.bsky.feed.like","subject":{"uri":"%s","cid":"%s"},"createdAt":"2026-05-22T00:00:02.000Z"}}' "$post_uri" "$post_cid")") test "$invalid_like_status" = "400" grep -q '"error":"InvalidRequest"' "$work/zds-invalid-like.json" # a guard that writes an xrpc error must end the request. requireRepoMatches # once wrote 400 and returned success, and createRecord went on to write the # record and answer 200 over it. wrong_repo_body="${work}/zds-smoke-wrong-repo.json" wrong_repo_status=$(curl -sS -o "$wrong_repo_body" -w '%{http_code}' -X POST "$base/xrpc/com.atproto.repo.createRecord" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data '{"repo":"did:plc:someoneelse","collection":"app.bsky.feed.post","record":{"$type":"app.bsky.feed.post","text":"must not land","createdAt":"2026-05-22T00:00:03.000Z"}}') test "$wrong_repo_status" = "400" grep -q '"error":"InvalidRepo"' "$wrong_repo_body" ! curl -fsS "$base/xrpc/com.atproto.repo.listRecords?repo=did:plc:smoketest&collection=app.bsky.feed.post&limit=10" | grep -q 'must not land' # and a 404 from requirePublicRepoAvailable stays a 404 rather than a 500 missing_repo_status=$(curl -sS -o "$wrong_repo_body" -w '%{http_code}' "$base/xrpc/com.atproto.repo.getRecord?repo=did:plc:nobody&collection=app.bsky.feed.post&rkey=3zds") test "$missing_repo_status" = "404" grep -q '"error":"RepoNotFound"' "$wrong_repo_body" # the xrpc proxy path bypasses dispatch, so its HandledResponse must be caught # there too: a bad token on a proxied method is a 401 InvalidToken json body, # never a plain-text 500 (the top-level handler once overwrote it). proxy_body="${work}/zds-smoke-proxy.json" proxy_status=$(curl -sS -o "$proxy_body" -w '%{http_code}' -X POST "$base/xrpc/app.bsky.notification.updateSeen" \ -H "authorization: Bearer not-a-real-token" \ -H 'atproto-proxy: did:web:api.bsky.app#bsky_appview' \ -H 'content-type: application/json' \ --data '{"seenAt":"2026-05-22T00:00:03.000Z"}') test "$proxy_status" = "401" grep -q '"error":"InvalidToken"' "$proxy_body" records=$(curl -fsS "$base/xrpc/com.atproto.repo.listRecords?repo=did:plc:smoketest&collection=app.bsky.feed.post&limit=10") printf '%s' "$records" | grep -q '"text":"smoke"' latest=$(curl -fsS "$base/xrpc/com.atproto.sync.getLatestCommit?did=did:plc:smoketest") printf '%s' "$latest" | grep -q '"cid":"' printf '%s' "$latest" | grep -q '"rev":"' repo_car="${work}/zds-smoke.car" code=$(curl -sS -o "$repo_car" -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRepo?did=did:plc:smoketest") test "$code" = "200" test "$(wc -c < "$repo_car")" -gt 100 repo_head=$(curl -sSI -o /dev/null -w '%{http_code}' "$base/xrpc/com.atproto.sync.getRepo?did=did:plc:smoketest") test "$repo_head" = "200" blob_payload="${work}/zds-smoke-blob.jpg" printf '\377\330\377\340zds-smoke' > "$blob_payload" blob=$(curl -fsS -X POST "$base/xrpc/com.atproto.repo.uploadBlob" \ -H "authorization: Bearer $token" \ -H 'content-type: image/jpeg' \ --data-binary "@$blob_payload") printf '%s' "$blob" | grep -q '"blob":' printf '%s' "$blob" | grep -q '"mimeType":"image/jpeg"' blob_cid=$(printf '%s' "$blob" | sed -n 's/.*"\$link":"\([^"]*\)".*/\1/p') test -n "$blob_cid" blob_head=$(curl -sSI -o /dev/null -w '%{http_code}' "$base/xrpc/com.atproto.sync.getBlob?did=did:plc:smoketest&cid=$blob_cid") test "$blob_head" = "404" blob_ref=$(curl -fsS -X POST "$base/xrpc/com.atproto.repo.createRecord" \ -H "authorization: Bearer $token" \ -H 'content-type: application/json' \ --data "$(jq -nc --arg cid "$blob_cid" '{repo:"did:plc:smoketest",collection:"dev.zds.smoke.blob",record:{"$type":"dev.zds.smoke.blob",blob:{"$type":"blob",ref:{"$link":$cid},mimeType:"image/jpeg",size:13}}}')") printf '%s' "$blob_ref" | grep -q '"uri":"at://did:plc:smoketest/dev.zds.smoke.blob/' blob_head=$(curl -sSI -o /dev/null -w '%{http_code}' "$base/xrpc/com.atproto.sync.getBlob?did=did:plc:smoketest&cid=$blob_cid") test "$blob_head" = "200" large_blob_payload="${work}/zds-smoke-large-blob.jpg" dd if=/dev/zero bs=1048576 count=17 of="$large_blob_payload" 2>/dev/null large_blob=$(curl -fsS -X POST "$base/xrpc/com.atproto.repo.uploadBlob" \ -H "authorization: Bearer $token" \ -H 'content-type: image/jpeg' \ --data-binary "@$large_blob_payload") printf '%s' "$large_blob" | grep -q '"blob":' printf '%s' "$large_blob" | grep -q '"size":17825792' oauth_info=$(curl -fsS -H 'accept: application/json' "$base/oauth/authorize?request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Asmoke-oauth") printf '%s' "$oauth_info" | grep -q '"login_hint":"smoke.test"' oauth_page="${work}/zds-smoke-oauth.html" curl -fsS "$base/oauth/authorize?request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Asmoke-oauth" > "$oauth_page" grep -q 'value="smoke.test"' "$oauth_page" grep -Fq 'repo:*?action=create' "$oauth_page" grep -Fq 'blob:*/*' "$oauth_page" sqlite3 "$db" "insert into oauth_requests (request_id, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, login_hint, expires_at) values ('smoke-loopback', 'http://localhost?scope=atproto', 'http://127.0.0.1:5199/', 'atproto', 'state', 'challenge', 'S256', 'smoke.test', unixepoch() + 600)" curl -fsS "$base/oauth/authorize?request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Asmoke-loopback" | grep -q 'development client' python3 tools/smoke-browser-login.py "$base" "$db" echo "zds smoke ok"