Files
ocdp-go/docs/test-scenarios.md
Ivan087 7f238a3168 refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics
- Add Workspace domain (entity, repository, service, handler, DTO)
- Add multi-tenant K8s client with tenant binding and quota management
- Add K8s diagnostics client (instance diagnostics)
- Add authorization middleware (authz package)
- Restructure frontend to feature-based architecture (features/)
- Add User Management page in configuration
- Add AccessDenied page and route guards
- Refactor shared components (form inputs, layout, UI)
- Update Tailwind config for new design system
- Add comprehensive documentation (docs/, tasks/, plans)
- Improve cluster service with better kubeconfig handling
- Add tests for crypto, config, helm client, tenant binding
2026-05-12 16:15:14 +08:00

1641 lines
65 KiB
Markdown

# OCDP Test Scenarios
> **Platform**: OCDP (Open Cloud Deployment Platform)
> **Deployed at**: http://10.6.80.114:18080
> **Scope**: Full-stack test scenarios covering authentication, configuration, artifact browser, instance lifecycle, monitoring, user management, multi-tenancy, UI/UX, data persistence, security, and edge cases.
---
## Category 1: Authentication & Authorization (25+ cases)
### AUTH-001 — Login with valid credentials
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Admin account exists in the system |
| **Steps** | 1. Navigate to `/` <br> 2. Enter valid username and password <br> 3. Click "Login" |
| **Expected Result** | User is authenticated, redirected to `/home`, token stored in localStorage/session, toast "Welcome, [username]!" displayed |
### AUTH-002 — Login with incorrect password
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Valid username exists |
| **Steps** | 1. Enter valid username with wrong password <br> 2. Click "Login" |
| **Expected Result** | Login fails with 401 error, red error message displayed, user stays on login page |
### AUTH-003 — Login with non-existent username
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Enter username that does not exist <br> 2. Enter any password <br> 3. Click "Login" |
| **Expected Result** | 401 returned, error message shown, no user enumerated |
### AUTH-004 — Login with empty credentials
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Leave username and password empty <br> 2. Click "Login" |
| **Expected Result** | HTML5 form validation prevents submission, or backend returns validation error |
### AUTH-005 — Login with special characters in username
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Enter username with SQL injection patterns: `admin' OR '1'='1` <br> 2. Enter password <br> 3. Click "Login" |
| **Expected Result** | Login fails, no SQL injection succeeds, no data leak |
### AUTH-006 — Successful login response contains expected fields
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Valid credentials |
| **Steps** | 1. Call `POST /api/v1/auth/login` <br> 2. Inspect response body |
| **Expected Result** | Response contains `accessToken`, `refreshToken`, `username`, `role`, `permissions`, `userId`, `workspaceId` |
### AUTH-007 — JWT token sent in Authorization header
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Valid token obtained |
| **Steps** | 1. Capture XHR request to any protected API <br> 2. Inspect Authorization header |
| **Expected Result** | Header contains `Bearer <token>` |
### AUTH-008 — Access protected route without token
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Clear all auth tokens |
| **Steps** | 1. Navigate directly to `/home` <br> 2. Navigate to `/artifact/instances` <br> 3. API call to `/api/v1/clusters` without token |
| **Expected Result** | Frontend redirects to `/`, backend returns 401 |
### AUTH-009 — Access protected API without token
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | None |
| **Steps** | 1. Call `GET /api/v1/clusters` without Authorization header |
| **Expected Result** | 401 Unauthorized returned |
### AUTH-010 — Token expiry handling
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Use a token near expiry or manipulate expiry |
| **Steps** | 1. Make API call with expired token |
| **Expected Result** | Backend returns 401, frontend should redirect to login page or attempt token refresh |
### AUTH-011 — Token refresh flow
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Valid refresh token exists |
| **Steps** | 1. Call `POST /api/v1/auth/refresh` with valid refresh token <br> 2. Call with expired/invalid refresh token |
| **Expected Result** | Valid refresh returns new access token; invalid returns 401 |
### AUTH-012 — Logout behavior
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | User is logged in |
| **Steps** | 1. Click logout/sign out button <br> 2. Try to navigate to previously visited protected page |
| **Expected Result** | Token cleared from storage, redirected to login page, protected routes inaccessible |
### AUTH-013 — Logout clears token from localStorage
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User is logged in |
| **Steps** | 1. Inspect localStorage for auth tokens after login <br> 2. Logout <br> 3. Inspect localStorage again |
| **Expected Result** | Tokens removed after logout |
### AUTH-014 — Role-based page access: admin
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Admin user logged in |
| **Steps** | 1. Navigate to `/configuration/users` <br> 2. Navigate to `/configuration/clusters` <br> 3. Navigate to `/artifact/instances` |
| **Expected Result** | All pages accessible |
### AUTH-015 — Role-based page access: regular user
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Regular user logged in (non-admin) |
| **Steps** | 1. Navigate to `/configuration/users` <br> 2. Navigate to `/admin` |
| **Expected Result** | Redirected to `/forbidden` or access denied page |
### AUTH-016 — Regular user can access own resources pages
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Regular user logged in |
| **Steps** | 1. Navigate to `/home` <br> 2. Navigate to `/configuration/clusters` <br> 3. Navigate to `/configuration/registries` <br> 4. Navigate to `/artifact/registries` <br> 5. Navigate to `/artifact/instances` |
| **Expected Result** | All pages accessible (user sees own resources) |
### AUTH-017 — Login page redirect when already authenticated
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User is logged in |
| **Steps** | 1. Navigate to `/` <br> 2. Observe behavior |
| **Expected Result** | Redirected to `/home` instead of showing login form |
### AUTH-018 — Login page UI elements
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Not authenticated |
| **Steps** | 1. Observe login page <br> 2. Check for OCDP Console branding, username input, password input, Login button |
| **Expected Result** | Page displays brand icon, "OCDP Console" title, username/password fields with correct autocomplete attributes, Login button |
### AUTH-019 — Login button loading state
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Enter credentials and click Login <br> 2. Observe button state during API call |
| **Expected Result** | Button shows spinner/loading state, text changes to "Logging in...", button disabled during request |
### AUTH-020 — Login error display
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Enter wrong credentials and submit <br> 2. Observe error message |
| **Expected Result** | Red error text appears below the login button, message is user-friendly (not a raw stack trace) |
### AUTH-021 — Password change flow (mustChangePassword)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User created with `mustChangePassword: true` |
| **Steps** | 1. Login as that user <br> 2. Observe redirect/behavior <br> 3. Change password <br> 4. Login again with new password |
| **Expected Result** | First login forces password change, old password rejected after change |
### AUTH-022 — Refresh token expiry logout
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Both access and refresh tokens expired |
| **Steps** | 1. Wait for full token expiry <br> 2. Make any API call that triggers refresh |
| **Expected Result** | User is logged out, redirected to login page |
### AUTH-023 — Concurrent login sessions
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | User account exists |
| **Steps** | 1. Login in browser tab 1 <br> 2. Login in browser tab 2 with same credentials <br> 3. Perform operations in both tabs |
| **Expected Result** | Both sessions work independently, no cross-tab interference |
### AUTH-024 — Admin login shows "Admin only" badge on User Management
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Admin logged in |
| **Steps** | 1. Navigate to `/configuration/users` <br> 2. Check for admin badge |
| **Expected Result** | "Admin only" badge visible in the User Management page header |
### AUTH-025 — Token manipulation (tampered JWT)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Get valid token, modify its payload |
| **Steps** | 1. Decode JWT, change `role` to "admin" for a regular user token <br> 2. Re-encode with modified payload and send API request |
| **Expected Result** | Backend rejects tampered token (signature verification fails), returns 401 |
---
## Category 2: Cluster CRUD (15+ cases)
### CLU-001 — Create cluster with all required fields
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Logged in as admin/user with cluster permissions |
| **Steps** | 1. Navigate to `/configuration/clusters` <br> 2. Click "Add Cluster" <br> 3. Fill in name, API Server URL, CA cert, client cert, client key <br> 4. Click "Save" |
| **Expected Result** | Cluster created successfully, success toast shown, cluster appears in the list |
### CLU-002 — Create cluster with token auth
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Logged in |
| **Steps** | 1. Click "Add Cluster" <br> 2. Fill name, API Server URL, Bearer Token (leave cert fields empty) <br> 3. Click "Save" |
| **Expected Result** | Cluster created using token authentication |
### CLU-003 — Create cluster with empty name
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Leave name empty <br> 2. Fill all other required fields <br> 3. Click "Save" |
| **Expected Result** | Validation error "Cluster name is required" displayed near the name field |
### CLU-004 — Create cluster with invalid API Server URL
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Enter name <br> 2. Enter invalid URL (e.g., `not-a-url`, `ftp://...`) <br> 3. Click "Save" |
| **Expected Result** | Validation error "Invalid URL format" displayed |
### CLU-005 — Create cluster without auth credentials
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Enter name and URL <br> 2. Leave all cert/key/token fields empty <br> 3. Click "Save" |
| **Expected Result** | Validation errors on CA/Client Cert/Client Key fields |
### CLU-006 — Edit cluster name and URL
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Existing cluster present |
| **Steps** | 1. Click edit on existing cluster <br> 2. Change name and host <br> 3. Click "Save" |
| **Expected Result** | Cluster updated, changes reflected in list |
### CLU-007 — Edit cluster with new certificate (overwrite)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Existing cluster with cert auth |
| **Steps** | 1. Edit cluster <br> 2. Enter new CA cert, client cert, client key in the "new" fields <br> 3. Click "Save" |
| **Expected Result** | Certificate updated, "hasCaData" still appears as configured |
### CLU-008 — Edit cluster leaving cert fields empty (no change)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Existing cluster with cert auth |
| **Steps** | 1. Edit cluster <br> 2. Leave the "new" cert fields empty <br> 3. Click "Save" |
| **Expected Result** | Cluster updated, existing certs retained |
### CLU-009 — Delete cluster
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Existing cluster with no running instances (or expected behavior defined) |
| **Steps** | 1. Click delete icon on a cluster <br> 2. Confirm deletion in browser confirm dialog |
| **Expected Result** | Cluster removed from list, success toast shown |
### CLU-010 — Delete cluster cancellation
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Existing cluster |
| **Steps** | 1. Click delete on a cluster <br> 2. Click "Cancel" in the confirmation dialog |
| **Expected Result** | Cluster not deleted, still visible in the list |
### CLU-011 — Health check on reachable cluster
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | A reachable Kubernetes cluster configured |
| **Steps** | 1. Click health check / test button on the cluster row |
| **Expected Result** | Success toast with connection healthy message |
### CLU-012 — Health check on unreachable cluster
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster with invalid host/cert configured |
| **Steps** | 1. Click health check / test button on the cluster |
| **Expected Result** | Error toast with connection failure message |
### CLU-013 — Empty clusters state
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | No clusters configured |
| **Steps** | 1. Navigate to `/configuration/clusters` |
| **Expected Result** | Empty state message displayed, add cluster action available |
### CLU-014 — Cluster list with multiple clusters
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | 3+ clusters configured |
| **Steps** | 1. Navigate to `/configuration/clusters` <br> 2. Scroll list |
| **Expected Result** | All clusters listed with name, URL, status indicators |
### CLU-015 — Cluster description display in list
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Cluster with description exists |
| **Steps** | 1. View cluster list <br> 2. Check if description is visible |
| **Expected Result** | Description shown as subtitle or tooltip in the cluster row |
### CLU-016 — Cluster CRUD as regular user
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Regular user logged in |
| **Steps** | 1. Create a new cluster <br> 2. Edit the cluster <br> 3. Delete the cluster |
| **Expected Result** | User can manage their own clusters, or see appropriate empty/permission state |
### CLU-017 — Cluster form modal close/reset
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Create modal open with partially filled form |
| **Steps** | 1. Fill partial data <br> 2. Click Cancel |
| **Expected Result** | Modal closes, form data cleared when reopened |
---
## Category 3: Registry CRUD (15+ cases)
### REG-001 — Create registry with all required fields
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Logged in |
| **Steps** | 1. Navigate to `/configuration/registries` <br> 2. Click "Add Registry" <br> 3. Fill name, URL, username, password <br> 4. Click "Save" |
| **Expected Result** | Registry created, success toast shown, appears in list |
### REG-002 — Create registry with insecure flag
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Logged in |
| **Steps** | 1. Open add registry modal <br> 2. Fill required fields <br> 3. Check "Allow insecure connection" <br> 4. Click "Save" |
| **Expected Result** | Registry created with `insecure: true`, works for HTTP/self-signed registries |
### REG-003 — Create registry without name
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Leave name empty <br> 2. Fill other fields <br> 3. Click "Save" |
| **Expected Result** | HTML5 form validation prevents submission (required attribute) |
### REG-004 — Create registry with invalid URL
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Enter non-URL string for URL field (type=url) <br> 2. Fill other fields <br> 3. Click "Save" |
| **Expected Result** | HTML5 form validation prevents submission (type=url validation) |
### REG-005 — Test registry connection
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Saved registry exists, it's reachable |
| **Steps** | 1. Edit an existing registry <br> 2. Click "Test Connection" button |
| **Expected Result** | Connection test runs, success/error toast based on connectivity |
### REG-006 — Test registry connection without saving first
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Creating new registry (unsaved) |
| **Steps** | 1. Fill registry form but do not save <br> 2. Check if "Test Connection" is available |
| **Expected Result** | "Test Connection" button is not shown (only visible for saved registries) |
### REG-007 — Edit registry name and URL
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Existing registry |
| **Steps** | 1. Edit a registry <br> 2. Change its name and URL <br> 3. Save |
| **Expected Result** | Registry updated, changes reflected |
### REG-008 — Edit registry with new password
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Existing registry with password set |
| **Steps** | 1. Edit registry <br> 2. Enter new password in the "New Password" field <br> 3. Save |
| **Expected Result** | Password updated, "hasPassword" indicator shows as configured |
### REG-009 — Edit registry leaving password empty
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Existing registry |
| **Steps** | 1. Edit registry <br> 2. Leave new password field empty <br> 3. Save |
| **Expected Result** | Registry updated, existing password retained |
### REG-010 — Delete registry
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Existing registry with no active dependencies |
| **Steps** | 1. Click delete on a registry <br> 2. Confirm deletion |
| **Expected Result** | Registry removed from list, success toast |
### REG-011 — Delete registry with existing instances
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Registry has active instances deployed from it |
| **Steps** | 1. Try to delete registry that has active instances deriving from it |
| **Expected Result** | Backend should return error preventing deletion, or handle cascading gracefully |
### REG-012 — Empty registries state
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | No registries configured |
| **Steps** | 1. Navigate to `/configuration/registries` |
| **Expected Result** | Empty state message displayed |
### REG-013 — Registry toggle insecure flag
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Existing registry |
| **Steps** | 1. Edit registry <br> 2. Toggle insecure checkbox <br> 3. Save |
| **Expected Result** | Insecure flag updated |
### REG-014 — Registry list display
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Multiple registries exist |
| **Steps** | 1. View the registries page <br> 2. Check each row |
| **Expected Result** | Each registry shows name, URL, username, insecure badge (if enabled) |
### REG-015 — Registry CRUD as regular user
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Regular user logged in |
| **Steps** | 1. Create a new registry <br> 2. Edit the registry <br> 3. Delete the registry |
| **Expected Result** | User can manage their own registries |
---
## Category 4: Chart Browser / Launch Instance (20+ cases)
### CHT-001 — Browse registries in chart browser
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Registries configured with Helm charts |
| **Steps** | 1. Navigate to `/artifact/registries` <br> 2. Observe left panel |
| **Expected Result** | Registries listed with expand/collapse toggle, count badge |
### CHT-002 — Expand registry tree and list repositories
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Registry has chart repositories |
| **Steps** | 1. Click on a registry to expand it <br> 2. Observe repositories listed underneath |
| **Expected Result** | Repositories displayed as clickable items, each showing name |
### CHT-003 — Empty repository list message
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Registry exists but has no chart repositories |
| **Steps** | 1. Expand registry <br> 2. Observe sub-items |
| **Expected Result** | "No chart repositories found." message shown |
### CHT-004 — Select repository and view artifacts
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Repository with chart artifacts exists |
| **Steps** | 1. Click on a repository in the left panel <br> 2. Observe right panel |
| **Expected Result** | Repository name displayed in header, artifact tags shown as cards |
### CHT-005 — Filter artifacts by Charts / All tags
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Repository has both chart and non-chart artifacts |
| **Steps** | 1. Select a repository <br> 2. Click "Charts" filter button <br> 3. Click "All tags" filter button |
| **Expected Result** | "Charts" filter shows only chart artifacts, "All tags" shows all |
### CHT-006 — Filter toggle active state
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Repository selected |
| **Steps** | 1. Toggle between Charts and All tags |
| **Expected Result** | Active filter button has blue highlight, inactive has default styling |
### CHT-007 — Tag card displays correct info
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Artifact loaded |
| **Steps** | 1. Observe a tag card |
| **Expected Result** | Card shows tag name, artifact type badge (chart/image), repository path, size |
### CHT-008 — Launch button visible only for chart tags
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Chart and non-chart artifacts exist |
| **Steps** | 1. Observe a chart tag card <br> 2. Observe a non-chart tag card |
| **Expected Result** | Chart tag card has blue "Launch" button; non-chart card does not |
### CHT-009 — Copy pull command from tag card
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Tag card displayed |
| **Steps** | 1. Click "Copy" on a tag card |
| **Expected Result** | Helm pull command copied to clipboard, success toast shown |
### CHT-010 — Search registries/repositories
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Multiple registries with repositories exist |
| **Steps** | 1. Type in the search box in the left panel <br> 2. Observe filtering |
| **Expected Result** | List filters to matching registries and repositories; non-matching entries hidden |
### CHT-011 — Open Launch modal
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Chart tag selected |
| **Steps** | 1. Click "Launch" on a chart tag |
| **Expected Result** | Launch modal opens with repository:tag header, cluster selector, instance name, namespace, values options |
### CHT-012 — Launch modal loads clusters
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Clusters exist in the system |
| **Steps** | 1. Open Launch modal <br> 2. Observe cluster dropdown |
| **Expected Result** | Cluster dropdown populated with available clusters |
### CHT-013 — Launch modal: no clusters available
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | No clusters configured |
| **Steps** | 1. Open Launch modal <br> 2. Observe cluster section |
| **Expected Result** | Warning message "No clusters available. Please add a cluster first." displayed, Launch button disabled |
### CHT-014 — Launch modal: instance name validation
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Launch modal open with cluster selected |
| **Steps** | 1. Leave instance name empty <br> 2. Click Launch |
| **Expected Result** | Toast error "Instance name is required" |
### CHT-015 — Launch modal: namespace validation
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster with namespace policy configured |
| **Steps** | 1. Select a disallowed namespace (not in allowedNamespaces) <br> 2. Click Launch |
| **Expected Result** | Toast error "Selected namespace is not allowed for this cluster." |
### CHT-016 — Launch modal: Quick / Form / YAML input modes
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Launch modal open |
| **Steps** | 1. Click each mode button (Quick, Guided, YAML) <br> 2. Observe content changes |
| **Expected Result** | Quick: info panel about chart defaults. Guided: schema form (if schema exists). YAML: textarea for YAML input. Active mode highlighted. |
### CHT-017 — Launch modal: YAML validation
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | YAML input mode selected |
| **Steps** | 1. Enter invalid YAML (e.g., `key: [invalid`) <br> 2. Observe error state |
| **Expected Result** | Red error text below textarea, Launch button disabled |
### CHT-018 — Launch modal: Load Defaults from values.yaml
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Chart has values.yaml with defaults |
| **Steps** | 1. Switch to YAML mode <br> 2. Click "Load Defaults from values.yaml" |
| **Expected Result** | values.yaml content loaded into the textarea |
### CHT-019 — Submit launch and navigate to instances
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | All required fields filled with valid data |
| **Steps** | 1. Fill cluster, instance name, namespace <br> 2. Click Launch <br> 3. Wait for redirect |
| **Expected Result** | Instance creation API called, success toast, redirected to `/artifact/instances`, instance shown with "Pending Install" status |
### CHT-020 — Launch modal: namespace controlled by workspace policy
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster has namespace readOnly policy |
| **Steps** | 1. Open Launch modal <br> 2. Select cluster with readonly namespace policy <br> 3. Check namespace field |
| **Expected Result** | Namespace field is disabled with blue info message: "Namespace is controlled by your workspace policy." |
### CHT-021 — Launch modal: namespace dropdown (allowed namespaces)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster has allowedNamespaces configured |
| **Steps** | 1. Select such cluster <br> 2. Observe namespace field |
| **Expected Result** | Namespace becomes a dropdown with only allowed values |
### CHT-022 — Launch modal: user's default cluster pre-selected
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | User has defaultClusterId set |
| **Steps** | 1. Open Launch modal |
| **Expected Result** | Default cluster auto-selected in the dropdown |
---
## Category 5: Instance Management (20+ cases)
### INS-001 — View instances (all clusters)
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Instances exist across clusters |
| **Steps** | 1. Navigate to `/artifact/instances` |
| **Expected Result** | All instances listed grouped by cluster, stats cards show totals |
### INS-002 — Filter instances by cluster
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Multiple clusters with instances |
| **Steps** | 1. Navigate to instances page <br> 2. Select a specific cluster from dropdown |
| **Expected Result** | Only instances from that cluster displayed |
### INS-003 — Instance status: Deployed
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Instance in deployed state |
| **Steps** | 1. Look for a deployed instance card |
| **Expected Result** | Green "DEPLOYED" badge with checkmark icon, status reason shown |
### INS-004 — Instance status: Failed
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance in failed state |
| **Steps** | 1. Look for a failed instance card |
| **Expected Result** | Red "FAILED" badge, error details visible (lastError section appears) |
### INS-005 — Instance status: Pending (Install/Upgrade/Rollback/Delete)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance in transition state |
| **Steps** | 1. Look for pending instance card |
| **Expected Result** | Amber/yellow "PENDING INSTALL/UPGRADE/ROLLBACK/DELETE" badge |
### INS-006 — Instance status: Unknown
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Instance with unknown status |
| **Steps** | 1. Look for unknown instance card |
| **Expected Result** | Gray "UNKNOWN" badge |
### INS-007 — Refresh instance status
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance exists |
| **Steps** | 1. Click "Refresh" button on the instance card |
| **Expected Result** | Instance status re-fetched, card updates with latest status |
### INS-008 — Instance card displays metadata correctly
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance exists |
| **Steps** | 1. Examine instance card content |
| **Expected Result** | Card shows: instance name, repository, version tag, namespace, revision, launch date, status reason |
### INS-009 — Instance action buttons visibility
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance exists |
| **Steps** | 1. Check the action bar at bottom of instance card |
| **Expected Result** | Five buttons visible: Refresh, Entries, Diagnostics, Modify, Delete |
### INS-010 — View entries (Services)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance with services |
| **Steps** | 1. Click "Entries" on the instance card <br> 2. Observe modal |
| **Expected Result** | Modal shows Services with name, type, cluster IP, ports; source badge visible |
### INS-011 — View entries (Ingresses)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance with ingresses |
| **Steps** | 1. Open entries modal <br> 2. Check for Ingresses section |
| **Expected Result** | Ingresses listed with host, paths, TLS status |
### INS-012 — View diagnostics (Describe)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance |
| **Steps** | 1. Click "Diagnostics" on instance card <br> 2. Observe Describe tab |
| **Expected Result** | Modal shows Pods (with status, node, restarts, containers) and Services summary |
### INS-013 — View diagnostics (Events)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance |
| **Steps** | 1. Open diagnostics <br> 2. Click "Events" tab |
| **Expected Result** | Kubernetes events listed with type badge, reason, message, timestamp, count |
### INS-014 — View diagnostics (Pod Logs)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance with running pods |
| **Steps** | 1. Open diagnostics <br> 2. Click "Pod Logs" tab |
| **Expected Result** | Pod logs displayed in dark terminal-style blocks, copy button available |
### INS-015 — Copy pod logs
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Diagnostics with logs loaded |
| **Steps** | 1. Open pod logs <br> 2. Click "Copy Logs" |
| **Expected Result** | Combined logs copied to clipboard, success toast shown |
### INS-016 — Modify instance version tag
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance, new chart version available |
| **Steps** | 1. Click "Modify" on instance <br> 2. Change version tag <br> 3. Confirm |
| **Expected Result** | Instance upgrade initiated, instance moves to "Pending Upgrade" status |
### INS-017 — Modify instance with values changes
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Deployed instance |
| **Steps** | 1. Open modify modal <br> 2. Switch to YAML input <br> 3. Update values <br> 4. Confirm |
| **Expected Result** | Instance upgraded with modified values |
### INS-018 — Terminate/delete instance with confirmation
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Deployed instance exists |
| **Steps** | 1. Click "Delete" on instance card <br> 2. Confirm in browser dialog |
| **Expected Result** | Deletion initiated, instance enters "Pending Delete" status, eventually disappears |
### INS-019 — Terminate instance cancellation
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Deployed instance |
| **Steps** | 1. Click "Delete" <br> 2. Click "Cancel" in the confirmation dialog |
| **Expected Result** | Instance not deleted, dialog dismissed |
### INS-020 — Empty instances state
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | No instances deployed |
| **Steps** | 1. Navigate to `/artifact/instances` |
| **Expected Result** | Empty state displayed: "No instances found. Launch your first service instance from Artifact Registries" |
### INS-021 — Instances auto-refresh
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Instances page open |
| **Steps** | 1. Stay on instances page <br> 2. Observe network requests for 30+ seconds |
| **Expected Result** | Background auto-refresh fires every 30 seconds without user interaction |
---
## Category 6: Cluster Monitoring (10+ cases)
### MON-001 — View cluster health monitoring
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Clusters configured |
| **Steps** | 1. Navigate to `/monitoring/clusters` |
| **Expected Result** | Cluster monitoring cards displayed with health status badges, metrics grid |
### MON-002 — Stats cards display summary
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | 3+ clusters with varying health |
| **Steps** | 1. Navigate to monitoring page |
| **Expected Result** | Stats cards show: Total Clusters, Healthy count, Warning count, Error count |
### MON-003 — Monitoring card shows cluster metrics
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Healthy cluster |
| **Steps** | 1. Observe a cluster monitoring card |
| **Expected Result** | Card shows: cluster name, uptime, node count, pod count, GPU usage, CPU usage bar, memory usage bar, last checked time |
### MON-004 — Expand node details
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster has nodes |
| **Steps** | 1. Click "Show Nodes" button on a cluster card <br> 2. Observe node list |
| **Expected Result** | Node list expands showing individual node metrics (CPU, memory, GPU per node) |
### MON-005 — Healthy cluster status display
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster is healthy |
| **Steps** | 1. Check card header |
| **Expected Result** | Green "Healthy" badge, green checkmark icon |
### MON-006 — Error cluster status display
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster is unhealthy/error |
| **Steps** | 1. Check card header |
| **Expected Result** | Red "Error" badge, red X icon |
### MON-007 — Auto-refresh monitoring
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Monitoring page open |
| **Steps** | 1. Stay on page <br> 2. Observe metrics updates over time |
| **Expected Result** | Page auto-refreshes every 30 seconds, "Auto-refresh every 30 seconds" text visible |
### MON-008 — Manual refresh
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Monitoring page open |
| **Steps** | 1. Click "Refresh" button |
| **Expected Result** | Data reloaded, loading state shown during refresh |
### MON-009 — Empty monitoring state
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | No clusters configured |
| **Steps** | 1. Navigate to monitoring page |
| **Expected Result** | "No Clusters Available" empty state displayed |
### MON-010 — Error state when cluster unreachable
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster monitoring API returns error |
| **Steps** | 1. Simulate API failure <br> 2. Observe page |
| **Expected Result** | Error state with retry button shown, error message displayed |
---
## Category 7: User Management (Admin) (15+ cases)
### USR-001 — Create user with role "user"
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Admin logged in |
| **Steps** | 1. Navigate to `/configuration/users` <br> 2. Fill username, password, role=User <br> 3. Set namespace, default cluster, resource limits <br> 4. Click "Create User" |
| **Expected Result** | User created, appears in accounts table |
### USR-002 — Create user with role "admin"
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Admin logged in |
| **Steps** | 1. Open create user form <br> 2. Select Role=Admin <br> 3. Fill username and password only (namespace/limits hidden for admin) <br> 4. Click "Create User" |
| **Expected Result** | Admin user created, namespace/limits not required, role badge shows "admin" |
### USR-003 — Create user with mustChangePassword
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create user modal |
| **Steps** | 1. Ensure "Require password change after first login" checkbox is checked <br> 2. Create user |
| **Expected Result** | User created and must change password on first login |
### USR-004 — Create user without required fields
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create form open |
| **Steps** | 1. Leave username or password empty <br> 2. Click "Create User" |
| **Expected Result** | Validation error toast "Username and initial password are required." |
### USR-005 — Edit user resource limits
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Non-admin user exists |
| **Steps** | 1. Click "Limits" on a user row <br> 2. Change CPU, Memory, GPU, GPU Mem values <br> 3. Click "Save Limits" |
| **Expected Result** | Limits modal closes, success toast, updated values shown in table |
### USR-006 — Toggle user role (user ↔ admin)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User exists |
| **Steps** | 1. Click "Make Admin" on a user row <br> 2. Observe role change <br> 3. Click "Make User" to revert |
| **Expected Result** | Role toggled, badge updates, admin users can access all pages after re-login |
### USR-007 — Enable/disable user
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Active user exists |
| **Steps** | 1. Click "Disable" on an active user <br> 2. Observe badge change <br> 3. Try to login as that user |
| **Expected Result** | Badge changes to "Disabled", disabled user cannot login (returns 401) |
### USR-008 — Delete user
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Non-self user exists |
| **Steps** | 1. Click "Delete" on a user <br> 2. Confirm deletion |
| **Expected Result** | User removed from table |
### USR-009 — Cannot delete own admin account
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Current admin logged in |
| **Steps** | 1. Look at own user row |
| **Expected Result** | Delete button is disabled (or not rendered) for the current user |
### USR-010 — Cannot disable own admin account
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Current admin logged in |
| **Steps** | 1. Look at own user row <br> 2. Check Disable button state |
| **Expected Result** | Disable button is disabled for current user |
### USR-011 — User Management page admin-only badge
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Admin logged in |
| **Steps** | 1. Observe page header |
| **Expected Result** | "Admin only" badge visible near the title |
### USR-012 — User table displays all columns
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Users exist |
| **Steps** | 1. Observe the accounts table |
| **Expected Result** | Columns: User (username+email), Role (badge), Status (Active/Disabled), Namespace, Quota (CPU/Mem/GPU), Actions |
### USR-013 — Namespace auto-generation for user
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Creating user with role=user |
| **Steps** | 1. Enter username <br> 2. Check namespace field (before user edits it) |
| **Expected Result** | Namespace auto-populated as `ocdp-u-<sanitized-username>` |
### USR-014 — Create user with resource limits
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create form open, role=User |
| **Steps** | 1. Set specific CPU, Memory, GPU, GPU memory limits <br> 2. Create user <br> 3. View user in table |
| **Expected Result** | Limits stored and displayed in the quota column |
### USR-015 — User list refresh
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Users page open |
| **Steps** | 1. Click "Refresh" button |
| **Expected Result** | User list reloaded, loading state shown |
---
## Category 8: Multi-tenancy & Permissions (15+ cases)
### MTN-001 — User A cannot see User B's clusters
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Two regular users (A, B), each with their own cluster |
| **Steps** | 1. Login as User A <br> 2. List clusters via API <br> 3. Login as User B <br> 4. List clusters via API |
| **Expected Result** | User A sees only their clusters, User B sees only their clusters. No cross-tenant leakage |
### MTN-002 — User A cannot see User B's registries
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Two regular users with separate registries |
| **Steps** | 1. List registries as User A <br> 2. List registries as User B |
| **Expected Result** | Each user sees only their own registries |
### MTN-003 — User A cannot delete User B's instances
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User A and B each have instances |
| **Steps** | 1. As User A, try to call DELETE on User B's instance |
| **Expected Result** | Backend returns 403 Forbidden or 404 Not Found |
### MTN-004 — User A cannot modify User B's instances
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User B has an instance |
| **Steps** | 1. As User A, try to update User B's instance |
| **Expected Result** | Backend returns 403 Forbidden |
### MTN-005 — Admin can see all clusters
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Admin user, clusters belonging to multiple users exist |
| **Steps** | 1. Login as admin <br> 2. List clusters |
| **Expected Result** | Admin sees all clusters across all users |
### MTN-006 — Admin can see all registries
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Admin user, registries belonging to multiple users exist |
| **Steps** | 1. Login as admin <br> 2. List registries |
| **Expected Result** | Admin sees all registries across all users |
### MTN-007 — Admin can see all instances
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instances exist across different users |
| **Steps** | 1. Login as admin <br> 2. List instances per cluster |
| **Expected Result** | Admin sees instances from all users' releases |
### MTN-008 — ResourceQuota enforcement (CPU)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User with CPU quota set, deploying |
| **Steps** | 1. As user with CPU quota=4, try to deploy chart requesting >4 CPU <br> 2. Check deployment outcome |
| **Expected Result** | Deployment should fail or ResourceQuota enforced in the namespace |
### MTN-009 — ResourceQuota enforcement (GPU)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | User with GPU quota=0 |
| **Steps** | 1. Try to deploy a chart requiring GPU |
| **Expected Result** | Deployment fails due to quota enforcement |
### MTN-010 — Namespace isolation across users
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Users A and B configured with different namespaces |
| **Steps** | 1. User A deploys instance to their namespace <br> 2. User B deploys instance to their namespace <br> 3. Verify User A cannot see User B's pods/instances |
| **Expected Result** | Instances isolated by namespace, no cross-tenant visibility |
### MTN-011 — Regular user cannot access admin pages
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Regular user logged in |
| **Steps** | 1. Navigate to `/configuration/users` <br> 2. Navigate to `/admin` |
| **Expected Result** | Redirected to `/forbidden`, access denied page shown |
### MTN-012 — Regular user does not see "Users" in home setup card
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Regular user logged in |
| **Steps** | 1. Navigate to `/home` <br> 2. Check "Setup" section |
| **Expected Result** | "Users" card is not rendered for non-admin users |
### MTN-013 — Default user permissions match expected set
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Regular user created without custom permissions |
| **Steps** | 1. Get user info from `/api/v1/auth/me` or similar <br> 2. Inspect permissions array |
| **Expected Result** | Default permissions include: home:view, configuration:clusters:manage_own, configuration:registries:manage_own, artifact:registries:view, artifact:instances:manage_own |
### MTN-014 — User workspace metadata stored and returned
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | User exists |
| **Steps** | 1. Login and inspect user response <br> 2. Check workspaceId, workspaceName, namespace, defaultClusterId |
| **Expected Result** | Workspace metadata present and consistent |
### MTN-015 — Admin can create resources under any user scope
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Admin logged in |
| **Steps** | 1. Check if admin can create clusters/registries without ownership restriction |
| **Expected Result** | Admin-created resources are accessible to admin (global scope) |
---
## Category 9: UI/UX Bugs (20+ cases)
### UI-001 — Page layout does not overflow horizontally
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Navigate to each page at 1440px viewport width <br> 2. Check for horizontal scrollbar |
| **Expected Result** | No horizontal overflow, all content fits within viewport |
### UI-002 — Responsive layout at mobile breakpoint (768px)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Resize browser to 768px width <br> 2. Navigate through all pages |
| **Expected Result** | Navigation collapses, content stacks vertically, no broken layout |
### UI-003 — Responsive layout at tablet breakpoint (1024px)
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Resize to 1024px width <br> 2. Check all pages |
| **Expected Result** | Content reflows gracefully, no overlap |
### UI-004 — Loading state displays correctly
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Slow network (simulated) |
| **Steps** | 1. Enable network throttling <br> 2. Navigate to each page |
| **Expected Result** | Loading spinner/message appears while data is being fetched, content appears after load |
### UI-005 — No flickering during page transitions
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Navigate between pages rapidly <br> 2. Observe visual transitions |
| **Expected Result** | Smooth transitions, no white flash or layout shift |
### UI-006 — Empty states show informative messages
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Fresh/empty system |
| **Steps** | 1. Check clusters page (empty) <br> 2. Check registries page (empty) <br> 3. Check instances page (empty) |
| **Expected Result** | Each page has a distinct, informative empty state message with relevant icon |
### UI-007 — Error states show retry action
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | API returns error |
| **Steps** | 1. Simulate backend error <br> 2. Observe error state |
| **Expected Result** | Error message displayed with a "Retry" button |
### UI-008 — Form validation feedback is visible
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Submit forms with invalid data |
| **Expected Result** | Red error text appears near the invalid field, or toast notification with specific message |
### UI-009 — Toast notifications appear and disappear
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Perform actions that trigger toasts (save, delete, error) <br> 2. Observe toast behavior |
| **Expected Result** | Toast appears at expected position, auto-dismisses after timeout, can be dismissed manually |
### UI-010 — Button states: disabled
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Find disabled buttons (Launch when no clusters, Submit with invalid YAML) |
| **Expected Result** | Disabled buttons have reduced opacity, no pointer cursor, cannot be clicked |
### UI-011 — Button states: loading
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Action in progress |
| **Steps** | 1. Click a button that triggers an API call <br> 2. Observe button during request |
| **Expected Result** | Button shows spinner/loading indicator, disabled during request |
### UI-012 — Truncation of long text labels
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Long names exist |
| **Steps** | 1. Create resources with very long names <br> 2. Observe display in cards and lists |
| **Expected Result** | Long text is truncated with ellipsis, no layout breakage |
### UI-013 — Sidebar navigation highlight matches current page
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Navigate to each page <br> 2. Check sidebar nav item highlight |
| **Expected Result** | Current page's nav item is highlighted/active |
### UI-014 — Page header shows correct title and icon
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Navigate to each page |
| **Expected Result** | Page header displays correct title, icon, and description |
### UI-015 — Color contrast meets readability
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Inspect text colors against backgrounds using DevTools |
| **Expected Result** | All text meets WCAG AA contrast ratio (4.5:1 for normal text, 3:1 for large text) |
### UI-016 — Access denied page renders correctly
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | User with insufficient permissions |
| **Steps** | 1. Access a restricted page |
| **Expected Result** | Access denied page shown with "Back Home" button |
### UI-017 — Cluster list shows health status indicator
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Clusters exist |
| **Steps** | 1. Navigate to cluster config page <br> 2. Check each cluster row |
| **Expected Result** | Each cluster shows a health status indicator (green/yellow/red dot or similar) |
### UI-018 — Search/filter in chart browser works correctly
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Multiple registries/repositories exist |
| **Steps** | 1. Type partial name in search box <br> 2. Type a query that matches no results |
| **Expected Result** | Matching entries remain visible, non-matching hidden. "No registries" state when nothing matches. |
### UI-019 — Modal backdrop click behavior
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Any modal open |
| **Steps** | 1. Open a modal (e.g., Launch modal, Cluster form, Modify modal) <br> 2. Click on the dark backdrop |
| **Expected Result** | Modal closes (or stays open depending on design). Should not cause errors. |
### UI-020 — Home page displays all sections
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Logged in as admin |
| **Steps** | 1. Navigate to `/home` |
| **Expected Result** | Three sections visible: primary actions (Launch Instance, Instances, Cluster Monitoring), runtime focus sidebar, setup section |
---
## Category 10: Data Persistence (10+ cases)
### PER-001 — Data survives page refresh (clusters)
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Clusters exist |
| **Steps** | 1. Navigate to clusters page <br> 2. Refresh the page (F5) |
| **Expected Result** | Clusters still displayed after refresh |
### PER-002 — Data survives page refresh (registries)
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Registries exist |
| **Steps** | 1. Navigate to registries page <br> 2. Refresh |
| **Expected Result** | Registries still displayed |
### PER-003 — Data survives page refresh (instances)
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Instances exist |
| **Steps** | 1. Navigate to instances page <br> 2. Refresh |
| **Expected Result** | Instances still displayed |
### PER-004 — Data survives browser tab close/reopen
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Resources exist |
| **Steps** | 1. Close browser tab <br> 2. Open new tab and navigate to app <br> 3. Login <br> 4. Check all pages |
| **Expected Result** | All data intact after session restoration |
### PER-005 — Created cluster persists after logout/login
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster was created |
| **Steps** | 1. Logout <br> 2. Login again <br> 3. Check cluster list |
| **Expected Result** | Cluster still present |
### PER-006 — Created registry persists after logout/login
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Registry was created |
| **Steps** | 1. Logout <br> 2. Login <br> 3. Check registry list |
| **Expected Result** | Registry still present |
### PER-007 — Instance deployment persists across page navigation
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance was launched |
| **Steps** | 1. Navigate away from instances page <br> 2. Navigate back to instances page |
| **Expected Result** | Instance still listed with its status |
### PER-008 — Cache consistency: new cluster appears in Launch dropdown
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Clusters page and artifact browser cached |
| **Steps** | 1. Add a new cluster <br> 2. Navigate to chart browser <br> 3. Open Launch modal <br> 4. Check cluster dropdown |
| **Expected Result** | New cluster visible in dropdown (cache refreshed properly) |
### PER-009 — Cache consistency: new registry appears in chart browser
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Registry page open, then chart browser |
| **Steps** | 1. Add a new registry <br> 2. Navigate to chart browser <br> 3. Check left panel |
| **Expected Result** | New registry visible (after refresh or auto-reload) |
### PER-010 — Delete data persists (no phantom data)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Item deleted earlier |
| **Steps** | 1. Delete a cluster/registry <br> 2. Refresh page <br> 3. Check list |
| **Expected Result** | Deleted item does not reappear |
---
## Category 11: Security (15+ cases)
### SEC-001 — XSS via form inputs (cluster name)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Logged in |
| **Steps** | 1. Create cluster with name `<script>alert('xss')</script>` <br> 2. Observe if script executes on the list page |
| **Expected Result** | Script tag is escaped/rendered as text, no XSS execution |
### SEC-002 — XSS via form inputs (registry description)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Logged in |
| **Steps** | 1. Create registry with description containing HTML/script tags <br> 2. Observe rendering |
| **Expected Result** | HTML is escaped, no script execution |
### SEC-003 — XSS via instance name
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Logged in |
| **Steps** | 1. Launch instance with name `<img onerror="alert(1)" src=x>` <br> 2. Navigate to instances page |
| **Expected Result** | Name is rendered safely, no XSS |
### SEC-004 — IDOR: access another user's instance detail
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | User A has an instance, User B knows its ID |
| **Steps** | 1. Login as User B <br> 2. Try to access User A's instance detail by ID |
| **Expected Result** | Backend returns 403 Forbidden or 404 |
### SEC-005 — IDOR: modify another user's instance
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | User B has instance ID of User A |
| **Steps** | 1. Login as User A <br> 2. Attempt PUT on User B's instance |
| **Expected Result** | 403 Forbidden |
### SEC-006 — IDOR: delete another user's cluster
| Field | Value |
|-------|-------|
| **Priority** | P0 |
| **Preconditions** | Two regular users exist |
| **Steps** | 1. User A creates a cluster <br> 2. User B attempts to delete it using cluster ID |
| **Expected Result** | 403 Forbidden |
### SEC-007 — Sensitive data in API responses
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Call `GET /api/v1/clusters` <br> 2. Inspect response for raw certs/keys/tokens |
| **Expected Result** | Sensitive fields are masked or encrypted (e.g., `hasCaData: true` instead of raw cert) |
### SEC-008 — Sensitive data in registry responses
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Call `GET /api/v1/registries` <br> 2. Check response for password exposure |
| **Expected Result** | Password not returned in plain text; `hasPassword` boolean used instead |
### SEC-009 — JWT token manipulation: signature removed
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Valid JWT obtained |
| **Steps** | 1. Strip JWT signature, keep base64 payload <br> 2. Send API request with tampered token |
| **Expected Result** | Backend rejects token, returns 401 |
### SEC-010 — JWT token manipulation: alg changed to "none"
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Valid JWT obtained |
| **Steps** | 1. Change JWT header `alg` to `none` <br> 2. Send modified token |
| **Expected Result** | Backend rejects, returns 401 |
### SEC-011 — Directory traversal in repository name
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Try to access artifacts with `../../etc/passwd` as repository name |
| **Expected Result** | Returns 400 Bad Request or 404, no directory traversal occurs |
### SEC-012 — Rate limiting on login endpoint
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Send rapid login requests (20+ in 1 second) with wrong passwords |
| **Expected Result** | After threshold, rate limiting kicks in (429 Too Many Requests) |
### SEC-013 — Brute force protection
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Attempt login with wrong password 10+ times in succession |
| **Expected Result** | Account should be temporarily locked or delayed responses introduced |
### SEC-014 — Session fixation test
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Capture pre-auth token <br> 2. Login <br> 3. Check if pre-auth token is still valid |
| **Expected Result** | Pre-auth token invalidated, new token issued on login |
### SEC-015 — No sensitive data in error messages
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | None |
| **Steps** | 1. Trigger various API errors (invalid auth, bad request, server error) <br> 2. Inspect error responses |
| **Expected Result** | Error messages do not reveal stack traces, SQL queries, or system internals |
---
## Category 12: Edge Cases (10+ cases)
### EDG-001 — Rapid double-click on submit buttons
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Create modal open |
| **Steps** | 1. Click "Save" or "Create" button rapidly multiple times |
| **Expected Result** | Button is disabled after first click (loading state), duplicate submissions prevented |
### EDG-002 — Very long instance name
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Launch modal open |
| **Steps** | 1. Enter instance name of 253+ characters <br> 2. Submit |
| **Expected Result** | Backend validates Kubernetes naming constraints, returns error if too long |
### EDG-003 — Special characters in namespace
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Launch modal open |
| **Steps** | 1. Enter namespace with uppercase letters or special characters <br> 2. Submit |
| **Expected Result** | Backend validates DNS-1123 label constraints, returns error |
### EDG-004 — Browser back/forward navigation
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Authenticated |
| **Steps** | 1. Navigate to page A, then page B <br> 2. Click browser back button <br> 3. Click browser forward button |
| **Expected Result** | Navigation works correctly, no infinite redirects, no blank pages |
### EDG-005 — Concurrent operations: launch instance in two tabs
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Same user, same cluster, same namespace |
| **Steps** | 1. Tab 1: Launch instance "test-a" <br> 2. Tab 2 (simultaneously): Launch instance "test-b" |
| **Expected Result** | Both instances created, no data race or corruption |
### EDG-006 — Delete cluster with running instances
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Cluster has active Helm releases |
| **Steps** | 1. Attempt to delete a cluster that has running instances |
| **Expected Result** | Backend should reject deletion or return a warning about active instances |
### EDG-007 — Instance name collision (same namespace)
| Field | Value |
|-------|-------|
| **Priority** | P1 |
| **Preconditions** | Instance "test" already exists in namespace "default" on cluster X |
| **Steps** | 1. Try to create another instance named "test" in the same namespace and cluster |
| **Expected Result** | Backend returns conflict error, instance not created |
### EDG-008 — Rapid create/delete/create same resource name
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | None |
| **Steps** | 1. Create cluster named "test-cluster" <br> 2. Delete it <br> 3. Immediately create another cluster named "test-cluster" |
| **Expected Result** | Second creation succeeds after deletion completes |
### EDG-009 — Helm release name collision across namespaces
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Helm release exists in namespace-a |
| **Steps** | 1. Launch instance with same name in namespace-b on the same cluster |
| **Expected Result** | Helm releases are namespaced, so creation should succeed in different namespace |
### EDG-010 — YAML values with non-object top-level structure
| Field | Value |
|-------|-------|
| **Priority** | P2 |
| **Preconditions** | Launch modal open, YAML mode |
| **Steps** | 1. Enter just a string `"hello"` or array `[1,2,3]` as YAML values <br> 2. Click Launch |
| **Expected Result** | YAML validation error: "Values YAML must be an object" |
---
## Priority Distribution Summary
| Priority | Count |
|----------|-------|
| P0 (Critical) | 26 |
| P1 (High) | 87 |
| P2 (Medium) | 34 |
| **Total** | **147** |
## Existing Test Coverage Reference
The following test scripts already exist in `test/` and cover portions of these scenarios:
| Test Script | Coverage |
|-------------|----------|
| `current-platform-smoke.sh` | Login, registry health, chart browsing, optional deploy/cleanup |
| `frontend-playwright-smoke.py` | Login UI, chart browser rendering, instance page, mobile layout |
| `frontend-interactions-audit.py` | Auth, navigation, config modals, health buttons, launch modes |
| `multitenant_rbac_api_contract.py` | Auth denial, RBAC differences, resource isolation, admin cleanup |
| `multitenant_rbac_ui_playwright.py` | Multi-tenant UI isolation tests |
| `vllm_k3s_deploy_smoke.py` | Real k3s deployment, GPU quota, ResourceQuota, diagnostics |
| `chart_values_yaml_api_contract.py` | Values YAML API contract validation |
| `user_namespace_quota_api_contract.py` | User namespace and quota API contract |
| `instance_card_action_layout_playwright.py` | Instance card action button layout |