280 lines
11 KiB
Plaintext
280 lines
11 KiB
Plaintext
# =============================================================
|
||
# OxiCloud – Cursor-paginated trash listing (GET /api/trash/resources)
|
||
# =============================================================
|
||
# Depends on files-folders.hurl having run first (home folder exists).
|
||
# Runs after trash.hurl, which leaves an empty trash.
|
||
#
|
||
# Verifies:
|
||
# - Empty trash returns items:[] and no next_cursor
|
||
# - 401 without auth
|
||
# - Default order_by = deletion_date (soonest-expiring first)
|
||
# - order_by=name groups folders before files
|
||
# - order_by=size: folders cluster via -1 sentinel
|
||
# - resource_types=file / =folder filters narrow the result set
|
||
# - Pagination: limit=1 + cursor walks the whole set
|
||
# - Cursor is opaque (returned next_cursor is a non-empty string)
|
||
#
|
||
# Run:
|
||
# hurl --variables-file tests/api/test.env --test tests/api/trash_resources.hurl
|
||
# =============================================================
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 1 – Login + capture token
|
||
# ─────────────────────────────────────────────────────────────
|
||
POST {{base_url}}/api/auth/login
|
||
Content-Type: application/json
|
||
{
|
||
"username": "{{username}}",
|
||
"password": "{{password}}"
|
||
}
|
||
|
||
HTTP 200
|
||
[Captures]
|
||
token: jsonpath "$.access_token"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 2 – Trash starts empty (trash.hurl left it that way)
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" isCollection
|
||
jsonpath "$.items" count == 0
|
||
jsonpath "$.next_cursor" not exists
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 4 – Capture the home folder ID
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/folders
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Captures]
|
||
home_folder_id: jsonpath "$[0].id"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 5 – Create three folders + two files to trash
|
||
# folders: trash-res-A, trash-res-B
|
||
# files: alpha.txt (in home), beta.txt (in home)
|
||
# ─────────────────────────────────────────────────────────────
|
||
POST {{base_url}}/api/folders
|
||
Authorization: Bearer {{token}}
|
||
Content-Type: application/json
|
||
{
|
||
"name": "trash-res-A",
|
||
"parent_id": "{{home_folder_id}}"
|
||
}
|
||
HTTP 201
|
||
[Captures]
|
||
folder_a_id: jsonpath "$.id"
|
||
|
||
|
||
POST {{base_url}}/api/folders
|
||
Authorization: Bearer {{token}}
|
||
Content-Type: application/json
|
||
{
|
||
"name": "trash-res-B",
|
||
"parent_id": "{{home_folder_id}}"
|
||
}
|
||
HTTP 201
|
||
[Captures]
|
||
folder_b_id: jsonpath "$.id"
|
||
|
||
|
||
POST {{base_url}}/api/files/upload
|
||
Authorization: Bearer {{token}}
|
||
[MultipartFormData]
|
||
folder_id: {{home_folder_id}}
|
||
file: file,fixtures/hello.txt; text/plain
|
||
HTTP 201
|
||
[Captures]
|
||
file_alpha_id: jsonpath "$.id"
|
||
|
||
|
||
# Rename uploaded file to alpha.txt so order_by=name has a predictable result
|
||
PUT {{base_url}}/api/files/{{file_alpha_id}}/rename
|
||
Authorization: Bearer {{token}}
|
||
Content-Type: application/json
|
||
{
|
||
"name": "alpha.txt"
|
||
}
|
||
|
||
HTTP 200
|
||
|
||
|
||
POST {{base_url}}/api/files/upload
|
||
Authorization: Bearer {{token}}
|
||
[MultipartFormData]
|
||
folder_id: {{home_folder_id}}
|
||
file: file,fixtures/hello.txt; text/plain
|
||
HTTP 201
|
||
[Captures]
|
||
file_beta_id: jsonpath "$.id"
|
||
|
||
|
||
PUT {{base_url}}/api/files/{{file_beta_id}}/rename
|
||
Authorization: Bearer {{token}}
|
||
Content-Type: application/json
|
||
{
|
||
"name": "beta.txt"
|
||
}
|
||
|
||
HTTP 200
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 6 – Move all four items to trash
|
||
# ─────────────────────────────────────────────────────────────
|
||
DELETE {{base_url}}/api/trash/files/{{file_alpha_id}}
|
||
Authorization: Bearer {{token}}
|
||
HTTP 200
|
||
|
||
|
||
DELETE {{base_url}}/api/trash/files/{{file_beta_id}}
|
||
Authorization: Bearer {{token}}
|
||
HTTP 200
|
||
|
||
|
||
DELETE {{base_url}}/api/trash/folders/{{folder_a_id}}
|
||
Authorization: Bearer {{token}}
|
||
HTTP 200
|
||
|
||
|
||
DELETE {{base_url}}/api/trash/folders/{{folder_b_id}}
|
||
Authorization: Bearer {{token}}
|
||
HTTP 200
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 7 – Default listing: 4 items, all carry trashed_at + deletion_date
|
||
# + drive_id (D2b — enables per-drive grouping in the UI).
|
||
# The listing is also drive-membership scoped (D2b stage 2): the
|
||
# admin sees their own personal drive's trashed items, just as
|
||
# before, because a personal drive Owner has all roles needed.
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 4
|
||
jsonpath "$.items[0].trashed_at" isString
|
||
jsonpath "$.items[0].deletion_date" isString
|
||
# D2b: drive_id is non-null on every trashed item.
|
||
jsonpath "$.items[0].drive_id" isString
|
||
jsonpath "$.items[1].drive_id" isString
|
||
jsonpath "$.items[2].drive_id" isString
|
||
jsonpath "$.items[3].drive_id" isString
|
||
jsonpath "$.items[0].resource_type" matches "^(file|folder)$"
|
||
jsonpath "$.items[*].resource.id" contains {{folder_a_id}}
|
||
jsonpath "$.items[*].resource.id" contains {{folder_b_id}}
|
||
jsonpath "$.items[*].resource.id" contains {{file_alpha_id}}
|
||
jsonpath "$.items[*].resource.id" contains {{file_beta_id}}
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 8 – order_by=name: folders sort before files (folder_first axis),
|
||
# then alphabetic. Expected order:
|
||
# [trash-res-A, trash-res-B, alpha.txt, beta.txt]
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources?order_by=name
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items[0].resource.name" == "trash-res-A"
|
||
jsonpath "$.items[1].resource.name" == "trash-res-B"
|
||
jsonpath "$.items[2].resource.name" == "alpha.txt"
|
||
jsonpath "$.items[3].resource.name" == "beta.txt"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 9 – order_by=size: folders use sentinel -1 so they cluster
|
||
# together (before files with size >= 0).
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources?order_by=size
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 4
|
||
jsonpath "$.items[0].resource_type" == "folder"
|
||
jsonpath "$.items[1].resource_type" == "folder"
|
||
jsonpath "$.items[2].resource_type" == "file"
|
||
jsonpath "$.items[3].resource_type" == "file"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 10 – resource_types=file → only files (2)
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources?resource_types=file
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 2
|
||
jsonpath "$.items[0].resource_type" == "file"
|
||
jsonpath "$.items[1].resource_type" == "file"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 11 – resource_types=folder → only folders (2)
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources?resource_types=folder
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 2
|
||
jsonpath "$.items[0].resource_type" == "folder"
|
||
jsonpath "$.items[1].resource_type" == "folder"
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 12 – Pagination: limit=1 returns 1 item + a next_cursor
|
||
# ─────────────────────────────────────────────────────────────
|
||
GET {{base_url}}/api/trash/resources?order_by=name&limit=1
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Captures]
|
||
page1_cursor: jsonpath "$.next_cursor"
|
||
[Asserts]
|
||
jsonpath "$.items" count == 1
|
||
jsonpath "$.items[0].resource.name" == "trash-res-A"
|
||
jsonpath "$.next_cursor" isString
|
||
|
||
|
||
# Step 13 – Walk to page 2 with the captured cursor
|
||
GET {{base_url}}/api/trash/resources?order_by=name&limit=1&cursor={{page1_cursor}}
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 1
|
||
jsonpath "$.items[0].resource.name" == "trash-res-B"
|
||
jsonpath "$.next_cursor" isString
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────
|
||
# Step 14 – Cleanup: empty trash so subsequent tests see a clean slate
|
||
# ─────────────────────────────────────────────────────────────
|
||
DELETE {{base_url}}/api/trash/empty
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
|
||
|
||
GET {{base_url}}/api/trash/resources
|
||
Authorization: Bearer {{token}}
|
||
|
||
HTTP 200
|
||
[Asserts]
|
||
jsonpath "$.items" count == 0
|