Skip to main content

max / makenotwork

s3-storage: cover the part-size floor, the default, and the listing filter Three more from the mutation run with the live suite in it (114 missed to 51): the 10 MiB default nothing exercised, the 5 MiB floor at one byte under it, and the per-key filter in list_multipart_uploads_for_key, which if widened hands a caller somebody else's upload id and abort_multipart_upload then cancels an upload that was going fine.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-27 02:59 UTC
Signed with PGP, not checked
Commit: 3f89f1ca9ab91aa21cb13ba8aebf5ef2869789a7
Parent: fdff741
1 file changed, +104 insertions, -0 deletions
@@ -356,6 +356,110 @@
356 356 cleanup(&s3, &key).await;
357 357 }
358 358
359 + #[tokio::test]
360 + #[ignore = "live S3"]
361 + async fn the_default_part_size_uploads_a_file_larger_than_one_part() {
362 + // `part_size: None` takes the 10 MiB default, which no other test exercises
363 + // and which nothing asserts: the default is written as an arithmetic
364 + // expression, and a wrong one is either a refusal at the 5 MiB floor or an
365 + // upload in more parts than intended. 12 MiB is two parts at the real
366 + // default and one at any smaller one.
367 + let s3 = client().await;
368 + let key = prefix("default-part/big.bin");
369 + let body = pattern(12 * 1024 * 1024);
370 +
371 + let file = std::env::temp_dir().join(format!("s3-live-default-{}.bin", std::process::id()));
372 + std::fs::write(&file, &body).expect("writing the source file");
373 + let uploaded = s3
374 + .upload_multipart(&key, "application/octet-stream", &file, None)
375 + .await;
376 + std::fs::remove_file(&file).ok();
377 + uploaded.expect("upload_multipart with the default part size");
378 +
379 + let (got, _) = s3.download(&key).await.expect("download");
380 + assert_same_bytes(&body, &got, "default part size round trip");
381 +
382 + cleanup(&s3, &key).await;
383 + }
384 +
385 + #[tokio::test]
386 + #[ignore = "live S3"]
387 + async fn a_part_size_below_the_s3_floor_is_refused_before_anything_is_created() {
388 + // The floor is 5 MiB and the check is `<`, so one byte under is refused and
389 + // exactly 5 MiB is not. Refused BEFORE the upload is created, which is why
390 + // this asserts on the pending-upload list as well as on the error: a
391 + // pre-flight that creates first and validates second strands an upload
392 + // nobody will ever complete or be billed for noticing.
393 + let s3 = client().await;
394 + let key = prefix("floor/rejected.bin");
395 + let file = std::env::temp_dir().join(format!("s3-live-floor-{}.bin", std::process::id()));
396 + std::fs::write(&file, pattern(1024)).expect("writing the source file");
397 +
398 + let err = s3
399 + .upload_multipart(
400 + &key,
401 + "application/octet-stream",
402 + &file,
403 + Some(5 * 1024 * 1024 - 1),
404 + )
405 + .await
406 + .expect_err("a part below the floor must be refused");
407 + assert!(
408 + err.contains("at least 5 MB"),
409 + "refused for the wrong reason: {err}"
410 + );
411 + std::fs::remove_file(&file).ok();
412 +
413 + assert!(
414 + s3.list_multipart_uploads_for_key(&key)
415 + .await
416 + .expect("list")
417 + .is_empty(),
418 + "the refusal created an upload and left it pending"
419 + );
420 + }
421 +
422 + #[tokio::test]
423 + #[ignore = "live S3"]
424 + async fn pending_uploads_are_listed_for_their_own_key_only() {
425 + // The listing filters a bucket-wide response down to one key. A filter that
426 + // widened would hand a caller somebody else's upload id, and
427 + // abort_multipart_upload would then cancel an upload that was going fine.
428 + let s3 = client().await;
429 + let root = prefix("listing");
430 + let mine = format!("{root}/mine.bin");
431 + let theirs = format!("{root}/theirs.bin");
432 +
433 + let mine_id = s3
434 + .create_multipart_upload(&mine, "application/octet-stream")
435 + .await
436 + .expect("create mine");
437 + let theirs_id = s3
438 + .create_multipart_upload(&theirs, "application/octet-stream")
439 + .await
440 + .expect("create theirs");
441 +
442 + let listed = s3
443 + .list_multipart_uploads_for_key(&mine)
444 + .await
445 + .expect("list mine");
446 + assert!(
447 + listed.contains(&mine_id),
448 + "our own upload is missing: {listed:?}"
449 + );
450 + assert!(
451 + !listed.contains(&theirs_id),
452 + "the listing reached past its key: {listed:?}"
453 + );
454 +
455 + s3.abort_multipart_upload(&mine, &mine_id)
456 + .await
457 + .expect("abort mine");
458 + s3.abort_multipart_upload(&theirs, &theirs_id)
459 + .await
460 + .expect("abort theirs");
461 + }
462 +
359 463 #[tokio::test]
360 464 #[ignore = "live S3"]
361 465 async fn a_multipart_copy_preserves_every_byte() {