Something went wrong. Try again.
experiments in a post-browser web
Something went wrong. Try again.
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659#!/usr/bin/env bashset -euo pipefail
# ============================================================================# E2E Sync & Version Compatibility Test## Tests sync between local server, headless desktop, and iOS simulator# across multiple accounts, multiple profiles, and all 9 version permutations.## Usage:# ./scripts/e2e-version-test.sh # run all phases# ./scripts/e2e-version-test.sh --phase 4 # run only phase 4 (setup is automatic)# ./scripts/e2e-version-test.sh --phase 2,3 # run phases 2 and 3# ./scripts/e2e-version-test.sh --phase 4,5 # run mobile phases## Phases:# 0 Prerequisites (always runs)# 1 Setup: server, users, profiles, seed data (always runs)# 2 Desktop sync tests (automated)# 3 Version permutation tests (automated)# 4 Mobile sync test (semi-automated)# 5 Mobile version mismatch test (semi-automated)# ============================================================================
# --- Argument parsing -------------------------------------------------------
RUN_PHASES="" # empty = all
while [[ $# -gt 0 ]]; do case "$1" in --phase|--phases) RUN_PHASES="$2" shift 2 ;; -h|--help) head -22 "$0" | tail -16 exit 0 ;; *) echo "Unknown argument: $1" exit 1 ;; esacdone
should_run() { local phase="$1" # Empty = run all [ -z "$RUN_PHASES" ] && return 0 # Check if phase is in comma-separated list echo ",$RUN_PHASES," | grep -q ",$phase,"}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"VERSION_JS="$PROJECT_ROOT/apps/server/version.js"
TS="$(date +%s)"LOG_FILE="/tmp/e2e-version-test-$(date +%Y%m%d-%H%M%S).log"exec > >(tee -a "$LOG_FILE") 2>&1
# --- Logging helpers --------------------------------------------------------
FAILURES=0PASSES=0PHASE="init"
log() { echo "[$(date +%H:%M:%S)] [$1] $2"; }log_pass() { log "$1" "PASS: $2"; PASSES=$((PASSES + 1)); }log_fail() { log "$1" "FAIL: $2"; FAILURES=$((FAILURES + 1)); }log_skip() { log "$1" "SKIP: $2"; }
assert_status() { local TEST_NAME="$1" EXPECTED="$2" ACTUAL="$3" local RESPONSE_BODY="${4:-}" RESPONSE_HEADERS="${5:-}" if [ "$ACTUAL" -eq "$EXPECTED" ]; then log_pass "$PHASE" "$TEST_NAME: $ACTUAL" else log_fail "$PHASE" "$TEST_NAME: got $ACTUAL, expected $EXPECTED" [ -n "$RESPONSE_HEADERS" ] && log "$PHASE" " Response headers: $RESPONSE_HEADERS" [ -n "$RESPONSE_BODY" ] && log "$PHASE" " Response body: $RESPONSE_BODY" if [ -f "$SERVER_LOG" ]; then log "$PHASE" " Server log (last 20 lines):" tail -20 "$SERVER_LOG" | while IFS= read -r line; do log "$PHASE" " $line" done fi fi}
# --- Configuration ----------------------------------------------------------
PORT=$((20000 + RANDOM % 10000))SERVER_TEMP_DIR="$(mktemp -d /tmp/e2e-peek-server-XXXXXX)"SERVER_LOG="$SERVER_TEMP_DIR/server.log"SERVER_PID=""
USER_A_KEY="e2e-a-key-$TS"USER_B_KEY="e2e-b-key-$TS"
PROFILE_A_DEFAULT_ID=""PROFILE_A_WORK_ID=""PROFILE_B_DEFAULT_ID=""
DESKTOP_PROFILE_A_DEFAULT="test-e2e-a-default-$TS"DESKTOP_PROFILE_A_WORK="test-e2e-a-work-$TS"DESKTOP_PROFILE_B_DEFAULT="test-e2e-b-default-$TS"
USER_DATA_PATH="$HOME/Library/Application Support/Peek"
ORIGINAL_VERSION_JS=""
log "init" "E2E Sync & Version Compatibility Test"log "init" "Log file: $LOG_FILE"log "init" "Server temp dir: $SERVER_TEMP_DIR"log "init" "Port: $PORT"if [ -n "$RUN_PHASES" ]; then log "init" "Running phases: $RUN_PHASES (setup always included)"else log "init" "Running all phases"fi
# --- Cleanup trap -----------------------------------------------------------
cleanup() { log "cleanup" "Starting cleanup..."
# Kill server if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then kill "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true log "cleanup" "Killed server (PID $SERVER_PID)" fi
# Restore version.js if [ -n "$ORIGINAL_VERSION_JS" ]; then echo "$ORIGINAL_VERSION_JS" > "$VERSION_JS" log "cleanup" "Restored version.js" fi
# Remove server temp dir rm -rf "$SERVER_TEMP_DIR" log "cleanup" "Removed $SERVER_TEMP_DIR"
# Remove desktop test profiles for p in "$DESKTOP_PROFILE_A_DEFAULT" "$DESKTOP_PROFILE_A_WORK" "$DESKTOP_PROFILE_B_DEFAULT"; do local dir="$USER_DATA_PATH/$p" if [ -d "$dir" ]; then rm -rf "$dir" log "cleanup" "Removed desktop profile dir: $p" fi done
# Clean test profiles from .dev-profiles.db local profiles_db="$USER_DATA_PATH/.dev-profiles.db" if [ -f "$profiles_db" ]; then sqlite3 "$profiles_db" "DELETE FROM profiles WHERE name LIKE 'test-e2e-%';" 2>/dev/null || true log "cleanup" "Cleaned test profiles from .dev-profiles.db" fi
# Clear mobile sync config clear_mobile_config
log "cleanup" "Cleanup complete" print_results log "cleanup" "Log file: $LOG_FILE"}
trap cleanup EXIT
# --- Helper: start/stop server ----------------------------------------------
start_server() { DATA_DIR="$SERVER_TEMP_DIR" PORT="$PORT" node "$PROJECT_ROOT/apps/server/index.js" > "$SERVER_LOG" 2>&1 & SERVER_PID=$! log "$PHASE" "Starting server on port $PORT (PID: $SERVER_PID)"
# Wait for server ready local attempts=0 while ! curl -sf "http://localhost:$PORT/" > /dev/null 2>&1; do attempts=$((attempts + 1)) if [ $attempts -gt 30 ]; then log_fail "$PHASE" "Server failed to start after 30 attempts" log "$PHASE" "Server log:" cat "$SERVER_LOG" exit 1 fi sleep 0.5 done log "$PHASE" "Server ready (health check OK)"}
stop_server() { if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then kill "$SERVER_PID" 2>/dev/null || true wait "$SERVER_PID" 2>/dev/null || true log "$PHASE" "Stopped server (PID $SERVER_PID)" SERVER_PID="" fi}
write_version_js() { local DS="$1" PROTO="$2" cat > "$VERSION_JS" <<VEOFconst DATASTORE_VERSION = $DS;const PROTOCOL_VERSION = $PROTO;module.exports = { DATASTORE_VERSION, PROTOCOL_VERSION };VEOF log "$PHASE" "Wrote version.js: DS=$DS, PROTO=$PROTO"}
# --- Helper: mobile config --------------------------------------------------
clear_mobile_config() { local container container="$(xcrun simctl get_app_container booted com.dietrich.peek-mobile groups 2>/dev/null \ | grep 'group.com.dietrich.peek-mobile' | awk '{print $2}')" || true if [ -n "$container" ]; then local pjson="$container/profiles.json" if [ -f "$pjson" ]; then python3 -c "import json, sysp = '$pjson'try: d = json.load(open(p)) for prof in d.get('profiles', []): prof['server_url'] = '' prof['api_key'] = '' prof.pop('server_profile_id', None) d.pop('sync', None) json.dump(d, open(p, 'w'), indent=2)except Exception: pass" log "cleanup" "Cleared mobile sync config in profiles.json" fi fi}
# --- Results ----------------------------------------------------------------
print_results() { local TOTAL=$((PASSES + FAILURES)) echo "" echo "==========================================" echo " E2E Sync & Version Test Results" echo "==========================================" echo "" echo "Total: $PASSES/$TOTAL passed" if [ "$FAILURES" -gt 0 ]; then echo " *** $FAILURES FAILURE(S) ***" fi echo "" echo "Log: $LOG_FILE" echo "=========================================="}
# ============================================================================# Phase 0: Prerequisites (always runs)# ============================================================================PHASE="prereqs"log "$PHASE" "Checking prerequisites..."
for tool in node electron curl sqlite3 python3 xcrun; do if command -v "$tool" &>/dev/null; then log "$PHASE" " $tool: $(command -v "$tool")" else if [ "$tool" = "xcrun" ]; then log "$PHASE" " $tool: not found (mobile tests will be skipped)" else log_fail "$PHASE" "$tool not found" exit 1 fi fidone
# Save original version.jsORIGINAL_VERSION_JS="$(cat "$VERSION_JS")"log "$PHASE" "Saved original version.js ($(wc -c < "$VERSION_JS") bytes)"
# Build desktoplog "$PHASE" "Building desktop app (yarn build)..."(cd "$PROJECT_ROOT" && yarn build) 2>&1 | tail -5log "$PHASE" "Desktop build complete"
# ============================================================================# Phase 1: Setup (always runs)# ============================================================================PHASE="setup"log "$PHASE" "=== Phase 1: Setup ==="
# Create test userslog "$PHASE" "Creating test users..."USER_JSON="$(DATA_DIR="$SERVER_TEMP_DIR" USER_A_KEY="$USER_A_KEY" USER_B_KEY="$USER_B_KEY" \ node "$SCRIPT_DIR/e2e-setup-users.js")"log "$PHASE" "Users created: $USER_JSON"
# Start serverstart_server
# Create profiles for account-alog "$PHASE" "Creating profiles for account-a..."RESP="$(curl -sf -X POST "http://localhost:$PORT/profiles" \ -H "Authorization: Bearer $USER_A_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Default"}')"PROFILE_A_DEFAULT_ID="$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['profile']['id'])")"log "$PHASE" "Created profile Default for account-a → id=$PROFILE_A_DEFAULT_ID"
RESP="$(curl -sf -X POST "http://localhost:$PORT/profiles" \ -H "Authorization: Bearer $USER_A_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Work"}')"PROFILE_A_WORK_ID="$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['profile']['id'])")"log "$PHASE" "Created profile Work for account-a → id=$PROFILE_A_WORK_ID"
# Create profile for account-blog "$PHASE" "Creating profile for account-b..."RESP="$(curl -sf -X POST "http://localhost:$PORT/profiles" \ -H "Authorization: Bearer $USER_B_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Default"}')"PROFILE_B_DEFAULT_ID="$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['profile']['id'])")"log "$PHASE" "Created profile Default for account-b → id=$PROFILE_B_DEFAULT_ID"
# Seed test datalog "$PHASE" "Seeding test data..."
seed_item() { local KEY="$1" PROFILE_ID="$2" TYPE="$3" CONTENT="$4" LABEL="$5" local STATUS BODY BODY="$(curl -s -o /dev/null -w '%{http_code}' -X POST \ "http://localhost:$PORT/items?profile=$PROFILE_ID" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -H "X-Peek-Datastore-Version: 1" \ -H "X-Peek-Protocol-Version: 1" \ -d "{\"type\":\"$TYPE\",\"content\":\"$CONTENT\"}")" log "$PHASE" "Seeded $LABEL → HTTP $BODY"}
# Account A / default (2 items)seed_item "$USER_A_KEY" "$PROFILE_A_DEFAULT_ID" "url" "https://acct-a-default-1.example.com" "account-a/default item 1 (url)"seed_item "$USER_A_KEY" "$PROFILE_A_DEFAULT_ID" "text" "acct-a-default-note-$TS" "account-a/default item 2 (text)"
# Account A / work (2 items)seed_item "$USER_A_KEY" "$PROFILE_A_WORK_ID" "url" "https://acct-a-work-1.example.com" "account-a/work item 1 (url)"seed_item "$USER_A_KEY" "$PROFILE_A_WORK_ID" "text" "acct-a-work-note-$TS" "account-a/work item 2 (text)"
# Account B / default (2 items)seed_item "$USER_B_KEY" "$PROFILE_B_DEFAULT_ID" "url" "https://acct-b-default-1.example.com" "account-b/default item 1 (url)"seed_item "$USER_B_KEY" "$PROFILE_B_DEFAULT_ID" "text" "acct-b-default-note-$TS" "account-b/default item 2 (text)"
log "$PHASE" "Seeded 6 items total"
# ============================================================================# Phase 2: Desktop Sync Tests# ============================================================================if should_run 2; thenPHASE="desktop"log "$PHASE" "=== Phase 2: Desktop Sync Tests ==="
run_desktop_sync() { local PROFILE_NAME="$1" API_KEY="$2" SERVER_SLUG="$3" EXPECTED_COUNT="$4" LABEL="$5"
log "$PHASE" "Syncing profile $PROFILE_NAME (serverProfileId=$SERVER_SLUG)..."
PROFILE="$PROFILE_NAME" \ SERVER_URL="http://localhost:$PORT" \ API_KEY="$API_KEY" \ SERVER_PROFILE_ID="$SERVER_SLUG" \ electron "$SCRIPT_DIR/preconfigure-sync.mjs" 2>&1 | while IFS= read -r line; do log "$PHASE" " [electron] $line" done
# Verify via sqlite3 local DB_PATH="$USER_DATA_PATH/$PROFILE_NAME/datastore.sqlite" if [ ! -f "$DB_PATH" ]; then log_fail "$PHASE" "$LABEL — database not found at $DB_PATH" return fi
local ITEM_COUNT ITEM_COUNT="$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM items WHERE deletedAt = 0;")" log "$PHASE" " Item count: $ITEM_COUNT (expected $EXPECTED_COUNT)"
if [ "$ITEM_COUNT" -eq "$EXPECTED_COUNT" ]; then log_pass "$PHASE" "$LABEL: $ITEM_COUNT/$EXPECTED_COUNT items, isolation OK" else log_fail "$PHASE" "$LABEL: got $ITEM_COUNT items, expected $EXPECTED_COUNT" log "$PHASE" " Items in DB:" sqlite3 "$DB_PATH" "SELECT id, type, content, syncedAt FROM items WHERE deletedAt = 0;" | while IFS= read -r row; do log "$PHASE" " $row" done fi}
run_desktop_sync "$DESKTOP_PROFILE_A_DEFAULT" "$USER_A_KEY" "$PROFILE_A_DEFAULT_ID" 2 "Account A default"run_desktop_sync "$DESKTOP_PROFILE_A_WORK" "$USER_A_KEY" "$PROFILE_A_WORK_ID" 2 "Account A work"run_desktop_sync "$DESKTOP_PROFILE_B_DEFAULT" "$USER_B_KEY" "$PROFILE_B_DEFAULT_ID" 2 "Account B default"
else log "desktop" "=== Phase 2: SKIPPED ==="fi
# ============================================================================# Phase 3: Version Permutation Tests# ============================================================================if should_run 3; thenPHASE="version"log "$PHASE" "=== Phase 3: Version Permutation Tests ==="
# Test matrix: server_ds, server_proto, expected_status, labelVERSION_TESTS=( "1 1 200 match" "2 1 409 DS-server-higher" "0 1 409 DS-server-lower" "1 2 409 PROTO-server-higher" "1 0 409 PROTO-server-lower" "2 2 409 both-higher" "0 0 409 both-lower" "2 0 409 DS-high-PROTO-low" "0 2 409 DS-low-PROTO-high")
TEST_NUM=0for test_line in "${VERSION_TESTS[@]}"; do read -r SRV_DS SRV_PROTO EXPECTED LABEL <<< "$test_line" TEST_NUM=$((TEST_NUM + 1))
log "$PHASE" "Test #$TEST_NUM: Server DS=$SRV_DS, PROTO=$SRV_PROTO (expect $EXPECTED — $LABEL)"
stop_server write_version_js "$SRV_DS" "$SRV_PROTO" start_server
# Test GET /items GET_HEADERS="$(mktemp)" GET_BODY="$(curl -s -D "$GET_HEADERS" -o - -w '\n%{http_code}' \ "http://localhost:$PORT/items?profile=$PROFILE_A_DEFAULT_ID" \ -H "Authorization: Bearer $USER_A_KEY" \ -H "X-Peek-Datastore-Version: 1" \ -H "X-Peek-Protocol-Version: 1")" GET_STATUS="$(echo "$GET_BODY" | tail -1)" GET_RESPONSE="$(echo "$GET_BODY" | sed '$d')" GET_HDRS="$(cat "$GET_HEADERS")" rm -f "$GET_HEADERS"
assert_status "#$TEST_NUM GET ($LABEL)" "$EXPECTED" "$GET_STATUS" "$GET_RESPONSE" "$GET_HDRS"
# Verify response headers contain server version values DS_HDR="$(echo "$GET_HDRS" | grep -i 'x-peek-datastore-version' | tr -d '\r' | awk '{print $2}')" PROTO_HDR="$(echo "$GET_HDRS" | grep -i 'x-peek-protocol-version' | tr -d '\r' | awk '{print $2}')" if [ "$DS_HDR" = "$SRV_DS" ] && [ "$PROTO_HDR" = "$SRV_PROTO" ]; then log_pass "$PHASE" "#$TEST_NUM headers: DS=$DS_HDR, PROTO=$PROTO_HDR" else log_fail "$PHASE" "#$TEST_NUM headers: DS=$DS_HDR (expected $SRV_DS), PROTO=$PROTO_HDR (expected $SRV_PROTO)" fi
# For 409 responses, verify JSON body fields if [ "$EXPECTED" -eq 409 ]; then ERROR_FIELD="$(echo "$GET_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('error',''))" 2>/dev/null || echo "")" TYPE_FIELD="$(echo "$GET_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('type',''))" 2>/dev/null || echo "")" if [ "$ERROR_FIELD" = "Version mismatch" ] && [ -n "$TYPE_FIELD" ]; then log_pass "$PHASE" "#$TEST_NUM body: error='$ERROR_FIELD', type='$TYPE_FIELD'" else log_fail "$PHASE" "#$TEST_NUM body: error='$ERROR_FIELD', type='$TYPE_FIELD'" fi fi
# Test POST /items POST_HEADERS="$(mktemp)" POST_BODY="$(curl -s -D "$POST_HEADERS" -o - -w '\n%{http_code}' \ -X POST "http://localhost:$PORT/items?profile=$PROFILE_A_DEFAULT_ID" \ -H "Authorization: Bearer $USER_A_KEY" \ -H "Content-Type: application/json" \ -H "X-Peek-Datastore-Version: 1" \ -H "X-Peek-Protocol-Version: 1" \ -d "{\"type\":\"text\",\"content\":\"version-test-$TEST_NUM-$TS\"}")" POST_STATUS="$(echo "$POST_BODY" | tail -1)" POST_RESPONSE="$(echo "$POST_BODY" | sed '$d')" POST_HDRS="$(cat "$POST_HEADERS")" rm -f "$POST_HEADERS"
assert_status "#$TEST_NUM POST ($LABEL)" "$EXPECTED" "$POST_STATUS" "$POST_RESPONSE" "$POST_HDRS"done
# Restore version.js and restart with correct versionslog "$PHASE" "Restoring version.js to original (DS=1, PROTO=1)..."echo "$ORIGINAL_VERSION_JS" > "$VERSION_JS"stop_serverstart_server
else log "version" "=== Phase 3: SKIPPED ==="fi
# ============================================================================# Phase 4: Mobile Sync Test (Semi-Automated)# ============================================================================if should_run 4; thenPHASE="mobile"log "$PHASE" "=== Phase 4: Mobile Sync Test ==="
# Ensure server is running with correct versionsif [ -z "$SERVER_PID" ] || ! kill -0 "$SERVER_PID" 2>/dev/null; then echo "$ORIGINAL_VERSION_JS" > "$VERSION_JS" start_serverfi
MOBILE_CONTAINER=""
if command -v xcrun &>/dev/null; then MOBILE_CONTAINER="$(xcrun simctl get_app_container booted com.dietrich.peek-mobile groups 2>/dev/null \ | grep 'group.com.dietrich.peek-mobile' | awk '{print $2}')" || truefi
if [ -z "$MOBILE_CONTAINER" ]; then log_skip "$PHASE" "No booted simulator or app not installed — skipping mobile tests"else LOCAL_IP="$(ipconfig getifaddr en0 2>/dev/null || echo "")" if [ -z "$LOCAL_IP" ]; then log_skip "$PHASE" "Could not determine local IP (en0) — skipping mobile tests" else log "$PHASE" "App Group container: $MOBILE_CONTAINER" log "$PHASE" "Local IP: $LOCAL_IP"
# Write sync config to profiles.json PROFILES_JSON="$MOBILE_CONTAINER/profiles.json" python3 -c "import json, ospath = '$PROFILES_JSON'data = {'profiles': []}if os.path.exists(path): try: data = json.load(open(path)) except Exception: pass
# Set sync config on ALL profile entries + top-level sync sectionprofiles = data.get('profiles', [])for p in profiles: p['server_url'] = 'http://$LOCAL_IP:$PORT' p['api_key'] = '$USER_A_KEY' p['server_profile_id'] = '$PROFILE_A_DEFAULT_ID'
# Ensure at least one profile existsif not profiles: profiles.append({ 'id': 'e2e-default-profile', 'name': 'Default', 'createdAt': '2026-01-01T00:00:00+00:00', 'lastUsed': '2026-01-01T00:00:00+00:00', 'server_url': 'http://$LOCAL_IP:$PORT', 'api_key': '$USER_A_KEY', 'server_profile_id': '$PROFILE_A_DEFAULT_ID' }) data['currentProfileId'] = 'e2e-default-profile'
data['profiles'] = profilesdata['sync'] = { 'server_url': 'http://$LOCAL_IP:$PORT', 'api_key': '$USER_A_KEY', 'auto_sync': False}json.dump(data, open(path, 'w'), indent=2)print('Wrote profiles.json')" log "$PHASE" "Wrote sync config to $PROFILES_JSON"
# Clear stale last_sync from per-profile databases for dbfile in "$MOBILE_CONTAINER"/peek-*.db; do if [ -f "$dbfile" ]; then sqlite3 "$dbfile" "DELETE FROM settings WHERE key = 'last_sync';" 2>/dev/null || true log "$PHASE" "Cleared last_sync in $(basename "$dbfile")" fi done
echo "" echo "============================================" echo " MOBILE SYNC TEST" echo "============================================" echo " Server: http://$LOCAL_IP:$PORT" echo " Account: account-a (default profile)" echo " Server profile: $PROFILE_A_DEFAULT_ID" echo "" echo " 1. Force-quit and relaunch the app in simulator" echo " 2. Go to Settings → verify server URL and API key" echo " 3. Tap 'Sync All'" echo " 4. Verify 2 items pulled (Account A default profile data)" echo "" echo -n " Press ENTER when done (or 's' to skip): " read -r MOBILE_RESPONSE
if [ "$MOBILE_RESPONSE" = "s" ]; then log_skip "$PHASE" "Mobile sync test skipped by user" else log_pass "$PHASE" "Mobile sync pull: user confirmed" fi fifi
else log "mobile" "=== Phase 4: SKIPPED ==="fi
# ============================================================================# Phase 5: Mobile Version Mismatch Test (Semi-Automated)# ============================================================================if should_run 5; thenPHASE="mobile-version"log "$PHASE" "=== Phase 5: Mobile Version Mismatch Test ==="
MOBILE_CONTAINER=""if command -v xcrun &>/dev/null; then MOBILE_CONTAINER="$(xcrun simctl get_app_container booted com.dietrich.peek-mobile groups 2>/dev/null \ | grep 'group.com.dietrich.peek-mobile' | awk '{print $2}')" || truefi
if [ -z "$MOBILE_CONTAINER" ]; then log_skip "$PHASE" "No booted simulator or app not installed — skipping"else stop_server write_version_js 2 1 start_server
echo "" echo "============================================" echo " MOBILE VERSION MISMATCH TEST" echo "============================================" echo " Server now has DS=2 (mismatch with mobile's DS=1)." echo "" echo " 1. Trigger sync in the app" echo " 2. Expect: Error message about version mismatch" echo "" echo -n " Press ENTER when done (or 's' to skip): " read -r MISMATCH_RESPONSE
if [ "$MISMATCH_RESPONSE" = "s" ]; then log_skip "$PHASE" "Mobile version mismatch test skipped by user" else log_pass "$PHASE" "Mobile version mismatch: user confirmed error shown" fi
# Restore for cleanup echo "$ORIGINAL_VERSION_JS" > "$VERSION_JS" stop_serverfi
else log "mobile-version" "=== Phase 5: SKIPPED ==="fi
# ============================================================================# Done (cleanup handled by trap)# ============================================================================PHASE="done"log "$PHASE" "All requested phases complete."