Files
Oxicloud/tests/api/favorites.hurl
T
2026-07-05 22:53:05 +02:00

236 lines
9.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================
# OxiCloud – Favorites API end-to-end scenario
# =============================================================
# Depends on files-folders.hurl having run first:
# - home folder exists with sub-folders test1 and test2-renamed
# - test2-renamed contains hello-renamed.txt
#
# Run:
# hurl --variables-file tests/api/test.env --test tests/api/favorites.hurl
# =============================================================
# ─────────────────────────────────────────────────────────────
# Step 1 – Login and capture the JWT token
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/auth/login
Content-Type: application/json
{
"username": "{{username}}",
"password": "{{password}}"
}
HTTP 200
[Captures]
token: jsonpath "$.access_token"
[Asserts]
jsonpath "$.access_token" isString
# ─────────────────────────────────────────────────────────────
# Step 2 – No favorites yet
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.items" isCollection
jsonpath "$.items" count == 0
# ─────────────────────────────────────────────────────────────
# Step 3 – Discover item IDs from the folder structure
# Folders are ORDER BY name: test1 ($[0]), test2-renamed ($[1])
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/folders
Authorization: Bearer {{token}}
HTTP 200
[Captures]
home_folder_id: jsonpath "$[0].id"
GET {{base_url}}/api/folders/{{home_folder_id}}/resources?resource_types=folder
Authorization: Bearer {{token}}
HTTP 200
[Captures]
test1_id: jsonpath "$.items[0].resource.id"
test2_id: jsonpath "$.items[1].resource.id"
[Asserts]
jsonpath "$.items[0].resource.name" == "test1"
jsonpath "$.items[1].resource.name" == "test2-renamed"
GET {{base_url}}/api/files?folder_id={{test2_id}}
Authorization: Bearer {{token}}
HTTP 200
[Captures]
file_id: jsonpath "$[0].id"
[Asserts]
jsonpath "$[0].name" == "hello-renamed.txt"
# ─────────────────────────────────────────────────────────────
# Step 4 – Add hello-renamed.txt to favorites
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/favorites/file/{{file_id}}
Authorization: Bearer {{token}}
HTTP 201
# ─────────────────────────────────────────────────────────────
# Step 5 – Favorites contains only hello-renamed.txt
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.items" count == 1
jsonpath "$.items[0].resource.id" == {{file_id}}
jsonpath "$.items[0].resource_type" == "file"
jsonpath "$.items[0].resource.name" == "hello-renamed.txt"
# ─────────────────────────────────────────────────────────────
# Step 6 – Add test1 folder to favorites
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/favorites/folder/{{test1_id}}
Authorization: Bearer {{token}}
HTTP 201
# ─────────────────────────────────────────────────────────────
# Step 7 – Favorites contains both items (order-independent)
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.items" count == 2
jsonpath "$.items[*].resource.id" contains {{file_id}}
jsonpath "$.items[*].resource.id" contains {{test1_id}}
# ─────────────────────────────────────────────────────────────
# Step 8 – Remove hello-renamed.txt from favorites
# ─────────────────────────────────────────────────────────────
DELETE {{base_url}}/api/favorites/file/{{file_id}}
Authorization: Bearer {{token}}
HTTP 200
# ─────────────────────────────────────────────────────────────
# Step 9 – Favorites contains only test1 folder
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.items" count == 1
jsonpath "$.items[0].resource.id" == {{test1_id}}
jsonpath "$.items[0].resource_type" == "folder"
jsonpath "$.items[0].resource.name" == "test1"
# ─────────────────────────────────────────────────────────────
# Step 10 – Cleanup: remove test1 from favorites
# ─────────────────────────────────────────────────────────────
DELETE {{base_url}}/api/favorites/folder/{{test1_id}}
Authorization: Bearer {{token}}
HTTP 200
# ─────────────────────────────────────────────────────────────
# Step 11 – Favorites is empty again
# ─────────────────────────────────────────────────────────────
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.items" count == 0
# ─────────────────────────────────────────────────────────────
# Step 12 — Cross-tenant regression (post-Drive AuthZ audit,
# Round 1 HIGH). Before this fix, `POST /api/favorites/…`
# accepted any UUID and enrolled it; the listing endpoint
# then JOINed back to storage.files/folders and returned
# name/mime/size/drive_id for anything the caller had
# managed to add — an information oracle over the whole
# tenant. Now the write path calls `authz.require(Read, …)`
# per item; a caller with no grant gets 404 (anti-enum)
# + `authz.denied` audit line. See
# `docs/plan/authz_audit/rest_storage.md`.
# ─────────────────────────────────────────────────────────────
# Create a second, unprivileged user. Idempotent: `HTTP *` accepts
# either 201 (first run) or 409 (subsequent runs). The login below
# is the actual precondition — if it succeeds we know the user
# exists with the expected password.
POST {{base_url}}/api/admin/users
Authorization: Bearer {{token}}
Content-Type: application/json
{ "username": "fav_mallory", "password": "FavMalloryPassword1!", "email": "fav_mallory@example.com", "role": "user" }
HTTP *
POST {{base_url}}/api/auth/login
Content-Type: application/json
{ "username": "fav_mallory", "password": "FavMalloryPassword1!" }
HTTP 200
[Captures]
mallory_token: jsonpath "$.access_token"
# Step 12a — Single-add on admin's file: 404 (anti-enum shape).
POST {{base_url}}/api/favorites/file/{{file_id}}
Authorization: Bearer {{mallory_token}}
HTTP 404
# Step 12b — Single-add on admin's folder: 404.
POST {{base_url}}/api/favorites/folder/{{test1_id}}
Authorization: Bearer {{mallory_token}}
HTTP 404
# Step 12c — Batch: must fail wholesale on the first denial. A partial
# success would still leak "which items are valid" — the same
# oracle we're closing.
POST {{base_url}}/api/favorites/batch
Authorization: Bearer {{mallory_token}}
Content-Type: application/json
{
"items": [
{ "item_id": "{{file_id}}", "item_type": "file" },
{ "item_id": "{{test1_id}}", "item_type": "folder" }
]
}
HTTP 404
# Step 12d — Mallory's favorites list is EMPTY — no partial success
# slipped through.
GET {{base_url}}/api/favorites/resources
Authorization: Bearer {{mallory_token}}
HTTP 200
[Asserts]
jsonpath "$.items" count == 0