#!/usr/bin/env bash # ============================================================= # OxiCloud – Storage disk-cleanup verification # ============================================================= # 1. Moves every live file and folder to trash via the REST API. # 2. Calls DELETE /api/trash/empty to permanently delete all # remaining trash items (including any left by previous tests). # 3. Asserts that no regular files remain under # $OXICLOUD_STORAGE_PATH/.thumbnails or .blobs. # # Called by run.sh after all Hurl tests have passed. # Can also be run standalone (server must already be up): # bash tests/api/storage_cleanup_check.sh # ============================================================= cat <&2; exit 1; } # ── 1. Login ────────────────────────────────────────────────────────────────── TOKEN=$(curl -sf -X POST "$base_url/api/auth/login" \ -H "Content-Type: application/json" \ -d "{\"username\":\"$username\",\"password\":\"$password\"}" \ | jq -r '.access_token') [[ -z "$TOKEN" || "$TOKEN" == "null" ]] && fail "login failed" log "Logged in." AUTH="Authorization: Bearer $TOKEN" # ── 1b. Upload a probe image and verify its blob + thumbnail exist on disk ───── # shellcheck source=../common/internal_storage_helper.sh source "$REPO_ROOT/tests/common/internal_storage_helper.sh" FIXTURE="$REPO_ROOT/tests/fixtures/blue-image.png" HOME_FOLDER_ID=$(curl -sf -H "$AUTH" "$base_url/api/folders" | jq -r '.[0].id') [[ -z "$HOME_FOLDER_ID" || "$HOME_FOLDER_ID" == "null" ]] && fail "could not get home folder id" PROBE_FILE_ID=$(curl -sf -X POST -H "$AUTH" \ -F "folder_id=$HOME_FOLDER_ID" \ -F "file=@$FIXTURE;type=image/png" \ "$base_url/api/files/upload" | jq -r '.id') [[ -z "$PROBE_FILE_ID" || "$PROBE_FILE_ID" == "null" ]] && fail "probe file upload failed" log "Probe file uploaded (id=$PROBE_FILE_ID)." # GET thumbnail to trigger on-demand generation HTTP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -H "$AUTH" \ "$base_url/api/files/$PROBE_FILE_ID/thumbnail/icon") [[ "$HTTP_STATUS" != "200" ]] && fail "thumbnail GET returned HTTP $HTTP_STATUS (expected 200)" log "Thumbnail fetched (HTTP 200)." assert_local_blob_existsy "$FIXTURE" "$STORAGE_PATH" || fail "probe blob not found on disk" assert_preview_existsy "$FIXTURE" "$STORAGE_PATH" || fail "probe thumbnail not found on disk" log "Probe blob and thumbnail confirmed present on disk." # ── 1c. Delete every non-admin user created by earlier Hurl tests ───────────── # # Tests like permissions.hurl and grants.hurl create user accounts (bob, # dave, eve, adam, frank, …) that own their own folders/files. The probe # cleanup below only sees admin-owned roots, so those other users' files # would leak as orphan blobs on disk. Deleting the users cascades through # the schema (storage.folders/storage.files via ON DELETE CASCADE), which # fires the file-delete trigger and decrements blob ref_counts. The # subsequent trash-empty triggers garbage_collect() to remove the # now-orphaned blob files from disk. # /api/admin/users returns { users: [...], total, limit, offset } USERS_JSON=$(curl -sf -H "$AUTH" "$base_url/api/admin/users?limit=500") ADMIN_USER_ID=$(echo "$USERS_JSON" \ | jq -r --arg u "$username" '.users[] | select(.username == $u) | .id') [[ -z "$ADMIN_USER_ID" || "$ADMIN_USER_ID" == "null" ]] && fail "could not resolve admin user id" OTHER_USER_IDS=$(echo "$USERS_JSON" \ | jq -r --arg admin_id "$ADMIN_USER_ID" '.users[] | select(.id != $admin_id) | .id') OTHER_USER_COUNT=0 while IFS= read -r uid; do [[ -z "$uid" ]] && continue OTHER_USER_COUNT=$((OTHER_USER_COUNT + 1)) curl -sf -X DELETE -H "$AUTH" "$base_url/api/admin/users/$uid" >/dev/null \ || fail "failed to delete user $uid" done <<< "$OTHER_USER_IDS" log "Deleted $OTHER_USER_COUNT non-admin user(s) created by tests." # ── 2. Move all live files and folders to trash ─────────────────────────────── # # For each root folder, list its direct children and soft-delete them. # The server cascades folder deletion to all nested contents, so we only # need to iterate one level deep. ROOT_FOLDERS=$(curl -sf -H "$AUTH" "$base_url/api/folders" | jq -r '.[].id') for folder_id in $ROOT_FOLDERS; do CONTENTS=$(curl -sf -H "$AUTH" "$base_url/api/folders/$folder_id/listing") while IFS= read -r sub_id; do [[ -z "$sub_id" ]] && continue curl -sf -X DELETE -H "$AUTH" "$base_url/api/folders/$sub_id" >/dev/null done < <(echo "$CONTENTS" | jq -r '.folders[].id') while IFS= read -r file_id; do [[ -z "$file_id" ]] && continue curl -sf -X DELETE -H "$AUTH" "$base_url/api/files/$file_id" >/dev/null done < <(echo "$CONTENTS" | jq -r '.files[].id') done log "All live objects moved to trash." # ── 2b. Verify all root folders are empty according to the API ──────────────── for folder_id in $ROOT_FOLDERS; do CONTENTS=$(curl -sf -H "$AUTH" "$base_url/api/folders/$folder_id/listing") SUB_COUNT=$(echo "$CONTENTS" | jq '.folders | length') FILE_COUNT=$(echo "$CONTENTS" | jq '.files | length') if [[ "$SUB_COUNT" -ne 0 || "$FILE_COUNT" -ne 0 ]]; then fail "folder $folder_id still has $SUB_COUNT subfolder(s) and $FILE_COUNT file(s)" fi done log "API confirms all root folders are empty." # ── 3. Permanently delete everything in trash ───────────────────────────────── curl -sf -X DELETE -H "$AUTH" "$base_url/api/trash/empty" >/dev/null log "Trash emptied." # ── 3b. Verify trash is empty according to the API ─────────────────────────── TRASH_COUNT=$(curl -sf -H "$AUTH" "$base_url/api/trash/resources" | jq '.items | length') if [[ "$TRASH_COUNT" -ne 0 ]]; then fail "trash still contains $TRASH_COUNT item(s) after empty" fi log "API confirms trash is empty." # ── 4. Disk verification ────────────────────────────────────────────────────── THUMB_FILES=$(find "$STORAGE_PATH/.thumbnails" -type f 2>/dev/null || true) BLOB_FILES=$(find "$STORAGE_PATH/.blobs" -type f 2>/dev/null || true) if [[ -n "$THUMB_FILES" || -n "$BLOB_FILES" ]]; then # Async thumbnail/blob workers may still be flushing writes from the # last test's uploads when the cleanup phase reaches this point — # particularly on fast CI runners where the test loop outpaces the # worker. Poll for up to 5 s and exit the loop the moment storage # drains. TODO: replace with a deterministic worker-drain signal # (e.g. queue depth on /ready) when one exists. log "Thumb/blob leftovers detected — polling for async worker drain (race guard)" for attempt in 1 2 3 4 5; do sleep 1 THUMB_FILES=$(find "$STORAGE_PATH/.thumbnails" -type f 2>/dev/null || true) BLOB_FILES=$(find "$STORAGE_PATH/.blobs" -type f 2>/dev/null || true) [[ -z "$THUMB_FILES" && -z "$BLOB_FILES" ]] && break log " attempt $attempt: still present, retrying..." done fi # Chunked-upload spool. After every chunked-upload session is either # completed (assembled + promoted) or aborted, this dir MUST be empty # — a leftover chunk file means a session-cleanup path forgot its # `remove_dir_all`, which under sustained sync workloads is the # classic "disk fills up over the weekend" failure mode. # # `.uploads/` is the default chunked-upload root when # `OXICLOUD_CHUNK_DIR` is unset (see `common/di.rs`). REST sessions # land under `.uploads//`; NC sessions land under # `.uploads/nextcloud///`. # # We deliberately do NOT check the direct-PUT spool dir here: when # `OXICLOUD_UPLOAD_TEMP_DIR` is unset (the default in the test env) # it falls back to the OS temp dir (`/tmp/…`) which is shared with # the rest of the system and would produce false positives. To # extend the check to direct-PUT, set OXICLOUD_UPLOAD_TEMP_DIR in # tests/common/server.env to a path under $STORAGE_PATH and add it # to the find list below. UPLOAD_FILES=$(find "$STORAGE_PATH/.uploads" -type f 2>/dev/null || true) if [[ -n "$THUMB_FILES" ]]; then THUMB_COUNT=$(echo "$THUMB_FILES" | wc -l | tr -d ' ') log "Leftover thumbnail files ($THUMB_COUNT):" echo "$THUMB_FILES" fail "$THUMB_COUNT thumbnail file(s) remain on disk after full cleanup" fi if [[ -n "$BLOB_FILES" ]]; then BLOB_COUNT=$(echo "$BLOB_FILES" | wc -l | tr -d ' ') log "Leftover blob files ($BLOB_COUNT):" echo "$BLOB_FILES" fail "$BLOB_COUNT blob file(s) remain on disk after full cleanup" fi if [[ -n "$UPLOAD_FILES" ]]; then UPLOAD_COUNT=$(echo "$UPLOAD_FILES" | wc -l | tr -d ' ') log "Leftover chunked-upload files ($UPLOAD_COUNT):" echo "$UPLOAD_FILES" fail "$UPLOAD_COUNT chunked-upload file(s) remain in .uploads after full cleanup" fi log "OK — no blobs, thumbnails, or chunked-upload leftovers remain on disk."