feat(authz): test & cover batch cases

┌────────────────────────────────┬─────────────────────────────────┬───────────────────────┬─────────────────┬──────────────────────────────┐
  │            Endpoint            │        Phase 3A no-grant        │    Phase 3B Viewer    │ Phase 3C Editor │        Phase 3D Admin        │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/files/get      │ 400 (all failed)                │ 200 (2 successful)    │ —               │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/files/move     │ 400                             │ 400 (no Update)       │ 200             │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/files/copy     │ 400                             │ —                     │ 200             │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/files/delete   │ 400                             │ 400                   │ 400 (no Delete) │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/folders/get    │ 400                             │ 200                   │ —               │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/folders/create │ 400                             │ —                     │ 201             │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/folders/move   │ 400                             │ —                     │ 200             │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/folders/copy   │ 400                             │ —                     │ 200             │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/folders/delete │ 400                             │ —                     │ 400 (no Delete) │ 200                          │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/trash          │ 400                             │ —                     │ —               │ 400 (owner-only, documented) │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ POST /api/batch/download       │ 404 (NotFound)                  │ 200 + application/zip │ —               │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ GET /api/batch/download?...    │ 404                             │ 200 + zip             │ —               │ —                            │
  ├────────────────────────────────┼─────────────────────────────────┼───────────────────────┼─────────────────┼──────────────────────────────┤
  │ Phase 3E lifecycle cleanup     │ grants table empty after delete │                       │                 │                              │
  └────────────────────────────────┴─────────────────────────────────┴───────────────────────┴─────────────────┴──────────────────────────────┘
This commit is contained in:
Edouard Vanbelle
2026-05-21 20:27:25 +02:00
parent a53c09f361
commit eb95567a7d
3 changed files with 463 additions and 8 deletions
+23 -4
View File
@@ -715,6 +715,11 @@ impl BatchOperationService {
let buf_writer = BufWriter::with_capacity(256 * 1024, tokio_file);
let mut zip = ZipFileWriter::with_tokio(buf_writer);
// Track whether any item was authorized + added to the ZIP. If
// none were, return NotFound — empty ZIPs are useless and mask
// authz failures from the client.
let mut items_added: usize = 0;
// ── Add individual files at the root of the ZIP ──────────────────
for file_id in &file_ids {
match self
@@ -723,11 +728,14 @@ impl BatchOperationService {
.await
{
Ok(file_dto) => {
if let Err(e) = self
match self
.add_file_entry_streamed(&mut zip, file_id, &file_dto.name, user_id)
.await
{
info!("Could not add file {} to ZIP: {}", file_dto.name, e);
Ok(_) => items_added += 1,
Err(e) => {
info!("Could not add file {} to ZIP: {}", file_dto.name, e);
}
}
}
Err(e) => {
@@ -744,11 +752,14 @@ impl BatchOperationService {
.await
{
Ok(root_folder) => {
if let Err(e) = self
match self
.add_folder_subtree_to_zip(&mut zip, folder_id, &root_folder, user_id)
.await
{
info!("Could not add folder {} to ZIP: {}", root_folder.name, e);
Ok(_) => items_added += 1,
Err(e) => {
info!("Could not add folder {} to ZIP: {}", root_folder.name, e);
}
}
}
Err(e) => {
@@ -757,6 +768,14 @@ impl BatchOperationService {
}
}
// Bail out before finalizing the ZIP if nothing was authorized.
if items_added == 0 {
return Err(BatchOperationError::Domain(DomainError::not_found(
"BatchDownload",
"No accessible files or folders in the request",
)));
}
// ── Finalize ─────────────────────────────────────────────────────
let mut compat_writer = zip
.close()