Files
Oxicloud/tests/api
Edouard Vanbelle f4e47bad6d test(transcode): prove the transcode is computed once per content
Adds `GET /api/admin/transcode/stats` and a hurl scenario that uses it
to assert both halves of the caching contract.

The endpoint exists because the property was previously unobservable.
`derived_blob_copy.hurl` records the same limitation for thumbnails:
stored blob, RAM cache and a fresh re-render return identical bytes
with identical status, so no HTTP-level assertion can tell them apart.
Counters can. `transcodes` is work done; `cache_hits` and `disk_hits`
are work avoided, and a rising `transcodes` against a flat `disk_hits`
is exactly what a broken derived tier looks like from outside.

Each case uploads the same bytes as TWO distinct files. Re-fetching one
file would only prove moka works — that cache is keyed `{file_id}:{ext}`.
A second file with identical content is a guaranteed memory miss but the
same content hash, so avoiding a transcode there can only be the
content-keyed tier answering. That is the whole point of keying
derivations by content rather than by file, and this is the first test
that can see it.

The negative half is the one the row exists for: without it the server
re-runs a full decode + encode of a half-megabyte screenshot for every
file sharing that content, on every request, to discard the result each
time.

Assertions capture-then-compare rather than computing deltas — hurl has
no arithmetic in predicates, and pinning the exact prior value is
stricter anyway, since a transcode triggered from anywhere shows up.
Absolute values are never asserted: other scenarios in the same run
transcode too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 17:45:48 +02:00
..
2026-05-11 20:00:22 +02:00
2026-05-11 00:53:29 +02:00
2026-05-11 10:53:59 +02:00

API functional tests

Tests are written using Hurl — a plain-text, CLI-first HTTP testing tool.

Prerequisites

  • Hurl ≥ 4.0 Install: cargo install hurl or 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).