# Local Sanity Check Run this before every PR to catch regressions early. Follow [`LOCAL_TESTING.md`](LOCAL_TESTING.md) first to get the server running. All commands assume: ```bash BASE="http://localhost:8080/v1" KEY="sk_local_flexprice_test_key" ENV="-H 'x-environment-id: 00000000-0000-0000-0000-000000000000'" AUTH="-H 'x-api-key: sk_local_flexprice_test_key' -H 'x-environment-id: 00000000-0000-0000-0000-000000000000'" ``` --- ## 0. Health ```bash curl -s http://localhost:8080/health # ✅ {"status":"ok"} ``` --- ## 1. Customer ```bash # Create CUSTOMER=$(curl -s -X POST $BASE/customers \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{"external_id": "sanity-customer-001", "name": "Sanity Test Customer", "email": "sanity@test.local"}') echo $CUSTOMER | python3 -m json.tool CUSTOMER_ID=$(echo $CUSTOMER | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Customer ID: $CUSTOMER_ID" # ✅ customer created with id, tenant_id, environment_id populated # Fetch by external ID curl -s "$BASE/customers/external/sanity-customer-001" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ | python3 -m json.tool # ✅ returns same customer ``` --- ## 2. Meter ```bash METER=$(curl -s -X POST $BASE/meters \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{ "name": "API Calls", "event_name": "api_call", "aggregation": {"type": "COUNT"}, "filters": [] }') echo $METER | python3 -m json.tool METER_ID=$(echo $METER | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Meter ID: $METER_ID" # ✅ meter created with id ``` --- ## 3. Plan + Price ```bash # Create plan PLAN=$(curl -s -X POST $BASE/plans \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{"name": "Sanity Plan", "description": "Local sanity test plan"}') echo $PLAN | python3 -m json.tool PLAN_ID=$(echo $PLAN | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Plan ID: $PLAN_ID" # Create price on the plan PRICE=$(curl -s -X POST $BASE/prices \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d "{ \"plan_id\": \"$PLAN_ID\", \"currency\": \"USD\", \"amount\": \"0.001\", \"type\": \"usage\", \"billing_period\": \"monthly\", \"billing_period_count\": 1, \"billing_cadence\": \"recurring\", \"billing_model\": \"per_unit\", \"meter_id\": \"$METER_ID\" }") echo $PRICE | python3 -m json.tool PRICE_ID=$(echo $PRICE | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Price ID: $PRICE_ID" # ✅ price created, linked to plan and meter ``` --- ## 4. Subscription ```bash SUBSCRIPTION=$(curl -s -X POST $BASE/subscriptions \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d "{ \"customer_id\": \"$CUSTOMER_ID\", \"plan_id\": \"$PLAN_ID\", \"currency\": \"USD\", \"start_date\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" }") echo $SUBSCRIPTION | python3 -m json.tool SUB_ID=$(echo $SUBSCRIPTION | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "Subscription ID: $SUB_ID" # ✅ subscription created with status active/draft ``` --- ## 5. Event Ingestion (standard path) ```bash # Single event curl -s -X POST $BASE/events \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d "{ \"event_name\": \"api_call\", \"external_customer_id\": \"sanity-customer-001\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"properties\": {\"endpoint\": \"/v1/test\"} }" # ✅ {"message":"Event accepted for processing","event_id":"..."} sleep 2 # Verify event landed in ClickHouse docker compose exec clickhouse clickhouse-client \ --user=flexprice --password=flexprice123 --database=flexprice \ --query="SELECT id, external_customer_id, event_name FROM events WHERE external_customer_id='sanity-customer-001' ORDER BY timestamp DESC LIMIT 3 FORMAT PrettyCompact" # ✅ row visible ``` --- ## 6. Usage Query ```bash curl -s -X POST "$BASE/events/usage/meter" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d "{ \"meter_id\": \"$METER_ID\", \"external_customer_id\": \"sanity-customer-001\", \"start_time\": \"$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)\", \"end_time\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" }" | python3 -m json.tool # ✅ value >= 1 (the event we just ingested) ``` --- ## 7. Raw Event Ingestion + Filter (VAPI flow) ```bash # 7a. No filter — all events forwarded curl -s -X POST "$BASE/events/raw/bulk" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{ "events": [ {"id":"sanity-raw-001","orgId":"sanity-customer-001","methodName":"synthesizer","providerName":"deepgram","createdAt":"2026-01-01T00:00:00Z","data":{"durationMS":1000},"dataInterface":"DURATION_DATA","referenceType":"call","targetItemId":"ref-001","targetItemType":"call"}, {"id":"sanity-raw-002","orgId":"blocked-org-999","methodName":"synthesizer","providerName":"deepgram","createdAt":"2026-01-01T00:00:01Z","data":{"durationMS":500},"dataInterface":"DURATION_DATA","referenceType":"call","targetItemId":"ref-002","targetItemType":"call"} ] }' # ✅ {"batch_size":2,"message":"Raw events accepted for processing"} sleep 2 # Consumer log should show: success_count=2, skip_count=0 # 7b. Enable filter — only sanity-customer-001 allowed curl -s -X PUT "$BASE/settings/event_ingestion_filter" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{"value": {"enabled": true, "allowed_external_customer_ids": ["sanity-customer-001"]}}' # ✅ setting saved # Re-send same batch — should filter blocked-org-999 curl -s -X POST "$BASE/events/raw/bulk" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{ "events": [ {"id":"sanity-raw-003","orgId":"sanity-customer-001","methodName":"synthesizer","providerName":"deepgram","createdAt":"2026-01-01T00:01:00Z","data":{"durationMS":1000},"dataInterface":"DURATION_DATA","referenceType":"call","targetItemId":"ref-003","targetItemType":"call"}, {"id":"sanity-raw-004","orgId":"blocked-org-999","methodName":"synthesizer","providerName":"deepgram","createdAt":"2026-01-01T00:01:01Z","data":{"durationMS":500},"dataInterface":"DURATION_DATA","referenceType":"call","targetItemId":"ref-004","targetItemType":"call"} ] }' sleep 2 # ✅ Consumer log: success_count=1, skip_count=1 # Verify only allowed IDs in ClickHouse docker compose exec clickhouse clickhouse-client \ --user=flexprice --password=flexprice123 --database=flexprice \ --query="SELECT id, external_customer_id FROM events WHERE id IN ('sanity-raw-003','sanity-raw-004') FORMAT PrettyCompact" # ✅ only sanity-raw-003 appears; sanity-raw-004 (blocked) is absent # 7c. Disable filter (clean up) curl -s -X PUT "$BASE/settings/event_ingestion_filter" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ -H "Content-Type: application/json" \ -d '{"value": {"enabled": false, "allowed_external_customer_ids": []}}' ``` --- ## 8. Settings Round-trip ```bash # GET curl -s "$BASE/settings/event_ingestion_filter" \ -H "x-api-key: $KEY" -H "x-environment-id: 00000000-0000-0000-0000-000000000000" \ | python3 -m json.tool # ✅ enabled: false (from step 7c cleanup) ``` --- ## Checklist Copy this into your PR description or review notes: ``` - [ ] 0. Health check passes - [ ] 1. Customer create + fetch - [ ] 2. Meter create - [ ] 3. Plan + price create - [ ] 4. Subscription create - [ ] 5. Event ingested → visible in ClickHouse - [ ] 6. Usage query returns correct count - [ ] 7. Raw bulk ingest — no filter (success_count=2) - [ ] 7. Raw bulk ingest — filter enabled (success_count=1, skip_count=1) - [ ] 7. ClickHouse confirms only allowed event present - [ ] 8. Settings GET round-trip correct ```