2b9505f344
52c31a68 added a per-leftover refcount dump to storage_cleanup_check.sh but referenced COMPOSE_FILE, which that script never defines — only thumb_import_check.sh does. It would have run `docker compose -f ""`, failed, and been swallowed by the `|| true` guarding the loop. A silent no-op: the diagnosis would print nothing and the failure would look exactly as uninformative as the one it was written to explain. The same shape as the three bugs this suite has already caught — an error dressed up as an unremarkable result — and I wrote it into the tool meant to find them. The `|| true` stays, so one unreadable blob cannot abort the loop before the others report.
API functional tests
Tests are written using Hurl — a plain-text, CLI-first HTTP testing tool.
Prerequisites
- Hurl ≥ 4.0
Install:
cargo install hurlor via your package manager
Configuration
Edit test.env to match your local instance. This file is the single source of
truth: run.sh sources it for shell variables and passes it to Hurl as
--variables-file.
base_url=http://localhost:8087
username=admin
email=admin@example.com
password=TestPassword1!
Running the tests
# First-time setup (run once on a fresh instance)
hurl --variables-file tests/api/test.env --test tests/api/setup.hurl
# Contacts CRUD scenario
hurl --variables-file tests/api/test.env --test tests/api/contacts.hurl
# All scenarios at once
hurl --variables-file tests/api/test.env --test tests/api/setup.hurl tests/api/contacts.hurl
# With full request/response output
hurl --variables-file tests/api/test.env --test --verbose tests/api/contacts.hurl
# Generate an HTML report
hurl --variables-file tests/api/test.env --test --report-html /tmp/hurl-report tests/api/contacts.hurl
Test files
| File | Description |
|---|---|
setup.hurl |
One-time admin account creation; also asserts the endpoint is locked afterwards |
files-folders.hurl |
Files & folders CRUD scenario (22 steps, see below) |
favorites.hurl |
Favorites add/list/remove scenario (11 steps); depends on files-folders.hurl state |
trash.hurl |
Trash move/restore/purge scenario (16 steps); depends on files-folders.hurl state |
recent.hurl |
Recent items record/list/clear scenario (6 steps); depends on files-folders.hurl state |
contacts.hurl |
Full contacts CRUD scenario (14 steps, see below) |
test.env |
Variables: base_url, username, email, password — used by both Hurl and run.sh |
Scenario: files-folders.hurl
| Step | Description |
|---|---|
| 1 | Login – capture JWT token |
| 2 | List root folders – assert exactly 1 (home folder), capture home_folder_id, assert parent_id is null |
| 3 | Browse home folder sub-folders – assert empty |
| 4 | Browse home folder files – assert empty |
| 5 | Create folder named "/" – assert HTTP 400, error_type == "Invalid Input" |
| 6 | Create test1 inside home folder – capture test1_id, assert parent_id == home_folder_id |
| 7 | Create test2 inside test1 – capture test2_id, assert parent_id == test1_id |
| 8 | Browse home folder – assert exactly 1 sub-folder (test1) |
| 9 | Browse test1 – assert exactly 1 sub-folder (test2), assert parent_id |
| 10 | Upload fixtures/hello.txt into test2 – capture file_id, assert name/size/mime_type/folder_id |
| 11 | List files in test2 – assert count=1, mime_type == text/plain, icon_class == fas fa-file-alt |
| 12 | Move test2 from test1 into home folder – assert parent_id == home_folder_id |
| 13 | Browse test1 – assert empty (no more children) |
| 14 | Browse home folder – assert 2 sub-folders (test1 and test2) |
| 15 | Rename test2 → test2-renamed – assert new name, parent_id unchanged |
| 16 | Rename hello.txt → hello-renamed.txt – assert new name, folder_id unchanged |
| 17 | Rename hello-renamed.txt to "." – assert HTTP 400, error_type == "Invalid Input" |
| 18 | Upload fixtures/oxicloud-logo.jpg into home folder – capture logo_id, assert mime_type == image/jpeg |
| 19 | List files in home folder – assert count=1, icon_class == fas fa-file-image |
| 20 | GET /api/files/{logo_id}/thumbnail/icon → HTTP 200 |
| 21 | GET /api/files/{logo_id}/thumbnail/preview → HTTP 200 |
| 22 | GET /api/files/{logo_id}/thumbnail/large → HTTP 200 |
Scenario: favorites.hurl
Depends on files-folders.hurl having run first (test1, test2-renamed/hello-renamed.txt must exist).
| Step | Description |
|---|---|
| 1 | Login – capture JWT token |
| 2 | Assert no favorites yet |
| 3 | Discover item IDs: home folder → contents (test1=$[0], test2-renamed=$[1]) → files in test2-renamed |
| 4 | POST /api/favorites/file/{file_id} – add hello-renamed.txt → HTTP 201 |
| 5 | List favorites – assert count=1, item_type=file, item_name=hello-renamed.txt |
| 6 | POST /api/favorites/folder/{test1_id} – add test1 → HTTP 201 |
| 7 | List favorites – assert count=2, both IDs present (order-independent) |
| 8 | DELETE /api/favorites/file/{file_id} – remove hello-renamed.txt → HTTP 200 |
| 9 | List favorites – assert count=1, item_type=folder, item_name=test1 |
| 10 | Cleanup: DELETE /api/favorites/folder/{test1_id} → HTTP 200 |
| 11 | List favorites – assert count=0 |
Scenario: trash.hurl
Depends on files-folders.hurl having run first (home folder must exist).
| Step | Description |
|---|---|
| 1 | Login – capture JWT token |
| 2 | Assert trash is empty |
| 3 | Capture home folder ID |
| 4 | Create to-delete folder in home folder – capture to_delete_id |
| 5 | Upload hello.txt into to-delete |
| 6 | DELETE /api/folders/{to_delete_id} – moves folder to trash → HTTP 204 |
| 7 | List trash – assert count=1, item_type=folder, name=to-delete, original_id matches; capture trash_id |
| 8 | List home folder contents – assert to-delete is not present |
| 9 | POST /api/trash/{trash_id}/restore → HTTP 200, success == true |
| 10 | List home folder contents – assert to-delete is present |
| 11 | List files in to-delete – assert count=1, name=hello.txt |
| 12 | List trash – assert empty (restore removed the entry) |
| 13 | DELETE /api/folders/{to_delete_id} – move restored folder to trash again → HTTP 204 |
| 14 | DELETE /api/trash/empty – purge trash → HTTP 200, success == true |
| 15 | List trash – assert empty after purge |
| 16 | List home folder contents – assert to-delete is not present (permanently gone) |
Scenario: recent.hurl
Depends on files-folders.hurl having run first (test2-renamed/hello-renamed.txt must exist).
Recent items are not auto-recorded on upload — step 3 explicitly registers the access.
| Step | Description |
|---|---|
| 1 | Login – capture JWT token |
| 2 | Discover file_id of hello-renamed.txt via home folder → test2-renamed contents |
| 3 | POST /api/recent/file/{file_id} – record access → HTTP 200 |
| 4 | GET /api/recent – assert count=1, item_type=file, item_name=hello-renamed.txt |
| 5 | DELETE /api/recent/clear – clear all recent items → HTTP 200 |
| 6 | GET /api/recent – assert count=0 |
Scenario: contacts.hurl
| Step | Description |
|---|---|
| 1 | Login – capture JWT token |
| 2 | List address books – assert system book is present and read-only |
| 3 | Create personal address book – capture book_id |
| 4 | List contacts in new book – assert empty |
| 5 | Create contact John Doe – capture contact_id |
| 6 | List contacts – assert exactly 1 result with John Doe's id |
| 7 | Get John Doe – assert all fields, capture ETag |
| 8 | Update John Doe (nickname, org, notes) with If-Match – assert new values, capture refreshed ETag |
| 9 | Delete John Doe with If-Match |
| 10 | List contacts – assert empty again |
| 11 | Delete personal address book |
| 12 | List address books – assert book_id no longer present |
| 13 | List system address book – assert non-empty collection of OxiCloud users |
Legacy bash tests
test.sh and common.sh are the original curl/bash scripts kept for reference.
Run them with bash tests/api/test.sh from the repo root (requires jq).