04e0df0c89
Adds an Azurite service and a scenario that audits the Azure backend
through `?storage=azurite`. It is the only coverage of that code path in
the tree: `AzureBlobBackend` has unit tests for its name parser and
ordering, but nothing else speaks the protocol, and a paid account is
not an option for CI. Azurite implements the real Blob REST API, so this
exercises SharedKey signing, prefix/marker paging, and the 256-way shard
walk with its termination.
## Harness
`docker-compose.test.yml` gains an azurite service on 10000 (tmpfs, so
it dies with the stack). `spawn-db.sh` provisions the container itself,
because `AzureBlobBackend::initialize` verifies rather than creates —
signed by hand with curl + openssl rather than pulling a ~700 MB `az`
image for one PUT. Two traps are commented there: the account key is
base64 but HMAC wants raw bytes, and the canonicalized resource repeats
the account name (`/{acc}/{acc}/{container}`) because the emulator puts
in the path what real Azure puts in the host. Getting that wrong yields
403, not a hint.
The `azurite` entry is declared in `server.env` but never activated, so
the suite's active backend stays local and only this file reaches Azure.
## What it asserts, and what it cannot
A failure surfaces as `ok: false`, because an enumeration error now
fails the run rather than degrading to a per-row probe.
It deliberately asserts no finding count. The container starts empty and
the job's grace window is an hour, so a freshly-uploaded blob is skipped
in both directions by design — an audit here can only report zero, and
"zero findings" would pass whether enumeration worked or returned
nothing. The one positive assert, `scanned_count != 0`, therefore sits
on the local control, which does hold blobs; `scanned_count` accumulates
via `checkpoint`, which the empty-page early return skips.
## No cutover, deliberately
Putting real bytes in the container means `backend_migration
?storage=azurite`, which hangs on the first blob: `head_check` issues a
~40-byte ranged GET, `azure_core` 0.21 attaches
`x-ms-range-get-content-crc64` to anything under 4 MiB, Azurite 500s,
and the deterministic error is retried forever while
`migration_readonly` refuses writes app-wide. The full chain and the
rejected workaround are in the file header. The scenario is still
ordered last in `run.sh` — it is the only one needing a second service,
and the cutover comes back there once the official SDK lands.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
193 lines
9.8 KiB
Bash
193 lines
9.8 KiB
Bash
# Shared test-server environment variables.
|
|
# Sourced by tests/api/run.sh (shell) and read by tests/e2e/playwright.config.ts (Node).
|
|
# Do NOT include OXICLOUD_SERVER_PORT or OXICLOUD_STORAGE_PATH here —
|
|
# each test suite sets those to avoid port/directory conflicts.
|
|
|
|
DATABASE_URL=postgres://oxicloud_test:oxicloud_test@localhost:5433/oxicloud_test
|
|
OXICLOUD_DB_CONNECTION_STRING=postgres://oxicloud_test:oxicloud_test@localhost:5433/oxicloud_test
|
|
# `OXICLOUD_STATIC_PATH` intentionally NOT set — every remaining
|
|
# test suite either serves the built SvelteKit SPA from
|
|
# `static-dist/` (coverage suite via `start-server-spa.sh`) or
|
|
# doesn't need static assets at all (Hurl API / WebDAV / CalDAV
|
|
# suites). The legacy `./static` vanilla frontend was retired.
|
|
OXICLOUD_JWT_SECRET=test-secret-do-not-use-in-prod-minimum-32-chars
|
|
OXICLOUD_ENABLE_AUTH=true
|
|
OXICLOUD_ENABLE_TRASH=true
|
|
OXICLOUD_ENABLE_SEARCH=true
|
|
OXICLOUD_ENABLE_FILE_SHARING=true
|
|
OXICLOUD_ENABLE_MUSIC=true
|
|
# External file mounts — enabled so external_mounts.hurl can exercise the
|
|
# admin CRUD endpoints against a live mount registry.
|
|
OXICLOUD_ENABLE_EXTERNAL_MOUNTS=true
|
|
OXICLOUD_EXPOSE_SYSTEM_USERS=true
|
|
OXICLOUD_WOPI_ENABLED=true
|
|
# Fixed secret so the Hurl WOPI test can hand-craft valid access
|
|
# tokens with a known signing key. Prod deployments MUST override
|
|
# this to a random per-deployment value.
|
|
OXICLOUD_WOPI_SECRET=test-wopi-secret-do-not-use-in-prod-do-not-use-in-prod
|
|
# Discovery URL points at a black hole — VERB endpoints don't need
|
|
# discovery, and the WOPI Hurl suite deliberately does NOT touch
|
|
# `/api/wopi/editor-url` (the only path that would fetch it), so
|
|
# an unreachable URL keeps startup fast and hermetic.
|
|
OXICLOUD_WOPI_DISCOVERY_URL=http://127.0.0.1:9100/discovery.xml
|
|
OXICLOUD_WOPI_TOKEN_TTL_SECS=3600
|
|
OXICLOUD_OIDC_ENABLED=false
|
|
|
|
OXICLOUD_NEXTCLOUD_ENABLED=true
|
|
|
|
# Multi-entry storage config — see docs/plan/storage-multi-entry.md.
|
|
# `local_main` is FIRST so the boot fallback picks it when no active
|
|
# pointer is set in the DB yet (fresh test DB). Its root_dir falls
|
|
# back to $OXICLOUD_STORAGE_PATH (set in each test suite's run.sh).
|
|
# `s3_stub` is declared but never activated — it lets storage_config.hurl
|
|
# assert the entries table has more than one row without needing a
|
|
# real S3 backend.
|
|
OXICLOUD_STORAGE_ENTRIES=local_main,s3_stub,azurite
|
|
OXICLOUD_STORAGE_local_main_BACKEND=local
|
|
OXICLOUD_STORAGE_s3_stub_BACKEND=s3
|
|
OXICLOUD_STORAGE_s3_stub_S3_BUCKET=oxicloud-test-stub
|
|
OXICLOUD_STORAGE_s3_stub_S3_REGION=us-east-1
|
|
OXICLOUD_STORAGE_s3_stub_S3_ENDPOINT_URL=http://127.0.0.1:9999
|
|
OXICLOUD_STORAGE_s3_stub_S3_ACCESS_KEY=stub
|
|
OXICLOUD_STORAGE_s3_stub_S3_SECRET_KEY=stub
|
|
|
|
# `azurite` — a REAL, reachable Azure backend, unlike `s3_stub` above.
|
|
# It points at the Azurite emulator started by spawn-db.sh, which speaks
|
|
# the actual Blob REST API, so this is the only way to exercise the Azure
|
|
# path without an account.
|
|
#
|
|
# Declared but NOT activated: the active backend stays local_main, so the
|
|
# rest of the suite is untouched. Tests reach it explicitly through
|
|
# `?storage=azurite` on the jobs that accept an entry name — which is
|
|
# exactly what that parameter was added for.
|
|
#
|
|
# The credentials are Azurite's fixed, publicly-documented development
|
|
# pair. They are in every Microsoft quickstart; nothing here is secret.
|
|
OXICLOUD_STORAGE_azurite_BACKEND=azure
|
|
OXICLOUD_STORAGE_azurite_AZURE_ACCOUNT_NAME=devstoreaccount1
|
|
OXICLOUD_STORAGE_azurite_AZURE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
|
|
OXICLOUD_STORAGE_azurite_AZURE_CONTAINER=oxicloud-test
|
|
OXICLOUD_STORAGE_azurite_AZURE_ENDPOINT_URL=http://127.0.0.1:10000/devstoreaccount1
|
|
|
|
RUST_LOG="warn,audit=info,sqlx::migrate=info"
|
|
#RUST_LOG="warn,audit=info,oxicloud::quota=debug"
|
|
#RUST_LOG=debug
|
|
#RUST_LOG=info
|
|
|
|
# Per-chunk upload cap, exercised by chunked_upload_cap.hurl.
|
|
# 4 MiB: lets the existing grants.hurl single-chunk test (2.76 MB) pass
|
|
# under the cap, while the cap test sends a 5 MiB fixture to trigger 413.
|
|
OXICLOUD_CHUNK_MAX_BYTES=4194304
|
|
|
|
# Direct-PUT (non-chunked) cap, exercised by chunked_upload_cap.hurl.
|
|
# 4 MiB: same threshold as the chunked cap so the existing 5 MiB
|
|
# fixture (chunk-over-cap-5mb.bin) can prove BOTH caps with one
|
|
# generated file. All existing direct-PUT tests
|
|
# (test_dedup_webdav_multichunk.sh = 2.76 MB, _ref_count = ~66 KB,
|
|
# _nextcloud_put_blake3 = 32 B) stay safely under this cap.
|
|
OXICLOUD_DIRECT_PUT_MAX_BYTES=4194304
|
|
|
|
# Rate-limit ceiling for tests. `_MAX` alone isn't enough because
|
|
# the default `_WINDOW_SECS = 60` gives an effective rate of
|
|
# `MAX / 60` req/sec — a bursty Playwright suite (many workers,
|
|
# parallel logins + auto-refresh churn under DPoP required mode)
|
|
# overflows even at MAX=3600 (60 req/sec shared across the whole
|
|
# runner IP). Widening the window to 1 h means the same MAX is a
|
|
# 1-hour budget, well above what any single CI run consumes
|
|
# (< 5 min end-to-end). Applied to all three buckets so login /
|
|
# refresh / register all share the same generous test posture.
|
|
OXICLOUD_RATE_LIMIT_REFRESH_MAX=36000
|
|
OXICLOUD_RATE_LIMIT_REFRESH_WINDOW_SECS=3600
|
|
OXICLOUD_RATE_LIMIT_LOGIN_MAX=36000
|
|
OXICLOUD_RATE_LIMIT_LOGIN_WINDOW_SECS=3600
|
|
OXICLOUD_RATE_LIMIT_REGISTER_MAX=36000
|
|
OXICLOUD_RATE_LIMIT_REGISTER_WINDOW_SECS=3600
|
|
|
|
# Magic-link / external-users flow (PR 9). The mock SMTP captures every
|
|
# outbound message in-process so external_users.hurl can retrieve the
|
|
# invitation body and follow the magic-link URL. The `SMTP_FROM` value
|
|
# is required so the mock can build a valid Message; host/port are
|
|
# irrelevant in mock mode but kept set for completeness.
|
|
OXICLOUD_SMTP_MOCK=true
|
|
OXICLOUD_SMTP_HOST=localhost
|
|
OXICLOUD_SMTP_PORT=25
|
|
OXICLOUD_SMTP_FROM='OxiCloud Tests <test@oxicloud.local>'
|
|
OXICLOUD_SMTP_TLS=none
|
|
OXICLOUD_ALLOW_EXTERNAL_USERS=true
|
|
|
|
# Public-registration email-domain allowlist. Exercised by
|
|
# `registration.hurl` step "off-domain rejection" (attempts to
|
|
# register with @nowhere.invalid and asserts 403
|
|
# `RegistrationDomainNotAllowed`). Contains BOTH `example.com` (Hurl
|
|
# fixtures use it — charlie@example.com etc.) AND `example.test` (E2E
|
|
# login.spec uses it — reg-*@example.test). Every legitimate test
|
|
# path stays inside the allowlist; the rejection test picks a domain
|
|
# outside it deliberately.
|
|
OXICLOUD_REGISTRATION_ALLOWED_EMAIL_DOMAINS=example.com,example.test
|
|
|
|
# Auth-policy vector. Enables the "magic-link login is allowed for
|
|
# accounts that also have a password" branch — required by
|
|
# auth_magic_link_login.hurl (alice has a password AND requests a
|
|
# magic-link login). Inert for every other test: `has_password`
|
|
# refusal only fires when the endpoint is called, and no other file
|
|
# calls `magic-link/send` for a password-holding account.
|
|
OXICLOUD_AUTH_POLICIES=permit_magic_link_for_password_users
|
|
OXICLOUD_AUTH_METHODS=password,magic_link
|
|
# Explicit pin — `--config` now overrides shell env (main.rs uses
|
|
# `from_filename_override`), but pinning here documents the intended
|
|
# test-env state. Flip to true for the deferred `tests/verify_email/`
|
|
# suite; leaving false here keeps every other suite on the "verified
|
|
# email not required" path (charlie's classic register+login etc.).
|
|
OXICLOUD_REQUIRE_VERIFIED_EMAIL=false
|
|
|
|
# PR 12 — magic-link rate-limit caps lowered so external_users.hurl can
|
|
# exercise the cap behaviour with a small, deterministic request count.
|
|
# Production defaults are 50 / 5 / 200 respectively (see example.env).
|
|
OXICLOUD_MAGIC_LINK_INVITE_PER_CALLER_PER_HOUR=3
|
|
OXICLOUD_MAGIC_LINK_SEND_PER_EMAIL_PER_HOUR=2
|
|
OXICLOUD_MAGIC_LINK_SEND_PER_IP_PER_HOUR=50
|
|
|
|
# permits IP spoofing for tests
|
|
OXICLOUD_TRUST_PROXY_CIDR=0.0.0.0/0
|
|
|
|
# ── OPAQUE aPAKE (Phase 0 substrate) ────────────────────────────────
|
|
# Boot the OPAQUE service in Migrate mode so every Hurl run exercises:
|
|
# 1. OpaqueConfig::from_env parsing all five OPAQUE env vars.
|
|
# 2. effective_mode(&auth) permitting Migrate because password IS in
|
|
# OXICLOUD_AUTH_METHODS above (would auto-downgrade to Off if we
|
|
# had disabled it — that path is unit-tested in
|
|
# common::config::tests::effective_mode_downgrades_to_off_...).
|
|
# 3. OpaqueService::from_config accepting a valid serialised setup.
|
|
# 4. AppState wiring populating `opaque_service = Some(_)`.
|
|
#
|
|
# Phase 0 has no wire-facing endpoints, so this only proves the
|
|
# substrate loads cleanly; opaque_substrate.hurl asserts the legacy
|
|
# /api/auth/login path remains intact and the future OPAQUE endpoints
|
|
# still 404 (they flip to 200 when Phase 1 lands).
|
|
#
|
|
# The SERVER_SETUP below is a throwaway keypair generated once for the
|
|
# test env — real deployments call `oxicloud opaque setup` and paste
|
|
# the output. Never reuse this value outside CI. Regenerate any time
|
|
# with:
|
|
# cargo run --bin oxicloud -- opaque setup
|
|
OXICLOUD_AUTH_OPAQUE_MODE=migrate
|
|
OXICLOUD_AUTH_OPAQUE_SERVER_SETUP="ZY4hAGa1MNyE7Ht+8ksLcyMmi/K2iJvxQly+DdfllUxjiH0+CjCt4hG6+9Y68jGet2L213dV0hajCbr4fXnekkWtUxqLr+butVHEksZ9NJRuZTvS6SMC73yf/yku4WUHT1NSRB2yHurAFmYn75D9wdA1VaXTuwgO/u5i1pvcsQs="
|
|
# Fast Argon2id — CI machines are underpowered vs production (256 MiB
|
|
# default would drag every test-scaffold future OPAQUE handshake in
|
|
# Phase 1+). Matches the params used in the round-trip unit test.
|
|
OXICLOUD_AUTH_OPAQUE_KSF_MEMORY_KIB=8
|
|
OXICLOUD_AUTH_OPAQUE_KSF_ITERATIONS=1
|
|
OXICLOUD_AUTH_OPAQUE_KSF_PARALLELISM=1
|
|
|
|
# DPoP (RFC 9449) — required mode surfaces every verifier /
|
|
# nonce / replay bug as a hard 401 (opportunistic would swallow
|
|
# some cases as warnings). Safe today because unbound sessions —
|
|
# `session.dpop_jkt IS NULL`, which is where the test admin's
|
|
# legacy-login lands — are still exempted at the middleware; the
|
|
# session-level enforcement flip is Gate 9. Once that arrives,
|
|
# every bind-time downgrade path also gets caught here without
|
|
# a config change. `dpop-hurl-helper` needs this on or the
|
|
# middleware is a pass-through and its scenarios all silently 200.
|
|
OXICLOUD_DPOP_MODE=required
|
|
|