163 lines
6.8 KiB
Plaintext
163 lines
6.8 KiB
Plaintext
|
|
# =============================================================
|
||
|
|
# OxiCloud — Baseline: search surface
|
||
|
|
# =============================================================
|
||
|
|
# Pins `/api/search` and `/api/search/suggest` plus the
|
||
|
|
# cross-user isolation property: a search MUST NEVER surface a
|
||
|
|
# file the caller doesn't own (and isn't shared with). Search
|
||
|
|
# is the kind of feature where a sloppy SQL join is exactly
|
||
|
|
# what introduces a cross-user leak — this test catches that.
|
||
|
|
#
|
||
|
|
# Requires OXICLOUD_ENABLE_SEARCH=true (set in tests/common/server.env).
|
||
|
|
#
|
||
|
|
# Coverage:
|
||
|
|
# 1. Admin uploads `unique-search-needle-aaa.txt` to her home
|
||
|
|
# 2. GET /api/search?query=unique-search-needle returns the file
|
||
|
|
# 3. GET /api/search?query=does-not-exist-xyz returns 0 files
|
||
|
|
# 4. GET /api/search/suggest?query=unique-search-needle returns
|
||
|
|
# something (suggestion-shape is allowed to be permissive)
|
||
|
|
# 5. Cross-user: bob searches "unique-search-needle" → MUST NOT
|
||
|
|
# see admin's file (security baseline)
|
||
|
|
# 6. Teardown: delete the file
|
||
|
|
#
|
||
|
|
# Bob is (re-)created inline so this file is order-independent
|
||
|
|
# with respect to nc_second_user_setup.hurl (which runs later
|
||
|
|
# in run.sh).
|
||
|
|
# =============================================================
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# Setup — admin login + bob (re-)provisioning
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
POST {{base_url}}/api/auth/login
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "username": "{{username}}", "password": "{{password}}" }
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Captures]
|
||
|
|
admin_token: jsonpath "$.access_token"
|
||
|
|
|
||
|
|
|
||
|
|
GET {{base_url}}/api/folders
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Captures]
|
||
|
|
admin_home_id: jsonpath "$[0].id"
|
||
|
|
|
||
|
|
|
||
|
|
# Anti-enum registration: 200 whether bob existed or not.
|
||
|
|
POST {{base_url}}/api/auth/register
|
||
|
|
Content-Type: application/json
|
||
|
|
{
|
||
|
|
"username": "bob",
|
||
|
|
"email": "bob@example.com",
|
||
|
|
"password": "BobPassword1!"
|
||
|
|
}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
|
||
|
|
|
||
|
|
POST {{base_url}}/api/auth/login
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "username": "bob", "password": "BobPassword1!" }
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Captures]
|
||
|
|
bob_token: jsonpath "$.access_token"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 1 — Admin uploads `hello.txt` to a dedicated subfolder, then
|
||
|
|
# renames it to a deliberately unique name so the search
|
||
|
|
# assertion is unambiguous. The subfolder isolates this
|
||
|
|
# test from any other test that already left a `hello.txt`
|
||
|
|
# in admin's home (would otherwise 409).
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
POST {{base_url}}/api/folders
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "name": "search-basic-test", "parent_id": "{{admin_home_id}}" }
|
||
|
|
|
||
|
|
HTTP 201
|
||
|
|
[Captures]
|
||
|
|
search_folder_id: jsonpath "$.id"
|
||
|
|
|
||
|
|
|
||
|
|
POST {{base_url}}/api/files/upload
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
[MultipartFormData]
|
||
|
|
folder_id: {{search_folder_id}}
|
||
|
|
file: file,fixtures/hello.txt; text/plain
|
||
|
|
|
||
|
|
HTTP 201
|
||
|
|
[Captures]
|
||
|
|
needle_file_id: jsonpath "$.id"
|
||
|
|
|
||
|
|
|
||
|
|
PUT {{base_url}}/api/files/{{needle_file_id}}/rename
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
Content-Type: application/json
|
||
|
|
{ "name": "unique-search-needle-aaa.txt" }
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 2 — Search hits the seeded file by substring of its name.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/api/search?query=unique-search-needle
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Asserts]
|
||
|
|
jsonpath "$.files" count >= 1
|
||
|
|
body contains "{{needle_file_id}}"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 3 — A search for a phrase that can't match anything must
|
||
|
|
# return an empty result set, NOT an error. Empty-results
|
||
|
|
# is a hot path; we don't want it to start 500ing.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/api/search?query=does-not-exist-xyz-zzz-9999
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Asserts]
|
||
|
|
jsonpath "$.files" count == 0
|
||
|
|
jsonpath "$.folders" count == 0
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 4 — Suggest returns a usable payload (shape is permissive —
|
||
|
|
# just confirm the endpoint serves 200 and isn't truncating
|
||
|
|
# to an error envelope).
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/api/search/suggest?query=unique-search-needle
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 5 — HEADLINE: bob MUST NOT see admin's file. If this assertion
|
||
|
|
# ever flips, the search service has a cross-user leak.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
GET {{base_url}}/api/search?query=unique-search-needle
|
||
|
|
Authorization: Bearer {{bob_token}}
|
||
|
|
|
||
|
|
HTTP 200
|
||
|
|
[Asserts]
|
||
|
|
body not contains "unique-search-needle"
|
||
|
|
body not contains "{{needle_file_id}}"
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
# 6 — Teardown: removing the folder recursively takes the file
|
||
|
|
# with it, so a single DELETE is enough.
|
||
|
|
# ─────────────────────────────────────────────────────────────
|
||
|
|
DELETE {{base_url}}/api/folders/{{search_folder_id}}
|
||
|
|
Authorization: Bearer {{admin_token}}
|
||
|
|
|
||
|
|
HTTP 204
|