diff --git a/.gitignore b/.gitignore index 0f7c96ee..9ca83a52 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,4 @@ cds-feature-attachments/src/test/resources/schema.sql .secrets event.json -# CAP Notebook -cap-notebook/demoapp/ - -.DS_Store \ No newline at end of file +.DS_Store 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 2af22132..72df1920 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 @@ -18,7 +18,6 @@ import java.util.stream.Collectors; public final class AttachmentValidationHelper { - public static final List WILDCARD_MEDIA_TYPE = List.of("*/*"); private static AssociationCascader cascader = new AssociationCascader(); static void setCascader(AssociationCascader testCascader) { @@ -48,9 +47,16 @@ public static void validateMediaAttachments( return; } - // validate the media types of the attachments + // Resolve which media entities actually have the @Core.AcceptableMediaTypes annotation. + // If none do, skip the entire validation – no data extraction, no MIME resolution needed. Map> allowedTypesByElementName = MediaTypeResolver.getAcceptableMediaTypesFromEntity(cdsModel, mediaEntityNames); + + if (allowedTypesByElementName.isEmpty()) { + return; + } + + // validate the media types of the attachments Map> fileNamesByElementName = AttachmentDataExtractor.extractAndValidateFileNamesByElement(entity, data); validateAttachmentMediaTypes(fileNamesByElementName, allowedTypesByElementName); @@ -81,9 +87,12 @@ private static Map> findInvalidFilesByElementName( Map> invalidFiles = new HashMap<>(); fileNamesByElementName.forEach( (elementName, files) -> { - // Resolve the allowed media types for this field / element. - List acceptableTypes = - acceptableMediaTypesByElementName.getOrDefault(elementName, WILDCARD_MEDIA_TYPE); + // Only validate elements that have the @Core.AcceptableMediaTypes annotation. + // Elements not in the map are unconstrained and can accept any media type. + List acceptableTypes = acceptableMediaTypesByElementName.get(elementName); + if (acceptableTypes == null) { + return; + } // Filter out files whose media type is NOT allowed for this element List invalid = @@ -113,10 +122,7 @@ private static ServiceException buildUnsupportedFileTypeMessage( String element = entry.getKey(); String files = String.join(", ", entry.getValue()); String allowed = - String.join( - ", ", - acceptableMediaTypesByElementName.getOrDefault( - element, WILDCARD_MEDIA_TYPE)); + String.join(", ", acceptableMediaTypesByElementName.get(element)); return files + " (allowed: " + allowed + ") "; }) .collect(Collectors.joining("; ")); diff --git a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolver.java b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolver.java index 9554649c..b96d2e4c 100644 --- a/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolver.java +++ b/cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolver.java @@ -27,17 +27,16 @@ public static Map> getAcceptableMediaTypesFromEntity( } for (String entityName : mediaEntityNames) { CdsEntity mediaEntity = model.getEntity(entityName); - result.put(entityName, fetchAcceptableMediaTypes(mediaEntity)); + fetchAcceptableMediaTypes(mediaEntity).ifPresent(types -> result.put(entityName, types)); } return result; } - private static List fetchAcceptableMediaTypes(CdsEntity entity) { + private static Optional> fetchAcceptableMediaTypes(CdsEntity entity) { return getAcceptableMediaTypesAnnotation(entity) .map(CdsAnnotation::getValue) - .map(value -> objectMapper.convertValue(value, STRING_LIST_TYPE_REF)) - .orElse(AttachmentValidationHelper.WILDCARD_MEDIA_TYPE); + .map(value -> objectMapper.convertValue(value, STRING_LIST_TYPE_REF)); } public static Optional> getAcceptableMediaTypesAnnotation( 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 96b6cfb7..99e05b6c 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 @@ -60,6 +60,35 @@ void doesNothing_whenEntityNotFoundInModel() { } } + @Test + void doesNothing_whenNoEntityHasAcceptableMediaTypesAnnotation() { + CdsEntity entity = mockEntity("Entity"); + CdsRuntime runtime = mockRuntime(entity); + + try (MockedStatic helper = + mockStatic(ApplicationHandlerHelper.class); + MockedStatic resolver = mockStatic(MediaTypeResolver.class); + MockedStatic extractor = + mockStatic(AttachmentDataExtractor.class)) { + + helper.when(() -> ApplicationHandlerHelper.isMediaEntity(entity)).thenReturn(true); + + // MediaTypeResolver returns empty map = no entity has the annotation + resolver + .when( + () -> + MediaTypeResolver.getAcceptableMediaTypesFromEntity( + any(CdsModel.class), anyList())) + .thenReturn(Map.of()); + + // Verify that AttachmentDataExtractor is never called (early return) + assertDoesNotThrow( + () -> AttachmentValidationHelper.validateMediaAttachments(entity, List.of(), runtime)); + + extractor.verifyNoInteractions(); + } + } + @Test void doesNotThrow_whenNoFiles() { CdsEntity entity = mockEntity("Entity"); diff --git a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolverTest.java b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolverTest.java index fa2ea15d..f0056d06 100644 --- a/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolverTest.java +++ b/cds-feature-attachments/src/test/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/mimeTypeValidation/MediaTypeResolverTest.java @@ -47,7 +47,7 @@ void shouldReturnMediaTypesFromAnnotation() { } @Test - void shouldResolveMediaTypesUsingCascader() { + void shouldExcludeEntityWithoutAnnotation() { CdsModel model = mock(CdsModel.class); CdsEntity media = mock(CdsEntity.class); @@ -57,6 +57,7 @@ void shouldResolveMediaTypesUsingCascader() { Map> result = MediaTypeResolver.getAcceptableMediaTypesFromEntity(model, List.of("MediaEntity")); - assertThat(result).containsKey("MediaEntity"); + assertThat(result).doesNotContainKey("MediaEntity"); + assertThat(result).isEmpty(); } }