#!/usr/bin/env bash set -euo pipefail binary=${CONTROL_PLANE_BIN:-./control-plane} work=$(mktemp -d) port=${P4_CURSOR_PORT:-18091} addr="127.0.0.1:${port}" base="http://${addr}" cleanup() { [[ -n "${pid:-}" ]] && kill "$pid" 2>/dev/null || true; rm -rf "$work"; } trap cleanup EXIT WXAGENT_CONTROL_PLANE_ADDR="$addr" \ WXAGENT_CONTROL_PLANE_DATA="$work/control-plane.json" \ WXAGENT_CONTROL_PLANE_BACKUP_DIR="$work/backups" \ WXAGENT_NODE_ID=node-cursor \ WXAGENT_NODE_TOKEN=node-cursor-token \ WXAGENT_WEB_USER=admin \ WXAGENT_WEB_PASSWORD=fixture-password \ "$binary" >"$work/server.log" 2>&1 & pid=$! for _ in $(seq 1 40); do code=$(curl -sS -o /dev/null -w '%{http_code}' "$base/v1/auth/login" || true); [[ "$code" != "000" ]] && break; sleep .1; done curl -fsS -X POST "$base/v1/nodes/register" \ -H 'Authorization: Bearer node-cursor-token' -H 'Content-Type: application/json' \ -d '{"node_id":"node-cursor","connection_id":"connection-cursor","agent_version":"fixture","protocol_version":"v1","capabilities":["sync-data"],"accounts":[{"account_id":"account-cursor","active":true,"verified":true,"allowed_chats":[{"chat_id":"chat-cursor","chat_type":"Private"}]}]}' >/dev/null token=$(curl -fsS -X POST "$base/v1/auth/login" -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"fixture-password"}' | jq -r .access_token) post_batch() { local batch_id=$1 sequence=$2 payload=$3 messages if [[ $sequence == 1 ]]; then messages='[{"message_id":"message-2","chat_id":"chat-cursor","chat_type":"Private","source_message_id":"source-2","direction":"incoming","message_type":"text","text":"fixture-two","source_time":"2026-01-01T00:00:02Z","observed_at":"2026-01-01T00:00:02Z","source_version":"fixture","payload_hash":"message-hash-2"},{"message_id":"message-1","chat_id":"chat-cursor","chat_type":"Private","source_message_id":"source-1","direction":"incoming","message_type":"text","text":"fixture-one","source_time":"2026-01-01T00:00:01Z","observed_at":"2026-01-01T00:00:01Z","source_version":"fixture","payload_hash":"message-hash-1"}]' else messages='[{"message_id":"message-3","chat_id":"chat-cursor","chat_type":"Private","source_message_id":"source-3","direction":"incoming","message_type":"text","text":"fixture-newer","source_time":"2026-01-01T00:00:03Z","observed_at":"2026-01-01T00:00:03Z","source_version":"fixture","payload_hash":"message-hash-3"}]' fi jq -n --arg batch "$batch_id" --arg payload "$payload" --argjson messages "$messages" \ --argjson sequence "$sequence" --argjson cursor_end "$((sequence+1))" \ '{node_id:"node-cursor",account_id:"account-cursor",batch_id:$batch,source_generation:"account-cursor",stream_key:"messages",sequence:$sequence,cursor_start:($sequence|tostring),cursor_end:($cursor_end|tostring),payload_hash:$payload,coverage_state:"complete",conversations:[],messages:$messages}' | curl -fsS -X POST "$base/v1/data/batches" \ -H 'Authorization: Bearer node-cursor-token' -H 'Content-Type: application/json' --data-binary @- >/dev/null } request_page() { local label=$1 url=$2 local body="$work/$label.json" status status=$(curl -fsS -o "$body" -w '%{http_code}' "$url" -H "Authorization: Bearer $token") echo "REQUEST $label GET $url" echo "HTTP $status" jq '{items:[.items[]|{message_id,source_time,text}],limit,has_more,next_cursor}' "$body" } post_batch batch-cursor-1 1 payload-cursor-1 first_url="$base/v1/data/accounts/account-cursor/messages?chat_id=chat-cursor&limit=1" request_page first "$first_url" cursor=$(jq -r .next_cursor "$work/first.json") post_batch batch-cursor-2 2 payload-cursor-2 second_url="$base/v1/data/accounts/account-cursor/messages?chat_id=chat-cursor&limit=10&cursor=$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$cursor")" request_page second "$second_url"