# Testing Guide

## Quick Verification

```bash
# 1. Start server
bun run dev

# 2. Health check
curl http://localhost:3000/health

# 3. Open browser
open http://localhost:3000
```

## API Endpoints

| 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 |

## Test Checklist

### Backend

| 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 |

### Frontend

| 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!" |

### Deployment Flow

| Step | Indicator |
|------|-----------|
| Submit form | Progress bar appears |
| SSE updates | Log entries animate in |
| Success | Typewriter: "Deployment Complete" |
| Error | Typewriter: "Deployment Failed" |

## 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 |

## Full Deployment Test

```bash
# 1. Generate unique name
NAME="test-$(date +%s | tail -c 5)"

# 2. Check availability
curl -s http://localhost:3000/api/check/$NAME

# 3. Deploy
curl -s -X POST http://localhost:3000/api/deploy \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"$NAME\"}"

# 4. Monitor SSE (wait ~2-3 min)
curl -N http://localhost:3000/api/status/<deployment-id>

# 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

```bash
# Delete specific stack
curl -s -X DELETE http://localhost:3000/api/stack/my-stack

# 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

| 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 |

---

## Production Verification (2026-01-10)

### Verified Working

| Component | Status | Notes |
|-----------|--------|-------|
| Portal Health | ✅ | `https://portal.ai.flexinit.nl/health` returns healthy |
| Name Validation | ✅ | `/api/check/:name` validates correctly |
| Frontend UI | ✅ | 3 languages (NL, AR, EN), RTL support, typewriter animation |
| Stack Deployment | ✅ | Full flow: project → app → domain → deploy → health check |
| Stack Cleanup | ✅ | `DELETE /api/stack/:name` removes all resources |
| SSL/HTTPS | ✅ | Wildcard cert working for all `*.ai.flexinit.nl` |

### Critical Configuration

```bash
# These settings are REQUIRED for deployment to work
DOKPLOY_URL=https://app.flexinit.nl          # Public URL, NOT internal 10.100.0.20
STACK_IMAGE=git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest
STACK_REGISTRY_ID=bKDYM5X7NN34x_lRDjWbz      # Registry ID for Docker auth
```

### Known Gotchas (Fixed)

1. **Registry URL format** - Use `git.app.flexinit.nl`, NOT `https://git.app.flexinit.nl`
2. **Username in image path** - Must be `oussamadouhou`, not `odouhou`
3. **Dokploy URL** - Must use public URL for container-to-container communication
4. **Health check** - Now uses Dokploy API status check (not HTTP fetch)
5. **Resource limits** - Removed temporarily (Dokploy API format issues with CPU/memory values)
6. **Typewriter race condition** - Fixed by tracking active instances and canceling previous

### UI/UX Fixes (2026-01-10 v2)

| Issue | Fix | Status |
|-------|-----|--------|
| Double letters in typewriter | Track active instances, cancel before starting new | ✅ Verified |
| Flag emojis not showing | Replaced with text labels (NL/AR/EN) | ✅ Verified |
| SSE disconnects during deployment | Use Dokploy API status instead of HTTP health check | ✅ Verified |
| 'yourname' not translated | Added translation key per language | ✅ Verified |

### Health Check Change

The health check was changed from HTTP fetch (which failed from inside Docker) to Dokploy API status check:

```typescript
// Before: HTTP fetch (failed - container can't reach external URL)
const response = await fetch(`${state.url}/`);

// After: Dokploy API status check (works)
const app = await this.client.getApplication(applicationId);
if (app.applicationStatus === 'done') { /* success */ }
```

### Test Commands

```bash
# Quick health check
curl -s https://portal.ai.flexinit.nl/health | jq .

# Full deployment test
NAME="test-$(date +%s | tail -c 5)"
RESULT=$(curl -s -X POST https://portal.ai.flexinit.nl/api/deploy \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"$NAME\"}")
echo $RESULT | jq .

# Monitor deployment (use deploymentId from above)
curl -N "https://portal.ai.flexinit.nl/api/status/$(echo $RESULT | jq -r .deploymentId)"

# Verify stack accessible (after deployment completes)
curl -s -k https://$NAME.ai.flexinit.nl | head -5

# Cleanup
curl -s -X DELETE https://portal.ai.flexinit.nl/api/stack/$NAME | jq .
```

### Dokploy Direct Commands

```bash
# List all ai-stack projects
source .env && curl -s -H "x-api-key: $DOKPLOY_API_TOKEN" \
  https://app.flexinit.nl/api/project.all | \
  jq '[.[] | select(.name | startswith("ai-stack-")) | {name: .name, id: .projectId}]'

# Get application status
source .env && curl -s -H "x-api-key: $DOKPLOY_API_TOKEN" \
  "https://app.flexinit.nl/api/project.one?projectId=<PROJECT_ID>" | \
  jq '.environments[0].applications[0] | {name: .name, status: .applicationStatus}'
```

---

## Gitea Actions CI/CD Status

### Check Workflow Status

The `oh-my-opencode-free` Docker image is built via Gitea Actions. To check CI status:

**Web UI:**
```
https://git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free/actions
```

**API (requires token):**
```bash
# Get GITEA_API_TOKEN from BWS (key: GITEA_API_TOKEN)
GITEA_TOKEN="<your-token>"

# List recent workflow runs
curl -s -H "Authorization: token $GITEA_TOKEN" \
  "https://git.app.flexinit.nl/api/v1/repos/oussamadouhou/oh-my-opencode-free/actions/runs?limit=5" | \
  jq '.workflow_runs[] | {id, run_number, status, conclusion, display_title, head_sha: .head_sha[0:7]}'

# Get specific run details
curl -s -H "Authorization: token $GITEA_TOKEN" \
  "https://git.app.flexinit.nl/api/v1/repos/oussamadouhou/oh-my-opencode-free/actions/runs/<RUN_ID>" | jq .

# Get jobs for a specific run
curl -s -H "Authorization: token $GITEA_TOKEN" \
  "https://git.app.flexinit.nl/api/v1/repos/oussamadouhou/oh-my-opencode-free/actions/runs/<RUN_ID>/jobs" | jq .
```

### API Response Fields

| Field | Description |
|-------|-------------|
| `status` | `queued`, `in_progress`, `completed` |
| `conclusion` | `success`, `failure`, `cancelled`, `skipped` (only when status=completed) |
| `head_sha` | Commit SHA that triggered the run |
| `run_number` | Sequential run number |
| `display_title` | Commit message or PR title |

### Gitea API Authentication

From [Gitea docs](https://docs.gitea.com/development/api-usage):

```bash
# Header format
Authorization: token <your-api-token>

# Alternative: query parameter
?token=<your-api-token>
```

### BWS Token Reference

| Key | Purpose |
|-----|---------|
| `GITEA_API_TOKEN` | Gitea API access for workflow status |
| `DOKPLOY_API_TOKEN` | Dokploy deployment API (BWS ID: `6b3618fc-ba02-49bc-bdc8-b3c9004087bc`) |

---

## Testing Session: 2026-01-13

### Session Summary

**Goal:** Verify multi-environment deployment setup and shared project configuration.

### Completed Tasks

| Task | Status | Evidence |
|------|--------|----------|
| Workflow separation (dev/staging/main) | ✅ | Committed as `eb2745d` |
| Dollar sign escaping (`$${{project.VAR}}`) | ✅ | Verified in all docker-compose.*.yml |
| Shared project exists | ✅ | `ai-stack-portal` (ID: `2y2Glhz5Wy0dBNf6BOR_-`) |
| Environment IDs retrieved | ✅ | See below |
| Local dev server health | ✅ | `/health` returns healthy |

### Environment IDs

```
Project: ai-stack-portal
ID: 2y2Glhz5Wy0dBNf6BOR_-

Environments:
- production: _dKAmxVcadqi-z73wKpEB (default)
- deployments: RqE9OFMdLwkzN7pif1xN8 (for user stacks)
- test: KVKn5fXGz10g7KVxPWOQj
```

### Blockers Identified

#### BLOCKER: Dokploy API Token Permissions

**Symptom:** All Dokploy API calls return `Forbidden`

```bash
# Previously working
curl -s "https://app.flexinit.nl/api/project.all" -H "x-api-key: $DOKPLOY_API_TOKEN"
# Now returns: Forbidden

# Environment endpoint
curl -s "https://app.flexinit.nl/api/environment.one?environmentId=RqE9OFMdLwkzN7pif1xN8" -H "x-api-key: $DOKPLOY_API_TOKEN"
# Returns: Forbidden
```

**Root Cause:** The API token `app_deployment...` has been revoked or has limited scope.

**Impact:**
- Cannot verify Docker image exists in registry
- Cannot test name availability (requires `environment.one`)
- Cannot create applications or compose stacks
- Cannot deploy portal to Dokploy

**Resolution Required:**
1. Log into Dokploy UI at https://app.flexinit.nl
2. Navigate to Settings → API Keys
3. Generate new API key with full permissions:
   - Read/Write access to projects
   - Read/Write access to applications
   - Read/Write access to compose stacks
   - Read/Write access to domains
4. Update `.env` with new token
5. Update BWS secret (ID: `6b3618fc-ba02-49bc-bdc8-b3c9004087bc`)

### Local Testing Results

```bash
# Health check - WORKS
curl -s "http://localhost:3000/health"
# {"status":"healthy","timestamp":"2026-01-13T13:01:46.100Z","version":"0.2.0",...}

# Name check - FAILS (API token issue)
curl -s "http://localhost:3000/api/check/test-stack"
# {"available":false,"valid":false,"error":"Failed to check availability"}
```

### Required .env Configuration

```bash
# Added for shared project deployment
SHARED_PROJECT_ID=2y2Glhz5Wy0dBNf6BOR_-
SHARED_ENVIRONMENT_ID=RqE9OFMdLwkzN7pif1xN8
```

### Next Steps After Token Fix

1. Verify `project.all` API works with new token
2. Deploy portal to Dokploy (docker-compose.dev.yml)
3. Test end-to-end stack deployment
4. Verify stacks deploy to shared project
5. Clean up test deployments

### Commands Reference

```bash
# Test API token
source .env && curl -s "https://app.flexinit.nl/api/project.all" \
  -H "x-api-key: $DOKPLOY_API_TOKEN" | jq '.[].name'

# Get environment applications
source .env && curl -s "https://app.flexinit.nl/api/environment.one?environmentId=RqE9OFMdLwkzN7pif1xN8" \
  -H "x-api-key: $DOKPLOY_API_TOKEN" | jq '.applications'

# Deploy test stack
curl -X POST http://localhost:3000/api/deploy \
  -H "Content-Type: application/json" \
  -d '{"name":"test-'$(date +%s | tail -c 4)'"}'
```
