1923ff2a6f
The browser upload flow was built around one selected file and one global upload state. That made the existing chunk pool useful for a single file, but users could not start several selected files at the same time. Refactor the browser state into per-file upload items. Each selected file now has its own upload record, completed-chunk set, abort controller, retry state, progress row, and saved IndexedDB resume record. The picker accepts multiple files, `Start all` and `Resume all` use a bounded file-level pool, and each file keeps the existing bounded chunk pool. This keeps parallel uploads useful without letting one large selection create unbounded request fan-out. Keep the server API unchanged. Each file still receives a separate server upload id, and server-side progress remains authoritative before any missing chunks are scheduled. Terminal conflicts still stop the affected file without overwriting completed data. Update the user-facing markup, styles, project docs, and test checklist for the multi-file scheduler. Add a server regression test that interleaves two uploads and verifies the completed files contain exactly their own bytes. Test Plan: - just check - git diff --check
56 lines
1.5 KiB
Rust
56 lines
1.5 KiB
Rust
use std::{
|
|
net::{Ipv4Addr, SocketAddr},
|
|
path::Path,
|
|
};
|
|
|
|
use axum::{body::Body, http::Request};
|
|
use http_body_util::BodyExt;
|
|
use tower::ServiceExt;
|
|
use upl::app::{AppConfig, build_router};
|
|
|
|
#[tokio::test]
|
|
async fn serves_index_page() -> Result<(), Box<dyn std::error::Error>> {
|
|
let app = test_app();
|
|
|
|
let response = app
|
|
.oneshot(Request::builder().uri("/").body(Body::empty())?)
|
|
.await?;
|
|
|
|
assert_eq!(response.status(), axum::http::StatusCode::OK);
|
|
|
|
let body = response.into_body().collect().await?.to_bytes();
|
|
let body = String::from_utf8(body.to_vec())?;
|
|
|
|
assert!(body.contains("<title>upl</title>"));
|
|
assert!(body.contains("Choose files"));
|
|
assert!(body.contains("Selected uploads"));
|
|
assert!(body.contains("Saved upload progress"));
|
|
assert!(!body.contains("Server online"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reports_health() -> Result<(), Box<dyn std::error::Error>> {
|
|
let app = test_app();
|
|
|
|
let response = app
|
|
.oneshot(Request::builder().uri("/healthz").body(Body::empty())?)
|
|
.await?;
|
|
|
|
assert_eq!(response.status(), axum::http::StatusCode::OK);
|
|
|
|
let body = response.into_body().collect().await?.to_bytes();
|
|
assert_eq!(body.as_ref(), b"ok");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn test_app() -> axum::Router {
|
|
build_router(&AppConfig::new(
|
|
SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
|
|
concat!(env!("CARGO_MANIFEST_DIR"), "/static"),
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("target/test-data/static-server"),
|
|
))
|
|
}
|