#!/usr/bin/env bash # lib/http.sh — curl wrappers that capture latency, status, body. # # Each request emits a JSON file under raw/http//.json # describing the round-trip. Cases consume the JSON via assert.sh. # # Why JSON files instead of bash variables: gives the final report a # diffable, replayable record. Future runs can compare on disk without # re-executing the case. # # Functions: # proof_get [extra-curl-args...] # proof_post [extra-curl-args...] # proof_put [extra-curl-args...] # proof_delete [extra-curl-args...] # # Returns 0 always (capture is independent of HTTP outcome). # Stores result at: $PROOF_REPORT_DIR/raw/http//.json # Stores body at: $PROOF_REPORT_DIR/raw/http//.body _proof_http_emit() { local case_id="$1" probe="$2" method="$3" url="$4" status="$5" latency_ms="$6" body_path="$7" headers_path="$8" local dir="${PROOF_REPORT_DIR}/raw/http/${case_id}" mkdir -p "$dir" local body_sha="" [ -s "$body_path" ] && body_sha="$(sha256sum "$body_path" | awk '{print $1}')" cat > "${dir}/${probe}.json" </dev/null || echo 0) end_ms=$(date +%s%3N) local latency_ms=$((end_ms - start_ms)) _proof_http_emit "$case_id" "$probe" "$method" "$url" "$status" "$latency_ms" "$body_path" "$headers_path" cat "$body_path" } proof_get() { local case_id="$1" probe="$2" url="$3"; shift 3 _proof_http_run "$case_id" "$probe" GET "$url" "$@" } proof_post() { local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5 _proof_http_run "$case_id" "$probe" POST "$url" \ -H "Content-Type: ${content_type}" \ --data "$body" \ "$@" } # proof_put accepts either an inline body or @-prefixed file path # (curl --upload-file semantics for streaming). proof_put() { local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5 if [[ "$body" == @* ]]; then local file="${body#@}" _proof_http_run "$case_id" "$probe" PUT "$url" \ -H "Content-Type: ${content_type}" \ --upload-file "$file" \ "$@" else _proof_http_run "$case_id" "$probe" PUT "$url" \ -H "Content-Type: ${content_type}" \ --data "$body" \ "$@" fi } proof_delete() { local case_id="$1" probe="$2" url="$3"; shift 3 _proof_http_run "$case_id" "$probe" DELETE "$url" "$@" } # proof_call: escape hatch for cases that need full control of curl # args — multipart uploads (-F), custom headers, --form-string, etc. # proof_post / proof_put add a Content-Type header and --data body # that conflict with -F multipart, so use this for those cases. # # proof_call [curl-args...] # # Example multipart POST: # proof_call "$CASE_ID" "ingest" POST "$URL" -F "file=@${PATH}" proof_call() { local case_id="$1" probe="$2" method="$3" url="$4"; shift 4 _proof_http_run "$case_id" "$probe" "$method" "$url" "$@" } # proof_wait_for_sql: wait for a SQL probe to return 200, up to budget # seconds. Use when a case follows an ingest and queryd's view-refresh # (default 500ms tick) may not have fired yet. NOT a retry — a wait # for a known eventual-consistency event. No evidence emitted (this # is test setup, not a claim). # # proof_wait_for_sql # # Returns 0 if the probe succeeded; 1 on timeout. proof_wait_for_sql() { local budget="${1:-10}" sql="$2" local deadline=$(($(date +%s) + budget)) local body body=$(jq -nc --arg s "$sql" '{sql:$s}') while [ "$(date +%s)" -lt "$deadline" ]; do if curl -sf --max-time 1 -X POST \ -H 'Content-Type: application/json' \ -d "$body" \ "${PROOF_GATEWAY_URL}/v1/sql" >/dev/null 2>&1; then return 0 fi sleep 0.1 done return 1 } # Helper accessors — reads the per-probe JSON. proof_status_of() { local case_id="$1" probe="$2" jq -r '.status' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json" } proof_body_of() { local case_id="$1" probe="$2" cat "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.body" } proof_latency_of() { local case_id="$1" probe="$2" jq -r '.latency_ms' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json" }