chore: clean .gitignore cache
This commit is contained in:
@@ -1,711 +0,0 @@
|
||||
# Phase 6: User Management UI Implementation
|
||||
|
||||
> **Status**: Planning Complete
|
||||
> **Created**: 2026-01-24
|
||||
> **Estimated Effort**: L (Large) - Initially estimated 40-60 hours, **revised to 16-22 hours**
|
||||
> **Priority**: P2 - Feature Completeness
|
||||
> **Tests Targeted**: 19 skipped tests in `tests/settings/user-management.spec.ts`
|
||||
> **Dependencies**: Phase 5 (TestDataManager Auth Fix) - Infrastructure complete, blocked by environment config
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### Goals
|
||||
|
||||
Complete the User Management frontend to enable 19 currently-skipped Playwright E2E tests. This phase implements missing UI components including status badges with proper color classes, role badges, resend invite action, email validation, enhanced modal accessibility, and fixes a React anti-pattern bug.
|
||||
|
||||
### Key Finding
|
||||
|
||||
**Most UI components already exist.** After thorough analysis, the work is primarily:
|
||||
1. Verifying existing functionality (toast test IDs already exist)
|
||||
2. Implementing resend invite action (backend endpoint missing - needs implementation)
|
||||
3. Adding email format validation with visible error
|
||||
4. Fixing React anti-pattern in PermissionsModal
|
||||
5. Verification and unskipping tests
|
||||
|
||||
**Revised Effort**: 16-22 hours (pending backend resend endpoint scope).
|
||||
|
||||
**Solution**: Add missing test selectors, implement resend invite, add email validation UI, fix React bugs, and systematically unskip tests as they pass.
|
||||
|
||||
### Test Count Reconciliation
|
||||
|
||||
The original plan stated 22 tests, but verification shows **19 skipped test declarations**. The discrepancy came from counting 4 conditional `test.skip()` calls inside test bodies (not actual test declarations). See Section 2 for the complete inventory.
|
||||
|
||||
---
|
||||
|
||||
## 1. Current State Analysis
|
||||
|
||||
### What EXISTS (in `UsersPage.tsx`)
|
||||
|
||||
The Users page at `frontend/src/pages/UsersPage.tsx` already contains substantial functionality:
|
||||
|
||||
| Component | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| User list table | ✅ Complete | Columns: User, Role, Status, Permissions, Enabled, Actions |
|
||||
| InviteModal | ✅ Complete | Email, role, permission mode, host selection, URL preview |
|
||||
| PermissionsModal | ✅ Complete | Edit user permissions, host toggle |
|
||||
| Role badges | ✅ Complete | Purple for admin, blue for user, rounded styling |
|
||||
| Status indicators | ✅ Complete | Active (green), Pending (yellow), Expired (red) with icons |
|
||||
| Enable/Disable toggle | ✅ Complete | Switch component per user |
|
||||
| Delete button | ✅ Complete | Trash2 icon with confirmation |
|
||||
| Settings/Permissions button | ✅ Complete | For non-admin users |
|
||||
| React Query mutations | ✅ Complete | All CRUD operations |
|
||||
| Copy invite link | ✅ Complete | With clipboard API |
|
||||
| URL preview for invites | ✅ Complete | Shows invite URL before sending |
|
||||
|
||||
### What is PARTIALLY IMPLEMENTED
|
||||
|
||||
| Item | Issue | Fix Required |
|
||||
|------|-------|--------------|
|
||||
| Status badges | Class names may not match test expectations | Add explicit color classes |
|
||||
| Modal keyboard nav | Escape key handling may be missing | Add keyboard event handler |
|
||||
| PermissionsModal state init | **React anti-pattern: useState used like useEffect** | Fix to use useEffect (see Section 3.6) |
|
||||
|
||||
### What is MISSING
|
||||
|
||||
| Item | Description | Effort |
|
||||
|------|-------------|--------|
|
||||
| Email validation UI | Client-side format validation with visible error | 2 hours |
|
||||
| Resend invite action | Button + API for pending users | 6-10 hours (backend missing) |
|
||||
| Backend resend endpoint | `POST /api/v1/users/{id}/resend-invite` | See Phase 6.4 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Test Analysis
|
||||
|
||||
### Summary: 19 Skipped Tests
|
||||
|
||||
**File**: `tests/settings/user-management.spec.ts`
|
||||
|
||||
| # | Test Name | Line | Category | Skip Reason | Status |
|
||||
|---|-----------|------|----------|-------------|--------|
|
||||
| 1 | should show user status badges | 70 | User List | Status badges styling | ✅ Verify |
|
||||
| 2 | should display role badges | 110 | User List | Role badges selectors | ✅ Verify |
|
||||
| 3 | should show pending invite status | 164 | User List | Complex timing | ⚠️ Complex |
|
||||
| 4 | should open invite user modal | 217 | Invite | Outdated skip comment | ✅ Verify |
|
||||
| 5 | should validate email format | 283 | Invite | No client validation | 🔧 Implement |
|
||||
| 6 | should copy invite link | 442 | Invite | Toast verification | ✅ Verify |
|
||||
| 7 | should open permissions modal | 494 | Permissions | Settings icon | 🔒 Auth blocked |
|
||||
| 8 | should update permission mode | 538 | Permissions | Base URL auth | 🔒 Auth blocked |
|
||||
| 9 | should add permitted hosts | 612 | Permissions | Settings icon | 🔒 Auth blocked |
|
||||
| 10 | should remove permitted hosts | 669 | Permissions | Settings icon | 🔒 Auth blocked |
|
||||
| 11 | should save permission changes | 725 | Permissions | Settings icon | 🔒 Auth blocked |
|
||||
| 12 | should enable/disable user | 781 | Actions | TestDataManager | 🔒 Auth blocked |
|
||||
| 13 | should change user role | 828 | Actions | Not implemented | ❌ Future |
|
||||
| 14 | should delete user with confirmation | 848 | Actions | Delete button | 🔒 Auth blocked |
|
||||
| 15 | should resend invite for pending user | 956 | Actions | Not implemented | 🔧 Implement |
|
||||
| 16 | should be keyboard navigable | 1014 | A11y | Known flaky | ⚠️ Flaky |
|
||||
| 17 | should require admin role for access | 1091 | Security | Routing design | ℹ️ By design |
|
||||
| 18 | should show error for regular user access | 1125 | Security | Routing design | ℹ️ By design |
|
||||
| 19 | should have proper ARIA labels | 1157 | A11y | ARIA incomplete | ✅ Verify |
|
||||
|
||||
### Legend
|
||||
|
||||
- ✅ Verify: Likely already works, just needs verification
|
||||
- 🔧 Fix/Implement: Requires small code change
|
||||
- 🔒 Auth blocked: Blocked by Phase 5 (TestDataManager)
|
||||
- ⚠️ Complex/Flaky: Timing or complexity issues
|
||||
- ℹ️ By design: Intentional skip (routing behavior)
|
||||
- ❌ Future: Feature not prioritized
|
||||
|
||||
### Tests Addressable in Phase 6
|
||||
|
||||
**Without Auth Fix** (can implement now): 6 tests
|
||||
- Test 1: Status badges styling (verify only)
|
||||
- Test 2: Role badges (verify only)
|
||||
- Test 4: Open invite modal (verify only - button IS implemented)
|
||||
- Test 5: Email validation
|
||||
- Test 6: Copy invite link (verify only - toast test IDs already exist)
|
||||
- Test 19: ARIA labels (verify only)
|
||||
|
||||
**With Resend Invite**: 1 test
|
||||
- Test 15: Resend invite
|
||||
|
||||
**After Phase 5 Auth Fix**: 6 tests
|
||||
- Tests 7-12, 14: Permission/Action tests
|
||||
|
||||
### Detailed Test Requirements
|
||||
|
||||
#### Test 1: should show user status badges (Line 70)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const statusCell = page.locator('td').filter({
|
||||
has: page.locator('span').filter({
|
||||
hasText: /active|pending.*invite|invite.*expired/i,
|
||||
}),
|
||||
});
|
||||
|
||||
const activeStatus = page.locator('span').filter({ hasText: /^active$/i });
|
||||
|
||||
// Expects class to include 'green', 'text-green-400', or 'success'
|
||||
const hasGreenColor = await activeStatus.first().evaluate((el) => {
|
||||
return el.className.includes('green') ||
|
||||
el.className.includes('text-green-400') ||
|
||||
el.className.includes('success');
|
||||
});
|
||||
```
|
||||
|
||||
**Current code** (UsersPage.tsx line ~459):
|
||||
```tsx
|
||||
<span className="inline-flex items-center gap-1 text-green-400 text-xs">
|
||||
<Check className="h-3 w-3" />
|
||||
{t('common.active')}
|
||||
</span>
|
||||
```
|
||||
|
||||
**Analysis**: Current code already includes `text-green-400` class.
|
||||
|
||||
**Action**: ✅ **Verify only** - unskip and run test.
|
||||
|
||||
#### Test 2: should display role badges (Line 110)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const adminBadge = page.locator('span').filter({ hasText: /^admin$/i });
|
||||
|
||||
// Expects 'purple', 'blue', or 'rounded' in class
|
||||
const hasDistinctColor = await adminBadge.evaluate((el) => {
|
||||
return el.className.includes('purple') ||
|
||||
el.className.includes('blue') ||
|
||||
el.className.includes('rounded');
|
||||
});
|
||||
```
|
||||
|
||||
**Current code** (UsersPage.tsx line ~445):
|
||||
```tsx
|
||||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
||||
user.role === 'admin' ? 'bg-purple-900/30 text-purple-400' : 'bg-blue-900/30 text-blue-400'
|
||||
}`}>
|
||||
{user.role}
|
||||
</span>
|
||||
```
|
||||
|
||||
**Analysis**: ✅ Classes include `rounded` and `purple`/`blue`.
|
||||
|
||||
**Action**: ✅ **Verify only** - unskip and run test.
|
||||
|
||||
#### Test 4: should open invite user modal (Line 217)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const inviteButton = page.getByRole('button', { name: /invite.*user/i });
|
||||
await expect(inviteButton).toBeVisible();
|
||||
await inviteButton.click();
|
||||
// Verify modal is visible
|
||||
const modal = page.getByRole('dialog');
|
||||
await expect(modal).toBeVisible();
|
||||
```
|
||||
|
||||
**Current state**: ✅ Invite button IS implemented in UsersPage.tsx. The skip comment is outdated.
|
||||
|
||||
**Action**: ✅ **Verify only** - unskip and run test.
|
||||
|
||||
#### Test 5: should validate email format (Line 283)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const sendButton = page.getByRole('button', { name: /send.*invite/i });
|
||||
const isDisabled = await sendButton.isDisabled();
|
||||
// OR error message shown
|
||||
const errorMessage = page.getByText(/invalid.*email|email.*invalid|valid.*email/i);
|
||||
```
|
||||
|
||||
**Current code**: Button disabled when `!email`, but no format validation visible.
|
||||
|
||||
**Action**: 🔧 **Implement** - Add email regex validation with error display.
|
||||
|
||||
#### Test 6: should copy invite link (Line 442)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const copiedToast = page.locator('[data-testid="toast-success"]').filter({
|
||||
hasText: /copied|clipboard/i,
|
||||
});
|
||||
```
|
||||
|
||||
**Current state**: ✅ Toast component already has `data-testid={toast-${toast.type}}` at `Toast.tsx:31`.
|
||||
|
||||
**Action**: ✅ **Verify only** - unskip and run test. No code changes needed.
|
||||
|
||||
#### Test 15: should resend invite for pending user (Line 956)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const resendButton = page.getByRole('button', { name: /resend/i });
|
||||
await resendButton.first().click();
|
||||
await waitForToast(page, /sent|resend/i, { type: 'success' });
|
||||
```
|
||||
|
||||
**Current state**: ❌ Resend action not implemented.
|
||||
|
||||
**Action**: 🔧 **Implement** - Add resend button for pending users + API call.
|
||||
|
||||
#### Test 19: should have proper ARIA labels (Line 1157)
|
||||
|
||||
**Test code**:
|
||||
```typescript
|
||||
const inviteButton = page.getByRole('button', { name: /invite.*user/i });
|
||||
// Checks for accessible name on action buttons
|
||||
const ariaLabel = await button.getAttribute('aria-label');
|
||||
const title = await button.getAttribute('title');
|
||||
const text = await button.textContent();
|
||||
```
|
||||
|
||||
**Current state**:
|
||||
- Invite button: text content "Invite User" ✅
|
||||
- Delete button: `aria-label={t('users.deleteUser')}` ✅
|
||||
- Settings button: `aria-label={t('users.editPermissions')}` ✅
|
||||
|
||||
**Action**: ✅ **Verify only** - unskip and run test.
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Phases
|
||||
|
||||
### Phase 6.1: Verify Existing Functionality (3 hours)
|
||||
|
||||
**Goal**: Confirm tests 1, 2, 4, 6, 19 pass without code changes.
|
||||
|
||||
**Tests in Batch**:
|
||||
- Test 1: should show user status badges
|
||||
- Test 2: should display role badges
|
||||
- Test 4: should open invite user modal
|
||||
- Test 6: should copy invite link (toast test IDs already exist)
|
||||
- Test 19: should have proper ARIA labels
|
||||
|
||||
**Tasks**:
|
||||
1. Temporarily remove `test.skip` from tests 1, 2, 4, 6, 19
|
||||
2. Run tests individually
|
||||
3. Document results
|
||||
4. Permanently unskip passing tests
|
||||
|
||||
**Commands**:
|
||||
```bash
|
||||
# Test status badges
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should show user status badges" --project=chromium
|
||||
|
||||
# Test role badges
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should display role badges" --project=chromium
|
||||
|
||||
# Test invite modal opens
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should open invite user modal" --project=chromium
|
||||
|
||||
# Test copy invite link (toast test IDs already exist)
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should copy invite link" --project=chromium
|
||||
|
||||
# Test ARIA labels
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should have proper ARIA labels" --project=chromium
|
||||
```
|
||||
|
||||
**Expected outcome**: 4-5 tests pass immediately.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6.2: Email Validation UI (2 hours)
|
||||
|
||||
**Goal**: Add client-side email format validation with visible error.
|
||||
|
||||
**File to modify**: `frontend/src/pages/UsersPage.tsx` (InviteModal)
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```tsx
|
||||
// Add state in InviteModal component
|
||||
const [emailError, setEmailError] = useState<string | null>(null)
|
||||
|
||||
// Email validation function
|
||||
const validateEmail = (email: string): boolean => {
|
||||
if (!email) {
|
||||
setEmailError(null)
|
||||
return false
|
||||
}
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
if (!emailRegex.test(email)) {
|
||||
setEmailError(t('users.invalidEmail'))
|
||||
return false
|
||||
}
|
||||
setEmailError(null)
|
||||
return true
|
||||
}
|
||||
|
||||
// Update email input (replace existing Input)
|
||||
<div>
|
||||
<Input
|
||||
label={t('users.emailAddress')}
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value)
|
||||
validateEmail(e.target.value)
|
||||
}}
|
||||
placeholder="user@example.com"
|
||||
aria-invalid={!!emailError}
|
||||
aria-describedby={emailError ? 'email-error' : undefined}
|
||||
/>
|
||||
{emailError && (
|
||||
<p
|
||||
id="email-error"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{emailError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
// Update button disabled logic
|
||||
disabled={!email || !!emailError}
|
||||
```
|
||||
|
||||
**Translation key to add** (to appropriate i18n file):
|
||||
```json
|
||||
{
|
||||
"users.invalidEmail": "Please enter a valid email address"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
```bash
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should validate email format" --project=chromium
|
||||
```
|
||||
|
||||
**Expected outcome**: Test 5 passes.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6.3: Resend Invite Action (6-10 hours)
|
||||
|
||||
**Goal**: Add resend invite button for pending users.
|
||||
|
||||
#### Backend Verification
|
||||
|
||||
**REQUIRED**: Check if backend endpoint exists before proceeding:
|
||||
```bash
|
||||
grep -r "resend" backend/internal/api/handlers/
|
||||
grep -r "ResendInvite" backend/internal/api/
|
||||
```
|
||||
|
||||
**Result of verification**: Backend endpoint **does not exist**. Both grep commands return no results.
|
||||
|
||||
**Contingency**: If backend is missing (confirmed), effort increases to **8-10 hours** to implement:
|
||||
- Endpoint: `POST /api/v1/users/{id}/resend-invite`
|
||||
- Handler: Regenerate token, send email, return new token info
|
||||
- Tests: Unit tests for the new handler
|
||||
|
||||
#### Frontend Implementation
|
||||
|
||||
**File**: `frontend/src/api/users.ts`
|
||||
|
||||
Add API function:
|
||||
```typescript
|
||||
/**
|
||||
* Resends an invitation email to a pending user.
|
||||
* @param id - The user ID to resend invite to
|
||||
* @returns Promise resolving to InviteUserResponse with new token
|
||||
*/
|
||||
export const resendInvite = async (id: number): Promise<InviteUserResponse> => {
|
||||
const response = await client.post<InviteUserResponse>(`/users/${id}/resend-invite`)
|
||||
return response.data
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `frontend/src/pages/UsersPage.tsx`
|
||||
|
||||
Add mutation:
|
||||
```tsx
|
||||
const resendInviteMutation = useMutation({
|
||||
mutationFn: resendInvite,
|
||||
onSuccess: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] })
|
||||
if (data.email_sent) {
|
||||
toast.success(t('users.inviteResent'))
|
||||
} else {
|
||||
toast.success(t('users.inviteCreatedNoEmail'))
|
||||
}
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
const err = error as { response?: { data?: { error?: string } } }
|
||||
toast.error(err.response?.data?.error || t('users.resendFailed'))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Add button in user row actions:
|
||||
```tsx
|
||||
{user.invite_status === 'pending' && (
|
||||
<button
|
||||
onClick={() => resendInviteMutation.mutate(user.id)}
|
||||
className="p-1.5 text-gray-400 hover:text-blue-400 hover:bg-gray-800 rounded"
|
||||
title={t('users.resendInvite')}
|
||||
aria-label={t('users.resendInvite')}
|
||||
disabled={resendInviteMutation.isPending}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
```
|
||||
|
||||
**Translation keys**:
|
||||
```json
|
||||
{
|
||||
"users.resendInvite": "Resend Invite",
|
||||
"users.inviteResent": "Invitation resent successfully",
|
||||
"users.inviteCreatedNoEmail": "New invite created. Email could not be sent.",
|
||||
"users.resendFailed": "Failed to resend invitation"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
```bash
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "should resend invite" --project=chromium
|
||||
```
|
||||
|
||||
**Expected outcome**: Test 15 passes.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6.4: Modal Keyboard Navigation (2 hours)
|
||||
|
||||
**Goal**: Ensure Escape key closes modals.
|
||||
|
||||
**File to modify**: `frontend/src/pages/UsersPage.tsx`
|
||||
|
||||
**Implementation** (add to InviteModal and PermissionsModal):
|
||||
|
||||
```tsx
|
||||
// Add useEffect for keyboard handler
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [isOpen, handleClose])
|
||||
```
|
||||
|
||||
**Note**: Test 16 (keyboard navigation) is marked as **known flaky** and may remain skipped.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6.5: PermissionsModal useState Bug Fix (1 hour)
|
||||
|
||||
**Goal**: Fix React anti-pattern in PermissionsModal.
|
||||
|
||||
**File to modify**: `frontend/src/pages/UsersPage.tsx` (line 339)
|
||||
|
||||
**Bug**: `useState` is being used like a `useEffect`, which is a React anti-pattern:
|
||||
|
||||
```tsx
|
||||
// WRONG - useState used like an effect (current code at line 339)
|
||||
useState(() => {
|
||||
if (user) {
|
||||
setPermissionMode(user.permission_mode || 'allow_all')
|
||||
setSelectedHosts(user.permitted_hosts || [])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Fix**: Replace with proper `useEffect` with dependency:
|
||||
|
||||
```tsx
|
||||
// CORRECT - useEffect with dependency
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setPermissionMode(user.permission_mode || 'allow_all')
|
||||
setSelectedHosts(user.permitted_hosts || [])
|
||||
}
|
||||
}, [user])
|
||||
```
|
||||
|
||||
**Why this matters**: The useState initializer only runs once on mount. The current code appears to work incidentally but:
|
||||
1. Will not update state when `user` prop changes
|
||||
2. May cause stale data bugs
|
||||
3. Violates React's data flow principles
|
||||
|
||||
**Validation**:
|
||||
```bash
|
||||
# Run TypeScript check
|
||||
cd frontend && npm run typecheck
|
||||
|
||||
# Run related permission tests (after Phase 5 auth fix)
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "permissions" --project=chromium
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation Order
|
||||
|
||||
```
|
||||
Week 1 (10-14 hours)
|
||||
├── Phase 6.1: Verify Existing (3h) → Tests 1, 2, 4, 6, 19
|
||||
├── Phase 6.2: Email Validation (2h) → Test 5
|
||||
├── Phase 6.3: Resend Invite (6-10h) → Test 15
|
||||
│ └── Includes backend endpoint implementation
|
||||
└── Phase 6.5: PermissionsModal Bug Fix (1h) → Stability
|
||||
|
||||
Week 2 (2-3 hours)
|
||||
└── Phase 6.4: Modal Keyboard Nav (2h) → Partial for Test 16
|
||||
|
||||
Validation & Cleanup (3 hours)
|
||||
└── Run full suite, update skip comments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Files to Modify
|
||||
|
||||
### Priority 1: Required for Test Enablement
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `frontend/src/pages/UsersPage.tsx` | Email validation, resend invite button, keyboard nav, PermissionsModal useState→useEffect fix |
|
||||
| `frontend/src/api/users.ts` | Add `resendInvite` function |
|
||||
| `tests/settings/user-management.spec.ts` | Unskip verified tests |
|
||||
|
||||
### Priority 2: Backend (REQUIRED - endpoint missing)
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `backend/internal/api/handlers/user_handler.go` | Add resend-invite endpoint |
|
||||
| `backend/internal/api/routes.go` | Register new route |
|
||||
| `backend/internal/api/handlers/user_handler_test.go` | Add tests for resend endpoint |
|
||||
|
||||
### Priority 3: Translations
|
||||
|
||||
| File | Keys to Add |
|
||||
|------|-------------|
|
||||
| `frontend/src/i18n/locales/en.json` | `invalidEmail`, `resendInvite`, `inviteResent`, etc. |
|
||||
|
||||
### NOT Required (Already Implemented)
|
||||
|
||||
| File | Status |
|
||||
|------|--------|
|
||||
| `frontend/src/components/Toast.tsx` | ✅ Already has `data-testid={toast-${toast.type}}` |
|
||||
|
||||
---
|
||||
|
||||
## 6. Validation Strategy
|
||||
|
||||
### After Each Phase
|
||||
|
||||
```bash
|
||||
# Run specific tests
|
||||
npx playwright test tests/settings/user-management.spec.ts \
|
||||
--grep "<test name>" --project=chromium
|
||||
```
|
||||
|
||||
### Final Validation
|
||||
|
||||
```bash
|
||||
# Run all user management tests
|
||||
npx playwright test tests/settings/user-management.spec.ts --project=chromium
|
||||
|
||||
# Expected:
|
||||
# - ~12-14 tests passing (up from ~5)
|
||||
# - ~6-8 tests still skipped (auth blocked or by design)
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
```bash
|
||||
cd frontend && npm run test:coverage
|
||||
# Verify UsersPage.tsx >= 85%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Expected Outcomes
|
||||
|
||||
### Tests to Unskip After Phase 6
|
||||
|
||||
| Test | Expected Outcome |
|
||||
|------|------------------|
|
||||
| should show user status badges | ✅ Pass |
|
||||
| should display role badges | ✅ Pass |
|
||||
| should open invite user modal | ✅ Pass |
|
||||
| should validate email format | ✅ Pass |
|
||||
| should copy invite link | ✅ Pass |
|
||||
| should resend invite for pending user | ✅ Pass |
|
||||
| should have proper ARIA labels | ✅ Pass |
|
||||
|
||||
**Total**: 7 tests enabled
|
||||
|
||||
### Tests Remaining Skipped
|
||||
|
||||
| Test | Reason |
|
||||
|------|--------|
|
||||
| Tests 7-12, 14 (7 tests) | 🔒 TestDataManager auth (Phase 5) |
|
||||
| Test 3: pending invite status | ⚠️ Complex timing |
|
||||
| Test 13: change user role | ❌ Feature not implemented |
|
||||
| Test 16: keyboard navigation | ⚠️ Known flaky |
|
||||
| Tests 17-18: admin access | ℹ️ Routing design (intentional) |
|
||||
|
||||
**Total remaining skipped**: ~12 tests (down from 22)
|
||||
|
||||
---
|
||||
|
||||
## 8. Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|------|------------|--------|------------|
|
||||
| Backend resend endpoint missing | **CONFIRMED** | High | Backend implementation included in Phase 6.3 (6-10h) |
|
||||
| Tests pass locally, fail in CI | Medium | Medium | Run in both environments |
|
||||
| Translation keys missing | Low | Low | Add to all locale files |
|
||||
| PermissionsModal bug causes regressions | Low | Medium | Fix early in Phase 6.5 with testing |
|
||||
|
||||
---
|
||||
|
||||
## 9. Success Metrics
|
||||
|
||||
| Metric | Before Phase 6 | After Phase 6 | Target |
|
||||
|--------|----------------|---------------|--------|
|
||||
| User management tests passing | ~5 | ~12-14 | 15+ |
|
||||
| User management tests skipped | 19 | 10-12 | <10 |
|
||||
| Frontend coverage (UsersPage) | TBD | ≥85% | 85% |
|
||||
|
||||
---
|
||||
|
||||
## 10. Timeline
|
||||
|
||||
| Phase | Effort | Cumulative |
|
||||
|-------|--------|------------|
|
||||
| 6.1: Verify Existing (5 tests) | 3h | 3h |
|
||||
| 6.2: Email Validation | 2h | 5h |
|
||||
| 6.3: Resend Invite (backend included) | 6-10h | 11-15h |
|
||||
| 6.4: Modal Keyboard Nav | 2h | 13-17h |
|
||||
| 6.5: PermissionsModal Bug Fix | 1h | 14-18h |
|
||||
| Validation & Cleanup | 3h | 17-21h |
|
||||
| Buffer | 3h | **16-22h** |
|
||||
|
||||
**Total**: 16-22 hours (range depends on backend complexity)
|
||||
|
||||
---
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Author | Change |
|
||||
|------|--------|--------|
|
||||
| 2026-01-24 | Planning Agent | Initial plan created with detailed test analysis |
|
||||
| 2026-01-24 | Planning Agent | **REVISION**: Applied Supervisor corrections: |
|
||||
| | | - Toast test IDs already exist (Phase 6.2 removed) |
|
||||
| | | - Updated test line numbers to actual values (70, 110, 217, 283, 442, 956, 1014, 1157) |
|
||||
| | | - Added Test 4 and Test 6 to Phase 6.1 verification batch (5 tests total) |
|
||||
| | | - Added Phase 6.5: PermissionsModal useState bug fix |
|
||||
| | | - Backend resend endpoint confirmed missing (grep verification) |
|
||||
| | | - Corrected test count: 19 skipped tests (not 22) |
|
||||
| | | - Updated effort estimates: 16-22h (was 17h) |
|
||||
Reference in New Issue
Block a user