Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@ public void close() throws IOException {
}

private InputStream getDelegate() {
attachmentStatusValidator.verifyStatus(status);

if (delegate == null) {
logger.debug("Creating delegate input stream");
delegate = inputStreamSupplier.get();
try {
delegate = inputStreamSupplier.get();
attachmentStatusValidator.verifyStatus(status);
} catch (RuntimeException originalException) {
try {
attachmentStatusValidator.verifyStatus(status);
throw originalException;
} catch (AttachmentStatusException statusException) {
originalException.addSuppressed(statusException);
throw originalException;
}
}
}
return delegate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.StatusCode;
import com.sap.cds.feature.attachments.service.AttachmentService;
import com.sap.cds.services.ServiceException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -137,4 +138,76 @@ void noExceptionIfCorrectStatus() {

assertDoesNotThrow(() -> cut.read());
}

@Test
void originalExceptionPreservedWhenSupplierFails() {
// Test the core fix: original exception from supplier should be preserved
// even when status validation also fails
ServiceException originalException = new ServiceException("Failed to retrieve document content for file with ID: test123");
AttachmentStatusException statusException = AttachmentStatusException.getNotCleanException();

// Mock supplier to throw original exception
when(attachmentService.readAttachment(any())).thenThrow(originalException);
// Mock status validator to also throw exception
doThrow(statusException).when(attachmentStatusValidator).verifyStatus(StatusCode.INFECTED);

cut = new LazyProxyInputStream(
() -> attachmentService.readAttachment(any()),
attachmentStatusValidator,
StatusCode.INFECTED);

// The original exception should be thrown, not the status exception
ServiceException thrownException = assertThrows(ServiceException.class, () -> cut.read());

// Verify the original exception is preserved
assertThat(thrownException).isEqualTo(originalException);
assertThat(thrownException.getMessage()).isEqualTo("Failed to retrieve document content for file with ID: test123");

// Verify the status exception is added as suppressed exception for context
assertThat(thrownException.getSuppressed()).hasSize(1);
assertThat(thrownException.getSuppressed()[0]).isInstanceOf(AttachmentStatusException.class);
}

@Test
void originalExceptionPreservedWhenStatusValidationPasses() {
// Test edge case: supplier fails but status validation passes
// In this case, we still want the original exception, not status exception
ServiceException originalException = new ServiceException("Network connection failed");

// Mock supplier to throw original exception
when(attachmentService.readAttachment(any())).thenThrow(originalException);
// Status validator should pass (no exception thrown)

cut = new LazyProxyInputStream(
() -> attachmentService.readAttachment(any()),
attachmentStatusValidator,
StatusCode.CLEAN);

// The original exception should be thrown
ServiceException thrownException = assertThrows(ServiceException.class, () -> cut.read());

// Verify the original exception is preserved
assertThat(thrownException).isEqualTo(originalException);
assertThat(thrownException.getMessage()).isEqualTo("Network connection failed");

// No suppressed exceptions should be present since status validation passed
assertThat(thrownException.getSuppressed()).isEmpty();
}

@Test
void statusValidationOnlyWhenSupplierSucceeds() {
// Test that status validation happens after successful stream creation
// This ensures we don't pre-emptively block access due to status when the underlying issue might be different

cut = new LazyProxyInputStream(
() -> attachmentService.readAttachment(any()),
attachmentStatusValidator,
StatusCode.CLEAN);

assertDoesNotThrow(() -> cut.read());

// Verify status was validated after supplier was called
verify(attachmentService).readAttachment(any());
verify(attachmentStatusValidator).verifyStatus(StatusCode.CLEAN);
}
}
Loading