---
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
---

## Current Test
<!-- OVERWRITE each test - shows where we are -->

## 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.
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).
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.
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.
result: issue
reported: "Clicking Connect redirects browser to http://localhost:5173/api/cloud/oauth/initiate/google_drive and returns {\"detail\":\"Not authenticated\"}"
severity: major

### 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.
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
pending: 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"
  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."
  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."
  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"
  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"

- 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."
  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: "backend/tasks/document_tasks.py"
      issue: "Hardcodes get_storage_backend() (MinIO) instead of routing to cloud backend based on doc.storage_backend"
    - path: "backend/api/documents.py"
      issue: "No PATCH /{doc_id} endpoint for document metadata editing"
  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}"

- 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"
  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."
  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"
  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 <p> 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: "<p> 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 <p> in ConfirmBlock.vue"
