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.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: subject-group effective-members
|
||||
# =============================================================
|
||||
# Pins the transitive group-expansion contract that the ReBAC
|
||||
# permissions story relies on. The interesting case is nesting:
|
||||
# adding *user_X* into *Group_B*, and *Group_B* into *Group_A*,
|
||||
# must make *user_X* visible from `GET /api/groups/{A}/effective-members`
|
||||
# — the call site that authz lookups walk.
|
||||
#
|
||||
# Coverage:
|
||||
# 1. Admin creates `Group_A` and `Group_B`
|
||||
# 2. Admin creates fixture user `dora-eff`
|
||||
# 3. PUT dora into Group_B (direct membership)
|
||||
# 4. PUT Group_B into Group_A (nested membership)
|
||||
# 5. GET /api/groups/{B}/members (direct only) → dora
|
||||
# 6. GET /api/groups/{A}/members (direct only) → Group_B
|
||||
# NOT dora (she's transitive)
|
||||
# 7. GET /api/groups/{A}/effective-members → contains dora
|
||||
# 8. Cleanup: remove user, remove group-member, delete groups + user
|
||||
#
|
||||
# This single nested scenario is the load-bearing one — if the
|
||||
# transitive walk regresses, the ReBAC engine silently grants
|
||||
# 0 permissions to nested members.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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 — Create both groups. Names use kebab-case so the RFC-5321
|
||||
# local-part validator accepts them.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/groups
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "name": "eff-group-a", "description": "outer group" }
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
group_a_id: jsonpath "$.id"
|
||||
|
||||
|
||||
POST {{base_url}}/api/groups
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "name": "eff-group-b", "description": "inner group" }
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
group_b_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 2 — Create the fixture user. "dora-eff" — distinct from any
|
||||
# user created by other test files, so this is order-safe.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/admin/users
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"username": "dora-eff",
|
||||
"password": "DoraPassword1!",
|
||||
"email": "dora-eff@example.com",
|
||||
"role": "user"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
dora_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 3 — Put dora into Group_B (direct user member)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/groups/{{group_b_id}}/members
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "user_id": "{{dora_id}}" }
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 4 — Put Group_B into Group_A (nested group member)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/groups/{{group_a_id}}/members
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "group_id": "{{group_b_id}}" }
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 5 — Group_B direct membership: ONLY dora.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/groups/{{group_b_id}}/members
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "{{dora_id}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 6 — Group_A direct membership: Group_B, NOT dora. The
|
||||
# direct-members endpoint is non-transitive by contract;
|
||||
# mixing in transitive members here would silently
|
||||
# conflate the two surfaces.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/groups/{{group_a_id}}/members
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "{{group_b_id}}"
|
||||
body not contains "{{dora_id}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 7 — HEADLINE: Group_A effective-members reaches dora.
|
||||
# A regression here is the canary for any change that
|
||||
# breaks transitive expansion in the ReBAC layer.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/groups/{{group_a_id}}/effective-members
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "{{dora_id}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 8 — Teardown. Order matters: remove the nested group-member
|
||||
# before deleting Group_B, so the FK cascade doesn't get
|
||||
# ahead of us; remove dora's direct membership similarly.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/groups/{{group_a_id}}/members/group/{{group_b_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
HTTP 204
|
||||
|
||||
DELETE {{base_url}}/api/groups/{{group_b_id}}/members/user/{{dora_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
HTTP 204
|
||||
|
||||
DELETE {{base_url}}/api/groups/{{group_a_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
HTTP 204
|
||||
|
||||
DELETE {{base_url}}/api/groups/{{group_b_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
HTTP 204
|
||||
|
||||
DELETE {{base_url}}/api/admin/users/{{dora_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
HTTP 200
|
||||
Reference in New Issue
Block a user