tests: add webdav tests

This commit is contained in:
Edouard Vanbelle
2026-05-13 10:16:53 +02:00
parent 07d1f18bf4
commit f7946028f4
7 changed files with 405 additions and 0 deletions
+5
View File
@@ -188,6 +188,11 @@ jobs:
env:
BUILD_TARGET: release
- name: Run Webdav tests
run: bash tests/webdav/run.sh
env:
BUILD_TARGET: release
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
+1
View File
@@ -75,3 +75,4 @@ front-test-update-snapshot:
# Hurl API functional tests (starts postgres + server, tears down after)
api-test:
bash tests/api/run.sh
bash tests/webdav/run.sh
+1
View File
@@ -0,0 +1 @@
# purpose is to test webdav coverage + different scenarios
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
source test.env
err() {
echo "$*" >&2
}
warn() {
echo "$*" >&2
}
success() {
echo "ok";
}
# Create the first admin account if the server is freshly initialised.
# Silently succeeds when the account already exists (403 = already set up).
oxicloud_setup() {
SETUP_DATA='{"username":"'$username'","email":"'$email'","password":"'$password'"}'
SETUP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d "$SETUP_DATA" "$base_url/api/setup")
case "$SETUP_STATUS" in
201) echo "setup: admin account created" ;;
403) ;; # already initialised — normal on second run
*) err "setup: unexpected status $SETUP_STATUS"; exit 1 ;;
esac
}
# returns TOKEN variable
oxicloud_login() {
oxicloud_setup
LOGIN_DATA='{"username":"'$username'","password":"'$password'"}'
LOGIN_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$LOGIN_DATA" $base_url/api/auth/login)
echo $?
jq -r '.error' <<<$LOGIN_RESPONSE
if [[ "$(jq -r '.error' <<<$LOGIN_RESPONSE)" != "null" ]]
then
err "Login Error: $LOGIN_RESPONSE"
exit 1
fi
TOKEN=$(jq -r '.access_token' <<<$LOGIN_RESPONSE)
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]
then
echo access_token missing in response: $LOGIN_RESPONSE
exit 1
fi
echo "login successful, Got JWT token containing informations:" $(cut -d . -f 2 <<<$TOKEN | base64 -d)
}
# remove trailing /
base_url="${base_url%/}"
echo starting $0 tests on server $base_url
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Full Hurl API test runner.
# Starts postgres + OxiCloud server, runs Hurl tests, tears everything down.
#
# Usage (from repo root):
# bash tests/api/run.sh
#
# Prerequisites: docker, cargo, hurl ≥ 4.0
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
COMMON="$REPO_ROOT/tests/common"
WEBDAV_DIR="$REPO_ROOT/tests/webdav"
# test.env is the single source of truth for connection details and credentials.
# shellcheck source=test.env
source "$WEBDAV_DIR/test.env"
# Derive server port from base_url (e.g. http://localhost:8087 → 8087)
SERVER_PORT="${base_url##*:}"
# ── Helpers ───────────────────────────────────────────────────────────────────
log() { echo "[api-test] $*"; }
die() { echo "[api-test] ERROR: $*" >&2; exit 1; }
wait_for_http() {
local url="$1" timeout="${2:-60}"
local deadline=$(( $(date +%s) + timeout ))
until curl -sf "$url" >/dev/null 2>&1; do
[[ $(date +%s) -ge $deadline ]] && die "Timeout waiting for $url"
sleep 1
done
}
# ── Teardown (always runs on exit) ────────────────────────────────────────────
SERVER_PID=""
cleanup() {
if [[ -n "$SERVER_PID" ]]; then
log "Stopping OxiCloud server (pid $SERVER_PID)..."
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
bash "$COMMON/stop-db.sh"
}
trap cleanup EXIT
# ── 1. Start postgres ─────────────────────────────────────────────────────────
bash "$COMMON/spawn-db.sh"
# ── 2. Load shared server env + port from .env ───────────────────────────────
set -a
# shellcheck source=../common/server.env
source "$COMMON/server.env"
OXICLOUD_SERVER_PORT=$SERVER_PORT
OXICLOUD_STORAGE_PATH="$REPO_ROOT/tests/api/storage"
set +a
mkdir -p "$OXICLOUD_STORAGE_PATH"
# ── 3. Start OxiCloud server ──────────────────────────────────────────────────
BUILD_TARGET="${BUILD_TARGET:-debug}"
OXICLOUD_BIN="$REPO_ROOT/target/$BUILD_TARGET/oxicloud"
if [[ -x "$OXICLOUD_BIN" ]]; then
log "Starting pre-built OxiCloud server ($BUILD_TARGET) on port $SERVER_PORT..."
"$OXICLOUD_BIN" &
else
log "Building and starting OxiCloud server on port $SERVER_PORT..."
cd "$REPO_ROOT"
cargo run &
fi
SERVER_PID=$!
log "Waiting for server at $base_url..."
wait_for_http "$base_url/ready" 120
log "Server is ready."
# ── 4. Run Hurl tests ─────────────────────────────────────────────────────────
log "Running Hurl tests..."
for T in "$WEBDAV_DIR"/test_*.sh; do
bash "$T"
done
log "All tests passed."
+6
View File
@@ -0,0 +1,6 @@
# Test credentials for local/CI API tests — NOT real secrets.
base_url=http://localhost:8087
username=admin
email=admin@example.com
# gitguardian:ignore
password=TestPassword1!
+236
View File
@@ -0,0 +1,236 @@
#!/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'..."
STALE=$(rest_get "/api/trash" \
| jq -r --arg n "$REMOTE" 'first(.[] | select(.name == $n) | .id) // empty')
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 ───────────────────────────────
# /webdav always returns 204 (update_file_streaming handles create+update)
echo " step 1: PUT $REMOTE..."
STATUS=$(webdav_put "$REMOTE" "$FIXTURE_V1" "image/jpeg")
echo " step 1: WebDAV PUT → $STATUS"
[[ "$STATUS" == "204" ]] || fail "WebDAV PUT expected 204, got $STATUS"
pass "WebDAV PUT dedup-test.jpg → 204"
# ── 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"
TRASH_ITEM=$(rest_get "/api/trash" \
| jq -r --arg n "$REMOTE" '.[] | select(.name == $n) | .id // empty')
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