Files
Oxicloud/tests/api
Edouard Vanbelle 7705fca3af feat(transcode): count the decodes that pay nothing
Writing the hurl scenario surfaced a gap: a transcode that comes out
larger than the original runs a full decode + encode and increments no
counter at all. `transcodes` is bumped only on the success path, beside
`bytes_saved`, so the most expensive failure mode was invisible — a
multi-megapixel image decoded and re-encoded on every request, for
every file sharing that content, producing nothing.

That is precisely the cost the persisted negative verdict exists to
stop paying, and it could not be measured before or after. `not_beneficial`
counts it, kept separate from `transcodes` because conflating "work
done" with "work that paid off" would hide exactly what an operator
needs to see.

It is also what lets the hurl scenario assert the negative half: the
first fetch increments it, the second — a distinct file with identical
content — leaves it untouched, which is the negative row being read
rather than the verdict recomputed.

Assertions are exact equality against captured values throughout, no
`>` or `<`. A "greater than" would pass if a counter moved for the
wrong reason; equality against the prior reading catches any transcode
from any source, including one this scenario did not intend to cause.

Also fixes two URLs the first runs caught: file download is
`GET /api/files/{id}`, not `/content`, and the trash listing is
`/api/trash/resources`. And the duplicate uploads go to a second
folder — re-uploading the same filename into the same folder returns
the EXISTING file id, which would have made both halves of every
"two files, one content" pair the same row and left the scenario
asserting nothing.

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).