Files
Oxicloud/tests/api
Edouard Vanbelle f6bb677d91 test(api): exercise the deletion path, which nothing did
No test passed repair=true, so verify_and_unlink, the
sidecar_delete_unverified finding and the directory removal had never
executed. That left the one destructive part of the migration as its
least-tested code: everything else is additive and recoverable, this
unlinks files after a readback check, and a defect costs bytes.

Four assertions, because "the files are gone" cannot by itself tell a
correct drain from a destructive one:

  sidecars gone       — the drain happened
  rows still present  — it deleted the COPY, not the record. Removing
                        the row would strand the blob in exactly the way
                        the bulk-reap bug just did: a live reference
                        with nothing behind it, which GC is then correct
                        to refuse forever.
  unverified == 0     — every unlink passed its readback rather than
                        being skipped, which is the property that makes
                        deleting safe at all
  directory absent    — the signal step 10e gates on, and why remove_dir
                        is used: it refuses a non-empty directory, so
                        success proves emptiness rather than asserting it

Preconditions assert the sidecars exist first, or a no-op run would pass
all four by doing nothing. The directory check logs rather than fails,
since a concurrent render could legitimately repopulate it.
2026-08-30 13:41:05 +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).