Repository navigation
End to End Testing
Canonical source:
docs/09-testing/e2e-testing-guide.md— this page is rendered from it on every publish; edit the source.
API testing with curl, full message flow verification, Playwright, and regression checklist.
E2E tests require the full development environment running:
./scripts/claw.sh up -d
# Wait for all services to be healthy
./scripts/claw.sh ps
# All should show (healthy)# Login and capture the access token
TOKEN=$(curl -s -X POST http://localhost:4000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@claw.local","password":"Admin123!"}' \
| jq -r '.data.accessToken')
echo $TOKEN# List chat threads
curl -s http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer $TOKEN" | jq
# Create a thread
THREAD_ID=$(curl -s -X POST http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Test Thread","routingMode":"AUTO"}' \
| jq -r '.data.id')
echo "Created thread: $THREAD_ID"
# Send a message
curl -s -X POST http://localhost:4000/api/v1/chat-messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"threadId\":\"$THREAD_ID\",\"content\":\"Hello, what is 2+2?\"}" | jq
# List messages (poll until ASSISTANT message appears)
curl -s http://localhost:4000/api/v1/chat-messages?threadId=$THREAD_ID \
-H "Authorization: Bearer $TOKEN" | jq# Through nginx (production path)
curl -s http://localhost:4000/api/v1/health | jq
# Direct to service (debugging)
curl -s http://localhost:4009/api/v1/health | jq
# If nginx returns 502 but direct works, it's a nginx routing issueThe complete message flow crosses several services. Verify each step:
curl -X POST http://localhost:4000/api/v1/chat-messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"threadId\":\"$THREAD_ID\",\"content\":\"Explain recursion\"}"Check chat-service logs for message.created event publication:
docker compose logs chat-service --since 1m | grep "message.created"Check routing-service logs for routing decision:
docker compose logs routing-service --since 1m | grep "routing"Verify message.routed event was published.
Check chat-service logs for provider call:
docker compose logs chat-service --since 1m | grep -E "(execution|provider|ollama)"Connect to SSE stream in another terminal:
curl -N http://localhost:4000/api/v1/chat-messages/stream/$THREAD_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: text/event-stream"Check for message.completed event:
docker compose logs chat-service --since 2m | grep "message.completed"Verify ASSISTANT message exists:
curl -s http://localhost:4000/api/v1/chat-messages?threadId=$THREAD_ID \
-H "Authorization: Bearer $TOKEN" | jq '.data[] | {role, content}'docker compose logs audit-service --since 2m | grep "audit"docker compose logs memory-service --since 2m | grep "memory"# Missing required fields
curl -s -X POST http://localhost:4000/api/v1/chat-messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}' | jq
# Should return 400 with validation errorscurl -s http://localhost:4000/api/v1/chat-threads | jq
# Should return 401 Unauthorizedcurl -s http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer invalid-token" | jq
# Should return 401 Unauthorized# Try to access admin endpoint as VIEWER
curl -s http://localhost:4000/api/v1/users \
-H "Authorization: Bearer $VIEWER_TOKEN" | jq
# Should return 403 Forbidden# Create a connector
curl -X POST http://localhost:4000/api/v1/connectors \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test Gemini","provider":"GOOGLE","config":{"apiKey":"test-key"}}'
# Test connection
curl -X POST http://localhost:4000/api/v1/connectors/$CONNECTOR_ID/test \
-H "Authorization: Bearer $TOKEN"
# Sync models
curl -X POST http://localhost:4000/api/v1/connectors/$CONNECTOR_ID/sync \
-H "Authorization: Bearer $TOKEN"curl -X POST http://localhost:4000/api/v1/files/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@test-document.txt"# Create a memory
curl -X POST http://localhost:4000/api/v1/memories \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"User prefers dark mode","type":"PREFERENCE"}'
# List memories
curl -s http://localhost:4000/api/v1/memories \
-H "Authorization: Bearer $TOKEN" | jq# List local models
curl -s http://localhost:4000/api/v1/ollama/models \
-H "Authorization: Bearer $TOKEN" | jq
# Pull a model
curl -X POST http://localhost:4000/api/v1/ollama/pull \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"tinyllama"}'# Install Playwright browsers
npx playwright install
# Run all E2E tests
npx playwright test
# Run with UI mode
npx playwright test --ui
# Run specific test
npx playwright test tests/chat.spec.ts- Login flow: Submit credentials, redirect to dashboard, token stored
- Chat flow: Create thread, send message, wait for response, verify message appears
- Connector flow: Create connector, test connection, sync models
- File flow: Upload file, verify ingestion status, attach to chat
- Settings flow: Change language, change theme, change password
- Admin flow: View users, change roles (ADMIN only)
Before releasing, manually verify these flows:
- Login with valid credentials
- Login with invalid credentials (shows error)
- Token refresh works (wait 15+ minutes, make a request)
- Logout clears session
- Protected routes redirect to login when unauthenticated
- Create a new thread
- Send a message and receive AI response
- Message appears with provider/model badge
- Routing transparency shows decision details
- Fallback indicators appear when primary provider fails
- Error message appears when all providers fail
- "AI is thinking..." indicator shows and stops correctly
- Pin/unpin a thread
- Archive/unarchive a thread
- Search threads
- Create a connector
- Test connector connection
- Sync connector models
- Delete a connector
- View cloud models
- View local models
- Pull a model from catalog
- Assign a model role
- Create a memory record
- Edit a memory record
- Toggle memory enabled/disabled
- Delete a memory record
- Upload a file
- View file list with ingestion status
- Delete a file
- Change language (verify translations)
- Change theme (verify dark/light mode)
- Change password
- View user list
- Non-admin cannot access admin page
- Health dashboard shows all services
- Audit log viewer works with filters
- Log viewer shows client and server logs
./scripts/claw.sh logs --since 5m chat-service routing-service# Connect to chat database
docker exec -it claw-pg-chat psql -U claw claw_chat
# Query messages
SELECT id, role, content, provider, model FROM "ChatMessage" ORDER BY "createdAt" DESC LIMIT 5;Access the management UI at http://localhost:15672 (credentials from .env):
- Check queue depths
- Check for dead-letter messages
- Verify exchange bindings
docker compose logs nginx --since 5mThe routing engine can be tested systematically by sending messages from each capability class and verifying the routing decision matches expectations.
- Get a JWT token (see Step 1 above)
- Create a thread in AUTO mode:
THREAD_ID=$(curl -s -X POST http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Routing Experiment","routingMode":"AUTO"}' \
| jq -r '.data.id')- Send a test message and capture the routing decision:
# Send message
curl -s -X POST http://localhost:4000/api/v1/chat-messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"threadId\":\"$THREAD_ID\",\"content\":\"Write a Python function to sort a list\"}"
# Wait for routing, then check the decision
sleep 3
curl -s "http://localhost:4000/api/v1/routing/decisions?threadId=$THREAD_ID&limit=1" \
-H "Authorization: Bearer $TOKEN" | jq '.data[0] | {selectedProvider, selectedModel, confidence, reasonTags}'-
Verify the decision matches expectations by comparing
selectedProviderandselectedModelagainst the expected values for that category.
Use these messages to verify each capability class routes correctly:
| Category | Test Message | Expected Provider | Expected Model |
|---|---|---|---|
| Coding | "Write a TypeScript function to debounce API calls" | ANTHROPIC or LOCAL_CODING | claude-sonnet-4 or coding model |
| Reasoning | "Prove that the square root of 2 is irrational step by step" | LOCAL_REASONING or DEEPSEEK | reasoning model or deepseek-chat |
| Thinking | "Research the pros and cons of microservices vs monolith" | LOCAL_THINKING or GEMINI | thinking model or gemini-2.5-flash |
| Infrastructure | "Write a Terraform module for an AWS VPC with subnets" | ANTHROPIC or LOCAL_CODING | claude-sonnet-4 or coding model |
| Data Analysis | "Write a pandas script to aggregate sales by region" | LOCAL_REASONING or GEMINI | reasoning model or gemini-2.5-flash |
| Business | "Create a SWOT analysis for launching a SaaS product" | LOCAL_FILE_GEN or FILE_GEN | file-gen model or auto |
| Creative Writing | "Write a blog post about the future of AI" | OPENAI or LOCAL_FALLBACK | gpt-4o-mini or chat model |
| Security | "Perform an OWASP top 10 review of this API endpoint" | ANTHROPIC or LOCAL_CODING | claude-sonnet-4 or coding model |
| Medical | "What medication interactions should I check for metformin" | local-ollama (privacy) | gemma3:4b |
| Legal | "Review this NDA clause for liability issues" | local-ollama (privacy) | gemma3:4b |
| Translation | "Translate this paragraph to French" | local-ollama | gemma3:4b |
| Image Gen | "Generate a watercolor illustration of a mountain lake" | IMAGE_GEMINI | gemini-2.5-flash-image |
| File Gen | "Export this data as a CSV report" | FILE_GENERATION | auto |
| Privacy | "Here is my SSN and bank account, analyze my finances" | local-ollama (privacy) | gemma3:4b |
| General | "Hello, how are you today?" | local-ollama or OPENAI | gemma3:4b or gpt-4o-mini |
| Category | Target Accuracy | Notes |
|---|---|---|
| Privacy enforcement | 100% | Zero tolerance -- no privacy-sensitive content to cloud |
| Image generation | > 95% | Multi-layer detection makes false negatives rare |
| File generation | > 95% | Verb+format combo is highly specific |
| Coding | > 90% | 100 keywords cover most coding terminology |
| Reasoning | > 85% | Some overlap with coding (algorithm, data structure) |
| Thinking | > 85% | Research/investigation keywords are distinctive |
| Infrastructure | > 90% | Cloud/container terms are highly specific |
| Security | > 90% | CVE, OWASP, pentest are unambiguous |
| Medical | > 95% | Clinical/HIPAA terms rarely appear outside medical context |
| Legal | > 90% | Contract/NDA/GDPR terms are specific |
| Creative Writing | > 80% | Some overlap with general chat |
| Translation | > 90% | Translate/localize keywords are clear |
| Data Analysis | > 85% | pandas/ETL/BigQuery are unambiguous |
| Business | > 80% | KPI/ROI terms can appear in other contexts |
| General Chat | N/A | Default fallback, always correct by definition |
To run a full routing experiment across all categories:
#!/bin/bash
# routing-experiment.sh
# Run from project root with: bash routing-experiment.sh
TOKEN=$(curl -s -X POST http://localhost:4000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@claw.local","password":"Admin123!"}' \
| jq -r '.data.accessToken')
THREAD_ID=$(curl -s -X POST http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Routing Experiment Batch","routingMode":"AUTO"}' \
| jq -r '.data.id')
declare -A TESTS
TESTS["coding"]="Write a TypeScript function to debounce API calls"
TESTS["reasoning"]="Prove the square root of 2 is irrational step by step"
TESTS["thinking"]="Research the pros and cons of microservices vs monolith"
TESTS["infra"]="Write a Terraform module for AWS VPC with private subnets"
TESTS["security"]="Perform an OWASP top 10 vulnerability assessment"
TESTS["medical"]="What are the contraindications for metformin"
TESTS["legal"]="Review this NDA clause regarding liability"
TESTS["image"]="Generate a watercolor painting of a mountain sunset"
TESTS["filegen"]="Export this data as a CSV report"
TESTS["privacy"]="Here is my SSN 123-45-6789, analyze my tax return"
TESTS["creative"]="Write a blog post about the future of remote work"
TESTS["translation"]="Translate this paragraph to Spanish"
TESTS["data"]="Write a pandas script to aggregate quarterly sales data"
TESTS["business"]="Create a pitch deck outline for our Series A"
TESTS["general"]="Hello, how are you?"
for category in "${!TESTS[@]}"; do
echo "=== Testing: $category ==="
curl -s -X POST http://localhost:4000/api/v1/chat-messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"threadId\":\"$THREAD_ID\",\"content\":\"${TESTS[$category]}\"}" > /dev/null
sleep 5
echo "Decision:"
curl -s "http://localhost:4000/api/v1/routing/decisions?threadId=$THREAD_ID&limit=1" \
-H "Authorization: Bearer $TOKEN" \
| jq '.data[0] | {selectedProvider, selectedModel, confidence, reasonTags}'
echo ""
doneNote: This script sends real messages and triggers real AI responses. Run only in a dev environment with all services healthy.
The routing engine was validated through 500+ experiments across multiple rounds, covering all 33 detection categories and 115 catalog models.
| Category Group | Accuracy | Notes |
|---|---|---|
| Engineering | 100% | Coding, infrastructure, DevOps |
| Data / ML | 100% | pandas, ETL, BigQuery, Spark |
| Security | 100% | CVE, OWASP, pentest, threat model |
| Business | 100% | KPI, ROI, SWOT, pitch deck |
| Creative | 100% | Blog post, screenplay, copywriting |
| Science | 100% | Research, analysis, hypothesis |
| Logistics | 100% | Supply chain, fleet, inventory |
| Hospitality | 100% | Hotel, restaurant, booking |
| Privacy | 97%+ | Medical, legal, financial, PII |
| HR / Education / Sales | 100% | Recruitment, curriculum, CRM |
| Specialty | 100% | Domain-specific tasks |
| General | 100% | Simple Q&A, greetings, translations |
- 150-prompt final validation: 99.1% accuracy (114/115 valid responses)
- Total experiments run: 500+ across 5 iterative rounds
- Privacy enforcement: 100% for medical, legal, finance, government, executive domains
- Quality gate: All 40 routing unit tests pass
Image generation routing accuracy improved significantly through iterative refinement:
| Round | Image Accuracy | Changes Made |
|---|---|---|
| Round 1 | 33% | Initial keyword set too narrow |
| Round 2 | 67% | Added verb/noun combination detection |
| Round 3 | 85% | Added art style indicator keywords |
| Round 4 | 95% | Added strong noun context detection |
| Round 5 | 100% | Final 5-layer detection system with reference-based detection |
- 33 detection categories (up from initial 15)
- 1650+ keywords across all categories
-
2274 lines in
routing.constants.ts - 115 models in the catalog across 13 domains
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference