Files
poimen-memory/knowledge/curl-api-testing.md
T
rock f52bc7b88a
Build and Push / Test (push) Failing after 7s
Build and Push / Build and push image (push) Skipped
feat: add curl, tea CLI, verify-done knowledge for API verification
3 new knowledge files, 31 chunks ingested:
- curl-api-testing.md: API testing patterns, auth, error testing, k8s testing
- tea-cli.md: Gitea CLI for issues, PRs, CI runs, releases
- verify-done.md: definition of done checklist, verification workflow
2026-08-29 22:27:56 -07:00

4.8 KiB

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

# 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

# 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

# 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

# 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

# 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

#!/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

# 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