346 lines
8.6 KiB
Markdown
346 lines
8.6 KiB
Markdown
# Logout Tracking System - Testing Checklist
|
||
|
||
**Purpose:** Verify the logout tracking system works correctly for all scenarios
|
||
|
||
---
|
||
|
||
## Pre-Testing Setup
|
||
|
||
- [ ] Backend container restarted
|
||
- [ ] Frontend container restarted
|
||
- [ ] Browser DevTools console open
|
||
- [ ] Backend logs accessible (`docker logs -f igny8_backend`)
|
||
|
||
---
|
||
|
||
## Test 1: Manual Logout
|
||
|
||
**Steps:**
|
||
1. Login to the app
|
||
2. Navigate to any page (e.g., `/dashboard`)
|
||
3. Click the logout button
|
||
|
||
**Expected Results:**
|
||
- [ ] Console shows: `🚪 LOGOUT - User Action:`
|
||
- [ ] Redirected to `/signin`
|
||
- [ ] Yellow alert box appears on signin page
|
||
- [ ] Alert shows: "User manually logged out"
|
||
- [ ] Original page shown in alert: `/dashboard`
|
||
- [ ] Click ℹ️ icon shows technical details
|
||
- [ ] Technical details show:
|
||
- Code: `MANUAL_LOGOUT`
|
||
- Source: `manual_user_action`
|
||
- Path: `/dashboard` (or whatever page you were on)
|
||
- User email in context
|
||
- Timestamp
|
||
- [ ] After refresh, alert is gone (single display)
|
||
|
||
---
|
||
|
||
## Test 2: Token Expiration (401)
|
||
|
||
**Steps:**
|
||
1. Login to the app
|
||
2. Get the access token from localStorage
|
||
3. In browser console, manually expire it:
|
||
```javascript
|
||
localStorage.setItem('access_token', 'invalid_token')
|
||
```
|
||
4. Navigate to a protected page or make an API call
|
||
|
||
**Expected Results:**
|
||
- [ ] Console shows: `🚨 LOGOUT TRIGGERED - Token Refresh Failed` or `🚨 LOGOUT TRIGGERED - No Refresh Token`
|
||
- [ ] Redirected to `/signin`
|
||
- [ ] Yellow alert box appears
|
||
- [ ] Alert shows token-related message
|
||
- [ ] Original page shown in alert
|
||
- [ ] Technical details show correct error code
|
||
|
||
---
|
||
|
||
## Test 3: Session Contamination (Account Mismatch)
|
||
|
||
**Steps:**
|
||
1. Login with a user account
|
||
2. In Django admin or shell, create session contamination:
|
||
```python
|
||
# Get a session and modify _account_id to wrong value
|
||
from django.contrib.sessions.models import Session
|
||
session = Session.objects.first()
|
||
data = session.get_decoded()
|
||
data['_account_id'] = 99999 # Non-existent account
|
||
session.session_data = session.encode(data)
|
||
session.save()
|
||
```
|
||
3. Refresh the app or make an API call
|
||
|
||
**Expected Results:**
|
||
- [ ] Backend logs show: `[AUTO-LOGOUT] SESSION_ACCOUNT_MISMATCH`
|
||
- [ ] Console shows: `🚨 LOGOUT TRIGGERED - Backend Validation Failed`
|
||
- [ ] Redirected to `/signin`
|
||
- [ ] Yellow alert shows: "Session contamination: account ID mismatch"
|
||
- [ ] Technical details show:
|
||
- Code: `SESSION_ACCOUNT_MISMATCH`
|
||
- Source: `backend_middleware`
|
||
- Context: stored vs current account IDs
|
||
- [ ] Backend logs include IP, path, user ID, timestamp
|
||
|
||
---
|
||
|
||
## Test 4: Missing Account
|
||
|
||
**Steps:**
|
||
1. In Django admin, remove account from a user:
|
||
```python
|
||
user = User.objects.get(email='test@example.com')
|
||
user.account = None
|
||
user.save()
|
||
```
|
||
2. Try to login with that user
|
||
|
||
**Expected Results:**
|
||
- [ ] Backend logs show: `[AUTO-LOGOUT] ACCOUNT_MISSING`
|
||
- [ ] Console shows: `🚨 LOGOUT TRIGGERED`
|
||
- [ ] Alert shows: "Account not configured for this user"
|
||
- [ ] Technical details show:
|
||
- Code: `ACCOUNT_MISSING`
|
||
- Source: `backend_middleware` or `refresh_user_validation`
|
||
|
||
---
|
||
|
||
## Test 5: Inactive Plan
|
||
|
||
**Steps:**
|
||
1. In Django admin, deactivate user's plan:
|
||
```python
|
||
plan = Plan.objects.get(slug='starter')
|
||
plan.is_active = False
|
||
plan.save()
|
||
```
|
||
2. User makes an API call or refreshes page
|
||
|
||
**Expected Results:**
|
||
- [ ] Backend logs show: `[AUTO-LOGOUT] PLAN_INACTIVE`
|
||
- [ ] Console shows: `🚨 LOGOUT TRIGGERED`
|
||
- [ ] Alert shows: "Subscription plan is inactive"
|
||
- [ ] Technical details show:
|
||
- Code: `PLAN_INACTIVE`
|
||
- HTTP status 402 in context
|
||
|
||
---
|
||
|
||
## Test 6: Suspended Account
|
||
|
||
**Steps:**
|
||
1. In Django admin, suspend an account:
|
||
```python
|
||
account = Account.objects.get(id=1)
|
||
account.status = 'suspended'
|
||
account.save()
|
||
```
|
||
2. User tries to access protected page
|
||
|
||
**Expected Results:**
|
||
- [ ] Backend logs show: `[AUTO-LOGOUT] ACCOUNT_SUSPENDED`
|
||
- [ ] Alert shows: "Account is suspended"
|
||
- [ ] Technical details show:
|
||
- Code: `ACCOUNT_SUSPENDED`
|
||
- HTTP status 403
|
||
|
||
---
|
||
|
||
## Test 7: 403 Auth Credentials Error
|
||
|
||
**Steps:**
|
||
1. Login to the app
|
||
2. In browser console, corrupt the token:
|
||
```javascript
|
||
localStorage.setItem('access_token', 'Bearer corrupted')
|
||
```
|
||
3. Make an API call
|
||
|
||
**Expected Results:**
|
||
- [ ] Console shows: `🚨 LOGOUT TRIGGERED - Authentication Credentials Missing`
|
||
- [ ] Alert shows authentication credentials error
|
||
- [ ] Technical details show:
|
||
- Code: `AUTH_CREDENTIALS_MISSING`
|
||
- Source: `api_403_auth_error`
|
||
|
||
---
|
||
|
||
## Test 8: Page Path Accuracy
|
||
|
||
**Steps:**
|
||
1. Login and navigate through multiple pages
|
||
2. Trigger logout from different pages:
|
||
- From `/dashboard` → Check logout shows `/dashboard`
|
||
- From `/planner/keywords` → Check logout shows `/planner/keywords`
|
||
- From `/writer/articles` → Check logout shows `/writer/articles`
|
||
|
||
**Expected Results:**
|
||
- [ ] Every logout shows **exact page** where logout occurred
|
||
- [ ] **Never** shows `/signin` as logout path
|
||
- [ ] Path captured BEFORE redirect
|
||
|
||
---
|
||
|
||
## Test 9: Console Logging Format
|
||
|
||
**Expected in Browser Console:**
|
||
- [ ] 🚨 emoji for automatic logouts
|
||
- [ ] 🚪 emoji for manual logout
|
||
- [ ] Grouped logs with expandable details
|
||
- [ ] Full JSON context logged
|
||
- [ ] Timestamps present
|
||
- [ ] Error codes visible
|
||
|
||
**Expected in Backend Logs:**
|
||
```bash
|
||
[AUTO-LOGOUT] ERROR_CODE: Message. User=123, Account=456, Path=/page, IP=..., Timestamp=...
|
||
```
|
||
|
||
- [ ] `[AUTO-LOGOUT]` prefix present
|
||
- [ ] Error code included
|
||
- [ ] All context fields present
|
||
- [ ] Grep-friendly format
|
||
|
||
---
|
||
|
||
## Test 10: Alert Box UI
|
||
|
||
**Visual Checks:**
|
||
- [ ] Yellow background (not red)
|
||
- [ ] Clear heading: "Session Ended"
|
||
- [ ] User-friendly message displayed
|
||
- [ ] Original page path shown (if not /signin)
|
||
- [ ] Info icon (ℹ️) present
|
||
- [ ] Click info icon expands technical details
|
||
- [ ] Technical details show:
|
||
- Error code
|
||
- Source
|
||
- Timestamp (formatted)
|
||
- Context JSON (formatted with indentation)
|
||
- [ ] Click info icon again collapses details
|
||
- [ ] Alert disappears after page refresh
|
||
|
||
---
|
||
|
||
## Test 11: Multiple Logouts
|
||
|
||
**Steps:**
|
||
1. Trigger logout (any method)
|
||
2. See alert on signin page
|
||
3. Login again
|
||
4. Trigger different logout (different error)
|
||
5. Return to signin page
|
||
|
||
**Expected Results:**
|
||
- [ ] First logout reason cleared after first display
|
||
- [ ] Second logout shows new reason (not old one)
|
||
- [ ] No accumulation of old logout reasons
|
||
- [ ] Each logout tracked separately
|
||
|
||
---
|
||
|
||
## Test 12: Edge Cases
|
||
|
||
### No Logout Reason
|
||
**Steps:** Navigate directly to `/signin` without logout
|
||
|
||
**Expected:**
|
||
- [ ] No alert box shown
|
||
- [ ] No console errors
|
||
- [ ] No localStorage pollution
|
||
|
||
### localStorage Disabled
|
||
**Steps:** Disable localStorage in browser, trigger logout
|
||
|
||
**Expected:**
|
||
- [ ] Console warning about failed storage
|
||
- [ ] App still functions
|
||
- [ ] No crash
|
||
|
||
### Malformed Logout Reason
|
||
**Steps:** Manually corrupt logout_reason in localStorage
|
||
```javascript
|
||
localStorage.setItem('logout_reason', 'invalid json')
|
||
```
|
||
|
||
**Expected:**
|
||
- [ ] Console warning about parsing failure
|
||
- [ ] No alert shown
|
||
- [ ] No crash
|
||
|
||
---
|
||
|
||
## Test 13: Cross-Browser Testing
|
||
|
||
Test in multiple browsers:
|
||
- [ ] Chrome
|
||
- [ ] Firefox
|
||
- [ ] Safari
|
||
- [ ] Edge
|
||
|
||
**All features should work identically**
|
||
|
||
---
|
||
|
||
## Test 14: Mobile Responsiveness
|
||
|
||
Test on mobile devices/emulator:
|
||
- [ ] Alert box displays correctly
|
||
- [ ] Technical details expand/collapse works
|
||
- [ ] Text is readable
|
||
- [ ] No horizontal scroll
|
||
|
||
---
|
||
|
||
## Success Criteria
|
||
|
||
✅ **All tests pass**
|
||
✅ **Every logout has visible reason**
|
||
✅ **Exact page tracking works**
|
||
✅ **Console logs are clear**
|
||
✅ **Backend logs are searchable**
|
||
✅ **User sees helpful message**
|
||
✅ **Developers can debug easily**
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Alert not showing
|
||
- Check browser console for logout reason object
|
||
- Check if localStorage.getItem('logout_reason') returns data
|
||
- Verify SignInForm component mounted
|
||
|
||
### Wrong page shown
|
||
- Verify `window.location.pathname` captured BEFORE redirect
|
||
- Check logout trigger happens before navigation
|
||
- Ensure logout_reason stored BEFORE logout() call
|
||
|
||
### Backend logs missing
|
||
- Check logger level is WARNING or INFO
|
||
- Verify logger name: 'auth.middleware'
|
||
- Check LOGGING config in settings.py
|
||
|
||
### Console logs missing
|
||
- Check browser console filters (show all levels)
|
||
- Verify emoji not filtered out
|
||
- Check if code throwing before log statement
|
||
|
||
---
|
||
|
||
## Maintenance
|
||
|
||
**When adding new logout causes:**
|
||
|
||
1. Add error code to `LOGOUT_REASONS` dict
|
||
2. Update logout trigger to create structured reason
|
||
3. Store in localStorage before logout
|
||
4. Log to console with 🚨
|
||
5. Update this testing checklist
|
||
6. Update LOGOUT-CAUSES-COMPLETE-REFERENCE.md
|
||
|
||
**Keep documentation in sync with implementation!**
|