feat: production-ready deployment with multi-language UI
- Add multi-language support (NL, AR, EN) with RTL
- Improve health checks (SSL-tolerant, multi-endpoint)
- Add DELETE /api/stack/:name for cleanup
- Add persistent storage (portal-ai-workspace-{name})
- Improve rollback (delete domain, app, project)
- Increase SSE timeout to 255s
- Add deployment strategy documentation
This commit is contained in:
224
docs/TESTING.md
224
docs/TESTING.md
@@ -1,178 +1,110 @@
|
||||
# AI Stack Deployer - Testing Documentation
|
||||
# Testing Guide
|
||||
|
||||
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||
## Quick Verification
|
||||
|
||||
Your only and main job now is to accurate follow the orders from the user. Your job is to validate and audit all code for issues, failures or misconfiguration. Your test plan should look like:
|
||||
Phase 1: Preparation & Static Analysis
|
||||
|
||||
Code Review (Audit): Have the code reviewed by a colleague or use a static analysis tool (linter) to identify syntax and style errors prior to execution.
|
||||
Logic Validation: Verify that the code logic aligns with the initial requirements (checking what it is supposed to do, not necessarily if it runs yet).
|
||||
|
||||
Phase 2: Unit Testing (Automated)
|
||||
3. Run Unit Tests: Execute tests on small, isolated components (e.g., verifying that the authentication function returns the correct token).
|
||||
4. Check Code Coverage: Ensure that critical paths and functions are actually being tested.
|
||||
|
||||
Phase 3: Integration & Functional Testing
|
||||
5. Authentication Test: Verify that the application or script can successfully connect to required external systems (databases, APIs, login services). Note: This is a prerequisite for the next steps.
|
||||
6. Execute Scripts (Happy Path): Run the script or application in the standard, intended way.
|
||||
7. Monitor Logs: Monitor the output for error logs, warnings, or unexpected behavior during execution.
|
||||
|
||||
Phase 4: Evaluation & Reporting
|
||||
8. Analyze Results: Compare the actual output against the expected results.
|
||||
9. Report Status: If all tests pass, approve the code for release and inform the user.
|
||||
|
||||
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||
|
||||
**Last Updated**: 2026-01-09
|
||||
|
||||
## Infrastructure Context (from memory/docs)
|
||||
|
||||
| Service | IP | Port | Notes |
|
||||
|---------|-----|------|-------|
|
||||
| Dokploy | 10.100.0.20 | 3000 | Container orchestration, Grafana Loki also here |
|
||||
| Loki | 10.100.0.20 | 3100 | Logging aggregation |
|
||||
| Grafana | 10.100.0.20 | 3000 (UI) | Dashboards at https://logs.intra.flexinit.nl |
|
||||
| Traefik | 10.100.0.12 | - | VM 202 - Reverse proxy, SSL |
|
||||
| AI Server | 10.100.0.19 | - | VM 209 - OpenCode agents |
|
||||
|
||||
**Dokploy config location**: `/etc/dokploy/compose/` on 10.100.0.20
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 Test Results
|
||||
|
||||
### 1. Hono Server
|
||||
|
||||
| Test | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Server starts | ✅ PASS | Runs on port 3000 |
|
||||
| Health endpoint | ✅ PASS | Returns JSON with status, timestamp, version |
|
||||
| Root endpoint | ✅ PASS | Returns API endpoint list |
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
# Start dev server
|
||||
# 1. Start server
|
||||
bun run dev
|
||||
|
||||
# Test health endpoint
|
||||
# 2. Health check
|
||||
curl http://localhost:3000/health
|
||||
# Response: {"status":"healthy","timestamp":"2026-01-09T14:13:50.237Z","version":"0.1.0","service":"ai-stack-deployer"}
|
||||
|
||||
# Test root endpoint
|
||||
curl http://localhost:3000/
|
||||
# Response: {"message":"AI Stack Deployer API","endpoints":{...}}
|
||||
# 3. Open browser
|
||||
open http://localhost:3000
|
||||
```
|
||||
|
||||
### 2. Hetzner DNS Client
|
||||
## API Endpoints
|
||||
|
||||
| Test | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Connection test | ✅ PASS | Successfully connects to Hetzner Cloud API |
|
||||
| Zone access | ✅ PASS | Zone "flexinit.nl" (ID: 343733) accessible |
|
||||
| RRSets listing | ✅ PASS | Returns 75 RRSets |
|
||||
| Endpoint | Method | Purpose |
|
||||
|----------|--------|---------|
|
||||
| `/health` | GET | Server status |
|
||||
| `/api/check/:name` | GET | Name availability |
|
||||
| `/api/deploy` | POST | Start deployment |
|
||||
| `/api/status/:id` | GET | SSE progress stream |
|
||||
| `/api/stack/:name` | DELETE | Delete stack and cleanup |
|
||||
|
||||
**IMPORTANT FINDING:**
|
||||
- Hetzner DNS has been **migrated from dns.hetzner.com to api.hetzner.cloud**
|
||||
- The old DNS Console API at `dns.hetzner.com/api/v1` is deprecated
|
||||
- Must use new Hetzner Cloud API at `api.hetzner.cloud/v1`
|
||||
- Authentication: `Authorization: Bearer {token}` (NOT `Auth-API-Token`)
|
||||
- Endpoints: `/zones`, `/zones/{id}/rrsets`
|
||||
## Test Checklist
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
# Test Hetzner client
|
||||
bun run src/test-clients.ts
|
||||
### Backend
|
||||
|
||||
# Manual API test
|
||||
curl -s "https://api.hetzner.cloud/v1/zones" \
|
||||
-H "Authorization: Bearer $HETZNER_API_TOKEN"
|
||||
```
|
||||
| Test | Command | Expected |
|
||||
|------|---------|----------|
|
||||
| Server starts | `bun run dev` | "starting on http://0.0.0.0:3000" |
|
||||
| Health endpoint | `curl localhost:3000/health` | `{"status":"healthy"...}` |
|
||||
| TypeScript | `bun run typecheck` | No errors |
|
||||
| Dokploy connection | Check `/api/check/test-name` | Returns availability |
|
||||
|
||||
### 3. Dokploy Client
|
||||
### Frontend
|
||||
|
||||
| Test | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Connection test | ❌ FAIL | Returns "Unauthorized" |
|
||||
| Server accessible | ✅ PASS | Dokploy UI loads at http://10.100.0.20:3000 |
|
||||
| Test | Action | Expected |
|
||||
|------|--------|----------|
|
||||
| Page loads | Open localhost:3000 | Dark theme, centered content |
|
||||
| Typewriter | Wait 2s | "Choose Your Stack Name" animates |
|
||||
| Language switch | Click 🇲🇦 | Arabic text, RTL layout |
|
||||
| Name validation | Type "ab" | Error: too short |
|
||||
| Reserved name | Type "admin" | Error: reserved |
|
||||
| Valid name | Type "my-stack" | "✓ Name is available!" |
|
||||
|
||||
**BLOCKER:**
|
||||
- Token `app_deployment...` returns 401 Unauthorized
|
||||
- Token was created 2026-01-05 but may be expired or have insufficient permissions
|
||||
- **ACTION REQUIRED**: Generate new token from Dokploy dashboard
|
||||
### Deployment Flow
|
||||
|
||||
**Steps to generate new Dokploy API token:**
|
||||
1. Navigate to https://deploy.intra.flexinit.nl or http://10.100.0.20:3000
|
||||
2. Login with admin credentials
|
||||
3. Go to: Settings (gear icon) → Profile
|
||||
4. Scroll to "API Tokens" section
|
||||
5. Click "Generate" button
|
||||
6. Copy the new token (format: `app_deployment<random>`)
|
||||
7. Update BWS secret: `bws secret edit 6b3618fc-ba02-49bc-bdc8-b3c9004087bc`
|
||||
8. Update local `.env` file
|
||||
| Step | Indicator |
|
||||
|------|-----------|
|
||||
| Submit form | Progress bar appears |
|
||||
| SSE updates | Log entries animate in |
|
||||
| Success | Typewriter: "Deployment Complete" |
|
||||
| Error | Typewriter: "Deployment Failed" |
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
# Test Dokploy API (currently failing)
|
||||
curl -s "http://10.100.0.20:3000/api/project.all" \
|
||||
-H "Authorization: Bearer $DOKPLOY_API_TOKEN"
|
||||
# Response: {"message":"Unauthorized"}
|
||||
```
|
||||
## Infrastructure
|
||||
|
||||
---
|
||||
| Service | URL | Purpose |
|
||||
|---------|-----|---------|
|
||||
| Dokploy | https://app.flexinit.nl | Container orchestration |
|
||||
| Traefik | 144.76.116.169 | SSL termination |
|
||||
| Stacks | *.ai.flexinit.nl | Deployed AI assistants |
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### .env File (from .env.example)
|
||||
## Full Deployment Test
|
||||
|
||||
```bash
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
# 1. Generate unique name
|
||||
NAME="test-$(date +%s | tail -c 5)"
|
||||
|
||||
# Hetzner Cloud DNS API (WORKING)
|
||||
HETZNER_API_TOKEN=<from BWS - HETZNER_DNS_TOKEN>
|
||||
HETZNER_ZONE_ID=343733
|
||||
# 2. Check availability
|
||||
curl -s http://localhost:3000/api/check/$NAME
|
||||
|
||||
# Dokploy API (NEEDS NEW TOKEN)
|
||||
DOKPLOY_URL=http://10.100.0.20:3000
|
||||
DOKPLOY_API_TOKEN=<generate from Dokploy dashboard>
|
||||
# 3. Deploy
|
||||
curl -s -X POST http://localhost:3000/api/deploy \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"$NAME\"}"
|
||||
|
||||
STACK_DOMAIN_SUFFIX=ai.flexinit.nl
|
||||
STACK_IMAGE=git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest
|
||||
TRAEFIK_IP=144.76.116.169
|
||||
# 4. Monitor SSE (wait ~2-3 min)
|
||||
curl -N http://localhost:3000/api/status/<deployment-id>
|
||||
|
||||
RESERVED_NAMES=admin,api,www,root,system,test,demo,portal
|
||||
# 5. Verify stack accessible
|
||||
curl -s https://$NAME.ai.flexinit.nl
|
||||
|
||||
# 6. Cleanup
|
||||
curl -s -X DELETE http://localhost:3000/api/stack/$NAME
|
||||
```
|
||||
|
||||
---
|
||||
## Cleanup Commands
|
||||
|
||||
## BWS Secrets Reference
|
||||
```bash
|
||||
# Delete specific stack
|
||||
curl -s -X DELETE http://localhost:3000/api/stack/my-stack
|
||||
|
||||
| Secret | BWS Key | Status |
|
||||
|--------|---------|--------|
|
||||
| Hetzner API Token | `HETZNER_DNS_TOKEN` | ✅ Working |
|
||||
| Dokploy API Token | `DOKPLOY_API_TOKEN` (ID: 6b3618fc-ba02-49bc-bdc8-b3c9004087bc) | ❌ Expired/Invalid |
|
||||
# List all projects (direct Dokploy)
|
||||
source .env && curl -s -H "x-api-key: $DOKPLOY_API_TOKEN" \
|
||||
"$DOKPLOY_URL/api/project.all" | jq '.[].name'
|
||||
```
|
||||
|
||||
---
|
||||
## Common Issues
|
||||
|
||||
## Gotchas & Learnings
|
||||
|
||||
### 1. Hetzner DNS API Migration
|
||||
- **Old API**: `dns.hetzner.com/api/v1` with `Auth-API-Token` header
|
||||
- **New API**: `api.hetzner.cloud/v1` with `Authorization: Bearer` header
|
||||
- Zone ID 343733 works in new API
|
||||
- RRSets replace Records concept
|
||||
|
||||
### 2. Dokploy Token Format
|
||||
- Format: `app_deployment<random>`
|
||||
- Created from: Dashboard > Settings > Profile > API Tokens
|
||||
- Must have permissions for project/application management
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. [ ] Generate new Dokploy API token from dashboard
|
||||
2. [ ] Update BWS with new token
|
||||
3. [ ] Verify Dokploy client works
|
||||
4. [ ] Proceed to Phase 2 implementation
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| CSS not loading | Check `/style.css` returns CSS, not HTML |
|
||||
| 401 on Dokploy | Regenerate API token in Dokploy dashboard |
|
||||
| Typewriter not running | Check browser console for JS errors |
|
||||
| RTL not working | Verify `dir="rtl"` on `<html>` element |
|
||||
| Health check timeout | Container startup can take 1-2 min, timeout is 3 min |
|
||||
| SSL cert errors | Health check treats SSL errors as "alive" during provisioning |
|
||||
| SSE disconnects | idleTimeout set to 255s (max), long deployments should complete |
|
||||
|
||||
Reference in New Issue
Block a user