refactor(backend): split ThumbnailService.generate into stages with distinct logs
Addresses @felixbrandt — fix(backend): "the two try blocks in generate() overlap — a save failure logs 'generation failed' even though the thumbnail is already in S3 as an orphan". generate() now orchestrates four stages, each in its own try+log: readSourceImage / encodeThumbnail / uploadToStorage / persistThumbnailMetadata persistThumbnailMetadata emits the distinct "orphaned in storage as <key>" log line so an operator can see database-side failures after the upload completed. The deterministic key ensures the next run overwrites cleanly, so the orphan is self-healing. Also extracts THUMBNAIL_KEY_PREFIX/SUFFIX constants with a comment explaining the deterministic-overwrite contract. Adds test: generate_returnsFailed_whenPersistThrows_butUploadSucceeded. Refs #307 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,24 @@ class ThumbnailServiceTest {
|
||||
verify(documentRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generate_returnsFailed_whenPersistThrows_butUploadSucceeded() throws IOException {
|
||||
// Covers the "orphan thumbnail" edge case: S3 upload succeeded but the
|
||||
// entity update blew up. We must still return FAILED so the backfill
|
||||
// tally is honest, without losing the fact that we already put bytes in S3.
|
||||
Document doc = makeDoc("application/pdf", "documents/letter.pdf");
|
||||
when(fileService.downloadFileStream(anyString()))
|
||||
.thenReturn(new ByteArrayInputStream(createSamplePdf()));
|
||||
when(documentRepository.save(any()))
|
||||
.thenThrow(new RuntimeException("constraint violation"));
|
||||
|
||||
ThumbnailService.Outcome outcome = thumbnailService.generate(doc);
|
||||
|
||||
assertThat(outcome).isEqualTo(ThumbnailService.Outcome.FAILED);
|
||||
verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class));
|
||||
verify(documentRepository).save(any());
|
||||
}
|
||||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private Document makeDoc(String contentType, String filePath) {
|
||||
|
||||
Reference in New Issue
Block a user