test(e2e): webdav + nextcloud full e2e test coverage
add a full coverage of Webdav and Nextcloud
purpose: prepare move to Drives and ensure no regression at all
test scenarios are in docs/plan/BASELINE_TESTS_NC_WEBDAV.md
current existing bugs identified via these tests:
┌──────────┬─────────┬────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Bug │ Surface │ Pin location │
├──────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ G4/G5/K5 │ NC │ AlreadyExists → 500 instead of 412 (handle_move + trashbin restore) │
├──────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ G9 │ NC │ Folder DELETE not row-recursive — orphan descendants stay live │
├──────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ M5/M7 │ Native │ resolve_path_for_user mismatch — PUT writes, GET reads via lenient lookup, MOVE/DELETE can't find │
│ │ │ via strict │
├──────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ M8 │ Native │ COPY discards destination filename — collides with source │
├──────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ N2 │ Native │ LOCK creates the token, mutators don't check it — class-2 advertisement is aspirational │
└──────────┴─────────┴────────────────────────────────────────────────────────────────────────────────────────────────────┘
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: admin views another user's OCS profile
|
||||
# =============================================================
|
||||
# C4 from BASELINE_TESTS_NC_WEBDAV.md.
|
||||
#
|
||||
# Deferred from Batch 1 because it needed the bob fixture
|
||||
# that `nc_second_user_setup.hurl` now provides. Pins the
|
||||
# behaviour of the existing rule in
|
||||
# `interfaces/nextcloud/ocs_handler.rs::user_provisioning_response`:
|
||||
#
|
||||
# if user.username != userid && user.role != "admin" {
|
||||
# return Json(ocs_err(403, ...)).into_response();
|
||||
# }
|
||||
#
|
||||
# i.e. you can read your own profile always; you can read
|
||||
# anyone's profile if you're admin. Bob is not admin, so bob
|
||||
# CANNOT read admin's profile (the symmetric assertion).
|
||||
#
|
||||
# Uses admin's app password for Basic Auth (same pattern as
|
||||
# `nc_ocs_user_info.hurl`).
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 1 — JWT login as admin + mint NC app password.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
admin_jwt: jsonpath "$.access_token"
|
||||
|
||||
POST {{base_url}}/api/auth/app-passwords
|
||||
Authorization: Bearer {{admin_jwt}}
|
||||
Content-Type: application/json
|
||||
{ "label": "nc_admin_views_other_user hurl test" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
admin_nc_user: jsonpath "$.username"
|
||||
admin_nc_pw: jsonpath "$.password"
|
||||
admin_nc_pw_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 2 — JWT login as bob + mint NC app password.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "bob", "password": "BobPassword1!" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
bob_jwt: jsonpath "$.access_token"
|
||||
|
||||
POST {{base_url}}/api/auth/app-passwords
|
||||
Authorization: Bearer {{bob_jwt}}
|
||||
Content-Type: application/json
|
||||
{ "label": "nc_admin_views_other_user hurl test (bob)" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
bob_nc_user: jsonpath "$.username"
|
||||
bob_nc_pw: jsonpath "$.password"
|
||||
bob_nc_pw_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C4-positive — admin CAN read bob's OCS provisioning profile
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v1.php/cloud/users/bob?format=json
|
||||
[BasicAuth]
|
||||
{{admin_nc_user}}: {{admin_nc_pw}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.statuscode" == 100
|
||||
jsonpath "$.ocs.data.id" == "bob"
|
||||
jsonpath "$.ocs.data.email" == "bob@example.com"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C4-symmetric — bob (non-admin) CANNOT read admin's profile
|
||||
# (proves the admin-only branch isn't a no-op)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v1.php/cloud/users/{{username}}?format=json
|
||||
[BasicAuth]
|
||||
{{bob_nc_user}}: {{bob_nc_pw}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.statuscode" == 403
|
||||
jsonpath "$.ocs.meta.status" == "failure"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Teardown — revoke both app passwords.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/auth/app-passwords/{{admin_nc_pw_id}}
|
||||
Authorization: Bearer {{admin_jwt}}
|
||||
HTTP 200
|
||||
|
||||
DELETE {{base_url}}/api/auth/app-passwords/{{bob_nc_pw_id}}
|
||||
Authorization: Bearer {{bob_jwt}}
|
||||
HTTP 200
|
||||
@@ -0,0 +1,282 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: NC Basic Auth failure modes
|
||||
# =============================================================
|
||||
# Group P from BASELINE_TESTS_NC_WEBDAV.md.
|
||||
#
|
||||
# Coverage:
|
||||
# P1 — no Authorization header → 401 + WWW-Authenticate
|
||||
# P2 — wrong password (real user) → 401
|
||||
# P3 — N wrong attempts from same IP against a THROWAWAY
|
||||
# username trip the per-(account,IP) lockout
|
||||
# P4 — per-IP lockout scope (the #323 regression guard):
|
||||
# 6 wrong attempts from spoofed X-Forwarded-For: IP1
|
||||
# lock (admin, IP1), but the SAME correct credential
|
||||
# from spoofed X-Forwarded-For: IP2 still succeeds.
|
||||
# Depends on OXICLOUD_TRUST_PROXY_HEADERS=true in
|
||||
# tests/common/server.env so the server honours the
|
||||
# X-Forwarded-For header on localhost.
|
||||
#
|
||||
# Deliberately NOT covered here:
|
||||
# P5 — External user attempts NC Basic Auth. Externals can't
|
||||
# mint app passwords in the first place (the upstream gate
|
||||
# is asserted in tests/api/external_users.hurl), so the
|
||||
# in-middleware belt-and-braces check is unreachable via
|
||||
# a black-box HTTP test. Verified by code inspection.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 1 — JWT login (needed to mint the app password for P4's
|
||||
# positive control).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
jwt: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 2 — Mint admin's NC app password.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/app-passwords
|
||||
Authorization: Bearer {{jwt}}
|
||||
Content-Type: application/json
|
||||
{ "label": "nc_auth_failures P4 positive control" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
nc_username: jsonpath "$.username"
|
||||
nc_password: jsonpath "$.password"
|
||||
ap_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# P1 — PROPFIND without any Authorization header
|
||||
# → 401 with `WWW-Authenticate: Basic realm="OxiCloud"`.
|
||||
# NC desktop relies on this challenge to know it should
|
||||
# offer credentials at all.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
|
||||
HTTP 401
|
||||
[Asserts]
|
||||
header "WWW-Authenticate" contains "Basic"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# P2 — Wrong password against a real user
|
||||
# → 401 with the same WWW-Authenticate challenge.
|
||||
# Anti-enumeration: response shape identical whether the
|
||||
# user exists or not.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
[BasicAuth]
|
||||
{{username}}: definitely-wrong-password
|
||||
|
||||
HTTP 401
|
||||
[Asserts]
|
||||
header "WWW-Authenticate" contains "Basic"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# P3 — Lockout trip
|
||||
#
|
||||
# Consecutive bad-credential attempts against a throwaway
|
||||
# username trip the per-(account, IP) lockout. The default
|
||||
# threshold is 5 (`OXICLOUD_LOCKOUT_MAX_FAILURES = 5`) and
|
||||
# `tests/common/server.env` does NOT override it — the only
|
||||
# raised values in that env are the broader login / refresh /
|
||||
# register rate-limits, which prevent the rate-limiter from
|
||||
# firing AHEAD of the lockout under sustained test traffic.
|
||||
# The lockout itself still engages on the 6th attempt.
|
||||
#
|
||||
# Once engaged, every subsequent attempt for the same
|
||||
# (account, IP) pair short-circuits to 401 from the lockout
|
||||
# check, BEFORE app-password verification runs. The audit log
|
||||
# fires `WARN account_temporarily_locked` from
|
||||
# `login_lockout_service.rs` when the cap is hit — useful
|
||||
# correlation signal during a real run.
|
||||
#
|
||||
# A throwaway username (`nc-lockout-probe-…`) is used because
|
||||
# the lockout is keyed by (username, IP); locking a throwaway
|
||||
# pair never poisons admin's auth path, so downstream Hurl
|
||||
# tests in run.sh that authenticate as admin keep working.
|
||||
#
|
||||
# Limitation of a black-box HTTP probe: the wire response is
|
||||
# 401 with the same WWW-Authenticate header whether the 401
|
||||
# comes from "lockout engaged" or "still just rejecting bad
|
||||
# creds" — both look the same on the wire. Verification of
|
||||
# the lockout-engaged branch specifically lives in the unit
|
||||
# tests (`login_lockout_service.rs::tests`). What this Hurl
|
||||
# test guards is that 7 consecutive attempts keep returning
|
||||
# the same 401 shape (no 500s, no header drift), and the
|
||||
# server-side audit log confirms the lockout engaged at 5.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-1
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-2
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-3
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-4
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-5
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-6
|
||||
|
||||
HTTP 401
|
||||
|
||||
# 7th attempt: account+IP is now locked at the middleware level.
|
||||
# Continues to return 401.
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/nc-lockout-probe-001/
|
||||
[BasicAuth]
|
||||
nc-lockout-probe-001: bad-7
|
||||
|
||||
HTTP 401
|
||||
[Asserts]
|
||||
header "WWW-Authenticate" contains "Basic"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# P4 — Per-IP lockout scope (#323 regression guard)
|
||||
#
|
||||
# Goal: prove that locking out (admin, IP1) does NOT lock out
|
||||
# (admin, IP2) — the lockout is keyed by *both* parts, not by
|
||||
# username alone. This was the gap the reporter demonstrated:
|
||||
# an attacker spoofing X-Forwarded-For could lock a legitimate
|
||||
# user out from their own IP. The fix scoped the key.
|
||||
#
|
||||
# Mechanic:
|
||||
# 1. Pre-check: admin's app password works from the test's
|
||||
# default client IP (127.0.0.1, no X-Forwarded-For).
|
||||
# 2. 6 wrong attempts with X-Forwarded-For: 10.0.0.1 trip the
|
||||
# lockout for (admin, 10.0.0.1).
|
||||
# 3. CORRECT app password with X-Forwarded-For: 10.0.0.1 →
|
||||
# 401 (still locked from THIS IP — positive demonstration
|
||||
# that the lockout actually engaged, not just chance).
|
||||
# 4. CORRECT app password with X-Forwarded-For: 10.0.0.2 →
|
||||
# 207 (NOT locked from this IP — the load-bearing
|
||||
# assertion of P4).
|
||||
#
|
||||
# Why this doesn't break the rest of the suite: every other
|
||||
# test runs from the default client IP (127.0.0.1) without
|
||||
# X-Forwarded-For, so (admin, 127.0.0.1) is untouched. The
|
||||
# lockouts placed here are on (admin, 10.0.0.1) and
|
||||
# (admin, 10.0.0.2 — released by the success), neither of
|
||||
# which any other test touches.
|
||||
#
|
||||
# Requires: OXICLOUD_TRUST_PROXY_HEADERS=true in
|
||||
# tests/common/server.env.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Pre-check: app password works with no X-Forwarded-For ───
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 207
|
||||
|
||||
|
||||
# ── Step 1: burn the lockout for (admin, 10.0.0.1) ──────────
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-1
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-2
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-3
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-4
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-5
|
||||
|
||||
HTTP 401
|
||||
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{username}}: p4-bad-6
|
||||
|
||||
HTTP 401
|
||||
|
||||
|
||||
# ── Step 2: CORRECT app password from IP1 — still 401 ──────
|
||||
# This is the positive demonstration that the lockout engaged.
|
||||
# If this were 207, the lockout would not have fired and the
|
||||
# subsequent IP2 success wouldn't prove anything.
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.1
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 401
|
||||
|
||||
|
||||
# ── Step 3: CORRECT app password from IP2 — 207 ────────────
|
||||
# The load-bearing assertion of P4: a successful auth from a
|
||||
# DIFFERENT spoofed source IP proves the lockout was scoped
|
||||
# to (admin, 10.0.0.1) and not to admin alone.
|
||||
PROPFIND {{base_url}}/remote.php/dav/files/{{username}}/
|
||||
X-Forwarded-For: 10.0.0.2
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 207
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Teardown — Revoke the app password we minted. Keeps the
|
||||
# app-passwords table clean across re-runs.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/auth/app-passwords/{{ap_id}}
|
||||
Authorization: Bearer {{jwt}}
|
||||
|
||||
HTTP 200
|
||||
@@ -0,0 +1,109 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: NC avatar + preview
|
||||
# =============================================================
|
||||
# Group L from BASELINE_TESTS_NC_WEBDAV.md (3 scenarios).
|
||||
#
|
||||
# All NC routes (including avatars and previews) sit behind the
|
||||
# `basic_auth_middleware` wired in `interfaces/nextcloud/
|
||||
# routes.rs:174`. Even though the avatar payload is described as
|
||||
# "decorative, not security-critical" in `avatar_handler.rs`,
|
||||
# the request itself still requires a valid Basic Auth identity.
|
||||
# Both L1 and L3 therefore mint an app password and pass it.
|
||||
#
|
||||
# L1 / L3 pin the avatar handler's actual contract: once
|
||||
# authenticated, it ALWAYS returns 200 — stored profile image
|
||||
# when present, SVG-with-initials otherwise (including for
|
||||
# users that don't exist at all).
|
||||
#
|
||||
# L2 hits the authenticated preview endpoint with a fake file
|
||||
# id and pins the not-found path. A positive-path preview test
|
||||
# would require seeding an image file and resolving its NC
|
||||
# numeric id, which is more setup than this baseline needs.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 1 — JWT login (gives us a Bearer token to mint the
|
||||
# app password used by the avatar Basic Auth + the
|
||||
# preview JWT auth).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
jwt: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 2 — Mint an app password for the L1/L3 Basic Auth.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/app-passwords
|
||||
Authorization: Bearer {{jwt}}
|
||||
Content-Type: application/json
|
||||
{ "label": "nc_avatar_preview hurl test" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
nc_username: jsonpath "$.username"
|
||||
nc_password: jsonpath "$.password"
|
||||
ap_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# L1 — Avatar for an existing user (admin) always 200
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/index.php/avatar/{{username}}/64
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
header "Content-Type" startsWith "image/"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# L3 — Avatar for a nonexistent user
|
||||
#
|
||||
# Pinned current behaviour: 200 with the SVG-initials fallback.
|
||||
# The handler explicitly comments "decorative, not security-
|
||||
# critical" — it never returns 404 for an unknown name; it
|
||||
# renders initials from whatever string the caller passed.
|
||||
# RFC strictness would suggest 404 here, but NC desktop / Web
|
||||
# UI happily render the SVG.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/index.php/avatar/nonexistent-user-deadbeef/64
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
header "Content-Type" startsWith "image/"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# L2 — Preview of a non-existent file id → 404
|
||||
#
|
||||
# fileId=99999999 is well below any real NC id we'd ever
|
||||
# assign, so this exercises the "file not found" branch
|
||||
# without depending on a seeded image fixture.
|
||||
#
|
||||
# Auth: same Basic Auth as L1/L3 — the NC `/index.php/*`
|
||||
# surface is uniformly behind `basic_auth_middleware`, so Bearer
|
||||
# JWT is rejected at the middleware boundary before the handler
|
||||
# even sees the request.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/index.php/core/preview?fileId=99999999&x=128&y=128
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Teardown — revoke the app password.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/auth/app-passwords/{{ap_id}}
|
||||
Authorization: Bearer {{jwt}}
|
||||
HTTP 200
|
||||
@@ -0,0 +1,74 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: NC Login Flow v2
|
||||
# =============================================================
|
||||
# Group B from BASELINE_TESTS_NC_WEBDAV.md.
|
||||
#
|
||||
# Login Flow v2 is how NC desktop / iOS / Android bootstrap an
|
||||
# app password without ever seeing the user's real password.
|
||||
# Breaking the JSON shape returned by `/index.php/login/v2` or
|
||||
# `…/poll` means no new desktop client can pair.
|
||||
#
|
||||
# Coverage:
|
||||
# B1 — POST /index.php/login/v2 returns { login, poll: {…} }
|
||||
# B2 — POST …/login/v2/poll before grant → 404
|
||||
# B5 — POST …/login/v2/poll with unknown / expired token → 404
|
||||
#
|
||||
# Deliberately deferred:
|
||||
# B3 — Simulate the browser-side grant. This requires a
|
||||
# multi-step interaction with the grant page (HTML form
|
||||
# POST) that is awkward in Hurl and tied to the device-
|
||||
# auth-grant internals. Covered separately by
|
||||
# tests/webdav/ once the bash tooling for browser
|
||||
# simulation lands.
|
||||
# B4 — Poll AFTER grant. Same dependency as B3.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# B1 — POST /index.php/login/v2 (no auth)
|
||||
# → 200 with JSON `{ login: <url>, poll: { token, endpoint } }`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/index.php/login/v2
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
poll_token: jsonpath "$.poll.token"
|
||||
poll_endpoint: jsonpath "$.poll.endpoint"
|
||||
login_url: jsonpath "$.login"
|
||||
[Asserts]
|
||||
jsonpath "$.poll.token" exists
|
||||
jsonpath "$.poll.endpoint" exists
|
||||
jsonpath "$.login" exists
|
||||
# The login URL embeds the flow token as a PATH segment
|
||||
# (`/login/v2/flow/<token>`), not a query param — that's what
|
||||
# the NC desktop client follows after init.
|
||||
jsonpath "$.login" contains "/login/v2/flow/"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# B2 — POST /login/v2/poll BEFORE the user grants
|
||||
# → 404 (NC convention: "not yet ready").
|
||||
#
|
||||
# The token here is the one captured in B1, so the server
|
||||
# recognises the flow exists; it just hasn't been granted
|
||||
# yet.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: {{poll_token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# B5 — POST /login/v2/poll with an unknown / never-issued token
|
||||
# → 404. Same shape as B2 — server doesn't distinguish
|
||||
# "wrong token" from "right token, not granted yet" on the
|
||||
# wire (anti-enumeration: a probe can't tell which flows
|
||||
# exist).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: nonexistent-token-deadbeef-cafef00d
|
||||
|
||||
HTTP 404
|
||||
@@ -0,0 +1,160 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: OCS user-info + provisioning
|
||||
# =============================================================
|
||||
# Group C from BASELINE_TESTS_NC_WEBDAV.md.
|
||||
#
|
||||
# /ocs/v{1,2}.php/cloud/user is what NC desktop reads after
|
||||
# Login Flow v2 to learn its `data.id` — and that exact string
|
||||
# is then spliced into every subsequent DAV path
|
||||
# (`/remote.php/dav/files/{id}/…`). A regression in this shape
|
||||
# breaks 100% of subsequent syncs.
|
||||
#
|
||||
# Coverage:
|
||||
# C1 — GET /ocs/v1.php/cloud/user → statuscode 100 + payload
|
||||
# C2 — GET /ocs/v2.php/cloud/user → statuscode 200 + payload
|
||||
# C3 — GET /ocs/v1.php/cloud/users/admin (self provisioning)
|
||||
# C5 — GET /ocs/v2.php/apps/files_sharing/api/v1/sharees shape
|
||||
#
|
||||
# Deferred:
|
||||
# C4 — admin reading another user's provisioning profile.
|
||||
# Needs a second user fixture wired into setup.hurl.
|
||||
# Tracked as a TODO in BASELINE_TESTS_NC_WEBDAV.md §7.
|
||||
#
|
||||
# Setup pattern: this file mints its own app password inline
|
||||
# (steps 1–2) so it is self-contained and resilient to test
|
||||
# ordering. The mint uses the JWT-authenticated REST API
|
||||
# (`POST /api/auth/app-passwords`); the NC-side username
|
||||
# returned by the response is exactly the value the NC client
|
||||
# would use as the HTTP Basic Auth username.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 1 — JWT login (gives us a Bearer token to mint the
|
||||
# app password).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
jwt: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup 2 — Mint an app password for this test file. The
|
||||
# response carries the plaintext password (shown
|
||||
# exactly once) and the username to use in Basic Auth.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/app-passwords
|
||||
Authorization: Bearer {{jwt}}
|
||||
Content-Type: application/json
|
||||
{ "label": "nc_ocs_user_info hurl test" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
nc_username: jsonpath "$.username"
|
||||
nc_password: jsonpath "$.password"
|
||||
ap_id: jsonpath "$.id"
|
||||
[Asserts]
|
||||
jsonpath "$.password" matches "^oxicloud-"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C1 — GET /ocs/v1.php/cloud/user
|
||||
# data.id is what NC client splices into DAV URLs.
|
||||
#
|
||||
# Note: `handle_user_info` returns `statuscode: 200`
|
||||
# regardless of /v1.php vs /v2.php (unlike capabilities,
|
||||
# which switches on ocs_version). C1 and C2 therefore
|
||||
# both assert 200 here — this is the actual server
|
||||
# behaviour; if either diverges in future, this is the
|
||||
# pin that catches it.
|
||||
#
|
||||
# Hurl JSONPath doesn't accept `-` in dotted form, so the
|
||||
# assertion goes against `displayname` (the unhyphenated
|
||||
# alias the handler also emits) rather than `display-name`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v1.php/cloud/user?format=json
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.status" == "ok"
|
||||
jsonpath "$.ocs.meta.statuscode" == 200
|
||||
jsonpath "$.ocs.meta.message" == "OK"
|
||||
jsonpath "$.ocs.data.enabled" == true
|
||||
jsonpath "$.ocs.data.id" == "{{username}}"
|
||||
jsonpath "$.ocs.data.email" == "{{email}}"
|
||||
jsonpath "$.ocs.data.displayname" exists
|
||||
jsonpath "$.ocs.data.quota.used" exists
|
||||
jsonpath "$.ocs.data.quota.total" exists
|
||||
jsonpath "$.ocs.data.quota.free" exists
|
||||
jsonpath "$.ocs.data.quota.relative" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C2 — GET /ocs/v2.php/cloud/user
|
||||
# Same payload shape; OCS v2 envelope reports statuscode 200.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v2.php/cloud/user?format=json
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.status" == "ok"
|
||||
jsonpath "$.ocs.meta.statuscode" == 200
|
||||
jsonpath "$.ocs.data.id" == "{{username}}"
|
||||
jsonpath "$.ocs.data.email" == "{{email}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C3 — GET /ocs/v1.php/cloud/users/{userid} (self lookup)
|
||||
# Full provisioning profile: groups, lastLogin, backend.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v1.php/cloud/users/{{username}}?format=json
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.statuscode" == 100
|
||||
jsonpath "$.ocs.data.id" == "{{username}}"
|
||||
jsonpath "$.ocs.data.email" == "{{email}}"
|
||||
jsonpath "$.ocs.data.groups" exists
|
||||
jsonpath "$.ocs.data.backend" exists
|
||||
jsonpath "$.ocs.data.lastLogin" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# C5 — GET /ocs/v2.php/apps/files_sharing/api/v1/sharees
|
||||
# Sharees autocomplete shape — NC desktop uses this to
|
||||
# populate the share-dialog. Even with no shares set up
|
||||
# yet, the envelope + array slots must exist.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v2.php/apps/files_sharing/api/v1/sharees?format=json&search=ad&itemType=file
|
||||
[BasicAuth]
|
||||
{{nc_username}}: {{nc_password}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.status" == "ok"
|
||||
jsonpath "$.ocs.meta.statuscode" == 200
|
||||
jsonpath "$.ocs.data.exact" exists
|
||||
jsonpath "$.ocs.data.exact.users" exists
|
||||
jsonpath "$.ocs.data.exact.groups" exists
|
||||
jsonpath "$.ocs.data.users" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Teardown — Revoke the app password we minted (captured `ap_id`
|
||||
# from the create response, no list+lookup needed).
|
||||
# Keeps the test surface clean across re-runs.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/auth/app-passwords/{{ap_id}}
|
||||
Authorization: Bearer {{jwt}}
|
||||
|
||||
HTTP 200
|
||||
@@ -0,0 +1,54 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: second-user fixture (bob)
|
||||
# =============================================================
|
||||
# (Re)creates the `bob` user so the Group O cross-user
|
||||
# isolation scenarios (and Group C's C4 admin-reads-another-
|
||||
# user case) have a real second principal to test against.
|
||||
#
|
||||
# Run-order: this file runs AFTER `external_users.hurl`, which
|
||||
# deletes bob at its end, and AFTER `permissions.hurl`, which
|
||||
# creates+uses bob via the admin API. By the time this file
|
||||
# runs, bob may or may not exist — the anti-enumeration
|
||||
# registration endpoint returns 200 either way, so this is
|
||||
# safe in both states. The login step that follows is the
|
||||
# actual existence assertion: if bob can log in, downstream
|
||||
# bob-dependent tests (nc_admin_views_other_user.hurl here
|
||||
# and test_nc_cross_user_isolation.sh in the webdav suite)
|
||||
# will work.
|
||||
#
|
||||
# Bob's credentials are hardcoded fixtures (not env-driven) so
|
||||
# downstream test files don't need to coordinate via test.env.
|
||||
# Bob's password is unique per fixture; no overlap with admin.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 1 — Register bob via the email-anti-enumeration flow.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/register
|
||||
Content-Type: application/json
|
||||
{
|
||||
"username": "bob",
|
||||
"email": "bob@example.com",
|
||||
"password": "BobPassword1!"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.message" contains "request received"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 2 — Confirm bob can log in (proves registration
|
||||
# actually landed the account — the anti-enum response
|
||||
# alone wouldn't tell us).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "bob", "password": "BobPassword1!" }
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.access_token" exists
|
||||
jsonpath "$.user.username" == "bob"
|
||||
jsonpath "$.user.email" == "bob@example.com"
|
||||
@@ -0,0 +1,71 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Baseline: NC status + capabilities
|
||||
# =============================================================
|
||||
# Group A from BASELINE_TESTS_NC_WEBDAV.md (4 scenarios).
|
||||
#
|
||||
# These four endpoints are what every NC client probes first.
|
||||
# If any of them returns the wrong shape, NC desktop refuses to
|
||||
# even attempt sync, so the regression signal is loud and early.
|
||||
#
|
||||
# Anonymous (no auth) — these endpoints must be public.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# A1 — GET /status.php
|
||||
# Used by NC client for "is this server installed?".
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/status.php
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.installed" == true
|
||||
jsonpath "$.maintenance" == false
|
||||
jsonpath "$.version" exists
|
||||
jsonpath "$.versionstring" exists
|
||||
jsonpath "$.productname" == "OxiCloud"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# A2 — GET /index.php/204
|
||||
# NC mobile connectivity probe. Must be 204, empty body.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/index.php/204
|
||||
|
||||
HTTP 204
|
||||
[Asserts]
|
||||
bytes count == 0
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# A3 — GET /ocs/v1.php/cloud/capabilities
|
||||
# OCS v1 envelope must report statuscode 100.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v1.php/cloud/capabilities?format=json
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.status" == "ok"
|
||||
jsonpath "$.ocs.meta.statuscode" == 100
|
||||
jsonpath "$.ocs.meta.message" == "OK"
|
||||
jsonpath "$.ocs.data.version.major" exists
|
||||
jsonpath "$.ocs.data.capabilities" exists
|
||||
jsonpath "$.ocs.data.capabilities.theming.name" == "OxiCloud"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# A4 — GET /ocs/v2.php/cloud/capabilities
|
||||
# OCS v2 envelope must report statuscode 200 (200 ≡ 100
|
||||
# semantically — different status enum across versions).
|
||||
# Payload shape otherwise identical to v1.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/ocs/v2.php/cloud/capabilities?format=json
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ocs.meta.status" == "ok"
|
||||
jsonpath "$.ocs.meta.statuscode" == 200
|
||||
jsonpath "$.ocs.meta.message" == "OK"
|
||||
jsonpath "$.ocs.data.version.major" exists
|
||||
jsonpath "$.ocs.data.capabilities" exists
|
||||
jsonpath "$.ocs.data.capabilities.theming.name" == "OxiCloud"
|
||||
+40
-12
@@ -62,24 +62,39 @@ OXICLOUD_SERVER_PORT=$SERVER_PORT
|
||||
OXICLOUD_STORAGE_PATH="$REPO_ROOT/tests/api/storage"
|
||||
set +a
|
||||
|
||||
# ensure storage is empty before starting
|
||||
echo "Wipe $OXICLOUD_STORAGE_PATH to ensure clean startup"
|
||||
rm -rf "$OXICLOUD_STORAGE_PATH"
|
||||
mkdir -p "$OXICLOUD_STORAGE_PATH"
|
||||
# ensure storage is empty before starting (regex-gated rm -rf)
|
||||
# shellcheck source=../common/wipe-storage.sh
|
||||
source "$COMMON/wipe-storage.sh"
|
||||
wipe_storage "$OXICLOUD_STORAGE_PATH"
|
||||
|
||||
# ── 3. Start OxiCloud server ──────────────────────────────────────────────────
|
||||
|
||||
BUILD_TARGET="${BUILD_TARGET:-debug}"
|
||||
OXICLOUD_BIN="$REPO_ROOT/target/$BUILD_TARGET/oxicloud"
|
||||
|
||||
if [[ -x "$OXICLOUD_BIN" ]]; then
|
||||
log "Starting pre-built OxiCloud server ($BUILD_TARGET) on port $SERVER_PORT..."
|
||||
"$OXICLOUD_BIN" &
|
||||
else
|
||||
log "Building and starting OxiCloud server on port $SERVER_PORT..."
|
||||
cd "$REPO_ROOT"
|
||||
cargo run &
|
||||
# Build synchronously (no time cap — clean builds take minutes) BEFORE
|
||||
# starting the server, so the `/ready` poll below only times what we
|
||||
# actually want it to time: server startup, not compilation. Earlier
|
||||
# this ran `cargo run &` directly, which conflated the two and tripped
|
||||
# the 120 s readiness timeout on any `cargo clean` run.
|
||||
if [[ ! -x "$OXICLOUD_BIN" ]]; then
|
||||
log "Building OxiCloud server ($BUILD_TARGET) — this can take a few minutes after \`cargo clean\`..."
|
||||
# Cargo's debug profile is the implicit default (`cargo build` alone)
|
||||
# — there is NO `--profile debug` flag (it would error). Only the
|
||||
# release path needs an explicit flag.
|
||||
case "$BUILD_TARGET" in
|
||||
debug) (cd "$REPO_ROOT" && cargo build 2>&1 | tail -n 20) || die "cargo build failed" ;;
|
||||
release) (cd "$REPO_ROOT" && cargo build --release 2>&1 | tail -n 20) || die "cargo build --release failed" ;;
|
||||
*) die "Unsupported BUILD_TARGET='$BUILD_TARGET' (expected 'debug' or 'release')" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ ! -x "$OXICLOUD_BIN" ]]; then
|
||||
die "Build completed but $OXICLOUD_BIN is missing — wrong BUILD_TARGET?"
|
||||
fi
|
||||
|
||||
log "Starting OxiCloud server ($BUILD_TARGET) on port $SERVER_PORT..."
|
||||
"$OXICLOUD_BIN" &
|
||||
SERVER_PID=$!
|
||||
log "Waiting for server at $base_url..."
|
||||
wait_for_http "$base_url/ready" 120
|
||||
@@ -100,10 +115,20 @@ fi
|
||||
# ── 4. Run Hurl tests ─────────────────────────────────────────────────────────
|
||||
|
||||
log "Running Hurl tests..."
|
||||
# NC baseline tests (groups A + B + C from BASELINE_TESTS_NC_WEBDAV.md)
|
||||
# are interleaved early because they use a separate code surface and
|
||||
# their failures should not be masked by later test regressions.
|
||||
# The auth-failure / lockout file (group P) runs LAST — it locks out
|
||||
# a throwaway username so admin Basic Auth stays usable for everything
|
||||
# above it.
|
||||
hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test --jobs 1 \
|
||||
"$API_DIR/setup.hurl" \
|
||||
"$API_DIR/auth_login.hurl" \
|
||||
"$API_DIR/registration.hurl" \
|
||||
"$API_DIR/nc_status_capabilities.hurl" \
|
||||
"$API_DIR/nc_login_flow_v2.hurl" \
|
||||
"$API_DIR/nc_ocs_user_info.hurl" \
|
||||
"$API_DIR/nc_avatar_preview.hurl" \
|
||||
"$API_DIR/files-folders.hurl" \
|
||||
"$API_DIR/favorites.hurl" \
|
||||
"$API_DIR/trash.hurl" \
|
||||
@@ -117,7 +142,10 @@ hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test
|
||||
"$API_DIR/subject_groups.hurl" \
|
||||
"$API_DIR/grants_nested_groups.hurl" \
|
||||
"$API_DIR/external_users.hurl" \
|
||||
"$API_DIR/chunked_upload_cap.hurl"
|
||||
"$API_DIR/nc_second_user_setup.hurl" \
|
||||
"$API_DIR/nc_admin_views_other_user.hurl" \
|
||||
"$API_DIR/chunked_upload_cap.hurl" \
|
||||
"$API_DIR/nc_auth_failures.hurl"
|
||||
|
||||
#bash "$API_DIR/dedup_bulk_upload.sh"
|
||||
|
||||
|
||||
@@ -151,6 +151,26 @@ log "API confirms trash is empty."
|
||||
THUMB_FILES=$(find "$STORAGE_PATH/.thumbnails" -type f 2>/dev/null || true)
|
||||
BLOB_FILES=$(find "$STORAGE_PATH/.blobs" -type f 2>/dev/null || true)
|
||||
|
||||
# Chunked-upload spool. After every chunked-upload session is either
|
||||
# completed (assembled + promoted) or aborted, this dir MUST be empty
|
||||
# — a leftover chunk file means a session-cleanup path forgot its
|
||||
# `remove_dir_all`, which under sustained sync workloads is the
|
||||
# classic "disk fills up over the weekend" failure mode.
|
||||
#
|
||||
# `.uploads/` is the default chunked-upload root when
|
||||
# `OXICLOUD_CHUNK_DIR` is unset (see `common/di.rs`). REST sessions
|
||||
# land under `.uploads/<session_id>/`; NC sessions land under
|
||||
# `.uploads/nextcloud/<user>/<session_id>/`.
|
||||
#
|
||||
# We deliberately do NOT check the direct-PUT spool dir here: when
|
||||
# `OXICLOUD_UPLOAD_TEMP_DIR` is unset (the default in the test env)
|
||||
# it falls back to the OS temp dir (`/tmp/…`) which is shared with
|
||||
# the rest of the system and would produce false positives. To
|
||||
# extend the check to direct-PUT, set OXICLOUD_UPLOAD_TEMP_DIR in
|
||||
# tests/common/server.env to a path under $STORAGE_PATH and add it
|
||||
# to the find list below.
|
||||
UPLOAD_FILES=$(find "$STORAGE_PATH/.uploads" -type f 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$THUMB_FILES" ]]; then
|
||||
THUMB_COUNT=$(echo "$THUMB_FILES" | wc -l | tr -d ' ')
|
||||
log "Leftover thumbnail files ($THUMB_COUNT):"
|
||||
@@ -165,4 +185,11 @@ if [[ -n "$BLOB_FILES" ]]; then
|
||||
fail "$BLOB_COUNT blob file(s) remain on disk after full cleanup"
|
||||
fi
|
||||
|
||||
log "OK — no blobs or thumbnails remain on disk."
|
||||
if [[ -n "$UPLOAD_FILES" ]]; then
|
||||
UPLOAD_COUNT=$(echo "$UPLOAD_FILES" | wc -l | tr -d ' ')
|
||||
log "Leftover chunked-upload files ($UPLOAD_COUNT):"
|
||||
echo "$UPLOAD_FILES"
|
||||
fail "$UPLOAD_COUNT chunked-upload file(s) remain in .uploads after full cleanup"
|
||||
fi
|
||||
|
||||
log "OK — no blobs, thumbnails, or chunked-upload leftovers remain on disk."
|
||||
|
||||
Reference in New Issue
Block a user