2026-05-13 10:16:53 +02:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
# =============================================================
|
|
|
|
|
|
# OxiCloud – Bug 1 & 2: thumbnail refresh after WebDAV PUT
|
|
|
|
|
|
# =============================================================
|
|
|
|
|
|
# Bug 1: stale moka cache served after blob swap (overwrite)
|
|
|
|
|
|
# Bug 2: no background thumbnail generation after update
|
|
|
|
|
|
#
|
|
|
|
|
|
# Fix (webdav_handler.rs handle_put): after update branch,
|
|
|
|
|
|
# refresh_thumbnails_after_update() calls delete_thumbnails()
|
|
|
|
|
|
# (evicts moka) then spawns background regen from new blob.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Test strategy:
|
|
|
|
|
|
# 1. PUT dedup-test.jpg via /webdav → thumbnail generated
|
|
|
|
|
|
# 2. Prime the moka cache with GET /thumbnail
|
|
|
|
|
|
# 3. PUT oxicloud-logo.jpg to the same path (overwrite)
|
|
|
|
|
|
# 4. GET /thumbnail again → must return different bytes
|
|
|
|
|
|
#
|
|
|
|
|
|
# Bug 1 detection: if moka is not evicted, step 4 returns the
|
|
|
|
|
|
# cached dedup-test thumbnail → SHA-256 matches step 2 → test fails.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Prerequisites:
|
|
|
|
|
|
# - Server running at base_url with credentials from test.env
|
|
|
|
|
|
# - OXICLOUD_ENABLE_AUTH=true (/webdav uses JWT Bearer auth)
|
|
|
|
|
|
# - jq in PATH
|
|
|
|
|
|
#
|
|
|
|
|
|
# Run (from repo root):
|
|
|
|
|
|
# bash tests/webdav/test_thumbnail_update.sh
|
|
|
|
|
|
# =============================================================
|
|
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
|
|
cd "$SCRIPT_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
source test.env
|
|
|
|
|
|
source common.sh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── helpers ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
PASS=0
|
|
|
|
|
|
FAIL=0
|
|
|
|
|
|
|
|
|
|
|
|
pass() { PASS=$(( PASS + 1 )); echo " PASS: $*"; }
|
|
|
|
|
|
fail() { FAIL=$(( FAIL + 1 )); echo " FAIL: $*" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
|
|
# WebDAV PUT: returns HTTP status code
|
|
|
|
|
|
webdav_put() {
|
|
|
|
|
|
local remote_name="$1" local_file="$2" mime="${3:-application/octet-stream}"
|
|
|
|
|
|
curl -s -o /dev/null -w "%{http_code}" \
|
|
|
|
|
|
-X PUT \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
-H "Content-Type: $mime" \
|
|
|
|
|
|
--data-binary "@$local_file" \
|
|
|
|
|
|
"$base_url/webdav/$remote_name"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# WebDAV DELETE: returns HTTP status code
|
|
|
|
|
|
webdav_delete() {
|
|
|
|
|
|
local remote_name="$1"
|
|
|
|
|
|
curl -s -o /dev/null -w "%{http_code}" \
|
|
|
|
|
|
-X DELETE \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url/webdav/$remote_name"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# REST GET with JWT bearer
|
|
|
|
|
|
rest_get() {
|
|
|
|
|
|
curl -s \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url$1"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# REST DELETE with JWT bearer, returns HTTP status code
|
|
|
|
|
|
rest_delete() {
|
|
|
|
|
|
curl -s -o /dev/null -w "%{http_code}" \
|
|
|
|
|
|
-X DELETE \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url$1"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Download thumbnail and return its SHA-256 checksum
|
|
|
|
|
|
thumbnail_sha256() {
|
|
|
|
|
|
local file_id="$1"
|
|
|
|
|
|
curl -s \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url/api/files/$file_id/thumbnail/icon" \
|
|
|
|
|
|
| sha256sum | cut -d' ' -f1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# SHA-256 of an empty stream (thumbnail missing = empty body)
|
|
|
|
|
|
EMPTY_SHA="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
|
|
|
|
|
|
|
|
|
|
FIXTURE_V1="$REPO_ROOT/tests/fixtures/dedup-test.jpg"
|
|
|
|
|
|
FIXTURE_V2="$REPO_ROOT/tests/fixtures/oxicloud-logo.jpg"
|
|
|
|
|
|
|
|
|
|
|
|
[[ -f "$FIXTURE_V1" ]] || { echo "Missing fixture: $FIXTURE_V1" >&2; exit 1; }
|
|
|
|
|
|
[[ -f "$FIXTURE_V2" ]] || { echo "Missing fixture: $FIXTURE_V2" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
|
echo "=== Bug 1 & 2: thumbnail refresh after WebDAV PUT overwrite ==="
|
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
|
|
# ── authenticate ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
oxicloud_login
|
|
|
|
|
|
|
|
|
|
|
|
# ── home folder ID ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
echo " home folder..."
|
|
|
|
|
|
HOME_FOLDER_ID=$(rest_get "/api/folders" | jq -r '.[0].id')
|
|
|
|
|
|
[[ -n "$HOME_FOLDER_ID" && "$HOME_FOLDER_ID" != "null" ]] \
|
|
|
|
|
|
|| fail "Could not retrieve home folder ID"
|
|
|
|
|
|
echo " home folder id: $HOME_FOLDER_ID"
|
|
|
|
|
|
|
|
|
|
|
|
REMOTE="webdav-thumb-bug12.jpg"
|
|
|
|
|
|
|
|
|
|
|
|
# ── Pre-test cleanup (idempotent) ─────────────────────────────
|
|
|
|
|
|
# Remove any leftover from a previous run via the REST API so that
|
|
|
|
|
|
# the first WebDAV PUT below is guaranteed to be a CREATE (201).
|
|
|
|
|
|
|
|
|
|
|
|
echo " cleanup: checking regular listing for '$REMOTE'..."
|
|
|
|
|
|
EXISTING_ID=$(rest_get "/api/files?folder_id=$HOME_FOLDER_ID" \
|
|
|
|
|
|
| jq -r --arg n "$REMOTE" 'first(.[] | select(.name == $n) | .id) // empty')
|
|
|
|
|
|
if [[ -n "$EXISTING_ID" ]]; then
|
|
|
|
|
|
echo " cleanup: found existing file id=$EXISTING_ID — deleting..."
|
|
|
|
|
|
ST=$(rest_delete "/api/files/$EXISTING_ID")
|
|
|
|
|
|
echo " cleanup: DELETE /api/files/$EXISTING_ID → $ST"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo " cleanup: no existing file in regular listing"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo " cleanup: checking trash for '$REMOTE'..."
|
2026-06-11 03:06:47 +02:00
|
|
|
|
STALE=$(rest_get "/api/trash/resources" \
|
|
|
|
|
|
| jq -r --arg n "$REMOTE" 'first(.items[] | select(.resource.name == $n) | .resource.id) // empty')
|
2026-05-13 10:16:53 +02:00
|
|
|
|
if [[ -n "$STALE" ]]; then
|
|
|
|
|
|
echo " cleanup: found trash item id=$STALE — purging..."
|
|
|
|
|
|
ST=$(rest_delete "/api/trash/$STALE")
|
|
|
|
|
|
echo " cleanup: DELETE /api/trash/$STALE → $ST"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo " cleanup: trash is clean"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# ── Step 1: PUT dedup-test.jpg ───────────────────────────────
|
2026-06-30 20:45:08 +02:00
|
|
|
|
# Post commit 43cf4a2b, /webdav distinguishes create (201) from
|
|
|
|
|
|
# overwrite (204) per RFC 7231 §4.3.4. The cleanup loop above
|
|
|
|
|
|
# (regular-listing + trash purge) guarantees this is a fresh
|
|
|
|
|
|
# resource, so we expect 201. Step 2 below tests the overwrite
|
|
|
|
|
|
# case (expects 204) — the 201/204 split itself is the regression
|
|
|
|
|
|
# guard.
|
2026-05-13 10:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
echo " step 1: PUT $REMOTE..."
|
|
|
|
|
|
STATUS=$(webdav_put "$REMOTE" "$FIXTURE_V1" "image/jpeg")
|
|
|
|
|
|
echo " step 1: WebDAV PUT → $STATUS"
|
2026-06-30 20:45:08 +02:00
|
|
|
|
[[ "$STATUS" == "201" ]] || fail "WebDAV PUT expected 201, got $STATUS"
|
|
|
|
|
|
pass "WebDAV PUT dedup-test.jpg → 201"
|
2026-05-13 10:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
# ── find file_id from REST listing ───────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 1: resolving file_id..."
|
|
|
|
|
|
FILE_ID=$(rest_get "/api/files?folder_id=$HOME_FOLDER_ID" \
|
|
|
|
|
|
| jq -r --arg n "$REMOTE" '.[] | select(.name == $n) | .id')
|
|
|
|
|
|
[[ -n "$FILE_ID" && "$FILE_ID" != "null" ]] \
|
|
|
|
|
|
|| fail "File '$REMOTE' not found in folder listing after WebDAV PUT"
|
|
|
|
|
|
pass "File found via REST API — id=$FILE_ID"
|
|
|
|
|
|
|
|
|
|
|
|
# ── Step 2: GET thumbnail to prime moka cache ────────────────
|
|
|
|
|
|
# Background generation may still be running; wait briefly.
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 2: waiting 1s for background thumbnail generation..."
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 2: GET /thumbnail/icon..."
|
|
|
|
|
|
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url/api/files/$FILE_ID/thumbnail/icon")
|
|
|
|
|
|
echo " step 2: GET /thumbnail → $HTTP"
|
|
|
|
|
|
[[ "$HTTP" == "200" ]] \
|
|
|
|
|
|
|| fail "GET /thumbnail after initial upload expected 200, got $HTTP"
|
|
|
|
|
|
|
|
|
|
|
|
THUMB_V1=$(thumbnail_sha256 "$FILE_ID")
|
|
|
|
|
|
echo " step 2: thumbnail sha256=$THUMB_V1"
|
|
|
|
|
|
[[ -n "$THUMB_V1" && "$THUMB_V1" != "$EMPTY_SHA" ]] \
|
|
|
|
|
|
|| fail "Thumbnail after initial upload is empty (sha256=$THUMB_V1)"
|
|
|
|
|
|
pass "Initial thumbnail present and non-empty (sha256=$THUMB_V1)"
|
|
|
|
|
|
|
|
|
|
|
|
# ── Step 3: PUT oxicloud-logo.jpg (overwrite) ────────────────
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 3: PUT $REMOTE (overwrite)..."
|
|
|
|
|
|
STATUS=$(webdav_put "$REMOTE" "$FIXTURE_V2" "image/jpeg")
|
|
|
|
|
|
echo " step 3: WebDAV PUT → $STATUS"
|
|
|
|
|
|
[[ "$STATUS" == "204" ]] || fail "WebDAV PUT (overwrite) expected 204, got $STATUS"
|
|
|
|
|
|
pass "WebDAV PUT oxicloud-logo.jpg overwrite → 204 No Content"
|
|
|
|
|
|
|
|
|
|
|
|
# ── Step 4: GET thumbnail after overwrite ────────────────────
|
|
|
|
|
|
# Fix: delete_thumbnails() evicts moka (bug 1),
|
|
|
|
|
|
# background regen populates new blob thumbnail (bug 2).
|
|
|
|
|
|
# Without the fix the stale dedup-test thumbnail is served from moka.
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 4: waiting 1s for background thumbnail regeneration..."
|
|
|
|
|
|
sleep 1
|
|
|
|
|
|
|
|
|
|
|
|
echo " step 4: GET /thumbnail/icon after overwrite..."
|
|
|
|
|
|
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
|
|
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
|
|
|
|
"$base_url/api/files/$FILE_ID/thumbnail/icon")
|
|
|
|
|
|
echo " step 4: GET /thumbnail → $HTTP"
|
|
|
|
|
|
[[ "$HTTP" == "200" ]] \
|
|
|
|
|
|
|| fail "GET /thumbnail after overwrite expected 200, got $HTTP"
|
|
|
|
|
|
|
|
|
|
|
|
THUMB_V2=$(thumbnail_sha256 "$FILE_ID")
|
|
|
|
|
|
echo " step 4: thumbnail sha256=$THUMB_V2"
|
|
|
|
|
|
[[ -n "$THUMB_V2" && "$THUMB_V2" != "$EMPTY_SHA" ]] \
|
|
|
|
|
|
|| fail "Thumbnail after overwrite is empty"
|
|
|
|
|
|
|
|
|
|
|
|
[[ "$THUMB_V1" != "$THUMB_V2" ]] \
|
|
|
|
|
|
|| fail "Bug 1 present: moka cache not evicted — thumbnail unchanged after overwrite (sha256=$THUMB_V1)"
|
|
|
|
|
|
pass "Thumbnail changed after overwrite — bugs 1 & 2 fixed (sha256=$THUMB_V2)"
|
|
|
|
|
|
|
|
|
|
|
|
# ── cleanup ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
echo " cleanup: WebDAV DELETE $REMOTE..."
|
|
|
|
|
|
STATUS=$(webdav_delete "$REMOTE")
|
|
|
|
|
|
echo " cleanup: WebDAV DELETE → $STATUS"
|
|
|
|
|
|
[[ "$STATUS" == "204" ]] || fail "WebDAV DELETE expected 204, got $STATUS"
|
|
|
|
|
|
pass "WebDAV DELETE → 204"
|
|
|
|
|
|
|
2026-06-11 03:06:47 +02:00
|
|
|
|
TRASH_ITEM=$(rest_get "/api/trash/resources" \
|
|
|
|
|
|
| jq -r --arg n "$REMOTE" 'first(.items[] | select(.resource.name == $n) | .resource.id) // empty')
|
2026-05-13 10:16:53 +02:00
|
|
|
|
if [[ -n "$TRASH_ITEM" ]]; then
|
|
|
|
|
|
ST=$(rest_delete "/api/trash/$TRASH_ITEM")
|
|
|
|
|
|
echo " cleanup: DELETE /api/trash/$TRASH_ITEM → $ST"
|
|
|
|
|
|
pass "Permanently deleted from trash"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# ── summary ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
|
echo "Results: $PASS passed, $FAIL failed."
|
|
|
|
|
|
[[ "$FAIL" -eq 0 ]] && echo "All tests passed." || exit 1
|