From 9e0262c4550e7f1e689b2df769c2299f076ab6dd Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Tue, 21 Jul 2026 12:10:46 +0200 Subject: [PATCH 1/2] fix: enforce acceptable media types on update and draft patch Acceptable-media-types validation was only applied on create/draft-new. Non-draft updates (UpdateAttachmentsHandler) and draft patches (DraftPatchAttachmentsHandler) stored replacement content and metadata without validation, so a user able to update an attachment could replace it (including a metadata-only mimeType/fileName change) with a disallowed media type that bypassed @Core.AcceptableMediaTypes. Both handlers now validate via AttachmentValidationHelper before dispatching the modification, matching the create path. On update the check runs in a dedicated @Before(BEFORE) method so it also covers metadata-only changes. CdsRuntime is injected into both handlers to resolve the CDS model. --- .../attachments/configuration/Registration.java | 3 +-- .../applicationservice/CreateAttachmentsHandler.java | 8 ++------ .../applicationservice/UpdateAttachmentsHandler.java | 11 +++++++++++ .../AttachmentValidationHelper.java | 5 ++--- .../draftservice/DraftPatchAttachmentsHandler.java | 6 ++++++ .../UpdateAttachmentsHandlerTest.java | 3 ++- .../DraftPatchAttachmentsHandlerTest.java | 5 ++++- 7 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java index 92c477234..3da9e77dd 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/configuration/Registration.java @@ -156,8 +156,7 @@ public void eventHandlers(CdsRuntimeConfigurer configurer) { boolean hasApplicationServices = serviceCatalog.getServices(ApplicationService.class).findFirst().isPresent(); if (hasApplicationServices) { - configurer.eventHandler( - new CreateAttachmentsHandler(eventFactory, storage, defaultMaxSize, runtime)); + configurer.eventHandler(new CreateAttachmentsHandler(eventFactory, storage, defaultMaxSize)); configurer.eventHandler( new UpdateAttachmentsHandler( eventFactory, attachmentsReader, outboxedAttachmentService, storage, defaultMaxSize)); diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandler.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandler.java index c5c2c863a..482f754e5 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandler.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandler.java @@ -23,7 +23,6 @@ import com.sap.cds.services.handler.annotations.HandlerOrder; import com.sap.cds.services.handler.annotations.On; import com.sap.cds.services.handler.annotations.ServiceName; -import com.sap.cds.services.runtime.CdsRuntime; import com.sap.cds.services.utils.OrderConstants; import java.util.ArrayList; import java.util.List; @@ -42,17 +41,14 @@ public class CreateAttachmentsHandler implements EventHandler { private final ModifyAttachmentEventFactory eventFactory; private final ThreadDataStorageReader storageReader; private final String defaultMaxSize; - private final CdsRuntime cdsRuntime; public CreateAttachmentsHandler( ModifyAttachmentEventFactory eventFactory, ThreadDataStorageReader storageReader, - String defaultMaxSize, - CdsRuntime cdsRuntime) { + String defaultMaxSize) { this.eventFactory = eventFactory; this.storageReader = storageReader; this.defaultMaxSize = defaultMaxSize; - this.cdsRuntime = cdsRuntime; } @Before @@ -69,7 +65,7 @@ void processBeforeForDraft(CdsCreateEventContext context, List data) { @HandlerOrder(HandlerOrder.BEFORE) void processBeforeForMetadata(EventContext context, List data) { CdsEntity target = context.getTarget(); - AttachmentValidationHelper.validateMediaAttachments(target, data, cdsRuntime); + AttachmentValidationHelper.validateMediaAttachments(target, data, context.getModel()); } @Before diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandler.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandler.java index 7e7b06c6c..5c6763ac3 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandler.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandler.java @@ -8,6 +8,7 @@ import com.sap.cds.feature.attachments.handler.applicationservice.helper.ModifyApplicationHandlerHelper; import com.sap.cds.feature.attachments.handler.applicationservice.helper.ReadonlyDataContextEnhancer; import com.sap.cds.feature.attachments.handler.applicationservice.helper.ThreadDataStorageReader; +import com.sap.cds.feature.attachments.handler.applicationservice.helper.mimeTypeValidation.AttachmentValidationHelper; import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEventFactory; import com.sap.cds.feature.attachments.handler.common.ApplicationHandlerHelper; import com.sap.cds.feature.attachments.handler.common.AttachmentsReader; @@ -68,6 +69,16 @@ void processBeforeForDraft(CdsUpdateEventContext context, List data) { context.getTarget(), data, storageReader.get()); } + @Before + @HandlerOrder(HandlerOrder.BEFORE) + void processBeforeForMetadata(CdsUpdateEventContext context, List data) { + // Enforce @Core.AcceptableMediaTypes on updates as well, so replacement content or a + // metadata-only change (e.g. mimeType/fileName) cannot bypass the media type policy that is + // applied on create. + AttachmentValidationHelper.validateMediaAttachments( + context.getTarget(), data, context.getModel()); + } + @Before @HandlerOrder(HandlerOrder.LATE) void processBefore(CdsUpdateEventContext context, List data) { diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelper.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelper.java index 72df1920b..3032605f8 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelper.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelper.java @@ -10,7 +10,6 @@ import com.sap.cds.reflect.CdsModel; import com.sap.cds.services.ErrorStatuses; import com.sap.cds.services.ServiceException; -import com.sap.cds.services.runtime.CdsRuntime; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,14 +28,14 @@ static void setCascader(AssociationCascader testCascader) { * * @param entity the {@link CdsEntity entity} type of the given data * @param data the list of {@link CdsData} to process + * @param cdsModel the (tenant-specific) {@link CdsModel} to resolve media entities against * @throws ServiceException if the media type of the attachment is not acceptable */ public static void validateMediaAttachments( - CdsEntity entity, List data, CdsRuntime cdsRuntime) { + CdsEntity entity, List data, CdsModel cdsModel) { if (entity == null) { return; } - CdsModel cdsModel = cdsRuntime.getCdsModel(); List mediaEntityNames = ApplicationHandlerHelper.isMediaEntity(entity) diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandler.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandler.java index d62134bd7..56ef5e2b8 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandler.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandler.java @@ -9,6 +9,7 @@ import com.sap.cds.Result; import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.Attachments; import com.sap.cds.feature.attachments.handler.applicationservice.helper.ModifyApplicationHandlerHelper; +import com.sap.cds.feature.attachments.handler.applicationservice.helper.mimeTypeValidation.AttachmentValidationHelper; import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEventFactory; import com.sap.cds.feature.attachments.handler.common.ApplicationHandlerHelper; import com.sap.cds.ql.Select; @@ -55,6 +56,11 @@ void processBeforeDraftPatch(DraftPatchEventContext context, List { CdsEntity draftEntity = DraftUtils.getDraftEntity(path.target().entity()); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java index 8ea36c180..cebdde102 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java @@ -85,7 +85,8 @@ void setup() { attachmentsReader, attachmentService, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + runtime); event = mock(ModifyAttachmentEvent.class); updateContext = mock(CdsUpdateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java index 035dd766b..bcf40a2cc 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java @@ -63,7 +63,10 @@ void setup() { eventFactory = mock(ModifyAttachmentEventFactory.class); cut = new DraftPatchAttachmentsHandler( - persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); + persistence, + eventFactory, + ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, + runtime); eventContext = mock(DraftPatchEventContext.class); event = mock(ModifyAttachmentEvent.class); when(eventFactory.getEvent(any(), any(), any())).thenReturn(event); From 1e4cd588229cfad251a90a21a237c6dc8dee4d62 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Tue, 21 Jul 2026 12:21:46 +0200 Subject: [PATCH 2/2] test: cover media type validation on update and draft patch Adds integration tests asserting that replacing an attachment with a disallowed media type is rejected (415) on the non-draft update path and on the draft patch path. --- .../CreateAttachmentsHandlerTest.java | 15 ++++--- .../UpdateAttachmentsHandlerTest.java | 3 +- .../AttachmentValidationHelperTest.java | 40 ++++++------------- .../DraftPatchAttachmentsHandlerTest.java | 6 +-- .../MediaValidatedAttachmentsDraftTest.java | 25 ++++++++++++ ...MediaValidatedAttachmentsNonDraftTest.java | 27 +++++++++++++ 6 files changed, 75 insertions(+), 41 deletions(-) diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java index 37ed28c8a..2c102a41c 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/CreateAttachmentsHandlerTest.java @@ -34,6 +34,7 @@ import com.sap.cds.feature.attachments.handler.applicationservice.readhelper.CountingInputStream; import com.sap.cds.feature.attachments.handler.helper.RuntimeHelper; import com.sap.cds.reflect.CdsEntity; +import com.sap.cds.reflect.CdsModel; import com.sap.cds.services.ErrorStatuses; import com.sap.cds.services.EventContext; import com.sap.cds.services.ServiceException; @@ -75,10 +76,7 @@ void setup() { storageReader = mock(ThreadDataStorageReader.class); cut = new CreateAttachmentsHandler( - eventFactory, - storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, - runtime); + eventFactory, storageReader, ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); createContext = mock(CdsCreateEventContext.class); event = mock(ModifyAttachmentEvent.class); @@ -355,19 +353,20 @@ void processBeforeForMetadata_executesValidation() { EventContext context = mock(EventContext.class); CdsEntity entity = mock(CdsEntity.class); List data = List.of(mock(CdsData.class)); + CdsModel model = runtime.getCdsModel(); when(context.getTarget()).thenReturn(entity); + when(context.getModel()).thenReturn(model); try (MockedStatic helper = mockStatic(AttachmentValidationHelper.class)) { helper - .when(() -> AttachmentValidationHelper.validateMediaAttachments(entity, data, runtime)) + .when(() -> AttachmentValidationHelper.validateMediaAttachments(entity, data, model)) .thenAnswer(invocation -> null); // when - new CreateAttachmentsHandler(eventFactory, storageReader, "400MB", runtime) + new CreateAttachmentsHandler(eventFactory, storageReader, "400MB") .processBeforeForMetadata(context, data); // then - helper.verify( - () -> AttachmentValidationHelper.validateMediaAttachments(entity, data, runtime)); + helper.verify(() -> AttachmentValidationHelper.validateMediaAttachments(entity, data, model)); } } diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java index cebdde102..8ea36c180 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/UpdateAttachmentsHandlerTest.java @@ -85,8 +85,7 @@ void setup() { attachmentsReader, attachmentService, storageReader, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, - runtime); + ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); event = mock(ModifyAttachmentEvent.class); updateContext = mock(CdsUpdateEventContext.class); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelperTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelperTest.java index 99e05b6cf..5be06f7fe 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelperTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/AttachmentValidationHelperTest.java @@ -17,7 +17,6 @@ import com.sap.cds.reflect.CdsEntity; import com.sap.cds.reflect.CdsModel; import com.sap.cds.services.ServiceException; -import com.sap.cds.services.runtime.CdsRuntime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -47,8 +46,6 @@ void doesNothing_whenEntityNotFoundInModel() { CdsModel model = mock(CdsModel.class); when(model.findEntity("Entity")).thenReturn(Optional.empty()); - CdsRuntime runtime = mockRuntime(model); - try (MockedStatic helper = mockStatic(ApplicationHandlerHelper.class)) { helper.when(() -> ApplicationHandlerHelper.isMediaEntity(entity)).thenReturn(false); @@ -56,14 +53,14 @@ void doesNothing_whenEntityNotFoundInModel() { setupMockCascader(entity, model, false); assertDoesNotThrow( - () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), model)); } } @Test void doesNothing_whenNoEntityHasAcceptableMediaTypesAnnotation() { CdsEntity entity = mockEntity("Entity"); - CdsRuntime runtime = mockRuntime(entity); + CdsModel model = mockModel(entity); try (MockedStatic helper = mockStatic(ApplicationHandlerHelper.class); @@ -83,7 +80,7 @@ void doesNothing_whenNoEntityHasAcceptableMediaTypesAnnotation() { // Verify that AttachmentDataExtractor is never called (early return) assertDoesNotThrow( - () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), model)); extractor.verifyNoInteractions(); } @@ -100,7 +97,7 @@ void doesNotThrow_whenNoFiles() { MockedStatic resolver = mockStatic(MediaTypeResolver.class); MockedStatic extractor = mockStatic(AttachmentDataExtractor.class)) { - CdsRuntime runtime = mockRuntime(entity); + CdsModel model = mockModel(entity); helper.when(() -> ApplicationHandlerHelper.isMediaEntity(entity)).thenReturn(true); resolver @@ -116,7 +113,7 @@ void doesNotThrow_whenNoFiles() { .thenReturn(null); assertDoesNotThrow( - () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), model)); } } @@ -125,7 +122,7 @@ void doesNotThrow_whenNoFiles() { void doesNotThrow_whenFilesAreValid(boolean isMediaEntity) { CdsEntity entity = mockEntity("Entity"); - CdsRuntime runtime = mockRuntime(entity); + CdsModel model = mockModel(entity); Map> allowed = Map.of(ATTACHMENTS_ENTITY, List.of("image/png")); Map> files = Map.of(ATTACHMENTS_ENTITY, Set.of("file.png")); @@ -137,7 +134,7 @@ void doesNotThrow_whenFilesAreValid(boolean isMediaEntity) { mockStatic(AttachmentDataExtractor.class)) { helper.when(() -> ApplicationHandlerHelper.isMediaEntity(entity)).thenReturn(isMediaEntity); - setupMockCascader(entity, runtime.getCdsModel(), !isMediaEntity); + setupMockCascader(entity, model, !isMediaEntity); resolver .when( @@ -152,7 +149,7 @@ void doesNotThrow_whenFilesAreValid(boolean isMediaEntity) { .thenReturn(files); assertDoesNotThrow( - () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), model)); } } @@ -168,7 +165,7 @@ private static Stream validFileScenarios() { void throwsException_whenFilesAreInvalid(boolean isMediaEntity) { CdsEntity entity = mockEntity("Entity"); - CdsRuntime runtime = mockRuntime(entity); + CdsModel model = mockModel(entity); Map> allowed = Map.of(ATTACHMENTS_ENTITY, List.of("image/png")); Map> files = Map.of(ATTACHMENTS_ENTITY, Set.of("file.txt")); @@ -180,7 +177,7 @@ void throwsException_whenFilesAreInvalid(boolean isMediaEntity) { mockStatic(AttachmentDataExtractor.class)) { helper.when(() -> ApplicationHandlerHelper.isMediaEntity(entity)).thenReturn(isMediaEntity); - setupMockCascader(entity, runtime.getCdsModel(), !isMediaEntity); + setupMockCascader(entity, model, !isMediaEntity); resolver .when( @@ -197,8 +194,7 @@ void throwsException_whenFilesAreInvalid(boolean isMediaEntity) { ServiceException ex = assertThrows( ServiceException.class, - () -> - AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), model)); assertTrue(ex.getMessage().contains("Unsupported file types detected")); } @@ -215,20 +211,10 @@ private void setupMockCascader(CdsEntity entity, CdsModel model, boolean hasAtta AttachmentValidationHelper.setCascader(cascader); } - private CdsRuntime mockRuntime(CdsEntity entity) { + private CdsModel mockModel(CdsEntity entity) { CdsModel model = mock(CdsModel.class); when(model.findEntity(entity.getQualifiedName())).thenReturn(Optional.of(entity)); - - CdsRuntime runtime = mock(CdsRuntime.class); - when(runtime.getCdsModel()).thenReturn(model); - - return runtime; - } - - private CdsRuntime mockRuntime(CdsModel model) { - CdsRuntime runtime = mock(CdsRuntime.class); - when(runtime.getCdsModel()).thenReturn(model); - return runtime; + return model; } private CdsEntity mockEntity(String name) { diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java index bcf40a2cc..e0595125d 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/draftservice/DraftPatchAttachmentsHandlerTest.java @@ -63,10 +63,7 @@ void setup() { eventFactory = mock(ModifyAttachmentEventFactory.class); cut = new DraftPatchAttachmentsHandler( - persistence, - eventFactory, - ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER, - runtime); + persistence, eventFactory, ModifyApplicationHandlerHelper.DEFAULT_SIZE_WITH_SCANNER); eventContext = mock(DraftPatchEventContext.class); event = mock(ModifyAttachmentEvent.class); when(eventFactory.getEvent(any(), any(), any())).thenReturn(event); @@ -195,5 +192,6 @@ private void getEntityAndMockContext(String cdsName) { private void mockTargetInUpdateContext(CdsEntity serviceEntity) { when(eventContext.getTarget()).thenReturn(serviceEntity); + when(eventContext.getModel()).thenReturn(runtime.getCdsModel()); } } diff --git a/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/draftservice/MediaValidatedAttachmentsDraftTest.java b/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/draftservice/MediaValidatedAttachmentsDraftTest.java index d12763a9b..9330e2643 100644 --- a/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/draftservice/MediaValidatedAttachmentsDraftTest.java +++ b/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/draftservice/MediaValidatedAttachmentsDraftTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.provider.CsvSource; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MvcResult; @ActiveProfiles(Profiles.TEST_HANDLER_DISABLED) public class MediaValidatedAttachmentsDraftTest extends DraftOdataRequestValidationBase { @@ -72,6 +73,30 @@ void shouldPass_whenFileNameMissing_inDraft() throws Exception { buildDraftAttachmentCreationUrl(rootId), metadata, status().isCreated()); } + @Test + void shouldValidateMediaType_whenPatchingDraftAttachment() throws Exception { + String rootId = createDraftRootAndReturnId(); + CdsData created = + requestHelper.executePostWithODataResponseAndAssertStatusCreated( + buildDraftAttachmentCreationUrl(rootId), + objectMapper.writeValueAsString(Map.of("fileName", "image.jpg"))); + String attachmentId = (String) created.get("ID"); + + String attachmentUrl = + BASE_URL + + "DraftRoots_mediaValidatedAttachments(up__ID=" + + rootId + + ",ID=" + + attachmentId + + ",IsActiveEntity=false)"; + + MvcResult result = + requestHelper.executePatch( + attachmentUrl, objectMapper.writeValueAsString(Map.of("fileName", "notes.txt"))); + + assertThat(result.getResponse().getStatus()).isEqualTo(415); + } + // Helper methods private String createDraftRootAndReturnId() throws Exception { CdsData response = diff --git a/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/nondraftservice/MediaValidatedAttachmentsNonDraftTest.java b/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/nondraftservice/MediaValidatedAttachmentsNonDraftTest.java index cc8c2e099..7ce27fec0 100644 --- a/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/nondraftservice/MediaValidatedAttachmentsNonDraftTest.java +++ b/integration-tests/generic/src/test/java/com/sap/cds/feature/attachments/integrationtests/nondraftservice/MediaValidatedAttachmentsNonDraftTest.java @@ -3,11 +3,14 @@ */ package com.sap.cds.feature.attachments.integrationtests.nondraftservice; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.sap.cds.CdsData; import com.sap.cds.Result; +import com.sap.cds.Struct; import com.sap.cds.feature.attachments.generated.integration.test.cds4j.sap.attachments.Attachments; import com.sap.cds.feature.attachments.generated.integration.test.cds4j.testservice.AttachmentEntity; import com.sap.cds.feature.attachments.generated.integration.test.cds4j.testservice.Roots; @@ -26,6 +29,7 @@ import org.junit.jupiter.params.provider.CsvSource; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MvcResult; @ActiveProfiles(Profiles.TEST_HANDLER_DISABLED) class MediaValidatedAttachmentsNonDraftTest extends OdataRequestValidationBase { @@ -82,6 +86,29 @@ void shouldRejectAttachment_whenFileNameIsEmpty() throws Exception { status().isBadRequest()); } + @Test + void shouldValidateMediaType_whenUpdatingNonDraftAttachment() throws Exception { + String rootId = createRootAndReturnId(); + CdsData created = + requestHelper.executePostWithODataResponseAndAssertStatusCreated( + createUrl(rootId, MEDIA_VALIDATED_ATTACHMENTS), + objectMapper.writeValueAsString(Map.of("fileName", "image.jpg"))); + Attachments attachment = Struct.access(created).as(Attachments.class); + String attachmentUrl = + createUrl(rootId, MEDIA_VALIDATED_ATTACHMENTS) + + "(ID=" + + attachment.getId() + + ",up__ID=" + + rootId + + ")"; + + MvcResult result = + requestHelper.executePatch( + attachmentUrl, objectMapper.writeValueAsString(Map.of("fileName", "notes.txt"))); + + assertThat(result.getResponse().getStatus()).isEqualTo(415); + } + @Test void shouldAcceptUppercaseExtension_whenMimeTypeIsAllowed() throws Exception { String rootId = createRootAndReturnId();