# 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 # Realtime WS keepalive interval (server-initiated protocol Ping). # Production default is 30 s; tests drop it to 1 s so the S5 scenario # in `tests/api/rt_bus_check.sh` can observe multiple keepalive frames # arriving within a bounded (few-seconds) wall-clock. Only observed # by `rt_ws_handler`, which reads it at each WS connect time. OXICLOUD_RT_WS_KEEPALIVE_SECONDS=1 # 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,s3_blackhole 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 # `s3_blackhole` — an endpoint that never answers, as opposed to # `s3_stub` above which refuses instantly. # # The distinction is the entire point. `127.0.0.1:9999` has nothing # listening, so a connection is REFUSED: the kernel returns ECONNREFUSED # immediately and the SDK reports an error straight away. That path was # always handled. A test built on it would pass even with no timeout # configured anywhere. # # `192.0.2.1` is TEST-NET-1 (RFC 5737), reserved for documentation and # guaranteed not to be routed. A SYN to it goes unanswered — no RST, no # ICMP — which is the failure that used to hang: with no bound, a socket # read blocks until the OS abandons retransmission, on the order of # fifteen minutes, during which the job is neither running nor failed. # # Declared but NEVER activated, like the other two. `backend_migration # ?storage=s3_blackhole` reaches it explicitly. # # If a CI network answers 192.0.2.1 with ICMP unreachable, the failure # degrades to the refused shape and `backend_migration_blackhole.hurl` # still passes — both classify transient and both pause. It would simply # stop pinning the timeout specifically. The assert on elapsed time in # that file is what would notice. OXICLOUD_STORAGE_s3_blackhole_BACKEND=s3 OXICLOUD_STORAGE_s3_blackhole_S3_BUCKET=oxicloud-blackhole OXICLOUD_STORAGE_s3_blackhole_S3_REGION=us-east-1 OXICLOUD_STORAGE_s3_blackhole_S3_ENDPOINT_URL=http://192.0.2.1:9999 OXICLOUD_STORAGE_s3_blackhole_S3_ACCESS_KEY=blackhole OXICLOUD_STORAGE_s3_blackhole_S3_SECRET_KEY=blackhole # `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 # The three above are keyed on the client IP. These two are keyed on the # CALLER ID, which is why raising the three did nothing: the whole suite # runs as a single `admin`, so every test shares one bucket. # # A 763-line e2e server log showed 81 × 429 — all on `http::api`, never # `http::api::auth` — against the 60/min default for user-profile # lookups. Admin views resolve an owner name per row, and the run # creates 34 users, so a minute of tests clears 60 easily. Nothing # failed, because the SPA degrades to an unresolved name, which is # precisely why it went unnoticed: the noise would hide a real # rate-limit regression. # # Same posture as above — a 1-hour window with a budget far beyond what # one run consumes, rather than a raised per-minute rate that would # still burst-trip. OXICLOUD_RATE_LIMIT_USER_PROFILE_MAX=36000 OXICLOUD_RATE_LIMIT_USER_PROFILE_WINDOW_SECS=3600 OXICLOUD_RATE_LIMIT_DELTA_UPLOAD_MAX=36000 OXICLOUD_RATE_LIMIT_DELTA_UPLOAD_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 ' 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