API Testing

CLI-first API testing patterns replacing GUI tools with scriptable workflows.

CLI-First API Testing

This entry covers API testing workflows from the CLI. Postman GUI is useful for exploration, but production workflows belong in scripts and CI.

curl as Primary Tool

Why curl over Postman for production work
curl           Scriptable, versionable, runs in CI, no vendor lock-in
Postman        GUI exploration, team sharing, but cloud dependency
httpx (Python) Programmatic, type-safe, async-capable
gh api         GitHub-specific, handles auth automatically

Postman Collections to curl

Export Postman collection and convert to curl
# Postman exports JSON collections
# Extract curl commands from collection
jq -r '.item[] | "# \(.name)\ncurl -X \(.request.method) \(.request.url.raw)\n"' collection.json
Convert curl to Python httpx — the useful direction
# Install curlconverter
pip install curlconverter

# Convert
curlconverter --language python \
  'curl -s -u admin:pass -H "Accept: application/json" https://api.example.com/v1/data'

Environment Variables Pattern

Postman uses environments — CLI uses shell variables
# Define API environment (source from .env or gopass)
export API_BASE="https://api.example.com"
export API_TOKEN="$(gopass show -o api/example/token)"

# Use consistently
curl -s -H "Authorization: Bearer $API_TOKEN" "$API_BASE/v1/devices" | jq .
Multiple environments — function-based switching
api-prod() {
    export API_BASE="https://api.example.com"
    export API_TOKEN="$(gopass show -o api/prod/token)"
    echo "Switched to PROD"
}

api-lab() {
    export API_BASE="https://api.lab.local"
    export API_TOKEN="$(gopass show -o api/lab/token)"
    echo "Switched to LAB"
}

Request Collections as Shell Scripts

Organized API test script — replaces Postman collections
#!/usr/bin/env bash
set -euo pipefail

BASE="https://api.example.com"
TOKEN="${API_TOKEN:?Set API_TOKEN}"
AUTH=(-H "Authorization: Bearer $TOKEN")
JSON=(-H "Content-Type: application/json" -H "Accept: application/json")

# Health check
echo "=== Health ==="
curl -s "${AUTH[@]}" "$BASE/v1/health" | jq .

# List devices
echo "=== Devices ==="
curl -s "${AUTH[@]}" "${JSON[@]}" "$BASE/v1/devices?limit=5" | jq '.[] | {name, status}'

# Create device
echo "=== Create ==="
curl -s -X POST "${AUTH[@]}" "${JSON[@]}" "$BASE/v1/devices" \
  -d '{"name": "test-device", "vlan": 99}' | jq .

ISE ERS Testing Pattern

ISE ERS test suite — verify API access and basic operations
#!/usr/bin/env bash
ISE="https://10.50.1.20:9060"
AUTH=(-u "$ISE_USER:$ISE_PASS")
HEADERS=(-H "Accept: application/json" -H "Content-Type: application/json")

echo "=== ERS Version ==="
curl -sk "${AUTH[@]}" "${HEADERS[@]}" "$ISE/ers/config/op/systemconfig/iseversion" | jq .

echo "=== Endpoint Count ==="
curl -sk "${AUTH[@]}" "${HEADERS[@]}" "$ISE/ers/config/endpoint?size=1" | jq '.SearchResult.total'

echo "=== Network Devices ==="
curl -sk "${AUTH[@]}" "${HEADERS[@]}" "$ISE/ers/config/networkdevice?size=5" | \
  jq '.SearchResult.resources[] | {name, id}'

Vault API Testing Pattern

Vault health and secret access
VAULT="https://10.50.1.60:8200"

echo "=== Vault Health ==="
curl -s "$VAULT/v1/sys/health" | jq '{initialized, sealed, version}'

echo "=== KV Secret ==="
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
  "$VAULT/v1/kv/data/test" | jq '.data.data'

echo "=== Token Info ==="
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
  "$VAULT/v1/auth/token/lookup-self" | jq '.data | {display_name, policies, ttl}'

Response Validation

Assert expected values — API contract testing from CLI
# Test that health endpoint returns 200
status=$(curl -s -o /dev/null -w '%{http_code}' https://api.example.com/v1/health)
[[ "$status" -eq 200 ]] && echo "PASS: health" || echo "FAIL: health (got $status)"

# Test that list returns array
count=$(curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.example.com/v1/devices | jq 'length')
[[ "$count" -gt 0 ]] && echo "PASS: devices ($count)" || echo "FAIL: no devices"

# Test response schema has expected fields
curl -s https://api.example.com/v1/devices/1 | \
  jq 'has("name", "ip", "vlan")' | grep -q true && \
  echo "PASS: schema" || echo "FAIL: missing fields"

pytest for API Testing

Python API tests — domus-api pattern
import httpx
import pytest

BASE = "http://localhost:8000"

def test_health():
    r = httpx.get(f"{BASE}/v1/health")
    assert r.status_code == 200
    assert r.json()["status"] == "ok"

def test_list_devices():
    r = httpx.get(f"{BASE}/v1/devices")
    assert r.status_code == 200
    assert isinstance(r.json(), list)

def test_create_device():
    r = httpx.post(f"{BASE}/v1/devices", json={"name": "test", "vlan": 10})
    assert r.status_code == 201
    assert r.json()["name"] == "test"