#!/usr/bin/env bash # Replay for the chat_history MCP tool — exercises the full SaaS-shape # wire that PRs #2472 (peer_id filter), #2474 (chat_history client), and # #2476 (before_ts paging) ride on. Runs against the prod-shape tenant # image, not unit-mock'd handlers, so any drift between the Go handler # and the Python tool's expectations surfaces here. # # What this catches that unit tests don't: # - Real Postgres planner behaviour on the (source_id = $X OR target_id = $X) # OR clause (issue #2478 — both indexes missing). # - cf-proxy header rewrites + TenantGuard middleware in the path. # - lib/pq + Postgres driver type binding for time.Time parameters. # - JSON encoding of created_at across the wire (timezone, precision). # # Phases: # A. Seed three a2a_receive rows for alpha with peer_id=beta, spread # across distinct timestamps. # B. Basic peer_id filter: GET ?type=a2a_receive&peer_id=beta&limit=10 # → assert 3 rows DESC. # C. Limit cap: limit=2 → assert 2 newest rows. # D. before_ts paging: take the 2nd-newest's created_at, GET with # before_ts=that → assert the 1 strictly-older row. # E. OR clause (target side): seed an a2a_send row where source=alpha, # target=beta. GET with type unset, peer_id=beta → assert that row # surfaces too (target_id match, not just source_id). # F. Trust-boundary: peer_id="not-a-uuid" → 400 + "peer_id must be a UUID". # G. Trust-boundary: before_ts="garbage" → 400 + RFC3339 example. # H. URL-encoded SQL-injection-shape peer_id → 400 (matches activity_test.go's # malicious-peer-id panel). set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HARNESS_ROOT="$(dirname "$HERE")" cd "$HARNESS_ROOT" if [ ! -f .seed.env ]; then echo "[replay] no .seed.env — running ./seed.sh first..." ./seed.sh fi # shellcheck source=/dev/null source .seed.env # shellcheck source=../_curl.sh source "$HARNESS_ROOT/_curl.sh" PASS=0 FAIL=0 assert() { local desc="$1" expected="$2" actual="$3" if [ "$expected" = "$actual" ]; then printf " PASS %s\n" "$desc" PASS=$((PASS + 1)) else printf " FAIL %s\n expected: %s\n got : %s\n" "$desc" "$expected" "$actual" >&2 FAIL=$((FAIL + 1)) fi } assert_contains() { local desc="$1" needle="$2" haystack="$3" if echo "$haystack" | grep -qF "$needle"; then printf " PASS %s\n" "$desc" PASS=$((PASS + 1)) else printf " FAIL %s\n expected to contain: %s\n got: %s\n" "$desc" "$needle" "$haystack" >&2 FAIL=$((FAIL + 1)) fi } echo "[replay] alpha=$ALPHA_ID beta=$BETA_ID" # ─── Phase A: seed the activity_logs table ───────────────────────────── # Inserted via psql so the seed is independent of the platform's HTTP # Notify path — that path itself ships through the same handler chain # we want to test, and seeding through it would conflate setup and # assertion. echo "" echo "[replay] A. seeding 3 a2a_receive rows for alpha←beta at distinct timestamps..." psql_exec >/dev/null </dev/null </dev/null <