From 6a4b3eb89e093ca6fbc2f4baec7080b8d2c6453b Mon Sep 17 00:00:00 2001 From: rabble Date: Sat, 28 Mar 2026 17:01:46 +1300 Subject: [PATCH 1/3] docs: add resumable contract alignment design and plan --- ...oad-server-resumable-contract-alignment.md | 371 ++++++++++++++++++ ...ver-resumable-contract-alignment-design.md | 194 +++++++++ 2 files changed, 565 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-28-divine-upload-server-resumable-contract-alignment.md create mode 100644 docs/superpowers/specs/2026-03-28-divine-upload-server-resumable-contract-alignment-design.md diff --git a/docs/superpowers/plans/2026-03-28-divine-upload-server-resumable-contract-alignment.md b/docs/superpowers/plans/2026-03-28-divine-upload-server-resumable-contract-alignment.md new file mode 100644 index 0000000..2deefc7 --- /dev/null +++ b/docs/superpowers/plans/2026-03-28-divine-upload-server-resumable-contract-alignment.md @@ -0,0 +1,371 @@ +# Divine Upload Server Resumable Contract Alignment Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Align `divine-upload-server` with the approved Divine resumable upload contract so `divine-blossom` can proxy the public control plane without reshaping data-plane responses. + +**Architecture:** Keep this repo as the resumable data plane on `upload.divine.video` and leave client-facing `HEAD /upload`, `POST /upload/init`, and `POST /upload/{uploadId}/complete` ownership with `divine-blossom`. In this repo, align JSON field names, expiry formatting, session headers, and completion payload shape to the approved contract while preserving legacy single-shot upload behavior. + +**Tech Stack:** Rust, Axum, Serde, Tokio, Google Cloud Storage, Python unittest + +--- + +**Scope split:** This plan covers `divine-upload-server` only. `divine-blossom` still needs a separate implementation plan for the public control-plane routes on `media.divine.video`. + +**File Structure** + +- `src/resumable.rs` + - owns resumable request and response models, expiry formatting, session state, and completion metadata +- `src/main.rs` + - owns HTTP headers and handler responses for session `HEAD` and `PUT` +- `src/landing.html` + - owns the operator-facing endpoint description for the upload host +- `README.md` + - owns the repo boundary and deployment contract documentation +- `tests/test_export_video_upload_hashes.py` + - unchanged behavior safety net for Python tooling + +## Chunk 1: Contract Models And Expiry Format + +### Task 1: Make resumable init request and response JSON match the approved contract + +**Files:** +- Modify: `src/resumable.rs` +- Test: `src/resumable.rs` + +- [ ] **Step 1: Write the failing serialization tests** + +```rust +#[test] +fn init_contract_request_accepts_camel_case_fields() { + let payload = serde_json::json!({ + "sha256": "abc", + "size": 12, + "contentType": "video/mp4", + "fileName": "clip.mp4" + }); + + let request: ResumableUploadInitRequest = serde_json::from_value(payload) + .expect("camelCase init request should deserialize"); + + assert_eq!(request.content_type, "video/mp4"); + assert_eq!(request.file_name.as_deref(), Some("clip.mp4")); +} + +#[test] +fn init_contract_response_serializes_camel_case_fields() { + let response = ResumableUploadInitResponse { + upload_id: "up_123".to_string(), + upload_url: "https://upload.divine.video/sessions/up_123".to_string(), + expires_at: "2026-03-28T04:00:00Z".to_string(), + chunk_size: 8 * 1024 * 1024, + next_offset: 0, + required_headers: std::collections::HashMap::new(), + capabilities: ResumableCapabilities { + resume: true, + query_offset: true, + }, + }; + + let json = serde_json::to_value(response).expect("serialize response"); + + assert!(json.get("uploadId").is_some()); + assert!(json.get("uploadUrl").is_some()); + assert!(json.get("expiresAt").is_some()); + assert!(json.get("chunkSize").is_some()); + assert!(json.get("nextOffset").is_some()); + assert!(json.get("capabilities").is_some()); + assert!(json.get("upload_id").is_none()); +} +``` + +- [ ] **Step 2: Run the targeted Rust tests to verify they fail** + +Run: `cargo test init_contract_ -- --nocapture` +Expected: FAIL because the current models only expose `snake_case` fields and have no `capabilities` object. + +- [ ] **Step 3: Implement the minimal model changes** + +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResumableUploadInitRequest { + pub sha256: String, + pub size: u64, + #[serde(alias = "content_type")] + pub content_type: String, + #[serde(alias = "file_name")] + pub file_name: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResumableCapabilities { + pub resume: bool, + pub query_offset: bool, +} +``` + +- [ ] **Step 4: Run the targeted Rust tests to verify they pass** + +Run: `cargo test init_contract_ -- --nocapture` +Expected: PASS + +- [ ] **Step 5: Commit the contract model changes** + +```bash +git add src/resumable.rs +git commit -m "feat: align resumable init payloads with divine contract" +``` + +### Task 2: Emit RFC 3339 expiries for init and session status + +**Files:** +- Modify: `src/resumable.rs` +- Test: `src/resumable.rs` + +- [ ] **Step 1: Write the failing expiry-format tests** + +```rust +#[test] +fn rfc3339_expiry_helper_formats_contract_timestamp() { + let formatted = format_epoch_secs_as_rfc3339(1_774_660_000); + assert_eq!(formatted, "2026-03-28T00:40:00Z"); +} + +#[tokio::test] +async fn rfc3339_expiry_is_used_in_session_status() { + let manager = manager(); + let response = manager + .init_session( + "owner_pubkey", + ResumableUploadInitRequest { + sha256: "5b48aa1fcf30af61243ac9307eb98b7fa22df1c58573c3ca5d1b14fc30099929" + .to_string(), + size: 1024 * 1024, + content_type: "video/mp4".to_string(), + file_name: None, + }, + ) + .await + .expect("init response"); + let auth = response.required_headers.get("Authorization").unwrap().to_string(); + let head = manager + .head_session(&response.upload_id, Some(&auth)) + .await + .expect("head session"); + + assert!(response.expires_at.ends_with('Z')); + assert!(head.expires_at.ends_with('Z')); +} +``` + +- [ ] **Step 2: Run the targeted Rust tests to verify they fail** + +Run: `cargo test rfc3339_expiry_ -- --nocapture` +Expected: FAIL because the current code emits epoch-second strings. + +- [ ] **Step 3: Implement the minimal formatting helper and response changes** + +```rust +fn format_epoch_secs_as_rfc3339(epoch_secs: u64) -> String { + time::OffsetDateTime::from_unix_timestamp(epoch_secs as i64) + .expect("valid epoch seconds") + .format(&time::format_description::well_known::Rfc3339) + .expect("rfc3339 timestamp") +} +``` + +- [ ] **Step 4: Run the targeted Rust tests to verify they pass** + +Run: `cargo test rfc3339_expiry_ -- --nocapture` +Expected: PASS + +- [ ] **Step 5: Commit the expiry-format changes** + +```bash +git add src/resumable.rs Cargo.toml Cargo.lock +git commit -m "feat: emit rfc3339 resumable expiry timestamps" +``` + +## Chunk 2: Session Headers And Completion Response + +### Task 3: Add the approved session expiry header to session `HEAD` and chunk `PUT` responses + +**Files:** +- Modify: `src/resumable.rs` +- Modify: `src/main.rs` +- Test: `src/main.rs` + +- [ ] **Step 1: Write the failing header test** + +```rust +#[test] +fn session_responses_include_upload_expires_at_header() { + let response = build_session_status_response(UploadSessionStatus { + next_offset: 0, + declared_size: 1024, + expires_at: "2026-03-28T00:40:00Z".to_string(), + chunk_size: 8 * 1024 * 1024, + }); + + assert_eq!( + response.headers().get("Upload-Expires-At").unwrap(), + "2026-03-28T00:40:00Z" + ); +} +``` + +- [ ] **Step 2: Run the targeted Rust test to verify it fails** + +Run: `cargo test session_responses_include_upload_expires_at_header -- --nocapture` +Expected: FAIL because the current responses only emit `Upload-Expires`. + +- [ ] **Step 3: Implement the minimal header helper and wire both handlers through it** + +```rust +const SESSION_EXPIRES_AT_HEADER: &str = "Upload-Expires-At"; +``` + +- [ ] **Step 4: Run the targeted Rust test to verify it passes** + +Run: `cargo test session_responses_include_upload_expires_at_header -- --nocapture` +Expected: PASS + +- [ ] **Step 5: Commit the session-header changes** + +```bash +git add src/main.rs src/resumable.rs +git commit -m "feat: advertise upload session expiry with contract header" +``` + +### Task 4: Return a mobile-compatible completion descriptor from resumable `complete` + +**Files:** +- Modify: `src/resumable.rs` +- Modify: `src/main.rs` +- Test: `src/resumable.rs` + +- [ ] **Step 1: Write the failing completion-shape test** + +```rust +#[test] +fn complete_response_serializes_public_descriptor_fields() { + let response = CompleteUploadResponse { + url: "https://media.divine.video/abc".to_string(), + fallback_url: Some("https://media.divine.video/abc".to_string()), + thumbnail: Some("https://media.divine.video/abc.jpg".to_string()), + streaming: Some(StreamingInfo { + hls_url: None, + mp4_url: None, + thumbnail_url: Some("https://media.divine.video/abc.jpg".to_string()), + status: Some("processing".to_string()), + }), + }; + + let json = serde_json::to_value(response).expect("serialize complete response"); + + assert_eq!(json.get("url").unwrap(), "https://media.divine.video/abc"); + assert_eq!(json.get("fallbackUrl").unwrap(), "https://media.divine.video/abc"); + assert!(json.get("streaming").is_some()); +} +``` + +- [ ] **Step 2: Run the targeted Rust test to verify it fails** + +Run: `cargo test complete_response_serializes_public_descriptor_fields -- --nocapture` +Expected: FAIL because the current completion response still exposes internal blob metadata fields. + +- [ ] **Step 3: Implement the minimal completion response rewrite** + +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct StreamingInfo { + pub hls_url: Option, + pub mp4_url: Option, + pub thumbnail_url: Option, + pub status: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompleteUploadResponse { + pub url: String, + pub fallback_url: Option, + pub thumbnail: Option, + pub streaming: Option, +} +``` + +- [ ] **Step 4: Run the targeted Rust test to verify it passes** + +Run: `cargo test complete_response_serializes_public_descriptor_fields -- --nocapture` +Expected: PASS + +- [ ] **Step 5: Commit the completion response changes** + +```bash +git add src/resumable.rs src/main.rs +git commit -m "feat: return public completion descriptor for resumable uploads" +``` + +## Chunk 3: Docs, Landing Page, And Full Verification + +### Task 5: Update docs so the upload host no longer claims the public control-plane role + +**Files:** +- Modify: `README.md` +- Modify: `src/landing.html` + +- [ ] **Step 1: Write the wording changes** + +```text +Control plane: media.divine.video +Data plane: upload.divine.video +Opaque session URLs are server-issued and resumable-session specific. +``` + +- [ ] **Step 2: Review the rendered copy in the edited files** + +Run: `sed -n '1,220p' README.md && sed -n '90,150p' src/landing.html` +Expected: Both files clearly describe `divine-blossom` as the public control plane and `divine-upload-server` as the opaque session data plane. + +- [ ] **Step 3: Commit the doc updates** + +```bash +git add README.md src/landing.html +git commit -m "docs: clarify divine upload control-plane boundary" +``` + +### Task 6: Run the full verification suite + +**Files:** +- Modify: none + +- [ ] **Step 1: Check formatting** + +Run: `cargo fmt --all -- --check` +Expected: PASS + +- [ ] **Step 2: Run clippy** + +Run: `cargo clippy --all-targets -- -D warnings` +Expected: PASS + +- [ ] **Step 3: Run Rust tests** + +Run: `cargo test --all` +Expected: PASS + +- [ ] **Step 4: Run Python tests** + +Run: `python3 -m unittest discover -s tests -p 'test_*.py'` +Expected: PASS + +- [ ] **Step 5: Commit any final fixups if verification required them** + +```bash +git add src/resumable.rs src/main.rs README.md src/landing.html Cargo.toml Cargo.lock +git commit -m "chore: finalize resumable contract alignment" +``` diff --git a/docs/superpowers/specs/2026-03-28-divine-upload-server-resumable-contract-alignment-design.md b/docs/superpowers/specs/2026-03-28-divine-upload-server-resumable-contract-alignment-design.md new file mode 100644 index 0000000..11ad69f --- /dev/null +++ b/docs/superpowers/specs/2026-03-28-divine-upload-server-resumable-contract-alignment-design.md @@ -0,0 +1,194 @@ +Status: Approved + +# Divine Upload Server Resumable Contract Alignment Design + +**Problem** + +`divine-upload-server` is live on `upload.divine.video`, but the public resumable upload contract approved for the mobile client is defined around a control plane on `media.divine.video` and an opaque data plane on `upload.divine.video`. Today the live services do not line up with that contract: `divine-blossom` is not advertising or serving the resumable control-plane routes, and this repo still exposes several draft-era data-plane mismatches in JSON shape, expiry format, and completion response shape. + +**Goals** + +- Keep `media.divine.video` as the canonical client-facing Blossom control plane. +- Keep `upload.divine.video` as the opaque resumable session data plane. +- Make this repo's resumable payloads and headers line up with the approved Divine resumable session draft closely enough that `divine-blossom` can proxy them without lossy translation. +- Preserve legacy single-shot `PUT /upload` behavior. +- Keep session uploads non-public and only publish the final verified blob. + +**Non-Goals** + +- Move `HEAD /upload`, `POST /upload/init`, or `POST /upload/{uploadId}/complete` ownership from `divine-blossom` to this repo's public responsibility. +- Teach the mobile client a tus-style or upload-host-specific protocol. +- Re-architect the existing single-shot upload path in this pass. +- Solve the entire cross-repo rollout inside this repository alone. + +**Approved Direction** + +We will keep the draft Divine resumable contract as the source of truth. + +- `divine-blossom` remains responsible for: + - `HEAD /upload` capability discovery on `media.divine.video` + - client-facing `POST /upload/init` + - client-facing `POST /upload/{uploadId}/complete` + - client-facing `DELETE /upload/{uploadId}` +- `divine-upload-server` remains responsible for: + - creating and tracking resumable upload sessions + - accepting `PUT` and `HEAD` on opaque session URLs under `upload.divine.video` + - finalizing verified uploads into canonical storage + - returning completion metadata that `divine-blossom` can proxy with minimal or no shape translation + +**Why This Direction** + +Option 1 is the only direction that matches the approved mobile spec in `divine-mobile` and preserves the intended Blossom boundary. Treating the currently live upload host as the protocol source of truth would force a mobile protocol fork, invalidate the March 26 design and plan, and couple the client to an unfinished backend shape rather than to the intended Divine contract. + +**Current State** + +- `README.md` already documents this repo as the data plane behind `upload.divine.video`, with `divine-blossom` owning `HEAD /upload` and the control-plane proxy role. +- `src/main.rs` already implements: + - `POST /upload/init` + - `POST /upload/:upload_id/complete` + - `DELETE /upload/:upload_id` + - `PUT /sessions/:upload_id` + - `HEAD /sessions/:upload_id` +- Live probes on March 28, 2026 show: + - `HEAD https://media.divine.video/upload` returns `200` without Divine capability headers + - `POST https://media.divine.video/upload/init` returns `404` + - `HEAD https://upload.divine.video/upload` returns `405` with `Allow: PUT,OPTIONS` + +**Observed Gaps In This Repo** + +1. Request and response JSON are still Rust-style `snake_case`, while the approved contract is `camelCase`. +2. Expiry values are emitted as raw epoch-second strings, while the approved contract uses RFC 3339 timestamps. +3. Session progress headers currently use `Upload-Expires`; the approved contract calls for `Upload-Expires-At`. +4. `POST /upload/{uploadId}/complete` returns internal blob metadata, not the public-facing Blossom descriptor shape expected by the mobile upload service. +5. The landing page overstates public endpoint ownership and does not explain the control-plane/data-plane split clearly enough. + +**Design** + +## 1. Keep The Repo Boundary Intact + +This repo will not become the public control plane. The public client contract still lives on `media.divine.video`, served by `divine-blossom`. + +Inside that split, this repo should make its resumable handler contract proxy-friendly: + +- accept the same field names the public control plane expects +- return the same field names the public control plane wants to forward +- expose the same session progress headers the mobile client is built against + +That reduces duplicated translation logic and lowers the risk of drift between repos. + +## 2. Align Init Payloads With The Draft Contract + +`ResumableUploadInitRequest` and `ResumableUploadInitResponse` in `src/resumable.rs` should become `camelCase` at the JSON boundary: + +- request: + - `sha256` + - `size` + - `contentType` + - `fileName` +- response: + - `uploadId` + - `uploadUrl` + - `expiresAt` + - `chunkSize` + - `nextOffset` + - `requiredHeaders` + - `capabilities` + +Compatibility detail: + +- accept legacy `content_type` and `file_name` as aliases during the transition so existing internal callers do not break accidentally +- emit only the approved `camelCase` names in responses + +## 3. Emit RFC 3339 Expiry Values + +The approved contract examples use timestamps such as `2026-03-26T15:00:00Z`, not epoch strings. + +This repo should therefore: + +- return `expiresAt` as RFC 3339 in init responses +- return `Upload-Expires-At` as RFC 3339 in session `HEAD` and chunk `PUT` responses + +For rollout safety, the data plane may also keep emitting `Upload-Expires` temporarily as a compatibility header if doing so is cheap, but the approved contract header must be present and tested. + +## 4. Return A Public Descriptor Shape From Complete + +The completion handler should return a response shaped for the existing mobile parser: + +- top-level `url` +- top-level `fallbackUrl` +- optional top-level `thumbnail` +- optional `streaming` object with: + - `hlsUrl` + - `mp4Url` + - `thumbnailUrl` or `thumbnail` + - `status` + +For this repo's first alignment pass: + +- `fallbackUrl` should point at the canonical media URL on `media.divine.video` +- `url` should also be set so existing clients always have a primary URL +- `streaming.status` may be `"processing"` when transcoding has been triggered but stream assets are not yet known +- `thumbnail` should reuse the existing thumbnail generation logic when available +- `dim` and other internal metadata can remain available internally, but the proxied completion contract should prioritize the public descriptor shape + +This keeps the response consumable by the current mobile upload service without forcing `divine-blossom` to manufacture fields it does not own. + +## 5. Clarify Docs And Operator Surfaces + +`README.md` and `src/landing.html` should be updated so they stop implying that clients discover resumable capability on the upload host. + +They should state clearly: + +- `media.divine.video` is the control plane +- `upload.divine.video` is the opaque session data plane +- session URLs are for server-issued resumable uploads only + +That makes the live service behavior easier to reason about during rollout and reduces confusion from probe-based debugging. + +**File Boundaries** + +- `src/resumable.rs` + - request/response schema alignment + - expiry formatting helpers + - completion response contract shape + - unit tests for serialization and completion metadata +- `src/main.rs` + - resumable response headers + - handler wiring if response structs change + - route-level tests where needed +- `README.md` + - public ownership clarification +- `src/landing.html` + - endpoint and control-plane/data-plane wording cleanup + +**Verification** + +- Rust unit tests in `src/resumable.rs` for: + - camelCase request parsing + - camelCase init response serialization + - RFC 3339 expiry formatting + - completion response shape +- Rust tests in `src/main.rs` or extracted helpers for: + - `Upload-Expires-At` presence on session responses + - compatibility header behavior if retained +- Full repo verification: + - `cargo fmt --all -- --check` + - `cargo clippy --all-targets -- -D warnings` + - `cargo test --all` + - `python3 -m unittest discover -s tests -p 'test_*.py'` + +**Cross-Repo Dependency** + +This repository alone cannot satisfy the mobile resumable contract. + +Separate `divine-blossom` work is still required to: + +- advertise capability headers on `HEAD /upload` +- expose public `init`, `complete`, and abort routes on `media.divine.video` +- proxy those routes to this service without distorting the approved contract + +That work should be tracked in a separate spec and implementation plan in the `divine-blossom` repository. + +**Recommendation** + +Align this repo to the approved Divine resumable draft now, then land the matching `divine-blossom` control-plane proxy work. That preserves the March 26 mobile contract and turns the current production gap into a deployment-alignment problem instead of a client-protocol rewrite. From f271406b14b5509fe1858e7263734cf34d46487a Mon Sep 17 00:00:00 2001 From: rabble Date: Sat, 28 Mar 2026 17:03:46 +1300 Subject: [PATCH 2/3] chore: ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9280984..456ad49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ __pycache__/ +.worktrees/ From c06a212fae9f02738b1cd4f9b74d51dfc28e9666 Mon Sep 17 00:00:00 2001 From: rabble Date: Sun, 29 Mar 2026 21:43:24 +1300 Subject: [PATCH 3/3] fix: cap advertised resumable chunk size to route limit --- README.md | 1 + src/main.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 851ea00..939b1ff 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The service reads configuration from environment variables: - `TRANSCRIBER_URL` - `RESUMABLE_SESSION_TTL_SECS` - `RESUMABLE_CHUNK_SIZE` +- `RESUMABLE_MAX_REQUEST_BODY_SIZE` to cap advertised chunk size to the real upload-route body limit ## Development diff --git a/src/main.rs b/src/main.rs index e1d287d..1f83bc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,14 @@ struct Config { impl Config { fn from_env() -> Self { + let requested_resumable_chunk_size = env::var("RESUMABLE_CHUNK_SIZE") + .ok() + .and_then(|value| value.parse().ok()) + .unwrap_or(resumable::DEFAULT_RESUMABLE_CHUNK_SIZE); + let resumable_request_body_limit = env::var("RESUMABLE_MAX_REQUEST_BODY_SIZE") + .ok() + .and_then(|value| value.parse().ok()); + Self { gcs_bucket: env::var("GCS_BUCKET") .unwrap_or_else(|_| "divine-blossom-media".to_string()), @@ -79,14 +87,24 @@ impl Config { .ok() .and_then(|value| value.parse().ok()) .unwrap_or(resumable::DEFAULT_RESUMABLE_SESSION_TTL_SECS), - resumable_chunk_size: env::var("RESUMABLE_CHUNK_SIZE") - .ok() - .and_then(|value| value.parse().ok()) - .unwrap_or(resumable::DEFAULT_RESUMABLE_CHUNK_SIZE), + resumable_chunk_size: effective_resumable_chunk_size( + requested_resumable_chunk_size, + resumable_request_body_limit, + ), } } } +fn effective_resumable_chunk_size( + configured_chunk_size: u64, + route_body_limit: Option, +) -> u64 { + match route_body_limit { + Some(limit) if limit > 0 => configured_chunk_size.min(limit), + _ => configured_chunk_size, + } +} + // App state shared across handlers struct AppState { gcs_client: GcsClient, @@ -1118,7 +1136,9 @@ async fn probe_video_dimensions(video_bytes: &[u8]) -> Result { #[cfg(test)] #[allow(clippy::items_after_test_module)] mod tests { - use super::{media_source_candidates, new_temp_media_path}; + use super::{ + effective_resumable_chunk_size, media_source_candidates, new_temp_media_path, + }; #[test] fn temp_media_paths_are_unique_per_request() { @@ -1141,6 +1161,22 @@ mod tests { assert_eq!(candidates[1], format!("{}/hls/stream_720p.ts", hash)); assert_eq!(candidates[2], format!("{}/hls/stream_480p.ts", hash)); } + + #[test] + fn advertised_chunk_size_never_exceeds_route_body_limit() { + assert_eq!( + effective_resumable_chunk_size(8 * 1024 * 1024, Some(16 * 1024 * 1024)), + 8 * 1024 * 1024 + ); + assert_eq!( + effective_resumable_chunk_size(8 * 1024 * 1024, Some(1024 * 1024)), + 1024 * 1024 + ); + assert_eq!( + effective_resumable_chunk_size(8 * 1024 * 1024, None), + 8 * 1024 * 1024 + ); + } } fn validate_auth(headers: &axum::http::HeaderMap, required_action: &str) -> Result {