Files
Oxicloud/tests/api/admin_user_ops.hurl
T
Edouard Vanbelle 6d65f7eb09 chore(test): add new API coverage
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.
2026-06-13 19:03:07 +02:00

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