chore: git cache cleanup
This commit is contained in:
190
docs/plans/archive/phase2_user_mgmt_discovery.md
Normal file
190
docs/plans/archive/phase2_user_mgmt_discovery.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Phase 2.2 - User Management Discovery & Root Cause Analysis
|
||||
|
||||
**Status:** Discovery Complete - Root Cause Identified
|
||||
**Date Started:** 2026-02-09
|
||||
**Objective:** Identify root causes of 6 failing user management tests
|
||||
|
||||
## Root Cause: Synchronous Email Blocking in InviteUser
|
||||
|
||||
### CRITICAL FINDING
|
||||
|
||||
**Code Location:** `/projects/Charon/backend/internal/api/handlers/user_handler.go` (lines 400-470)
|
||||
**Problem Method:** `InviteUser` handler
|
||||
**Issue:** Email sending **blocks HTTP response** - entire request hangs until SMTP completes or times out
|
||||
|
||||
### Why Tests Timeout (Test #248)
|
||||
|
||||
Request flow in `InviteUser`:
|
||||
```
|
||||
1. Check admin role ✅ <1ms
|
||||
2. Parse request JSON ✅ <1ms
|
||||
3. Check email exists ✅ Database query
|
||||
4. Generate invite token ✅ <1ms
|
||||
5. Create user in database (transaction) ✅ Database write
|
||||
6. ❌ BLOCKS: Call h.MailService.SendInvite() - SYNCHRONOUS SMTP
|
||||
└─ Connect to SMTP server
|
||||
└─ Authenticate
|
||||
└─ Send email
|
||||
└─ Wait for confirmation (NO TIMEOUT!)
|
||||
7. Return JSON response (if email succeeds)
|
||||
```
|
||||
|
||||
**The Problem:**
|
||||
Lines 462-469:
|
||||
```go
|
||||
// Try to send invite email
|
||||
emailSent := false
|
||||
if h.MailService.IsConfigured() {
|
||||
baseURL, ok := utils.GetConfiguredPublicURL(h.DB)
|
||||
if ok {
|
||||
appName := getAppName(h.DB)
|
||||
if err := h.MailService.SendInvite(user.Email, inviteToken, appName, baseURL); err == nil {
|
||||
emailSent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code **blocks the HTTP request** until `SendInvite()` returns.
|
||||
|
||||
### MailService Architecture
|
||||
|
||||
**File:** `/projects/Charon/backend/internal/services/mail_service.go`
|
||||
**Method:** `SendEmail()` at line 255
|
||||
|
||||
The `SendEmail()` method:
|
||||
- Makes **direct SMTP connections** via `smtp.SendMail()` (line 315)
|
||||
- OR custom TLS dialect for SSL/STARTTLS
|
||||
- **Waits for SMTP response** before returning
|
||||
- **No async queue, no goroutines, no background workers**
|
||||
|
||||
**Example:** If SMTP server takes 5 seconds to respond (or 30s timeout):
|
||||
→ HTTP request blocks for 5-30+ seconds
|
||||
→ Playwright test times out after 60s
|
||||
|
||||
### Why Test #248 Fails
|
||||
|
||||
Test expectation: "Invite user, get response, user appears in list"
|
||||
Actual behavior: "Invite user → blocks on SMTP → no response → test timeout"
|
||||
|
||||
**Test File:** `/projects/Charon/tests/monitoring/uptime-monitoring.spec.ts` (for reference)
|
||||
**When SMTP is configured:** Request hangs indefinitely
|
||||
**When SMTP is NOT configured:** Request completes quickly (MailService.IsConfigured() = false)
|
||||
|
||||
## Other Test Failures (Tests #258, #260, #262, #269-270)
|
||||
|
||||
### Status: Likely Unrelated to Email Blocking
|
||||
|
||||
These tests involve:
|
||||
- **#258:** Update permission mode
|
||||
- **#260:** Remove permitted hosts
|
||||
- **#262:** Enable/disable user toggle
|
||||
- **#269:** Update user role to admin
|
||||
- **#270:** Update user role to user
|
||||
|
||||
**Reason:** These endpoints (PUT /users/:id/permissions, PUT /users/:id) do NOT send emails
|
||||
|
||||
**Hypothesis for other timeouts:**
|
||||
- Possible slow database queries (missing indexes?)
|
||||
- Possible missing database preloading (N+1 queries?)
|
||||
- Frontend mocking/test infrastructure issue (not handler code)
|
||||
- Transaction deadlocks (concurrent test execution)
|
||||
|
||||
**Status:** Requires separate investigation
|
||||
|
||||
## Solution Approach for Phase 2.1
|
||||
|
||||
### Recommendation: Async Email Sending
|
||||
|
||||
**Change:** Convert email sending to **background job** pattern:
|
||||
1. ✅ Create user in database
|
||||
2. ✅ Return response immediately (201 Created)
|
||||
3. → Send email asynchronously (goroutine/queue)
|
||||
4. → If email fails, log error, user still created
|
||||
|
||||
**Before:**
|
||||
```go
|
||||
// User creation + email (both must succeed to return)
|
||||
tx.Create(&user) // ✅
|
||||
SendEmail(...) // ❌ BLOCKS - no timeout
|
||||
return JSON(user) // Only if above completes
|
||||
```
|
||||
|
||||
**After:**
|
||||
```go
|
||||
// User creation (fast) + async email (non-blocking)
|
||||
tx.Create(&user) // ✅ <100ms
|
||||
go SendEmailAsync(...) // 🔄 Background (non-blocking)
|
||||
return JSON(user) // ✅ Immediate response (~150ms total)
|
||||
```
|
||||
|
||||
## Manual Testing Findings
|
||||
|
||||
**SMTP Configuration Status:** NOT configured in test database
|
||||
**Result:** Invite endpoint returns immediately (emailSent=false skip)
|
||||
**Test Environment:** Application accessible at http://localhost:8080
|
||||
|
||||
**Code Verification:**
|
||||
- ✅ `POST /users/invite` endpoint EXISTS and is properly registered
|
||||
- ✅ `PUT /users/:id/permissions` endpoint EXISTS and is properly registered
|
||||
- ✅ `GET /users` endpoint EXISTS (for list display)
|
||||
- ✅ User models properly initialized with permission_mode and permitted_hosts
|
||||
- ✅ Database schema includes all required fields
|
||||
|
||||
## Root Cause Summary
|
||||
|
||||
| Issue | Severity | Root Cause | Impact |
|
||||
|-------|----------|-----------|--------|
|
||||
| Test #248 Timeout | CRITICAL | Sync SMTP blocking HTTP response | InviteUser endpoint completely unavailable when SMTP is slow |
|
||||
| Test #258-270 Timeout | UNKNOWN | Requires further investigation | May be database, mocking, or concurrency issues |
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate (Phase 2.1 Fix)
|
||||
|
||||
1. **Refactor InviteUser to async email**
|
||||
- Create user (fast)
|
||||
- Return immediately with 201 Created
|
||||
- Send email in background goroutine
|
||||
- Endpoint: <100ms response time
|
||||
|
||||
2. **Add timeout to SMTP calls**
|
||||
- If email takes >5s, fail gracefully
|
||||
- Never block HTTP response >1s
|
||||
|
||||
3. **Add feature flag for optional email**
|
||||
- Allow invite without email sending
|
||||
- Endpoint can pre-generate token for manual sharing
|
||||
|
||||
### Follow-up (Phase 2.2)
|
||||
|
||||
1. **Investigate Tests #258-270 separately** (they may be unrelated)
|
||||
2. **Profile UpdateUserPermissions endpoint** (database efficiency?)
|
||||
3. **Review E2E test mocking** (ensure fixtures don't interfere)
|
||||
|
||||
## Evidence & References
|
||||
|
||||
**Code files reviewed:**
|
||||
- `/projects/Charon/backend/internal/api/handlers/user_handler.go` (InviteUser, UpdateUserPermissions)
|
||||
- `/projects/Charon/backend/internal/services/mail_service.go` (SendEmail, SendInvite)
|
||||
- `/projects/Charon/backend/internal/models/user.go` (User model)
|
||||
- `/projects/Charon/tests/monitoring/uptime-monitoring.spec.ts` (E2E test patterns)
|
||||
|
||||
**Endpoints verified working:**
|
||||
- POST /api/v1/users/invite - EXISTS, properly registered
|
||||
- PUT /api/v1/users/:id/permissions - EXISTS, properly registered
|
||||
- GET /api/v1/users - EXISTS (all users endpoint)
|
||||
|
||||
**Test Database State:**
|
||||
- SMTP not configured (safe mode)
|
||||
- Users table has admin + test users
|
||||
- Permitted hosts associations work
|
||||
- Invite tokens generate successfully on user creation
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Root cause identified: Synchronous email blocking
|
||||
2. → Implement async email sending in InviteUser handler
|
||||
3. → Test with E2E suite
|
||||
4. → Document performance improvements
|
||||
5. → Investigate remaining test failures if needed
|
||||
Reference in New Issue
Block a user