diff --git a/.planning/phases/05-cloud-storage-backends/05-UAT.md b/.planning/phases/05-cloud-storage-backends/05-UAT.md index eae308d..d96e537 100644 --- a/.planning/phases/05-cloud-storage-backends/05-UAT.md +++ b/.planning/phases/05-cloud-storage-backends/05-UAT.md @@ -2,195 +2,133 @@ status: diagnosed phase: 05-cloud-storage-backends source: - - 05-01-SUMMARY.md - - 05-02-SUMMARY.md - - 05-03-SUMMARY.md - - 05-04-SUMMARY.md - - 05-05-SUMMARY.md - - 05-06-SUMMARY.md - - 05-07-SUMMARY.md - - 05-08-SUMMARY.md -started: 2026-05-29T00:00:00Z -updated: 2026-05-30T00:00:00Z + - 05-09-SUMMARY.md + - 05-10-SUMMARY.md + - 05-11-SUMMARY.md +mode: gap-reverification +started: 2026-05-30T10:00:00Z +updated: 2026-05-30T11:00:00Z --- ## Current Test -## Current Test - [testing complete] ## Tests -### 1. Settings Cloud Storage Tab — 3-tab layout -expected: Open the app and navigate to Settings. The page shows three tabs: "Preferences", "AI Configuration", and "Cloud Storage". Clicking the "Cloud Storage" tab switches to the cloud view without a page reload. +### 1. OAuth initiate — Google Drive redirect +expected: | + In Settings → Cloud Storage tab, clicking "Connect" on the Google Drive row now + uses an authenticated fetch (with Bearer token) to POST/GET /api/cloud/oauth/initiate/google_drive. + The backend returns JSON {"url": "https://accounts.google.com/..."} (not a 302 redirect). + The frontend then sets window.location.href to that URL, redirecting the browser to Google's + OAuth consent screen. No 401 "Not authenticated" error occurs. +result: pass +note: "Google Drive redirect works. OneDrive redirect does NOT work — logged as additional gap below." + +### 2. Disconnect confirmation fits within row +expected: | + Clicking "Remove" (or "Disconnect") on an active cloud provider connection shows an inline + confirmation message within the same provider row. The confirmation text ("Do you really + want to remove…") is fully visible — no overflow off-screen, no horizontal scrollbar, + no text cut off. The text wraps gracefully if it's long. result: pass -### 2. All 4 providers visible in Cloud Storage tab -expected: In the Cloud Storage tab, four provider rows are shown — Google Drive, OneDrive, Nextcloud, and WebDAV server — each with a "Not connected" status badge and a "Connect" button (when no connections exist). +### 3. Edit button on ERROR-state provider rows +expected: | + A cloud provider connection in "ERROR" state (failed auth, bad credentials) shows both + an "Edit" button and a "Remove" button in its row — matching the ACTIVE state layout. + Clicking "Edit" opens the credential modal pre-populated with the stored server URL + and username. The password field is empty (not returned from backend for security). result: pass -### 3. WebDAV / Nextcloud credential modal opens -expected: Clicking "Connect" on either the Nextcloud or WebDAV server row opens a modal overlay. The modal contains: Server URL field, Username field, Auth Method radio buttons ("App password" and "Account password"), and a Password field. Pressing Escape or clicking outside the modal closes it without saving. +### 4. Nextcloud custom endpoint preserved on re-edit +expected: | + When editing a Nextcloud or WebDAV connection that was originally saved with a custom + WebDAV path (not the auto-constructed /remote.php/dav/files/{username}/ default), the + edit modal opens with the Advanced section already expanded and the custom endpoint field + pre-populated with the exact stored URL. No data is silently discarded. result: pass -### 4. Cloud Storage sidebar section — collapsible -expected: The left sidebar shows a "Cloud Storage" collapsible section positioned between the Folders section and the Topics section. Clicking the section header collapses and expands it. -result: pass - -### 5. Cloud Storage sidebar empty state -expected: When no cloud connections are active, the Cloud Storage sidebar section shows "No cloud storage connected" text and a link or reference to Settings where the user can connect a provider. -result: pass - -### 6. OAuth initiate — Google Drive redirect -expected: In Settings → Cloud Storage tab, clicking "Connect" on the Google Drive row redirects the browser to Google's OAuth consent screen (accounts.google.com). Note: requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to be configured in .env. +### 5. Cloud document — open, re-analyze, edit +expected: | + For a document stored on a cloud backend (e.g. Nextcloud or WebDAV): + (a) Open/Preview: clicking the document opens a preview or download without a 401 error. + Content is fetched via authenticated proxy, not a bare unauthenticated URL. + (b) Re-analyze: triggering re-analysis on the document successfully extracts text + from the cloud-stored file (not from MinIO where the file doesn't exist). + (c) Edit/rename: if a rename or folder-move UI exists, it completes via PATCH endpoint + without a 404 "endpoint not found" error. result: issue -reported: "Clicking Connect redirects browser to http://localhost:5173/api/cloud/oauth/initiate/google_drive and returns {\"detail\":\"Not authenticated\"}" -severity: major +reported: "Nothing from a to c works and the drag and drop box for upload disappeared." +severity: blocker -### 7. OAuth initiate — OneDrive redirect -expected: In Settings → Cloud Storage tab, clicking "Connect" on the OneDrive row redirects the browser to Microsoft's login page (login.microsoftonline.com). Note: requires ONEDRIVE_CLIENT_ID and ONEDRIVE_CLIENT_SECRET in .env. -result: skipped -reason: No server-side OAuth credentials configured; same bug as test 6 expected - -### 8. OAuth callback — success toast and tab routing -expected: After completing an OAuth flow and being redirected back, the Settings page opens with the Cloud Storage tab already active. A success banner/toast appears ("Google Drive connected" or similar) and auto-dismisses after ~5 seconds. The provider row now shows "Active" status. -result: skipped -reason: Depends on OAuth initiation (tests 6-7) which require credentials not yet configured - -### 9. Disconnect provider — inline confirmation -expected: On a provider row with an active connection, clicking "Remove" (or "Disconnect") shows an inline confirmation UI (ConfirmBlock) within the same row rather than a modal. Confirming removes the connection and the row returns to "Not connected" status with the "Connect" button. -result: issue -reported: "I can remove my test nextcloud connection. But the text asking me if I really want to remove the nextcloud connection does not render correctly — text overflows off screen." -severity: minor - -### 10. REQUIRES_REAUTH banner -expected: If a provider connection is in "Requires re-authentication" state (expired or revoked token), the provider row shows a yellow warning banner with a "Reconnect" button. Other providers are unaffected. -result: skipped -reason: Only applies to OAuth providers (Google Drive, OneDrive); WebDAV/Nextcloud does not set REQUIRES_REAUTH on auth failure. Cannot test OAuth flow without client credentials configured. - -### 11. Active connection sidebar tree — expand and lazy-load folders -expected: When a cloud connection is active, its provider appears as a tree node in the sidebar Cloud Storage section. Clicking the expand arrow for the first time shows a "Loading…" state, then populates with the root-level folders from the cloud provider. Folders with sub-folders can be expanded recursively. +### 6. Admin hard-delete user with password confirmation +expected: | + In Admin → Users tab, each non-admin user row has a "Delete" button alongside the + existing "Deactivate" button. + Clicking "Delete" opens an inline confirmation panel (within the row, not a modal) + with an admin password field. Submitting with the wrong admin password is rejected + with an error message. Submitting with the correct admin password permanently removes + the user and closes the panel. The user no longer appears in the list. result: pass -### 12. Upload document to cloud backend -expected: Using the document upload flow with a target of a connected cloud backend (e.g. Google Drive), the upload completes successfully. The document appears in the document list with a storage indicator showing the cloud provider (not MinIO). The content can be viewed. -result: pass - -### 13. Cloud document content proxy -expected: Opening a document stored on a cloud backend loads and displays its content correctly (the file is streamed through the backend proxy). No error or missing content. -result: issue -reported: "I neither can open nor re-analyze nor edit any file stored on a cloud backend." -severity: major - -### 14. Admin user deletion cleans up cloud connections -expected: (Admin only) When an admin deletes a user account that has cloud connections, the deletion completes successfully (200 response). After deletion, no CloudConnection rows remain for that user in the database. The audit log contains a "cloud.credentials_purged" entry. -result: issue -reported: "I only can deactivate a user. I want to have a (admin-password protected) option to delete a user completely." -severity: major - ## Summary -total: 14 -passed: 7 -issues: 6 -skipped: 3 -blocked: 0 +total: 6 +passed: 5 +issues: 1 pending: 0 +skipped: 0 +blocked: 0 ## Gaps -- truth: "Admin panel must provide a hard-delete option (admin-password protected) to permanently remove a user and all associated data including cloud connections" +- truth: "Clicking Connect on OneDrive should redirect the browser to Microsoft's OAuth consent screen via authenticated fetch" status: failed - reason: "User reported: I only can deactivate a user. I want to have a (admin-password protected) option to delete a user completely." + reason: "User reported: Microsoft/OneDrive redirect does not work (Google Drive works)" severity: major - test: 14 - root_cause: "Backend DELETE /api/admin/users/{id} exists and correctly purges cloud connections + emits cloud.credentials_purged audit log. The gap is entirely in the frontend: adminDeleteUser() is absent from client.js, no Delete button exists in AdminUsersTab.vue, and the backend endpoint currently takes no body so cannot verify admin password before executing the delete." + test: 1-onedrive + root_cause: "Frontend and backend code are symmetric for both providers — the authenticated-fetch fix WAS applied to both. Most likely cause: ONEDRIVE_CLIENT_ID / ONEDRIVE_CLIENT_SECRET env vars are not configured. With empty credentials, msal.ConfidentialClientApplication raises an error or returns a malformed URL → backend returns 500 → frontend shows error toast. Google Drive credentials ARE configured, OneDrive are not." artifacts: - - path: "frontend/src/api/client.js" - issue: "Missing adminDeleteUser(id, adminPassword) function" - - path: "frontend/src/components/admin/AdminUsersTab.vue" - issue: "No Delete button or admin-password confirmation flow" - - path: "backend/api/admin.py" - issue: "DELETE endpoint takes no body; needs UserDeleteConfirm model to verify admin password before proceeding" + - path: "backend/config.py" + issue: "onedrive_client_id / onedrive_client_secret default to empty string; no validation that they are set before attempting OAuth flow" + - path: "backend/api/cloud.py" + issue: "oauth_initiate (lines 370-384): no pre-check for empty credentials before calling msal — a missing-config error looks identical to a code bug to the user" missing: - - "adminDeleteUser(id, adminPassword) in client.js calling DELETE /api/admin/users/{id}" - - "UserDeleteConfirm Pydantic model + password verification in delete_user handler" - - "Inline delete confirmation panel in AdminUsersTab.vue (mirroring confirmDeactivate pattern) with admin password field" + - "Add a pre-flight config check in oauth_initiate: if provider == 'onedrive' and not settings.onedrive_client_id, raise HTTPException(400, detail='OneDrive credentials not configured') before touching MSAL" + - "Configure ONEDRIVE_CLIENT_ID, ONEDRIVE_CLIENT_SECRET, ONEDRIVE_TENANT_ID in .env if OneDrive integration is needed" - truth: "Opening, re-analyzing, and editing a document stored on a cloud backend should work correctly via the backend proxy" status: failed - reason: "User reported: I neither can open nor re-analyze nor edit any file stored on a cloud backend." - severity: major - test: 13 - root_cause: "Three independent root causes: (1) Open — DocumentPreviewModal uses unauthenticated iframe :src and DocumentView uses window.open() to /content endpoint that requires Bearer auth; browser navigation never sends Authorization header → 401. (2) Re-analyze — document_tasks.py calls get_storage_backend() unconditionally returning MinIO; for cloud docs the MinIO key does not exist → NoSuchKey/extract_failed. (3) Edit/rename — no PATCH /api/documents/{id} endpoint exists at all." + reason: "User reported: Nothing from a to c works." + severity: blocker + test: 5 + root_cause: "Code fixes from 05-09 ARE in place in all files (confirmed by code review): fetchDocumentContent() with Bearer token in client.js, Blob URL in DocumentPreviewModal.vue + DocumentView.vue, PATCH endpoint in documents.py, cloud-aware re-analyze in document_tasks.py. Most likely runtime cause: (1) Celery worker was NOT restarted after 05-09 changes — celery has no --reload flag in docker-compose.yml so old MinIO-hardcoded task code runs until worker is restarted. (2) Preview/open: uvicorn has --reload so content endpoint is current, but the document being tested may have been uploaded before 05-09 with a bad object_key stored in DB, OR the CloudConnection status is not ACTIVE." artifacts: - - path: "frontend/src/components/documents/DocumentPreviewModal.vue" - issue: "Uses unauthenticated iframe :src for auth-required /content endpoint" - - path: "frontend/src/views/DocumentView.vue" - issue: "Uses window.open() for auth-required /content URL" - - path: "frontend/src/api/client.js" - issue: "getDocumentContentUrl() returns raw URL; no authenticated fetch" + - path: "docker-compose.yml" + issue: "celery-worker has no --reload; code changes to document_tasks.py require manual worker restart (docker compose restart celery-worker)" - path: "backend/tasks/document_tasks.py" - issue: "Hardcodes get_storage_backend() (MinIO) instead of routing to cloud backend based on doc.storage_backend" + issue: "Cloud-aware routing is present and correct — but only if the worker has reloaded the new code" - path: "backend/api/documents.py" - issue: "No PATCH /{doc_id} endpoint for document metadata editing" + issue: "stream_document_content: if CloudConnection status != ACTIVE, returns 503; if cloud backend get_object raises non-CloudConnectionError exception, returns 500 without a user-friendly message" missing: - - "Authenticated content fetch: either signed query-string token on /content endpoint, or frontend fetches bytes with Bearer header and creates Blob URL" - - "Cloud-aware re-analyze: detect doc.storage_backend != 'minio' and load CloudConnection in Celery task to fetch file bytes" - - "PATCH /api/documents/{doc_id} endpoint accepting {filename, folder_id}" + - "Restart celery-worker container: docker compose restart celery-worker" + - "Verify the test document's storage_backend field is set correctly (not 'minio') and object_key matches what the cloud backend expects" + - "Add user-friendly error in stream_document_content: catch Exception broadly and surface a 502 'Cloud backend unreachable' rather than 500" -- truth: "Nextcloud credential modal should accept just the server URL and auto-construct the WebDAV endpoint; full path should be hidden under an expandable Advanced option" +- truth: "Drag-and-drop upload box should be visible wherever the user expects to upload files" status: failed - reason: "User reported: modal requires the full WebDAV path causing connection failure. Fix: auto-construct https://{server}/remote.php/dav/files/{username}/ for Nextcloud; add Advanced override for non-standard installs." - severity: major - test: 9 - root_cause: "Modal already auto-constructs the WebDAV URL from server+username and hides the full path behind an Advanced collapsible — this part was already built. The actual bug is in the edit pre-population watch: it extracts only the hostname from any stored server_url, so if the stored URL was a custom endpoint it is silently discarded and the Advanced field is never re-populated, losing the custom path on re-edit." + reason: "User reported: the drag and drop box for upload disappeared." + severity: blocker + test: 5-regression + root_cause: "DropZone IS present unconditionally in FileManagerView (line 37) and CloudFolderView (line 30). It is ABSENT from CloudStorageView (/cloud — the new overview page added in commit 5250895). The sidebar 'Cloud Storage' link was changed from /settings to /cloud in the same commit. User navigating via sidebar 'Cloud Storage' now lands on CloudStorageView which has no upload zone, explaining why the DropZone 'disappeared'." artifacts: - - path: "frontend/src/components/cloud/CloudCredentialModal.vue" - issue: "watch handler (lines 195-208) always extracts only hostname match[1] and resets customEndpoint to ''; custom endpoint stored values are never restored on edit" + - path: "frontend/src/views/CloudStorageView.vue" + issue: "No DropZone component — shows cloud connections list only" + - path: "frontend/src/components/layout/AppSidebar.vue" + issue: "Cloud Storage sidebar link changed to /cloud (commit 5250895) which routes to DropZone-less CloudStorageView" missing: - - "Detect on edit whether stored server_url matches the auto-constructed pattern; if not, set showAdvanced=true and populate customEndpoint with the full stored URL" - -- truth: "User should be able to edit credentials of an existing connection without disconnecting first" - status: failed - reason: "User reported: no Edit button exists on connected provider rows; user must disconnect and re-enter all credentials to change any setting." - severity: major - test: 9 - root_cause: "Edit button exists for ACTIVE status Nextcloud/WebDAV rows but is absent from the ERROR status template block. A connection in error state forces the user to remove and re-enter credentials instead of editing in-place." - artifacts: - - path: "frontend/src/components/settings/SettingsCloudTab.vue" - issue: "ERROR status template block (lines 89-96) contains only a Remove button; no Edit button, unlike the ACTIVE block" - missing: - - "Add Edit button to the ERROR status template block mirroring the ACTIVE block" - -- truth: "Clicking Connect on Google Drive/OneDrive should redirect the browser to the provider's OAuth consent screen" - status: failed - reason: "User reported: window.location.href navigates to /api/cloud/oauth/initiate/{provider} without a JWT auth header; backend returns 401 Not authenticated. Fix: call /initiate via fetch() with Authorization header, receive OAuth URL in response, then redirect browser to that URL." - severity: major - test: 6 - root_cause: "handleConnect() in SettingsCloudTab.vue uses window.location.href = '/api/cloud/oauth/initiate/{provider}' — bare browser navigation sends no Authorization header. The endpoint uses Depends(get_regular_user) which requires Bearer token → returns 401. Fix: change oauth_initiate to return JSON {url: ...} (status 200) instead of 302 redirect; frontend calls it via fetch() with Bearer header then sets window.location.href to the returned URL." - artifacts: - - path: "frontend/src/components/settings/SettingsCloudTab.vue" - issue: "handleConnect uses window.location.href instead of authenticated fetch" - - path: "backend/api/cloud.py" - issue: "oauth_initiate returns RedirectResponse(302); needs to return JSON {url} so fetch() can consume it" - missing: - - "Replace window.location.href with fetch() + Authorization header in handleConnect" - - "Change oauth_initiate to return JSONResponse({url: authorization_url}) instead of RedirectResponse" - -- truth: "Disconnect confirmation text should render fully within the provider row without overflowing off screen" - status: failed - reason: "User reported: the text asking 'Do you really want to remove…' overflows off screen." - severity: minor - test: 9 - root_cause: "Confirmation wrapper div lacks w-full and overflow-hidden; sits inside a flex row that allows children to grow beyond viewport. ConfirmBlock's
has no break-words constraint." - artifacts: - - path: "frontend/src/components/settings/SettingsCloudTab.vue" - issue: "Confirmation wrapper div (line ~102) missing w-full overflow-hidden; may also need to be rendered outside the flex items-center row as a full-width block below it" - - path: "frontend/src/components/ui/ConfirmBlock.vue" - issue: "
message element missing break-words / overflow-wrap constraint" - missing: - - "Add w-full overflow-hidden to confirmation wrapper in SettingsCloudTab.vue" - - "Add break-words to message
in ConfirmBlock.vue" + - "Add DropZone + UploadProgress to CloudStorageView so users can upload without first navigating into a specific cloud folder" + - "OR add a note/CTA in CloudStorageView directing users to navigate into a folder to upload"