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
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
# tea CLI — Gitea/Forgejo Command Line
|
||||
|
||||
## Setup
|
||||
```bash
|
||||
# Login to Forgejo instance
|
||||
tea login add --name riotpiao \
|
||||
--url https://git.riotpiao.com \
|
||||
--token <your-token>
|
||||
|
||||
# Verify
|
||||
tea whoami
|
||||
```
|
||||
|
||||
## Repository Operations
|
||||
```bash
|
||||
# List repos
|
||||
tea repos ls
|
||||
|
||||
# Clone
|
||||
tea clone rock/poimen-memory
|
||||
|
||||
# Open in browser
|
||||
tea open
|
||||
```
|
||||
|
||||
## Issues & Tickets
|
||||
```bash
|
||||
# List issues
|
||||
tea issues ls --repo rock/poimen-memory
|
||||
tea issues ls --state open
|
||||
tea issues ls --labels bug
|
||||
|
||||
# Create issue
|
||||
tea issues create --title "Fix port conflict" --body "Port 8080 is in use"
|
||||
|
||||
# Close issue
|
||||
tea issues close 42
|
||||
|
||||
# Comment on issue
|
||||
tea comments create 42 --body "Fixed in commit abc123"
|
||||
|
||||
# Assign
|
||||
tea issues edit 42 --assignees rock
|
||||
```
|
||||
|
||||
## Pull Requests
|
||||
```bash
|
||||
# List PRs
|
||||
tea pr ls --repo rock/poimen-memory
|
||||
tea pr ls --state open
|
||||
|
||||
# Create PR from current branch
|
||||
tea pr create --title "feat: add learn command" --base main
|
||||
|
||||
# Checkout a PR locally
|
||||
tea pr checkout 15
|
||||
|
||||
# Merge PR
|
||||
tea pr merge 15 --style squash
|
||||
|
||||
# Review PR
|
||||
tea pr review 15 --approve
|
||||
tea pr review 15 --request-changes --body "Fix the unwrap()"
|
||||
```
|
||||
|
||||
## CI/CD — Actions & Workflows
|
||||
```bash
|
||||
# List workflow runs
|
||||
tea actions runs list --repo rock/poimen-memory
|
||||
tea actions runs list --repo rock/poimen-memory --limit 5
|
||||
|
||||
# View specific run
|
||||
tea actions runs view <run-id> --repo rock/poimen-memory
|
||||
|
||||
# List workflows
|
||||
tea actions workflows list --repo rock/poimen-memory
|
||||
|
||||
# Secrets management
|
||||
tea actions secrets list --repo rock/poimen-memory
|
||||
tea actions secrets create --repo rock/poimen-memory --name MY_SECRET --value "secret123"
|
||||
|
||||
# Variables
|
||||
tea actions variables list --repo rock/poimen-memory
|
||||
```
|
||||
|
||||
## Releases
|
||||
```bash
|
||||
# List releases
|
||||
tea releases ls --repo rock/poimen-memory
|
||||
|
||||
# Create release
|
||||
tea releases create --repo rock/poimen-memory \
|
||||
--tag v1.0.0 \
|
||||
--title "v1.0.0 — Production Release" \
|
||||
--note "First production release"
|
||||
```
|
||||
|
||||
## Wiki
|
||||
```bash
|
||||
# List wiki pages
|
||||
tea wiki ls --repo rock/poimen-memory
|
||||
|
||||
# Create wiki page
|
||||
tea wiki create --repo rock/poimen-memory \
|
||||
--title "Setup Guide" \
|
||||
--content "# Setup\n..."
|
||||
```
|
||||
|
||||
## API Direct Access
|
||||
```bash
|
||||
# Raw API call (authenticated)
|
||||
tea api /repos/rock/poimen-memory
|
||||
tea api /repos/rock/poimen-memory/issues?state=open
|
||||
|
||||
# POST via API
|
||||
tea api --method POST /repos/rock/poimen-memory/issues \
|
||||
--body '{"title":"test","body":"test issue"}'
|
||||
```
|
||||
|
||||
## Ticket Verification Workflow
|
||||
After completing a task, verify the ticket is done:
|
||||
```bash
|
||||
# 1. Check CI passed
|
||||
tea actions runs list --repo rock/poimen-memory --limit 1
|
||||
# Should show: ✓ completed / success
|
||||
|
||||
# 2. Check issue is closed or PR merged
|
||||
tea issues ls --repo rock/poimen-memory --state closed --limit 5
|
||||
|
||||
# 3. Verify the API endpoint works (curl the actual service)
|
||||
curl -s http://localhost:8080/health | jq .status
|
||||
# Should return: "ok"
|
||||
|
||||
# 4. Tag release if milestone complete
|
||||
tea releases create --tag v1.x.x --title "Milestone X complete"
|
||||
```
|
||||
|
||||
## Useful Flags
|
||||
- `--repo owner/name` — specify repo (or use current git context)
|
||||
- `--output simple` — machine-readable output
|
||||
- `--output yaml` — YAML format
|
||||
- `--output json` — JSON format for piping to jq
|
||||
- `--limit N` — limit results
|
||||
- `--state open|closed|all` — filter by state
|
||||
- `--fields name,status` — select columns
|
||||
@@ -0,0 +1,136 @@
|
||||
# Verify Done — Confirming Task Completion
|
||||
|
||||
## Principle
|
||||
A task is NOT done until it's verified with real data against the real service. Code review + CI green is necessary but not sufficient.
|
||||
|
||||
## Definition of Done Checklist
|
||||
1. **Code compiles** — `cargo build` / `go build` passes with no errors
|
||||
2. **Tests pass** — `cargo test` / `go test ./...` all green
|
||||
3. **CI green** — `tea actions runs list` shows latest run succeeded
|
||||
4. **API accessible** — `curl` the endpoint, get expected response
|
||||
5. **Auth works** — requests without token return 401, with token return 200
|
||||
6. **Error paths tested** — bad input returns proper error codes
|
||||
7. **Deployed** — ArgoCD synced, pod running, ingress reachable
|
||||
8. **Documented** — endpoint added to API docs or CLAUDE.md
|
||||
|
||||
## Verification Flow
|
||||
```
|
||||
Code change → Push → CI passes → ArgoCD deploys → curl test → Done
|
||||
↓
|
||||
If fails → fix → repeat
|
||||
```
|
||||
|
||||
## API Endpoint Verification Template
|
||||
For every new or changed endpoint, run:
|
||||
```bash
|
||||
ENDPOINT="https://api.riotpiao.com"
|
||||
|
||||
# 1. Is it alive?
|
||||
curl -s -o /dev/null -w "%{http_code}" $ENDPOINT/health
|
||||
# Expect: 200
|
||||
|
||||
# 2. Does the new endpoint exist?
|
||||
curl -s -o /dev/null -w "%{http_code}" $ENDPOINT/new-endpoint
|
||||
# Expect: NOT 404
|
||||
|
||||
# 3. Does auth gate work?
|
||||
curl -s -o /dev/null -w "%{http_code}" $ENDPOINT/new-endpoint
|
||||
# Expect: 401 (no token)
|
||||
|
||||
# 4. Does it return correct data?
|
||||
curl -s -H "Authorization: Bearer $TOKEN" $ENDPOINT/new-endpoint | jq .
|
||||
# Expect: meaningful JSON response
|
||||
|
||||
# 5. Does it handle bad input?
|
||||
curl -s -o /dev/null -w "%{http_code}" -X POST $ENDPOINT/new-endpoint \
|
||||
-H "Content-Type: application/json" -d '{}'
|
||||
# Expect: 400
|
||||
```
|
||||
|
||||
## CI Verification
|
||||
```bash
|
||||
# Check last CI run status
|
||||
tea actions runs list --repo rock/poimen-memory --limit 1
|
||||
|
||||
# If failed, check logs
|
||||
tea actions runs view <run-id> --repo rock/poimen-memory
|
||||
|
||||
# Check if ArgoCD synced
|
||||
kubectl get application -n argocd poimen-memory-app -o jsonpath='{.status.sync.status}'
|
||||
# Expect: Synced
|
||||
|
||||
# Check pod health
|
||||
kubectl get pods -n poimen | grep poimen-memory
|
||||
# Expect: Running, no restarts
|
||||
```
|
||||
|
||||
## Deployment Verification
|
||||
```bash
|
||||
# Pod running and ready
|
||||
kubectl get pods -n poimen -l app.kubernetes.io/name=poimen-memory
|
||||
# Expect: 1/1 Running
|
||||
|
||||
# Service reachable internally
|
||||
kubectl exec -n poimen deploy/poimen-memory -- curl -s http://localhost:8080/health
|
||||
|
||||
# Ingress reachable externally
|
||||
curl -sk https://api.riotpiao.com/health
|
||||
|
||||
# Logs clean (no panics, no errors on startup)
|
||||
kubectl logs -n poimen deploy/poimen-memory --tail=20
|
||||
```
|
||||
|
||||
## Git Commit Verification
|
||||
Before pushing, confirm:
|
||||
```bash
|
||||
# Commit message follows conventional commits
|
||||
git log -1 --oneline
|
||||
# Expect: feat: / fix: / docs: / refactor: prefix
|
||||
|
||||
# No secrets in diff
|
||||
git diff --cached | grep -iE "password|secret|token|api_key"
|
||||
# Expect: empty (no matches)
|
||||
|
||||
# No .env or key files staged
|
||||
git diff --cached --name-only | grep -iE "\.env|\.key|\.pem"
|
||||
# Expect: empty
|
||||
```
|
||||
|
||||
## Memory System Specific Verification
|
||||
```bash
|
||||
# After ingest changes
|
||||
curl -s -X POST https://api.riotpiao.com/memory/ingest \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project":"test","text":"verify ingest","kind":"L1"}' | jq .
|
||||
# Expect: 201 with chunk ID
|
||||
|
||||
# After query changes
|
||||
curl -s -X POST https://api.riotpiao.com/memory/query \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project":"test","query":"verify"}' | jq .
|
||||
# Expect: 200 with results array
|
||||
|
||||
# After context endpoint changes
|
||||
curl -s -X POST https://api.riotpiao.com/memory/context \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project":"test","tool":"cargo","task":"build"}' | jq .
|
||||
# Expect: 200 with tier, lessons, budget
|
||||
|
||||
# After rebuild changes
|
||||
curl -s -X POST https://api.riotpiao.com/memory/rebuild \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project":"test","dry_run":true}' | jq .
|
||||
# Expect: 200 with records_processed count
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
- ❌ "CI passed so it's done" — CI doesn't test the real deployment
|
||||
- ❌ "It works on my machine" — must work in-cluster
|
||||
- ❌ Marking done without curl-testing the endpoint
|
||||
- ❌ Skipping error path testing (400, 401, 404, 429)
|
||||
- ❌ Not checking ArgoCD sync status after push
|
||||
- ❌ Trusting `kubectl apply` over ArgoCD (let ArgoCD manage state)
|
||||
Reference in New Issue
Block a user