Skip to content

Commit c31590e

Browse files
Fix Max Size Validation on First Upload (#743)
Co-authored-by: hyperspace-insights[bot] <209611008+hyperspace-insights[bot]@users.noreply.github.com>
1 parent f1413c9 commit c31590e

4 files changed

Lines changed: 49 additions & 29 deletions

File tree

cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@
66
import com.sap.cds.CdsData;
77
import com.sap.cds.CdsDataProcessor;
88
import com.sap.cds.CdsDataProcessor.Converter;
9-
import com.sap.cds.CdsDataProcessor.Filter;
109
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.Attachments;
1110
import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEvent;
1211
import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEventFactory;
1312
import com.sap.cds.feature.attachments.handler.applicationservice.readhelper.CountingInputStream;
1413
import com.sap.cds.feature.attachments.handler.common.ApplicationHandlerHelper;
1514
import com.sap.cds.ql.cqn.Path;
15+
import com.sap.cds.reflect.CdsAnnotation;
1616
import com.sap.cds.reflect.CdsEntity;
1717
import com.sap.cds.services.ErrorStatuses;
1818
import com.sap.cds.services.EventContext;
1919
import com.sap.cds.services.ServiceException;
2020
import java.io.InputStream;
2121
import java.util.List;
2222
import java.util.Map;
23-
import java.util.concurrent.atomic.AtomicReference;
2423

2524
public final class ModifyApplicationHandlerHelper {
2625

@@ -30,11 +29,6 @@ public final class ModifyApplicationHandlerHelper {
3029
/** Effectively unlimited max size when no malware scanner binding is present. */
3130
public static final String UNLIMITED_SIZE = String.valueOf(Long.MAX_VALUE);
3231

33-
private static final Filter VALMAX_FILTER =
34-
(path, element, type) ->
35-
element.getName().contentEquals("content")
36-
&& element.findAnnotation("Validation.Maximum").isPresent();
37-
3832
/**
3933
* Handles attachments for entities.
4034
*
@@ -94,7 +88,7 @@ public static InputStream handleAttachmentForEntity(
9488
Attachments attachment = getExistingAttachment(keys, existingAttachments);
9589
String contentId = (String) path.target().values().get(Attachments.CONTENT_ID);
9690
String contentLength = eventContext.getParameterInfo().getHeader("Content-Length");
97-
String maxSizeStr = getValMaxValue(path.target().entity(), existingAttachments, defaultMaxSize);
91+
String maxSizeStr = getValMaxValue(path.target().entity(), defaultMaxSize);
9892
eventContext.put(
9993
"attachment.MaxSize",
10094
maxSizeStr); // make max size available in context for error handling later
@@ -125,23 +119,14 @@ public static InputStream handleAttachmentForEntity(
125119
}
126120
}
127121

128-
private static String getValMaxValue(
129-
CdsEntity entity, List<? extends CdsData> data, String defaultMaxSize) {
130-
AtomicReference<String> annotationValue = new AtomicReference<>();
131-
CdsDataProcessor.create()
132-
.addValidator(
133-
VALMAX_FILTER,
134-
(path, element, value) ->
135-
element
136-
.findAnnotation("Validation.Maximum")
137-
.ifPresent(
138-
annotation -> {
139-
if (annotation.getValue() != null && annotation.getValue() != "true") {
140-
annotationValue.set(annotation.getValue().toString());
141-
}
142-
}))
143-
.process(data, entity);
144-
return annotationValue.get() == null ? defaultMaxSize : annotationValue.get();
122+
private static String getValMaxValue(CdsEntity entity, String defaultMaxSize) {
123+
return entity
124+
.findElement("content")
125+
.flatMap(e -> e.findAnnotation("Validation.Maximum"))
126+
.map(CdsAnnotation::getValue)
127+
.filter(v -> !"true".equals(v.toString()))
128+
.map(Object::toString)
129+
.orElse(defaultMaxSize);
145130
}
146131

147132
private static Attachments getExistingAttachment(

cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelperTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void serviceExceptionDueToContentLength() {
7979
// Set Content-Length header to exceed 10KB (10240 bytes)
8080
when(parameterInfo.getHeader("Content-Length")).thenReturn("20000");
8181

82-
var existingAttachments = List.of(attachment);
82+
var existingAttachments = List.<Attachments>of();
8383

8484
// Act & Assert
8585
var exception =
@@ -133,7 +133,7 @@ void serviceExceptionDueToLimitExceeded() {
133133
return null;
134134
});
135135

136-
var existingAttachments = List.of(attachment);
136+
var existingAttachments = List.<Attachments>of();
137137

138138
// Act & Assert
139139
var exception =

integration-tests/srv/src/test/java/com/sap/cds/feature/attachments/integrationtests/draftservice/SizeLimitedAttachmentsSizeValidationDraftTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,41 @@ void uploadContentExceeding5MBLimitFails() throws Exception {
5151
// Assert: Error response with HTTP 413 status code indicates size limit exceeded
5252
}
5353

54+
@Test
55+
void uploadContentWithinLimitAndActivateDraftSucceeds() throws Exception {
56+
// Arrange: Create draft with sizeLimitedAttachments (no prior activation)
57+
var draftRoot = createNewDraftWithSizeLimitedAttachments();
58+
var attachment = draftRoot.getSizeLimitedAttachments().get(0);
59+
60+
// Act: Upload 3MB content (within 5MB limit)
61+
byte[] content = new byte[3 * 1024 * 1024]; // 3MB
62+
var url = buildDraftSizeLimitedAttachmentContentUrl(draftRoot.getId(), attachment.getId());
63+
requestHelper.setContentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
64+
requestHelper.executePutWithMatcher(url, content, status().isNoContent());
65+
66+
// Assert: Draft activation succeeds
67+
requestHelper.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
68+
var rootUrl = getRootUrl(draftRoot.getId(), false);
69+
var draftPrepareUrl = rootUrl + "/TestDraftService.draftPrepare";
70+
var draftActivateUrl = rootUrl + "/TestDraftService.draftActivate";
71+
requestHelper.executePostWithMatcher(
72+
draftPrepareUrl, "{\"SideEffectsQualifier\":\"\"}", status().isOk());
73+
requestHelper.executePostWithMatcher(draftActivateUrl, "{}", status().isOk());
74+
}
75+
76+
@Test
77+
void uploadContentExceedingLimitOnFirstDraftRejects() throws Exception {
78+
// Arrange: Create draft with sizeLimitedAttachments (no prior activation)
79+
var draftRoot = createNewDraftWithSizeLimitedAttachments();
80+
var attachment = draftRoot.getSizeLimitedAttachments().get(0);
81+
82+
// Act & Assert: Upload 6MB content to a brand-new draft attachment fails immediately
83+
byte[] content = new byte[6 * 1024 * 1024]; // 6MB
84+
var url = buildDraftSizeLimitedAttachmentContentUrl(draftRoot.getId(), attachment.getId());
85+
requestHelper.setContentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM);
86+
requestHelper.executePutWithMatcher(url, content, status().is(413));
87+
}
88+
5489
// Helper methods
5590
private DraftRoots createNewDraftWithSizeLimitedAttachments() throws Exception {
5691
// Create new draft

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@
7575
<cds.cdsdk-version>9.6.1</cds.cdsdk-version>
7676

7777
<!-- Latest versions of CAP Java and cds-dk used for integrations tests only -->
78-
<cds.services.latest-test-version>4.6.1</cds.services.latest-test-version>
79-
<cds.cdsdk.latest-test-version>9.6.1</cds.cdsdk.latest-test-version>
78+
<cds.services.latest-test-version>4.8.0</cds.services.latest-test-version>
79+
<cds.cdsdk.latest-test-version>9.7.2</cds.cdsdk.latest-test-version>
8080

8181
<spotless.check.skip>true</spotless.check.skip>
8282
</properties>

0 commit comments

Comments
 (0)