Files
upl/TESTS.md
T
ddidderr 1923ff2a6f feat: support parallel multi-file uploads
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
2026-05-30 18:32:29 +02:00

2.7 KiB

Test Scenarios

Keep this file as the reusable verification checklist while implementing PLAN.md.

Automated

  • just check
    • Runs formatting, all Rust tests, and clippy.
    • Current coverage:
      • GET / serves the static browser page.
      • GET /healthz reports ok.
      • POST /api/uploads creates meta.json, a temp upload file, and a completion-marker directory.
      • POST /api/uploads rejects an empty file name.
      • PUT /api/uploads/:id/chunks/:index writes validated chunks into the temp upload file and records completion markers.
      • PUT /api/uploads/:id/chunks/:index rejects wrong-size chunks.
      • PUT /api/uploads/:id/chunks/:index rejects out-of-range indexes.
      • PUT /api/uploads/:id/chunks/:index accepts duplicate chunks.
      • GET /api/uploads/:id reports completed chunks from disk markers.
      • POST /api/uploads/:id/complete renames the verified temp upload file and removes staging data.
      • Parallel upload requests for separate files complete without crossing bytes between temp upload files.
      • POST /api/uploads/:id/complete rejects incomplete uploads.
      • POST /api/uploads/:id/complete rejects tampered temp upload files.
      • static/app.js passes node --check.
  • just nginx-smoke
    • Runs upl behind nginx in Docker.
    • Uploads a 17 MiB file through nginx.
    • Restarts the Rust backend mid-upload, resumes through nginx, completes, and compares SHA-256 hashes.
    • Serves the completed file through nginx's final-upload alias.

Manual

These scenarios come from PLAN.md and remain useful for real browser and deployment retests.

  • Upload a small file in one pass.
  • Upload a file larger than one chunk.
  • Select multiple files and confirm several upload rows advance at the same time.
  • Kill the browser tab mid-upload and resume.
  • Restart the Rust server mid-upload and resume.
  • Interrupt the network and resume.
  • Pause from the browser controls and resume.
  • Reload the page and resume from the pending upload list.
  • In a browser with the File System Access API, resume without reselecting the file after granting read permission.
  • In a browser without the File System Access API, resume after reselecting the same file.
  • Retry a duplicate chunk and confirm it is accepted idempotently.
  • Attempt an invalid chunk index and confirm it is rejected.
  • Attempt a wrong-size non-final chunk and confirm it is rejected.
  • Install deploy/nginx/upl.conf.example on the deployment host, add the real TLS certificate and access-control settings, and repeat the resume scenarios through the public nginx URL.
  • Complete an upload and compare the final file with the source file:
sha256sum source-file data/complete/uploaded-file