6d65f7eb09
ensure better API test coverage on important routes 1. tests/api/public_shares.hurl — create a share token, verify, list contents, fetch a file, fetch a folder zip, then revoke and re-verify with the token. Same pattern as grants.hurl. ~30 min, biggest security ROI. 2. tests/api/auth_session_lifecycle.hurl — login → refresh → use new token → logout → refresh-rejected → login-again. Covers the session-family invalidation contract. 3. tests/api/admin_user_ops.hurl — admin disables / re-enables / changes role / resets password / sets quota for a fixture user. Five POSTs. 4. tests/api/groups_effective_members.hurl — nested groups: A contains B contains user X; effective-members returns X. Two scenarios, but it's the ReBAC contract under the Drive refactor. 5. tests/api/search_basic.hurl — upload foo.txt, search "foo", get the result; cross-user: bob can't search alice's foo.
187 lines
7.9 KiB
Plaintext
187 lines
7.9 KiB
Plaintext
# =============================================================
|
|
# OxiCloud — Baseline: admin user-mutation surface
|
|
# =============================================================
|
|
# Exercises the cluster of admin-only `PUT /api/admin/users/{id}/*`
|
|
# endpoints that operators rely on for incident response:
|
|
# - disable a compromised account
|
|
# - rotate its password
|
|
# - change its role
|
|
# - set / clear its quota
|
|
#
|
|
# Each is a one-shot mutation, but the failure mode of any one
|
|
# is severe (operator can't lock out an attacker, can't reset a
|
|
# password). Pinning them together keeps the cluster intact
|
|
# under refactors.
|
|
#
|
|
# Coverage:
|
|
# 1. Admin creates a fresh fixture user via POST /api/admin/users
|
|
# 2. Fixture user logs in successfully (baseline)
|
|
# 3. PUT /quota → fixture user's /me reports updated quota
|
|
# 4. PUT /role → fixture user becomes admin
|
|
# 5. PUT /password (admin reset) → old password no longer works,
|
|
# new password works
|
|
# 6. PUT /active=false → fixture user login → 403
|
|
# 7. PUT /active=true → fixture user login works again
|
|
# 8. Cleanup via DELETE /api/admin/users/{id}
|
|
# =============================================================
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# Setup — admin login
|
|
# ─────────────────────────────────────────────────────────────
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "{{username}}", "password": "{{password}}" }
|
|
|
|
HTTP 200
|
|
[Captures]
|
|
admin_token: jsonpath "$.access_token"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 1 — Admin creates the fixture user "charlie-ops"
|
|
# Uses a name that doesn't collide with charlie in
|
|
# registration.hurl (which uses just "charlie"), so this
|
|
# file is order-independent.
|
|
# ─────────────────────────────────────────────────────────────
|
|
POST {{base_url}}/api/admin/users
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{
|
|
"username": "charlie-ops",
|
|
"password": "OriginalPassword1!",
|
|
"email": "charlie-ops@example.com",
|
|
"role": "user"
|
|
}
|
|
|
|
HTTP 201
|
|
[Captures]
|
|
charlie_id: jsonpath "$.id"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 2 — Baseline: fixture user can log in with the password
|
|
# admin assigned.
|
|
# ─────────────────────────────────────────────────────────────
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "charlie-ops", "password": "OriginalPassword1!" }
|
|
|
|
HTTP 200
|
|
[Captures]
|
|
charlie_token_v1: jsonpath "$.access_token"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 3 — Set a non-default quota; charlie's own /me must reflect it.
|
|
# 200 MiB = 209715200 bytes — keeps the assertion exact while
|
|
# still being a believable per-user cap.
|
|
# ─────────────────────────────────────────────────────────────
|
|
PUT {{base_url}}/api/admin/users/{{charlie_id}}/quota
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{ "quota_bytes": 209715200 }
|
|
|
|
HTTP 200
|
|
|
|
GET {{base_url}}/api/auth/me
|
|
Authorization: Bearer {{charlie_token_v1}}
|
|
|
|
HTTP 200
|
|
[Asserts]
|
|
jsonpath "$.storage_quota_bytes" == 209715200
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 4 — Promote charlie to admin. After this the /me payload's
|
|
# role field must reflect the change.
|
|
# ─────────────────────────────────────────────────────────────
|
|
PUT {{base_url}}/api/admin/users/{{charlie_id}}/role
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{ "role": "admin" }
|
|
|
|
HTTP 200
|
|
|
|
GET {{base_url}}/api/auth/me
|
|
Authorization: Bearer {{charlie_token_v1}}
|
|
|
|
HTTP 200
|
|
[Asserts]
|
|
jsonpath "$.role" == "admin"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 5 — Admin resets charlie's password.
|
|
# ─────────────────────────────────────────────────────────────
|
|
PUT {{base_url}}/api/admin/users/{{charlie_id}}/password
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{ "new_password": "AdminResetPassword2!" }
|
|
|
|
HTTP 200
|
|
|
|
# Old password no longer works. Login failures map to 403
|
|
# (AccessDenied) in this codebase — both "invalid credentials"
|
|
# and "account deactivated" go through the same error kind.
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "charlie-ops", "password": "OriginalPassword1!" }
|
|
|
|
HTTP 403
|
|
|
|
# New password works.
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "charlie-ops", "password": "AdminResetPassword2!" }
|
|
|
|
HTTP 200
|
|
[Captures]
|
|
charlie_token_v2: jsonpath "$.access_token"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 6 — Disable the account. The next login attempt must report
|
|
# 403 (account disabled) — distinct from 401 (bad creds)
|
|
# so operators can tell "I locked you out" from "you typed
|
|
# the wrong password".
|
|
# ─────────────────────────────────────────────────────────────
|
|
PUT {{base_url}}/api/admin/users/{{charlie_id}}/active
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{ "active": false }
|
|
|
|
HTTP 200
|
|
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "charlie-ops", "password": "AdminResetPassword2!" }
|
|
|
|
HTTP 403
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 7 — Re-enable; login works again.
|
|
# ─────────────────────────────────────────────────────────────
|
|
PUT {{base_url}}/api/admin/users/{{charlie_id}}/active
|
|
Authorization: Bearer {{admin_token}}
|
|
Content-Type: application/json
|
|
{ "active": true }
|
|
|
|
HTTP 200
|
|
|
|
POST {{base_url}}/api/auth/login
|
|
Content-Type: application/json
|
|
{ "username": "charlie-ops", "password": "AdminResetPassword2!" }
|
|
|
|
HTTP 200
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────
|
|
# 8 — Teardown
|
|
# ─────────────────────────────────────────────────────────────
|
|
DELETE {{base_url}}/api/admin/users/{{charlie_id}}
|
|
Authorization: Bearer {{admin_token}}
|
|
|
|
HTTP 200
|