#!/usr/bin/env bash # lib/assert.sh — assertions that record evidence per the spec. # # Each assertion appends one record to: # $PROOF_REPORT_DIR/raw/cases/.jsonl # # Each line is a self-describing JSON object — case ID, claim, expected, # actual, result {pass|fail|skip}, optional evidence pointers. Cases # call multiple assertions; run_proof.sh aggregates JSONL → summary. # # Functions: # proof_assert_eq # proof_assert_ne # proof_assert_contains # proof_assert_lt # passes if a < b # proof_assert_gt # passes if a > b # proof_assert_status # proof_assert_json_eq # proof_skip # # All return 0 (case scripts decide their own halt-on-fail policy). _proof_record() { local case_id="$1" claim="$2" result="$3" expected="$4" actual="$5" detail="$6" local out="${PROOF_REPORT_DIR}/raw/cases/${case_id}.jsonl" mkdir -p "$(dirname "$out")" # JSON-escape the variable inputs. local e_claim e_expected e_actual e_detail e_claim=$(printf '%s' "$claim" | jq -Rs .) e_expected=$(printf '%s' "$expected" | jq -Rs .) e_actual=$(printf '%s' "$actual" | jq -Rs .) e_detail=$(printf '%s' "$detail" | jq -Rs .) local ts ts=$(date -u -Iseconds) cat >> "$out" < b)}'; then _proof_record "$case_id" "$claim" pass "${a} > ${b}" "${a}" "" return 0 fi _proof_record "$case_id" "$claim" fail "${a} > ${b}" "${a}" "${a} is not greater than ${b}" return 0 } # proof_assert_status compares the status from a previously-recorded # probe against an expected value. Probe must have run via lib/http.sh. proof_assert_status() { local case_id="$1" claim="$2" expected="$3" probe_name="$4" local actual actual=$(proof_status_of "$case_id" "$probe_name" 2>/dev/null || echo missing) proof_assert_eq "$case_id" "$claim" "$expected" "$actual" } # proof_assert_json_eq: jq-based equality on response body or a file. # body_or_file: if starts with @, read from file; otherwise treat as # literal JSON string. proof_assert_json_eq() { local case_id="$1" claim="$2" jq_path="$3" expected="$4" source="$5" local actual if [[ "$source" == @* ]]; then actual=$(jq -r "$jq_path" "${source#@}" 2>/dev/null || echo "") else actual=$(printf '%s' "$source" | jq -r "$jq_path" 2>/dev/null || echo "") fi proof_assert_eq "$case_id" "$claim" "$expected" "$actual" } proof_skip() { local case_id="$1" claim="$2" reason="$3" _proof_record "$case_id" "$claim" skip "" "" "$reason" return 0 } # proof_assert_status_in: pass if probe's status is in the space-separated # expected list. Use when a route legitimately has multiple OK codes (e.g. # 200 vs 204 vs 201 across services). Records a clean pass/fail with the # actual status echoed back. proof_assert_status_in() { local case_id="$1" claim="$2" expected_list="$3" probe="$4" local actual found actual=$(proof_status_of "$case_id" "$probe" 2>/dev/null || echo missing) found=0 for ok in $expected_list; do [ "$ok" = "$actual" ] && { found=1; break; } done if [ "$found" = 1 ]; then _proof_record "$case_id" "$claim" pass "in {${expected_list}}" "$actual" "" else _proof_record "$case_id" "$claim" fail "in {${expected_list}}" "$actual" "status not in expected list" fi return 0 } # proof_assert_status_4xx: pass if probe's status is in [400, 500). Use # for failure-mode contracts where the specific 4xx code is allowed to # vary (400 vs 422 vs 409) — only "is a client error" matters. proof_assert_status_4xx() { local case_id="$1" claim="$2" probe="$3" local actual actual=$(proof_status_of "$case_id" "$probe" 2>/dev/null || echo missing) if awk -v s="$actual" 'BEGIN{exit !(s+0 >= 400 && s+0 < 500)}'; then _proof_record "$case_id" "$claim" pass "4xx" "$actual" "" else _proof_record "$case_id" "$claim" fail "4xx" "$actual" "status is not in 400-499" fi return 0 }