refactor(user): apply chanoges to hurl tests

This commit is contained in:
Edouard Vanbelle
2026-08-21 23:19:00 +02:00
parent c583b26355
commit a8fa281a02
48 changed files with 310 additions and 244 deletions
+3 -3
View File
@@ -56,7 +56,7 @@ Content-Type: application/json
HTTP 201
[Captures]
charlie_id: jsonpath "$.id"
charlie_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -89,7 +89,7 @@ Authorization: Bearer {{charlie_token_v1}}
HTTP 200
[Asserts]
jsonpath "$.storage_quota_bytes" == 209715200
jsonpath "$.full.storage_quota_bytes" == 209715200
# ─────────────────────────────────────────────────────────────
@@ -108,7 +108,7 @@ Authorization: Bearer {{charlie_token_v1}}
HTTP 200
[Asserts]
jsonpath "$.role" == "admin"
jsonpath "$.full.user.role" == "admin"
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -19,7 +19,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.access_token" exists
jsonpath "$.user.email" == "{{email}}"
jsonpath "$.user.full.user.email" == "{{email}}"
# ─────────────────────────────────────────────────────────────
@@ -34,7 +34,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.access_token" exists
jsonpath "$.user.email" == "{{email}}"
jsonpath "$.user.full.user.email" == "{{email}}"
# ─────────────────────────────────────────────────────────────
+3 -3
View File
@@ -142,10 +142,10 @@ Authorization: Bearer {{alice_magic_access_token}}
HTTP 200
[Asserts]
jsonpath "$.email" == "{{email}}"
jsonpath "$.username" == "{{username}}"
jsonpath "$.full.user.email" == "{{email}}"
jsonpath "$.full.user.username" == "{{username}}"
[Captures]
admin_user_id: jsonpath "$.id"
admin_user_id: jsonpath "$.full.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -78,7 +78,7 @@ Authorization: Bearer {{access_v2}}
HTTP 200
[Asserts]
jsonpath "$.username" == "{{username}}"
jsonpath "$.full.user.username" == "{{username}}"
# ─────────────────────────────────────────────────────────────
+9 -9
View File
@@ -52,7 +52,7 @@ Content-Type: application/json
HTTP 201
[Captures]
bob_user_id: jsonpath "$.id"
bob_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -73,8 +73,8 @@ Authorization: Bearer {{bob_token}}
HTTP 200
[Asserts]
jsonpath "$.is_external" == true
jsonpath "$.storage_quota_bytes" == 0
jsonpath "$.full.user.is_external" == true
jsonpath "$.full.storage_quota_bytes" == 0
# ─────────────────────────────────────────────────────────────
@@ -88,8 +88,8 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.is_external" == false
jsonpath "$.storage_quota_bytes" > 0
jsonpath "$.full.user.is_external" == false
jsonpath "$.full.storage_quota_bytes" > 0
# ─────────────────────────────────────────────────────────────
@@ -100,8 +100,8 @@ Authorization: Bearer {{bob_token}}
HTTP 200
[Asserts]
jsonpath "$.is_external" == false
jsonpath "$.storage_quota_bytes" > 0
jsonpath "$.full.user.is_external" == false
jsonpath "$.full.storage_quota_bytes" > 0
# ─────────────────────────────────────────────────────────────
@@ -179,7 +179,7 @@ Content-Type: application/json
HTTP 201
[Captures]
carol_user_id: jsonpath "$.id"
carol_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -204,7 +204,7 @@ Authorization: Bearer {{carol_token}}
HTTP 200
[Asserts]
jsonpath "$.is_external" == true
jsonpath "$.full.user.is_external" == true
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -41,7 +41,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -120,7 +120,7 @@ Content-Type: application/json
HTTP 200
[Captures]
bob_token: jsonpath "$.access_token"
bob_user_id: jsonpath "$.user.id"
bob_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -24,7 +24,7 @@ Content-Type: application/json
HTTP 200
[Captures]
token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
[Asserts]
jsonpath "$.access_token" isString
jsonpath "$.token_type" == "Bearer"
@@ -344,7 +344,7 @@ Content-Type: application/json
HTTP 200
[Captures]
bob_token: jsonpath "$.access_token"
bob_user_id: jsonpath "$.user.id"
bob_user_id: jsonpath "$.user.full.user.id"
# Step 17 — Bob's book listing does NOT include Alice's book.
+1 -1
View File
@@ -71,7 +71,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -67,7 +67,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -249,7 +249,7 @@ Content-Type: application/json
HTTP *
[Captures]
alice_id: jsonpath "$.id"
alice_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
+1 -1
View File
@@ -103,7 +103,7 @@ Content-Type: application/json
HTTP *
[Captures]
fresh_user_id: jsonpath "$.id"
fresh_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -65,7 +65,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# Provision `dp_intruder` — a second internal user used only to
@@ -91,7 +91,7 @@ Content-Type: application/json
HTTP 200
[Captures]
intruder_token: jsonpath "$.access_token"
intruder_user_id: jsonpath "$.user.id"
intruder_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -60,7 +60,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -82,7 +82,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -107,7 +107,7 @@ Content-Type: application/json
HTTP 200
[Captures]
target_user_id: jsonpath "$.user.id"
target_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+3 -3
View File
@@ -31,7 +31,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -70,7 +70,7 @@ Content-Type: application/json
HTTP 201
[Captures]
alice_user_id: jsonpath "$.id"
alice_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
Authorization: Bearer {{admin_token}}
@@ -79,7 +79,7 @@ Content-Type: application/json
HTTP 201
[Captures]
bob_user_id: jsonpath "$.id"
bob_user_id: jsonpath "$.user.id"
# Alice's first login fires `PersonalDriveLifecycleHook::on_user_login`
+5 -5
View File
@@ -64,7 +64,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -113,7 +113,7 @@ Content-Type: application/json
HTTP 201
[Captures]
alice_user_id: jsonpath "$.id"
alice_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -470,7 +470,7 @@ Content-Type: application/json
HTTP 201
[Captures]
bob_user_id: jsonpath "$.id"
bob_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -657,7 +657,7 @@ Content-Type: application/json
HTTP 201
[Captures]
carol_user_id: jsonpath "$.id"
carol_user_id: jsonpath "$.user.id"
# 24a — Owner grants Carol Owner role (Owner-creates-Owner).
@@ -836,7 +836,7 @@ Content-Type: application/json
HTTP 201
[Captures]
dave_user_id: jsonpath "$.id"
dave_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
+36 -10
View File
@@ -23,7 +23,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
GET {{base_url}}/api/folders
Authorization: Bearer {{alice_token}}
@@ -226,13 +226,16 @@ Authorization: Bearer {{bob_access_token}}
HTTP 200
[Asserts]
# `/api/users/{id}` returns the slim `PublicUserDto` (9 fields) —
# id / email / username / role / image / is_external / given_name /
# family_name / is_online. Admin-visible fields like
# `email_verified_at` moved to `FullUserDto` under the three-layer
# refactor (docs/plan/userdto-refactor.md) and are checked below
# via `/api/admin/users`.
jsonpath "$.id" == "{{bob_user_id}}"
jsonpath "$.is_external" == true
jsonpath "$.email" == "bob@externalcompany.com"
jsonpath "$.username" not exists
# PR 23 — bob redeemed his invitation magic-link in Step 8, so his
# email_verified_at was stamped at that time and stays set.
jsonpath "$.email_verified_at" exists
# 11d — bob CAN look up Alice (his granter) — shared-grant relationship
# lets the external recipient resolve the sharer's display name +
@@ -244,14 +247,37 @@ HTTP 200
[Asserts]
jsonpath "$.id" == "{{alice_user_id}}"
jsonpath "$.is_external" == false
# Setup admin is auto-verified at creation. `setup_create_admin` stamps
# 11c/d/verify — admin (alice) observes email_verified_at on both
# users via `GET /api/admin/users/{id}` — returns `FullUserDto`
# (public identity in `.user` + admin-visible extras at top level).
#
# `email_verified_at` lives on `FullUserDto` (admin+self-visible),
# not on `PublicUserDto` — peer views via `/api/users/{id}` never
# expose it. The admin single-user endpoint is the correct
# observation surface. See `docs/plan/userdto-refactor.md` for the
# three-layer split.
#
# Setup admin auto-verified rationale: `setup_create_admin` stamps
# `email_verified_at = NOW()` — admin fiat counts as verification,
# matching the OIDC-JIT convention. Rationale: an operator running the
# first-run wizard is authoritative by construction (they set the
# password at the console on a fresh install). Without this, flipping
# matching the OIDC-JIT convention. An operator running the first-run
# wizard is authoritative by construction. Without this, flipping
# `OXICLOUD_REQUIRE_VERIFIED_EMAIL=true` on an existing deployment
# would lock the sole admin out of their own instance. The admin login
# exemption is a second layer of defense; this stamp is the primary.
# would lock the sole admin out of their own instance.
GET {{base_url}}/api/admin/users/{{bob_user_id}}
Authorization: Bearer {{alice_token}}
HTTP 200
[Asserts]
jsonpath "$.user.id" == "{{bob_user_id}}"
jsonpath "$.email_verified_at" exists
GET {{base_url}}/api/admin/users/{{alice_user_id}}
Authorization: Bearer {{alice_token}}
HTTP 200
[Asserts]
jsonpath "$.user.id" == "{{alice_user_id}}"
jsonpath "$.email_verified_at" exists
# 11e — bob CANNOT enumerate unrelated users. A random UUID returns 404
+2 -2
View File
@@ -19,11 +19,11 @@ Content-Type: application/json
HTTP 200
[Captures]
token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
[Asserts]
jsonpath "$.access_token" isString
jsonpath "$.token_type" == "Bearer"
jsonpath "$.user.id" isString
jsonpath "$.user.full.user.id" isString
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -29,7 +29,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
GET {{base_url}}/api/folders
@@ -57,7 +57,7 @@ Content-Type: application/json
HTTP 201
[Captures]
mallory_user_id: jsonpath "$.id"
mallory_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
+5 -5
View File
@@ -23,7 +23,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
GET {{base_url}}/api/folders
Authorization: Bearer {{alice_token}}
@@ -45,7 +45,7 @@ Content-Type: application/json
HTTP 201
[Captures]
dave_user_id: jsonpath "$.id"
dave_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
Authorization: Bearer {{alice_token}}
@@ -54,7 +54,7 @@ Content-Type: application/json
HTTP 201
[Captures]
eve_user_id: jsonpath "$.id"
eve_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -371,7 +371,7 @@ Content-Type: application/json
HTTP 201
[Captures]
adam_user_id: jsonpath "$.id"
adam_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -1044,7 +1044,7 @@ Content-Type: application/json
HTTP 201
[Captures]
frank_user_id: jsonpath "$.id"
frank_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
+1 -1
View File
@@ -44,7 +44,7 @@ Content-Type: application/json
HTTP 201
[Captures]
henry_user_id: jsonpath "$.id"
henry_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
+1 -1
View File
@@ -76,7 +76,7 @@ Content-Type: application/json
HTTP 201
[Captures]
dora_id: jsonpath "$.id"
dora_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -48,7 +48,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -45,7 +45,7 @@ Content-Type: application/json
HTTP 200
[Captures]
jwt: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+2 -2
View File
@@ -50,5 +50,5 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.access_token" exists
jsonpath "$.user.username" == "bob"
jsonpath "$.user.email" == "bob@example.com"
jsonpath "$.user.full.user.username" == "bob"
jsonpath "$.user.full.user.email" == "bob@example.com"
+5 -5
View File
@@ -45,7 +45,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_jwt: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ═════════════════════════════════════════════════════════════
@@ -71,7 +71,7 @@ Content-Type: application/json
HTTP 201
[Captures]
editor_user_id: jsonpath "$.id"
editor_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
Authorization: Bearer {{admin_jwt}}
@@ -85,7 +85,7 @@ Content-Type: application/json
HTTP 201
[Captures]
viewer_user_id: jsonpath "$.id"
viewer_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
Authorization: Bearer {{admin_jwt}}
@@ -99,7 +99,7 @@ Content-Type: application/json
HTTP 201
[Captures]
outsider_user_id: jsonpath "$.id"
outsider_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -434,7 +434,7 @@ Content-Type: application/json
HTTP 201
[Captures]
quota_owner_id: jsonpath "$.id"
quota_owner_id: jsonpath "$.user.id"
PUT {{base_url}}/api/admin/users/{{quota_owner_id}}/quota
+4 -4
View File
@@ -40,7 +40,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_jwt: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ═════════════════════════════════════════════════════════════
@@ -65,7 +65,7 @@ Content-Type: application/json
HTTP 201
[Captures]
editor_user_id: jsonpath "$.id"
editor_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
Authorization: Bearer {{admin_jwt}}
@@ -79,7 +79,7 @@ Content-Type: application/json
HTTP 201
[Captures]
viewer_user_id: jsonpath "$.id"
viewer_user_id: jsonpath "$.user.id"
# ─────────────────────────────────────────────────────────────
@@ -351,7 +351,7 @@ Content-Type: application/json
HTTP 201
[Captures]
quota_owner_id: jsonpath "$.id"
quota_owner_id: jsonpath "$.user.id"
PUT {{base_url}}/api/admin/users/{{quota_owner_id}}/quota
+1 -1
View File
@@ -85,7 +85,7 @@ Content-Type: application/json
HTTP 200
[Captures]
ncq_owner_jwt: jsonpath "$.access_token"
ncq_owner_id: jsonpath "$.user.id"
ncq_owner_id: jsonpath "$.user.full.user.id"
POST {{base_url}}/api/auth/app-passwords
Authorization: Bearer {{ncq_owner_jwt}}
+2 -2
View File
@@ -45,7 +45,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -126,7 +126,7 @@ Content-Type: application/json
HTTP 200
[Captures]
bob_token: jsonpath "$.access_token"
bob_user_id: jsonpath "$.user.id"
bob_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+18 -18
View File
@@ -55,7 +55,7 @@ Content-Type: application/json
HTTP 200
[Captures]
charlie_token: jsonpath "$.access_token"
charlie_user_id: jsonpath "$.user.id"
charlie_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -139,16 +139,16 @@ Authorization: Bearer {{pr18_access_token}}
HTTP 200
[Asserts]
jsonpath "$.email" == "pr18-emailonly@example.com"
jsonpath "$.is_external" == false
jsonpath "$.username" not exists
jsonpath "$.full.user.email" == "pr18-emailonly@example.com"
jsonpath "$.full.user.is_external" == false
jsonpath "$.full.user.username" not exists
# PR 23 — the user redeemed the welcome magic-link in Step 5b, so
# email_verified_at is stamped (the click IS the proof of inbox
# control, regardless of whether the redemption went through the
# direct or cross-browser-confirm path).
jsonpath "$.email_verified_at" exists
jsonpath "$.full.email_verified_at" exists
[Captures]
pr18_user_id: jsonpath "$.id"
pr18_user_id: jsonpath "$.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -162,9 +162,9 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.id" == "{{pr18_user_id}}"
jsonpath "$.username" not exists
jsonpath "$.given_name" not exists
jsonpath "$.full.user.id" == "{{pr18_user_id}}"
jsonpath "$.full.user.username" not exists
jsonpath "$.full.user.given_name" not exists
# ─────────────────────────────────────────────────────────────
@@ -178,9 +178,9 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.given_name" == "Pee Are"
jsonpath "$.family_name" == "Eighteen"
jsonpath "$.username" not exists
jsonpath "$.full.user.given_name" == "Pee Are"
jsonpath "$.full.user.family_name" == "Eighteen"
jsonpath "$.full.user.username" not exists
# ─────────────────────────────────────────────────────────────
@@ -220,7 +220,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.username" == "pr18handle"
jsonpath "$.full.user.username" == "pr18handle"
# ─────────────────────────────────────────────────────────────
@@ -263,7 +263,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.given_name" == "Pr18@Handle"
jsonpath "$.full.user.given_name" == "Pr18@Handle"
# ─────────────────────────────────────────────────────────────
@@ -276,10 +276,10 @@ Authorization: Bearer {{pr18_access_token}}
HTTP 200
[Asserts]
jsonpath "$.username" == "pr18handle"
jsonpath "$.given_name" == "Pr18@Handle"
jsonpath "$.family_name" == "Eighteen"
jsonpath "$.email_verified_at" exists
jsonpath "$.full.user.username" == "pr18handle"
jsonpath "$.full.user.given_name" == "Pr18@Handle"
jsonpath "$.full.user.family_name" == "Eighteen"
jsonpath "$.full.email_verified_at" exists
# ─────────────────────────────────────────────────────────────
@@ -80,7 +80,7 @@ Content-Type: application/json
HTTP 200
[Captures]
user_token: jsonpath "$.access_token"
user_user_id: jsonpath "$.user.id"
user_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+3 -3
View File
@@ -34,7 +34,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
GET {{base_url}}/api/folders
Authorization: Bearer {{admin_token}}
@@ -56,7 +56,7 @@ Content-Type: application/json
HTTP 201
[Captures]
renee_user_id: jsonpath "$.id"
renee_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/admin/users
@@ -66,7 +66,7 @@ Content-Type: application/json
HTTP 201
[Captures]
sam_user_id: jsonpath "$.id"
sam_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
+4 -1
View File
@@ -75,7 +75,10 @@ log "Probe blob and thumbnail confirmed present on disk."
# subsequent trash-empty triggers garbage_collect() to remove the
# now-orphaned blob files from disk.
# /api/admin/users returns { users: [...], total, limit, offset }
# /api/admin/users returns { users: [PublicUserDto…], total, limit, offset }
# under the default `?summary=false` path — flat public-identity rows. The
# `?summary=true` path emits nested FullUserDto rows instead (used by the
# admin table); see `docs/plan/userdto-refactor.md`.
USERS_JSON=$(curl -sf -H "$AUTH" "$base_url/api/admin/users?limit=500")
ADMIN_USER_ID=$(echo "$USERS_JSON" \
+2 -2
View File
@@ -35,7 +35,7 @@ Content-Type: application/json
HTTP 201
[Captures]
grace_user_id: jsonpath "$.id"
grace_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -268,7 +268,7 @@ Content-Type: application/json
HTTP 201
[Captures]
helper_user_id: jsonpath "$.id"
helper_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/groups/{{engineers_id}}/members
+2 -2
View File
@@ -51,7 +51,7 @@ Content-Type: application/json
HTTP 201
[Captures]
owner_user_id: jsonpath "$.id"
owner_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
@@ -209,7 +209,7 @@ Content-Type: application/json
HTTP 201
[Captures]
viewer_user_id: jsonpath "$.id"
viewer_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
Content-Type: application/json
+6 -6
View File
@@ -64,7 +64,7 @@ Content-Type: application/json
HTTP 200
[Captures]
owner_token: jsonpath "$.access_token"
owner_user_id: jsonpath "$.user.id"
owner_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -92,7 +92,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
[Asserts]
jsonpath "$.storage_used_bytes" == 0
jsonpath "$.full.storage_used_bytes" == 0
# ─────────────────────────────────────────────────────────────
@@ -173,7 +173,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
[Asserts]
jsonpath "$.storage_used_bytes" == 0
jsonpath "$.full.storage_used_bytes" == 0
# ─────────────────────────────────────────────────────────────
@@ -202,7 +202,7 @@ retry-interval: 200ms
HTTP 200
[Asserts]
jsonpath "$.storage_used_bytes" == 32
jsonpath "$.full.storage_used_bytes" == 32
# Confirm the sweep agrees with the delta — both code paths must
@@ -217,7 +217,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
[Asserts]
jsonpath "$.storage_used_bytes" == 32
jsonpath "$.full.storage_used_bytes" == 32
# ─────────────────────────────────────────────────────────────
@@ -247,7 +247,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
[Asserts]
jsonpath "$.storage_used_bytes" == 0
jsonpath "$.full.storage_used_bytes" == 0
# ─────────────────────────────────────────────────────────────
+1 -1
View File
@@ -174,7 +174,7 @@ Content-Type: application/json
HTTP 201
[Captures]
quota_owner_id: jsonpath "$.id"
quota_owner_id: jsonpath "$.user.id"
PUT {{base_url}}/api/admin/users/{{quota_owner_id}}/quota
+2 -2
View File
@@ -35,7 +35,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
@@ -54,7 +54,7 @@ Content-Type: application/json
HTTP 201
[Captures]
bob_user_id: jsonpath "$.id"
bob_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
+1 -1
View File
@@ -151,7 +151,7 @@ Content-Type: application/json
HTTP 200
[Captures]
wq_owner_token: jsonpath "$.access_token"
wq_owner_id: jsonpath "$.user.id"
wq_owner_id: jsonpath "$.user.full.user.id"
POST {{base_url}}/api/drives
+2 -2
View File
@@ -39,7 +39,7 @@ Content-Type: application/json
HTTP 200
[Captures]
alice_token: jsonpath "$.access_token"
alice_user_id: jsonpath "$.user.id"
alice_user_id: jsonpath "$.user.full.user.id"
GET {{base_url}}/api/folders
@@ -65,7 +65,7 @@ Content-Type: application/json
HTTP 201
[Captures]
bob_user_id: jsonpath "$.id"
bob_user_id: jsonpath "$.user.id"
POST {{base_url}}/api/auth/login
+1 -1
View File
@@ -47,7 +47,7 @@ Content-Type: application/json
HTTP 200
[Captures]
admin_token: jsonpath "$.access_token"
admin_user_id: jsonpath "$.user.id"
admin_user_id: jsonpath "$.user.full.user.id"
# ─────────────────────────────────────────────────────────────
+26 -26
View File
@@ -98,11 +98,11 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "{{username}}"
jsonpath "$.full.user.username" == "{{username}}"
# federation_kind is skip_serializing_if=Option::is_none, so a
# local user's response OMITS the field entirely.
jsonpath "$.federation_kind" not exists
jsonpath "$.federation_issuer" not exists
jsonpath "$.full.federation_kind" not exists
jsonpath "$.full.federation_issuer" not exists
# ─────────────────────────────────────────────────────────────
@@ -202,8 +202,8 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" not exists
jsonpath "$.federation_issuer" not exists
jsonpath "$.full.federation_kind" not exists
jsonpath "$.full.federation_issuer" not exists
# ═════════════════════════════════════════════════════════════
@@ -252,8 +252,8 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" not exists
jsonpath "$.federation_issuer" not exists
jsonpath "$.full.federation_kind" not exists
jsonpath "$.full.federation_issuer" not exists
# ═════════════════════════════════════════════════════════════
@@ -307,9 +307,9 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "{{username}}"
jsonpath "$.federation_kind" == "oidc"
jsonpath "$.federation_issuer" == "{{oidc_issuer}}"
jsonpath "$.full.user.username" == "{{username}}"
jsonpath "$.full.federation_kind" == "oidc"
jsonpath "$.full.federation_issuer" == "{{oidc_issuer}}"
# Scenario 9 — unlink success (admin has a password, so the
@@ -326,8 +326,8 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" not exists
jsonpath "$.federation_issuer" not exists
jsonpath "$.full.federation_kind" not exists
jsonpath "$.full.federation_issuer" not exists
# ═════════════════════════════════════════════════════════════
@@ -380,7 +380,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" == "oidc"
jsonpath "$.full.federation_kind" == "oidc"
# Unlink to reset state before the auto-link scenarios.
@@ -447,9 +447,9 @@ HTTP 200
[Asserts]
# Auto-link resolved to the pre-existing admin, NOT a fresh
# JIT-provisioned user. The load-bearing assertion.
jsonpath "$.user.username" == "{{username}}"
jsonpath "$.user.federation_kind" == "oidc"
jsonpath "$.user.federation_issuer" == "{{oidc_issuer}}"
jsonpath "$.user.full.user.username" == "{{username}}"
jsonpath "$.user.full.federation_kind" == "oidc"
jsonpath "$.user.full.federation_issuer" == "{{oidc_issuer}}"
[Captures]
# Fresh cookies replace the password session's; capture the
# new CSRF for the unlink below.
@@ -463,9 +463,9 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "{{username}}"
jsonpath "$.federation_kind" == "oidc"
jsonpath "$.federation_issuer" == "{{oidc_issuer}}"
jsonpath "$.full.user.username" == "{{username}}"
jsonpath "$.full.federation_kind" == "oidc"
jsonpath "$.full.federation_issuer" == "{{oidc_issuer}}"
# Reset admin state before the next scenario (auto-link would
@@ -535,7 +535,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" not exists
jsonpath "$.full.federation_kind" not exists
# Reset fake IdP state (email_verified back to true, sub back
@@ -599,7 +599,7 @@ X-CSRF-Token: {{autolink_csrf_token}}
HTTP 201
[Captures]
alias_user_id: jsonpath "$.id"
alias_user_id: jsonpath "$.user.id"
# Point the fake IdP at a fresh sub with admin's email. Both
@@ -636,7 +636,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" not exists
jsonpath "$.full.federation_kind" not exists
# Cleanup — delete the collider so later scenarios see the same
@@ -701,8 +701,8 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.federation_kind" == "oidc"
jsonpath "$.user.full.user.username" == "oidc_user"
jsonpath "$.user.full.federation_kind" == "oidc"
[Captures]
# Fresh CSRF from the OIDC session cookies — the admin CSRFs
# won't validate against these new cookies.
@@ -730,5 +730,5 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.federation_kind" == "oidc"
jsonpath "$.federation_issuer" == "{{oidc_issuer}}"
jsonpath "$.full.federation_kind" == "oidc"
jsonpath "$.full.federation_issuer" == "{{oidc_issuer}}"
+19 -19
View File
@@ -172,7 +172,7 @@ Content-Type: application/json
HTTP 200
[Captures]
oidc_session_user: jsonpath "$.user.username"
oidc_session_user: jsonpath "$.user.full.user.username"
# Snapshotted so Step 7's refresh can prove the tokens rotated
# rather than being re-issued unchanged. The refresh handler in
# auth_handler.rs always rotates all three cookies (access JWT,
@@ -184,8 +184,8 @@ initial_access_token: jsonpath "$.access_token"
initial_refresh_token: jsonpath "$.refresh_token"
initial_csrf_token: cookie "oxicloud_csrf"
[Asserts]
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.email" == "oidc@example.com"
jsonpath "$.user.full.user.username" == "oidc_user"
jsonpath "$.user.full.user.email" == "oidc@example.com"
jsonpath "$.access_token" isString
# Multiple Set-Cookie headers come back as a list of values, so
# `contains` only matches whole-element strings. Each cookie shows up
@@ -211,10 +211,10 @@ HTTP 200
# Stash the user id for the re-login check in Step 10 below — a
# second OIDC flow with the same `sub` must resolve back to this
# exact user, not silently create a duplicate.
oidc_user_id: jsonpath "$.id"
oidc_user_id: jsonpath "$.full.user.id"
[Asserts]
jsonpath "$.username" == "oidc_user"
jsonpath "$.email" == "oidc@example.com"
jsonpath "$.full.user.username" == "oidc_user"
jsonpath "$.full.user.email" == "oidc@example.com"
# Post the federation-identity rename (docs/plan/ocm.md § Schema
# rename) UserDto exposes federation_kind + federation_issuer as
# separate nullable fields. Local users have both null; OIDC users
@@ -222,24 +222,24 @@ jsonpath "$.email" == "oidc@example.com"
# the fake IdP (tests/oidc/fake_idp/server.js) that URL is the
# issuer published in its discovery document, which matches
# `oidc_issuer` from test.env.
jsonpath "$.federation_kind" == "oidc"
jsonpath "$.federation_issuer" == "{{oidc_issuer}}"
jsonpath "$.full.federation_kind" == "oidc"
jsonpath "$.full.federation_issuer" == "{{oidc_issuer}}"
# Full claim round-trip — the fake IdP (tests/oidc/fake_idp/server.js)
# pins these values and OxiCloud must persist each one verbatim during
# JIT provisioning (see auth_application_service.rs around line 2257).
# A regression that drops, swaps, or truncates a claim trips here.
# Note the field name flip on the API side: OIDC `picture` becomes
# UserDto.image (a URL or data URI).
jsonpath "$.given_name" == "OIDC"
jsonpath "$.family_name" == "Test"
jsonpath "$.image" == "https://example.com/oidc-test-user.png"
jsonpath "$.full.user.given_name" == "OIDC"
jsonpath "$.full.user.family_name" == "Test"
jsonpath "$.full.user.image" == "https://example.com/oidc-test-user.png"
# Group-to-role mapping. server-with-oidc.env sets
# OXICLOUD_OIDC_ADMIN_GROUPS=admin-users; the fake IdP's claims include
# `groups: ["admin-users"]`. The JIT path intersects the claim against
# the env and promotes the new user from `user` to `admin`. A
# regression here would silently strip (or wrongly grant) admin rights
# for every SSO deployment that uses group-based role mapping.
jsonpath "$.role" == "admin"
jsonpath "$.full.user.role" == "admin"
# ─────────────────────────────────────────────────────────────
@@ -274,7 +274,7 @@ refreshed_refresh_token: jsonpath "$.refresh_token"
# the (freshly-rotated) `oxicloud_csrf` cookie on the browser.
refreshed_csrf_token: cookie "oxicloud_csrf"
[Asserts]
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.full.user.username" == "oidc_user"
jsonpath "$.access_token" isString
jsonpath "$.refresh_token" isString
# All three cookies must rotate. If any value were re-used, a
@@ -296,7 +296,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "oidc_user"
jsonpath "$.full.user.username" == "oidc_user"
# ─────────────────────────────────────────────────────────────
@@ -412,8 +412,8 @@ HTTP 200
[Asserts]
# Same local id — proves the existing-user resolver matched on `sub`
# (or `oidc_provider + oidc_subject`) instead of minting a new row.
jsonpath "$.user.id" == "{{oidc_user_id}}"
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.full.user.id" == "{{oidc_user_id}}"
jsonpath "$.user.full.user.username" == "oidc_user"
# Role from the prior JIT-provisioned admin survives the re-login.
# Two regressions this catches: (a) the existing-user branch wiping
# the role to a default `user`; (b) the existing-user branch
@@ -421,7 +421,7 @@ jsonpath "$.user.username" == "oidc_user"
# IdP still emits `groups: ["admin-users"]`, OXICLOUD_OIDC_ADMIN_GROUPS
# still resolves to "admin"). Either way, the role should remain
# `admin` — otherwise we have a silent admin demotion on every login.
jsonpath "$.user.role" == "admin"
jsonpath "$.user.full.user.role" == "admin"
# ─────────────────────────────────────────────────────────────
@@ -876,7 +876,7 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.full.user.username" == "oidc_user"
# ─────────────────────────────────────────────────────────────
@@ -889,7 +889,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "oidc_user"
jsonpath "$.full.user.username" == "oidc_user"
# ─────────────────────────────────────────────────────────────
+3 -3
View File
@@ -123,10 +123,10 @@ Content-Type: application/json
HTTP 200
[Asserts]
jsonpath "$.user.username" == "oidc_user"
jsonpath "$.user.full.user.username" == "oidc_user"
# Group-to-role mapping worked — this is now the admin (and the only
# user).
jsonpath "$.user.role" == "admin"
jsonpath "$.user.full.user.role" == "admin"
# ─────────────────────────────────────────────────────────────
@@ -138,7 +138,7 @@ GET {{base_url}}/api/auth/me
HTTP 200
[Asserts]
jsonpath "$.username" == "oidc_user"
jsonpath "$.full.user.username" == "oidc_user"
# ─────────────────────────────────────────────────────────────
@@ -132,9 +132,9 @@ echo " app password minted (id=$APP_PASSWORD_ID)"
# failure path. `storage_quota_bytes == 0` is the unlimited
# sentinel (see `check_storage_quota`); we read it back here in
# case a prior test set a real value.
ADMIN_ID=$(rest_get "/api/auth/me" | jq -r '.id')
ADMIN_ID=$(rest_get "/api/auth/me" | jq -r '.full.user.id')
[[ -n "$ADMIN_ID" && "$ADMIN_ID" != "null" ]] || fail "Failed to read admin user id"
ORIGINAL_ADMIN_QUOTA=$(rest_get "/api/auth/me" | jq -r '.storage_quota_bytes // 0')
ORIGINAL_ADMIN_QUOTA=$(rest_get "/api/auth/me" | jq -r '.full.storage_quota_bytes // 0')
# Single cleanup on exit:
# - restore admin's original storage envelope (in case Case 3
@@ -247,7 +247,7 @@ echo "[3/3] QUOTA REJECTION — envelope tightened to (used + 100 B), PUT 200 B
# `current + 100` — leaves enough headroom that MKCOL passes
# (`used + 0 = used < used + 100`) while a 200 B chunk PUT
# overflows by exactly 100 (`used + 0 + 200 > used + 100`).
CURRENT_USED=$(rest_get "/api/auth/me" | jq -r '.storage_used_bytes')
CURRENT_USED=$(rest_get "/api/auth/me" | jq -r '.full.storage_used_bytes')
[[ -n "$CURRENT_USED" && "$CURRENT_USED" != "null" ]] || fail "Failed to read current used_bytes"
TIGHT_QUOTA=$(( CURRENT_USED + 100 ))