# curl — API Testing & Verification ## Why Test With curl - Every API endpoint must be testable with curl before marking a task done. - curl is the ground truth — if curl can't hit it, the API doesn't work. - Test locally first (`localhost`), then staging, then production. - Save working curl commands as documentation for future reference. ## Basic Patterns ```bash # GET request curl -s http://localhost:8080/health | jq . # POST with JSON body curl -s -X POST http://localhost:8080/memory/ingest \ -H "Content-Type: application/json" \ -d '{"project": "poimen", "text": "test record", "kind": "L1"}' # PUT update curl -s -X PUT http://localhost:8080/resource/123 \ -H "Content-Type: application/json" \ -d '{"field": "new_value"}' # DELETE curl -s -X DELETE http://localhost:8080/resource/123 ``` ## Authentication ```bash # Bearer token (JWT) curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/memory/query # Get JWT from Authentik TOKEN=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \ -d "grant_type=client_credentials" \ -d "client_id=$CLIENT_ID" \ -d "client_secret=$CLIENT_SECRET" | jq -r .access_token) # API key header curl -s -H "X-API-Key: $API_KEY" http://localhost:8080/endpoint ``` ## Response Inspection ```bash # Status code only curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health # Headers + body curl -sv http://localhost:8080/health 2>&1 # Response time curl -s -o /dev/null -w "%{time_total}s" http://localhost:8080/health # Follow redirects curl -sL http://localhost:8080/old-path # Pretty print JSON curl -s http://localhost:8080/health | python3 -m json.tool ``` ## Error Testing ```bash # Expect 401 — missing auth curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/memory/query # Should return: 401 # Expect 400 — bad request body curl -s -X POST http://localhost:8080/memory/ingest \ -H "Content-Type: application/json" \ -d '{"invalid": true}' # Should return: 400 # Expect 429 — rate limit for i in $(seq 1 200); do curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN" \ http://localhost:8080/memory/query -d '{"query":"test"}' done | sort | uniq -c # Should see 429 after limit exceeded # Expect 404 — not found curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/nonexistent ``` ## Kubernetes Service Testing ```bash # Port-forward to test in-cluster service kubectl port-forward -n poimen svc/poimen-memory 8080:8080 & curl -s http://localhost:8080/health # Direct pod exec curl kubectl exec -n poimen deploy/poimen-memory -- curl -s http://localhost:8080/health # Test from inside cluster (debug pod) kubectl run -it --rm curl-test --image=curlimages/curl -- \ curl -s http://poimen-memory.poimen.svc.cluster.local:8080/health # Test ingress from outside curl -sk https://api.riotpiao.com/health ``` ## Verification Checklist After completing any API work, verify with curl: 1. **Happy path** — correct input returns expected output and status code 2. **Auth required** — missing token returns 401, bad token returns 403 3. **Validation** — bad input returns 400 with error message 4. **Not found** — missing resource returns 404 5. **Idempotency** — same request twice returns same result (for POST with idempotency key) 6. **Rate limiting** — excessive requests return 429 7. **Content type** — response has correct Content-Type header 8. **Latency** — response time within SLA (< 500ms for queries, < 50ms for health) ## Scripted Verification ```bash #!/bin/bash # verify-api.sh — run after any API change BASE="http://localhost:8080" PASS=0; FAIL=0 check() { local desc="$1" expected="$2" actual="$3" if [ "$expected" = "$actual" ]; then echo "✓ $desc"; ((PASS++)) else echo "✗ $desc (expected $expected, got $actual)"; ((FAIL++)) fi } check "health returns 200" "200" \ "$(curl -s -o /dev/null -w '%{http_code}' $BASE/health)" check "query without auth returns 401" "401" \ "$(curl -s -o /dev/null -w '%{http_code}' -X POST $BASE/memory/query)" check "ingest with bad body returns 400" "400" \ "$(curl -s -o /dev/null -w '%{http_code}' -X POST $BASE/memory/ingest \ -H 'Content-Type: application/json' -d '{}')" echo "---" echo "$PASS passed, $FAIL failed" [ $FAIL -eq 0 ] && exit 0 || exit 1 ``` ## Data Piping ```bash # Ingest from file curl -s -X POST http://localhost:8080/memory/ingest \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d @payload.json # Query and filter with jq curl -s -H "Authorization: Bearer $TOKEN" \ http://localhost:8080/memory/query \ -d '{"project":"poimen","query":"rust error handling"}' | \ jq '.results[] | {score, text}' # Chain: query → pipe to next call CHUNK_ID=$(curl -s ... | jq -r '.results[0].id') curl -s http://localhost:8080/memory/chunk/$CHUNK_ID ```