Skip to content
Merged
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
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,4 @@ cds-feature-attachments/src/test/resources/schema.sql
.secrets
event.json

# CAP Notebook
cap-notebook/demoapp/

.DS_Store
.DS_Store
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.stream.Collectors;

public final class AttachmentValidationHelper {
public static final List<String> WILDCARD_MEDIA_TYPE = List.of("*/*");
private static AssociationCascader cascader = new AssociationCascader();

static void setCascader(AssociationCascader testCascader) {
Expand Down Expand Up @@ -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<String, List<String>> allowedTypesByElementName =
MediaTypeResolver.getAcceptableMediaTypesFromEntity(cdsModel, mediaEntityNames);

if (allowedTypesByElementName.isEmpty()) {
return;
}

// validate the media types of the attachments
Map<String, Set<String>> fileNamesByElementName =
AttachmentDataExtractor.extractAndValidateFileNamesByElement(entity, data);
validateAttachmentMediaTypes(fileNamesByElementName, allowedTypesByElementName);
Expand Down Expand Up @@ -81,9 +87,12 @@ private static Map<String, List<String>> findInvalidFilesByElementName(
Map<String, List<String>> invalidFiles = new HashMap<>();
fileNamesByElementName.forEach(
(elementName, files) -> {
// Resolve the allowed media types for this field / element.
List<String> 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<String> acceptableTypes = acceptableMediaTypesByElementName.get(elementName);
if (acceptableTypes == null) {
return;
}

// Filter out files whose media type is NOT allowed for this element
List<String> invalid =
Expand Down Expand Up @@ -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("; "));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@ public static Map<String, List<String>> 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<String> fetchAcceptableMediaTypes(CdsEntity entity) {
private static Optional<List<String>> 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<CdsAnnotation<Object>> getAcceptableMediaTypesAnnotation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,35 @@ void doesNothing_whenEntityNotFoundInModel() {
}
}

@Test
void doesNothing_whenNoEntityHasAcceptableMediaTypesAnnotation() {
CdsEntity entity = mockEntity("Entity");
CdsRuntime runtime = mockRuntime(entity);

try (MockedStatic<ApplicationHandlerHelper> helper =
mockStatic(ApplicationHandlerHelper.class);
MockedStatic<MediaTypeResolver> resolver = mockStatic(MediaTypeResolver.class);
MockedStatic<AttachmentDataExtractor> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void shouldReturnMediaTypesFromAnnotation() {
}

@Test
void shouldResolveMediaTypesUsingCascader() {
void shouldExcludeEntityWithoutAnnotation() {
CdsModel model = mock(CdsModel.class);
CdsEntity media = mock(CdsEntity.class);

Expand All @@ -57,6 +57,7 @@ void shouldResolveMediaTypesUsingCascader() {
Map<String, List<String>> result =
MediaTypeResolver.getAcceptableMediaTypesFromEntity(model, List.of("MediaEntity"));

assertThat(result).containsKey("MediaEntity");
assertThat(result).doesNotContainKey("MediaEntity");
assertThat(result).isEmpty();
}
}
Loading