From 2352e991fd13f4e81374e585566b74067070f8ce Mon Sep 17 00:00:00 2001 From: Rashmi Date: Fri, 17 Oct 2025 17:01:13 +0530 Subject: [PATCH 1/7] Translation of all the messages --- .../sap/cds/sdm/constants/SDMConstants.java | 45 ++ .../sdm/service/DocumentUploadService.java | 2 +- .../cds/sdm/service/SDMAdminServiceImpl.java | 40 +- .../sap/cds/sdm/service/SDMServiceImpl.java | 15 +- .../handler/SDMAttachmentsServiceHandler.java | 46 +- .../handler/SDMCustomServiceHandler.java | 11 +- .../handler/SDMServiceGenericHandler.java | 42 +- .../sdm/service/SDMAdminServiceImplTest.java | 25 +- .../SDMAttachmentsServiceHandlerTest.java | 700 +++++++++++++++++- .../handler/SDMCustomServiceHandlerTest.java | 40 +- .../handler/SDMServiceGenericHandlerTest.java | 374 ++++++++++ 11 files changed, 1293 insertions(+), 47 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java index b5915cc0..e2e4ff24 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java +++ b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java @@ -66,6 +66,18 @@ private SDMConstants() { public static final String ATTACHMENT_MAXCOUNT_ERROR_MSG = "SDM.Attachments.maxCountError"; public static final String MAX_COUNT_ERROR_MESSAGE = "Cannot upload more than %s attachments as set up by the application"; + + // Localized error message keys + public static final String VERSIONED_REPO_ERROR_MSG = "SDM.Repository.versionedRepoError"; + public static final String USER_NOT_AUTHORISED_ERROR_MSG = + "SDM.Authorization.userNotAuthorizedError"; + public static final String USER_NOT_AUTHORISED_ERROR_LINK_MSG = + "SDM.Authorization.userNotAuthorizedLinkError"; + public static final String FAILED_TO_EDIT_LINK_MSG = "SDM.Link.failedToEditLinkError"; + public static final String REPOSITORY_ERROR_MSG = "SDM.Repository.repositoryError"; + public static final String FILE_NOT_FOUND_ERROR_MSG = "SDM.File.fileNotFoundError"; + public static final String MIMETYPE_INVALID_ERROR_MSG = "SDM.File.mimetypeInvalidError"; + public static final String FAILED_TO_FETCH_FACET_MSG = "SDM.Facet.failedToFetchFacetError"; public static final String NO_SDM_BINDING = "No SDM binding found"; public static final String DI_TOKEN_EXCHANGE_ERROR = "Error fetching DI token with JWT bearer"; public static final String DI_TOKEN_EXCHANGE_PARAMS = @@ -86,6 +98,39 @@ private SDMConstants() { public static final String FAILED_TO_FETCH_FACET = "Invalid facet format, unable to extract required information."; + // Error messages for ServiceException + public static final String FAILED_TO_EDIT_LINK = "Failed to edit link"; + public static final String ERROR_IN_SETTING_TIMEOUT = "Error in setting timeout"; + public static final String SDM_CREDENTIALS_MISSING_OR_INVALID = + "SDM credentials are missing or invalid."; + public static final String FAILED_TO_RETRIEVE_SDM_CREDENTIALS = + "Failed to retrieve SDM credentials."; + public static final String FAILED_TO_CREATE_HTTP_CLIENT = "Failed to create HTTP client."; + public static final String ERROR_WHILE_CREATING_HTTP_CLIENT = "Error while creating HTTP client."; + public static final String FAILED_TO_SET_REPOSITORY_DETAILS = "Failed to set repository details."; + public static final String FAILED_TO_SERIALIZE_REPOSITORY_OBJECT_TO_JSON = + "Failed to serialize repository object to JSON."; + public static final String FAILED_TO_CREATE_STRING_ENTITY = "Failed to create StringEntity."; + public static final String CLIENT_CREDENTIALS_MISSING_OR_INVALID = + "Client credentials are missing or invalid."; + public static final String FAILED_TO_CREATE_CLIENT_CREDENTIALS = + "Failed to create client credentials."; + public static final String FAILED_TO_REPLACE_SUBDOMAIN_IN_BASE_TOKEN_URL = + "Failed to replace subdomain in base token URL."; + public static final String ERROR_WHILE_FETCHING_REPOSITORY_ID = + "Error while fetching repository ID."; + public static final String UNEXPECTED_ERROR_WHILE_FETCHING_REPOSITORY_ID = + "Unexpected error while fetching repository ID."; + public static final String FAILED_TO_OFFBOARD_REPOSITORY = "Failed to offboard repository."; + public static final String ERROR_WHILE_OFFBOARDING_REPOSITORY = + "Error while offboarding repository."; + public static final String UNEXPECTED_ERROR_WHILE_OFFBOARDING_REPOSITORY = + "Unexpected error while offboarding repository."; + public static final String FAILED_TO_PARSE_REPOSITORY_RESPONSE = + "Failed to parse repository response"; + public static final String ERROR_IN_SETTING_TIMEOUT_MESSAGE = "Error in setting timeout"; + public static final String FAILED_TO_CREATE_FOLDER = "Failed to create folder"; + public static String nameConstraintMessage( List fileNameWithRestrictedCharacters, String operation) { // Create the base message diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java b/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java index a4fa8def..af9cf95b 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/DocumentUploadService.java @@ -83,7 +83,7 @@ private void executeHttpPost( try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(uploadFile)) { formResponse(cmisDocument, finalResponse, response); } catch (IOException e) { - throw new ServiceException("Error in setting timeout", e); + throw new ServiceException(SDMConstants.ERROR_IN_SETTING_TIMEOUT, e); } } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java index 3387149b..b2ee530c 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMAdminServiceImpl.java @@ -47,11 +47,11 @@ public String onboardRepository(Repository repository) sdmCredentials = tokenHandler.getSDMCredentials(); if (sdmCredentials == null || sdmCredentials.getUrl() == null) { logger.error("SDM credentials are missing or invalid."); - throw new ServiceException("SDM credentials are missing or invalid."); + throw new ServiceException(SDMConstants.SDM_CREDENTIALS_MISSING_OR_INVALID); } } catch (Exception e) { logger.error("Failed to retrieve SDM credentials: " + e.getMessage()); - throw new ServiceException("Failed to retrieve SDM credentials.", e); + throw new ServiceException(SDMConstants.FAILED_TO_RETRIEVE_SDM_CREDENTIALS, e); } HttpClient httpClient = null; @@ -61,11 +61,11 @@ public String onboardRepository(Repository repository) null, null, repository.getSubdomain(), "TECHNICAL_CREDENTIALS_FLOW"); if (httpClient == null) { logger.error("Failed to create HTTP client."); - throw new ServiceException("Failed to create HTTP client."); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_HTTP_CLIENT); } } catch (Exception e) { logger.error("Error while creating HTTP client: " + e.getMessage()); - throw new ServiceException("Error while creating HTTP client.", e); + throw new ServiceException(SDMConstants.ERROR_WHILE_CREATING_HTTP_CLIENT, e); } String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES; @@ -78,7 +78,7 @@ public String onboardRepository(Repository repository) onboardRepository.setRepository(repository); } catch (Exception e) { logger.error("Failed to set repository details: " + e.getMessage()); - throw new ServiceException("Failed to set repository details.", e); + throw new ServiceException(SDMConstants.FAILED_TO_SET_REPOSITORY_DETAILS, e); } String json; @@ -86,7 +86,7 @@ public String onboardRepository(Repository repository) json = objectMapper.writeValueAsString(onboardRepository); } catch (JsonProcessingException e) { logger.error("Failed to serialize repository object to JSON: " + e.getMessage()); - throw new ServiceException("Failed to serialize repository object to JSON.", e); + throw new ServiceException(SDMConstants.FAILED_TO_SERIALIZE_REPOSITORY_OBJECT_TO_JSON, e); } StringEntity entity; @@ -94,7 +94,7 @@ public String onboardRepository(Repository repository) entity = new StringEntity(json); } catch (UnsupportedEncodingException e) { logger.error("Failed to create StringEntity: " + e.getMessage()); - throw new ServiceException("Failed to create StringEntity.", e); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_STRING_ENTITY, e); } onboardingReq.setEntity(entity); @@ -146,11 +146,11 @@ public String offboardRepository(String subdomain) { || sdmCredentials.getUrl() == null || sdmCredentials.getBaseTokenUrl() == null) { logger.error("SDM credentials are missing or invalid."); - throw new ServiceException("SDM credentials are missing or invalid."); + throw new ServiceException(SDMConstants.SDM_CREDENTIALS_MISSING_OR_INVALID); } } catch (Exception e) { logger.error("Failed to retrieve SDM credentials: " + e.getMessage()); - throw new ServiceException("Failed to retrieve SDM credentials.", e); + throw new ServiceException(SDMConstants.FAILED_TO_RETRIEVE_SDM_CREDENTIALS, e); } ClientCredentials clientCredentials; @@ -159,11 +159,11 @@ public String offboardRepository(String subdomain) { new ClientCredentials(sdmCredentials.getClientId(), sdmCredentials.getClientSecret()); if (clientCredentials.getId() == null || clientCredentials.getSecret() == null) { logger.error("Client credentials are missing or invalid."); - throw new ServiceException("Client credentials are missing or invalid."); + throw new ServiceException(SDMConstants.CLIENT_CREDENTIALS_MISSING_OR_INVALID); } } catch (Exception e) { logger.error("Failed to create client credentials: " + e.getMessage()); - throw new ServiceException("Failed to create client credentials.", e); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_CLIENT_CREDENTIALS, e); } String baseTokenUrl = sdmCredentials.getBaseTokenUrl(); @@ -174,7 +174,7 @@ public String offboardRepository(String subdomain) { baseTokenUrl = baseTokenUrl.replace(providersubdomain, subdomain); } catch (Exception e) { logger.error("Failed to replace subdomain in base token URL: " + e.getMessage()); - throw new ServiceException("Failed to replace subdomain in base token URL.", e); + throw new ServiceException(SDMConstants.FAILED_TO_REPLACE_SUBDOMAIN_IN_BASE_TOKEN_URL, e); } } @@ -196,11 +196,11 @@ public String offboardRepository(String subdomain) { httpClient = factory.createHttpClient(destination); if (httpClient == null) { logger.error("Failed to create HTTP client."); - throw new ServiceException("Failed to create HTTP client."); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_HTTP_CLIENT); } } catch (Exception e) { logger.error("Error while creating HTTP client: " + e.getMessage()); - throw new ServiceException("Error while creating HTTP client.", e); + throw new ServiceException(SDMConstants.ERROR_WHILE_CREATING_HTTP_CLIENT, e); } String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/"; @@ -215,10 +215,10 @@ public String offboardRepository(String subdomain) { } } catch (IOException e) { logger.error("Error while fetching repository ID: " + e.getMessage()); - throw new ServiceException("Error while fetching repository ID.", e); + throw new ServiceException(SDMConstants.ERROR_WHILE_FETCHING_REPOSITORY_ID, e); } catch (Exception e) { logger.error("Unexpected error while fetching repository ID: " + e.getMessage()); - throw new ServiceException("Unexpected error while fetching repository ID.", e); + throw new ServiceException(SDMConstants.UNEXPECTED_ERROR_WHILE_FETCHING_REPOSITORY_ID, e); } sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/" + repoId; @@ -234,17 +234,17 @@ public String offboardRepository(String subdomain) { return "Repository with ID " + SDMConstants.REPOSITORY_ID + " not found."; } logger.error("Failed to offboard repository : " + responseString); - throw new ServiceException("Failed to offboard repository.", responseString); + throw new ServiceException(SDMConstants.FAILED_TO_OFFBOARD_REPOSITORY, responseString); } logger.info("Repository " + repoId + " Offboarded"); return "Repository " + repoId + " Offboarded"; } catch (IOException e) { logger.error("Error while offboarding repository: " + e.getMessage()); - throw new ServiceException("Error while offboarding repository.", e); + throw new ServiceException(SDMConstants.ERROR_WHILE_OFFBOARDING_REPOSITORY, e); } catch (Exception e) { logger.error("Unexpected error while offboarding repository: " + e.getMessage()); - throw new ServiceException("Unexpected error while offboarding repository.", e); + throw new ServiceException(SDMConstants.UNEXPECTED_ERROR_WHILE_OFFBOARDING_REPOSITORY, e); } } @@ -268,7 +268,7 @@ private String getRepositoryId(String jsonString) { } } } catch (Exception e) { - throw new ServiceException("Failed to parse repository response", e); + throw new ServiceException(SDMConstants.FAILED_TO_PARSE_REPOSITORY_RESPONSE, e); } return null; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java index ac02336a..6f729e6b 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java @@ -127,7 +127,7 @@ private void executeHttpPost( try (var response = (CloseableHttpResponse) httpClient.execute(uploadFile)) { formResponse(cmisDocument, finalResponse, response); } catch (IOException e) { - throw new ServiceException("Error in setting timeout", e.getMessage()); + throw new ServiceException(SDMConstants.ERROR_IN_SETTING_TIMEOUT_MESSAGE, e.getMessage()); } } @@ -356,7 +356,14 @@ public void readDocument( if (responseCode != 200) { response.close(); if (responseCode == 404) { - throw new ServiceException(SDMConstants.FILE_NOT_FOUND_ERROR); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.File.fileNotFoundError", null, context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.FILE_NOT_FOUND_ERROR_MSG)) + throw new ServiceException(SDMConstants.FILE_NOT_FOUND_ERROR); + throw new ServiceException(errorMessage); } throw new ServiceException("Unexpected code"); } @@ -469,10 +476,10 @@ public String createFolder( else if (responseCode == 403) { throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR); } else { - throw new ServiceException("Failed to create folder. " + responseBody); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_FOLDER + ". " + responseBody); } } catch (IOException e) { - throw new ServiceException("Failed to create folder " + e.getMessage()); + throw new ServiceException(SDMConstants.FAILED_TO_CREATE_FOLDER + " " + e.getMessage()); } } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java index c5c95242..30599f29 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java @@ -1,5 +1,7 @@ package com.sap.cds.sdm.service.handler; +import static com.sap.cds.sdm.constants.SDMConstants.ATTACHMENT_MAXCOUNT_ERROR_MSG; + import com.sap.cds.Result; import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.MediaData; import com.sap.cds.feature.attachments.service.AttachmentService; @@ -145,7 +147,16 @@ private void validateRepository(AttachmentCreateEventContext eventContext) RepoValue repoValue = sdmService.checkRepositoryType(repositoryId, eventContext.getUserInfo().getTenant()); if (repoValue.getVersionEnabled()) { - throw new ServiceException(SDMConstants.VERSIONED_REPO_ERROR); + String errorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Repository.versionedRepoError", + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.VERSIONED_REPO_ERROR_MSG)) + throw new ServiceException(SDMConstants.VERSIONED_REPO_ERROR); + throw new ServiceException(errorMessage); } String len = eventContext.getParameterInfo().getHeaders().get("content-length"); long contentLen = !StringUtils.isEmpty(len) ? Long.parseLong(len) : -1; @@ -215,7 +226,16 @@ private void checkAttachmentConstraints( if (maxCount > 0 && rowCount >= maxCount) { String message = maxCountArr[1]; if (message != null && !"null".equalsIgnoreCase(message)) { - throw new ServiceException(message); + String errorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Attachments.maxCountError", + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(ATTACHMENT_MAXCOUNT_ERROR_MSG)) + throw new ServiceException(String.format(SDMConstants.MAX_COUNT_ERROR_MESSAGE, maxCount)); + throw new ServiceException(errorMessage); } throw new ServiceException(String.format(SDMConstants.MAX_COUNT_ERROR_MESSAGE, maxCount)); } @@ -300,9 +320,27 @@ private void handleCreateDocumentResult( case "fail": throw new ServiceException(createResult.get("message").toString()); case "unauthorized": - throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR); + String errorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Authorization.userNotAuthorizedError", + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.USER_NOT_AUTHORISED_ERROR_MSG)) + throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR); + throw new ServiceException(errorMessage); case "blocked": - throw new ServiceException(SDMConstants.MIMETYPE_INVALID_ERROR); + String errorMessage2 = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + "SDM.File.mimetypeInvalidError", + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage2.equalsIgnoreCase(SDMConstants.MIMETYPE_INVALID_ERROR_MSG)) + throw new ServiceException(SDMConstants.MIMETYPE_INVALID_ERROR); + throw new ServiceException(errorMessage2); default: cmisDocument.setObjectId(createResult.get("objectId").toString()); cmisDocument.setMimeType(createResult.get("mimeType").toString()); diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java index b87e0bb3..0a1064b3 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMCustomServiceHandler.java @@ -50,7 +50,16 @@ public SDMCustomServiceHandler( public void copyAttachments(AttachmentCopyEventContext context) throws IOException { String[] splitFacet = context.getFacet().split("\\."); if (splitFacet.length < 3) { - throw new ServiceException(SDMConstants.FAILED_TO_FETCH_FACET); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Facet.failedToFetchFacetError", + null, + context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.FAILED_TO_FETCH_FACET_MSG)) + throw new ServiceException(SDMConstants.FAILED_TO_FETCH_FACET); + throw new ServiceException(errorMessage); } String facet = splitFacet[2]; String upID = context.getUpId(); diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java index cdd7ca2c..696b0115 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java @@ -118,7 +118,16 @@ private void validateRepository(EventContext eventContext) throws ServiceExcepti RepoValue repoValue = sdmService.checkRepositoryType(repositoryId, eventContext.getUserInfo().getTenant()); if (repoValue.getVersionEnabled()) { - throw new ServiceException(SDMConstants.VERSIONED_REPO_ERROR); + String errorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Repository.versionedRepoError", + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.VERSIONED_REPO_ERROR_MSG)) + throw new ServiceException(SDMConstants.VERSIONED_REPO_ERROR); + throw new ServiceException(errorMessage); } } @@ -196,9 +205,25 @@ private void editLink(EventContext context) throws IOException { logger.info("Successfully edited link"); } else { if (status.equals("unauthorized")) { - throw new ServiceException(SDMConstants.SDM_MISSING_ROLES_EXCEPTION_MSG); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Authorization.userNotAuthorizedError", + null, + context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.USER_NOT_AUTHORISED_ERROR_MSG)) + throw new ServiceException(SDMConstants.SDM_MISSING_ROLES_EXCEPTION_MSG); + throw new ServiceException(errorMessage); } else { - throw new ServiceException("Failed to edit link"); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Link.failedToEditLinkError", null, context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.FAILED_TO_EDIT_LINK_MSG)) + throw new ServiceException(SDMConstants.FAILED_TO_EDIT_LINK); + throw new ServiceException(errorMessage); } } context.setCompleted(); @@ -279,7 +304,16 @@ private void handleCreateLinkResult( case "fail": throw new ServiceException(createResult.get("message").toString()); case "unauthorized": - throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR_LINK); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Authorization.userNotAuthorizedLinkError", + null, + context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.USER_NOT_AUTHORISED_ERROR_LINK_MSG)) + throw new ServiceException(SDMConstants.USER_NOT_AUTHORISED_ERROR_LINK); + throw new ServiceException(errorMessage); default: cmisDocument.setObjectId(createResult.get("objectId").toString()); cmisDocument.setParentId(upID); diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java index e289f470..cd0a959c 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMAdminServiceImplTest.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.sap.cds.sdm.constants.SDMConstants; import com.sap.cds.sdm.handler.TokenHandler; import com.sap.cds.sdm.model.Repository; import com.sap.cds.sdm.model.RepositoryParams; @@ -211,18 +212,20 @@ public void testOffboardRepository_success() throws Exception { HttpEntity mockDeleteEntity = mock(HttpEntity.class); String json = - """ + String.format( + """ { "repoAndConnectionInfos": [ { "repository": { - "externalId": "repoid", + "externalId": "%s", "id": "123" } } ] } -"""; +""", + SDMConstants.REPOSITORY_ID); InputStream getInputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); InputStream deleteInputStream = @@ -297,7 +300,7 @@ public void testOffboardRepository_subdomainnull() throws Exception { String result = sdmAdminService.offboardRepository(subdomain); assertNotNull(result); - assertEquals("Repository with ID repoid not found.", result); + assertEquals("Repository with ID " + SDMConstants.REPOSITORY_ID + " not found.", result); verify(httpClient, atLeastOnce()).execute(any()); } @@ -351,7 +354,7 @@ public void testOffboardRepository_subdomainempty() throws Exception { String result = sdmAdminService.offboardRepository(subdomain); assertNotNull(result); - assertEquals("Repository with ID repoid not found.", result); + assertEquals("Repository with ID " + SDMConstants.REPOSITORY_ID + " not found.", result); verify(httpClient, atLeastOnce()).execute(any()); } @@ -393,18 +396,20 @@ public void testOffboardRepository_deleteRequestFails_throwsException() throws E HttpEntity mockGetEntity = mock(HttpEntity.class); String json = - """ + String.format( + """ { "repoAndConnectionInfos": [ { "repository": { - "externalId": "repoid", + "externalId": "%s", "id": "123" } } ] } - """; + """, + SDMConstants.REPOSITORY_ID); InputStream getInputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); @@ -421,12 +426,12 @@ public void testOffboardRepository_deleteRequestFails_throwsException() throws E Exception exception = assertThrows( - RuntimeException.class, + ServiceException.class, () -> { sdmAdminService.offboardRepository(subdomain); }); - assertTrue(exception.getMessage().contains("Error while offboarding repository.")); + assertTrue(exception.getMessage().contains("Error while offboarding repository")); } @Test diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java index 00a67ddc..ce5e203c 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java @@ -1,5 +1,6 @@ package unit.com.sap.cds.sdm.service.handler; +import static com.sap.cds.sdm.constants.SDMConstants.ATTACHMENT_MAXCOUNT_ERROR_MSG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -44,6 +45,7 @@ import com.sap.cds.services.persistence.PersistenceService; import com.sap.cds.services.request.ParameterInfo; import com.sap.cds.services.request.UserInfo; +import com.sap.cds.services.runtime.CdsRuntime; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -67,6 +69,8 @@ public class SDMAttachmentsServiceHandlerTest { @Mock private MediaData mockMediaData; @Mock private CdsEntity mockDraftEntity; + @Mock private CdsRuntime cdsRuntime; + @Mock private AttachmentRestoreEventContext restoreEventContext; private SDMService sdmService; private DocumentUploadService documentUploadService; @@ -141,6 +145,9 @@ public void testCreateVersioned() throws IOException { when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.VERSIONED_REPO_ERROR_MSG); when(mockContext.getParameterInfo()).thenReturn(parameterInfo); when(parameterInfo.getHeaders()).thenReturn(headers); // Use assertThrows to expect a ServiceException and validate the message @@ -183,6 +190,73 @@ public void testCreateVersioned() throws IOException { assertEquals("Upload not supported for versioned repositories.", thrown.getMessage()); } + @Test + public void testCreateVersionedI18nMessage() throws IOException { + // Initialization of mocks and setup + Message mockMessage = mock(Message.class); + Messages mockMessages = mock(Messages.class); + MediaData mockMediaData = mock(MediaData.class); + CdsModel mockModel = mock(CdsModel.class); + try (MockedStatic sdmUtilsMockedStatic = mockStatic(SDMUtils.class); ) { + sdmUtilsMockedStatic + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("0__null"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(true); + when(sdmService.checkRepositoryType(anyString(), any())).thenReturn(repoValue); + when(mockContext.getMessages()).thenReturn(mockMessages); + when(mockMessages.error("Upload not supported for versioned repositories.")) + .thenReturn(mockMessage); + when(mockContext.getData()).thenReturn(mockMediaData); + when(mockContext.getModel()).thenReturn(mockModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn("Versioned repo error in German"); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(headers); + // Use assertThrows to expect a ServiceException and validate the message + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + + // Verify the exception message + assertEquals("Versioned repo error in German", thrown.getMessage()); + } + ParameterInfo mockParameterInfo = mock(ParameterInfo.class); + Map mockHeaders = new HashMap<>(); + mockHeaders.put("content-length", "12345"); + + when(mockContext.getParameterInfo()).thenReturn(mockParameterInfo); // Mock getParameterInfo + when(mockParameterInfo.getHeaders()).thenReturn(mockHeaders); // Mock getHeaders + RepoValue repoValue = new RepoValue(); + repoValue.setVersionEnabled(true); + when(sdmService.checkRepositoryType(anyString(), any())).thenReturn(repoValue); + when(mockContext.getMessages()).thenReturn(mockMessages); + when(mockMessages.error("Versioned repo error in German")).thenReturn(mockMessage); + when(mockContext.getData()).thenReturn(mockMediaData); + when(mockContext.getModel()).thenReturn(mockModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + // Use assertThrows to expect a ServiceException and validate the message + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + + // Verify the exception message + assertEquals("Versioned repo error in German", thrown.getMessage()); + } + @Test public void testCreateVirusEnabled() throws IOException { // Initialization of mocks and setup @@ -547,7 +621,7 @@ public void testCreateNonVersionedDIOther() throws IOException { } @Test - public void testCreateNonVersionedDIUnauthorized() throws IOException { + public void testCreateNonVersionedDIUnauthorizedI18n() throws IOException { // Initialization of mocks and setup Map mockAttachmentIds = new HashMap<>(); mockAttachmentIds.put("up__ID", "upid"); @@ -584,6 +658,9 @@ public void testCreateNonVersionedDIUnauthorized() throws IOException { when(mockContext.getData()).thenReturn(mockMediaData); when(mockContext.getAttachmentEntity()).thenReturn(mockDraftEntity); when(mockDraftEntity.getQualifiedName()).thenReturn("some.qualified.name"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.USER_NOT_AUTHORISED_ERROR); when(mockContext.getParameterInfo()).thenReturn(parameterInfo); when(parameterInfo.getHeaders()).thenReturn(headers); @@ -615,6 +692,78 @@ public void testCreateNonVersionedDIUnauthorized() throws IOException { } } + @Test + public void testCreateNonVersionedDIUnauthorized() throws IOException { + // Initialization of mocks and setup + Map mockAttachmentIds = new HashMap<>(); + mockAttachmentIds.put("up__ID", "upid"); + mockAttachmentIds.put("ID", "id"); + mockAttachmentIds.put("repositoryId", "repo1"); + + MediaData mockMediaData = mock(MediaData.class); + Result mockResult = mock(Result.class); + List nonEmptyRowList = List.of(mock(Row.class)); + CdsEntity mockDraftEntity = mock(CdsEntity.class); + CdsModel mockModel = mock(CdsModel.class); + CdsElement mockAssociationElement = mock(CdsElement.class); + CdsAssociationType mockAssociationType = mock(CdsAssociationType.class); + CqnElementRef mockCqnElementRef = mock(CqnElementRef.class); + + // Set up the JSON response for the "unauthorized" case + JSONObject mockCreateResult = new JSONObject(); + mockCreateResult.put("status", "unauthorized"); + + // Mock method calls + when(mockContext.getAttachmentIds()).thenReturn(mockAttachmentIds); + when(mockContext.getModel()).thenReturn(mockModel); + when(mockModel.findEntity(anyString())).thenReturn(Optional.of(mockDraftEntity)); + when(mockDraftEntity.findAssociation("up_")).thenReturn(Optional.of(mockAssociationElement)); + when(mockAssociationElement.getType()).thenReturn(mockAssociationType); + when(mockAssociationType.refs()).thenReturn(Stream.of(mockCqnElementRef)); + when(mockCqnElementRef.path()).thenReturn("ID"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t1"); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + when(mockContext.getData()).thenReturn(mockMediaData); + when(mockContext.getAttachmentEntity()).thenReturn(mockDraftEntity); + when(mockDraftEntity.getQualifiedName()).thenReturn("some.qualified.name"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn("Unauthorised error german"); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(headers); + + // Mock the behavior of createDocument and other dependencies + when(documentUploadService.createDocument(any(), any(), anyBoolean())) + .thenReturn(mockCreateResult); + doReturn(false).when(handlerSpy).duplicateCheck(any(), any(), any()); + when(dbQuery.getAttachmentsForUPID(any(), any(), anyString(), anyString())) + .thenReturn(mockResult); + when(dbQuery.getAttachmentsForUPIDAndRepository(any(), any(), anyString(), anyString())) + .thenReturn(mockResult); + when(mockResult.list()).thenReturn(nonEmptyRowList); + when(sdmService.getFolderId(any(), any(), any(), anyBoolean())).thenReturn("folderid"); + when(tokenHandler.getSDMCredentials()).thenReturn(mock(SDMCredentials.class)); + + try (MockedStatic sdmUtilsMockedStatic = mockStatic(SDMUtils.class)) { + sdmUtilsMockedStatic + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("10__null"); + + // Assert that a ServiceException is thrown and verify its message + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals("Unauthorised error german", thrown.getMessage()); + } + } + @Test public void testCreateNonVersionedDIBlocked() throws IOException { // Initialization of mocks and setup @@ -654,6 +803,9 @@ public void testCreateNonVersionedDIBlocked() throws IOException { when(mockContext.getData()).thenReturn(mockMediaData); when(mockContext.getAttachmentEntity()).thenReturn(mockDraftEntity); when(mockDraftEntity.getQualifiedName()).thenReturn("some.qualified.name"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.MIMETYPE_INVALID_ERROR); when(mockContext.getParameterInfo()).thenReturn(parameterInfo); when(parameterInfo.getHeaders()).thenReturn(headers); @@ -1228,7 +1380,7 @@ public void testRestoreAttachment() { } @Test - public void testMaxCountErrorMessage() throws IOException { + public void testMaxCountErrorMessagei18n() throws IOException { // Initialization of mocks and setup Map mockAttachmentIds = new HashMap<>(); mockAttachmentIds.put("up__ID", "upid"); @@ -1265,6 +1417,9 @@ public void testMaxCountErrorMessage() throws IOException { when(mockResult.rowCount()).thenReturn(3L); when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn("Only 1 Attachment is allowed"); when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); when(mockContext.getData()).thenReturn(mockMediaData); doReturn(false).when(handlerSpy).duplicateCheck(any(), any(), any()); @@ -1296,6 +1451,80 @@ public void testMaxCountErrorMessage() throws IOException { } } + @Test + public void testMaxCountErrorMessage() throws IOException { + // Initialization of mocks and setup + Map mockAttachmentIds = new HashMap<>(); + mockAttachmentIds.put("up__ID", "upid"); + mockAttachmentIds.put("ID", "id"); + mockAttachmentIds.put("repositoryId", "repo1"); + MediaData mockMediaData = mock(MediaData.class); + Result mockResult = mock(Result.class); + Row mockRow = mock(Row.class); + List nonEmptyRowList = List.of(mockRow); + CdsEntity mockEntity = mock(CdsEntity.class); + CdsElement mockAssociationElement = mock(CdsElement.class); + CdsAssociationType mockAssociationType = mock(CdsAssociationType.class); + CqnElementRef mockCqnElementRef = mock(CqnElementRef.class); + byte[] byteArray = "Example content".getBytes(); + InputStream contentStream = new ByteArrayInputStream(byteArray); + JSONObject mockCreateResult = new JSONObject(); + + when(mockMediaData.getFileName()).thenReturn("sample.pdf"); + when(mockMediaData.getContent()).thenReturn(contentStream); + when(mockContext.getModel()).thenReturn(cdsModel); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.of(mockEntity)); + when(mockEntity.findAssociation("up_")).thenReturn(Optional.of(mockAssociationElement)); + when(mockAssociationElement.getType()).thenReturn(mockAssociationType); + when(mockAssociationType.refs()).thenReturn(Stream.of(mockCqnElementRef)); + when(mockCqnElementRef.path()).thenReturn("ID"); + when(mockContext.getAttachmentIds()).thenReturn(mockAttachmentIds); + when(eventContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + when(mockResult.list()).thenReturn(nonEmptyRowList); + when(mockResult.rowCount()).thenReturn(3L); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(ATTACHMENT_MAXCOUNT_ERROR_MSG); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(mockContext.getData()).thenReturn(mockMediaData); + doReturn(false).when(handlerSpy).duplicateCheck(any(), any(), any()); + when(sdmService.getFolderId(any(), any(), any(), anyBoolean())).thenReturn("folderid"); + when(sdmService.createDocument(any(), any(), any())).thenReturn(mockCreateResult); + + try (MockedStatic sdmUtilsMockedStatic = mockStatic(SDMUtils.class); ) { + sdmUtilsMockedStatic + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("1__Only 1 Attachment is allowed"); + when(dbQuery.getAttachmentsForUPID(any(), any(), anyString(), anyString())) + .thenReturn(mockResult); + when(dbQuery.getAttachmentsForUPIDAndRepository(any(), any(), anyString(), anyString())) + .thenReturn(mockResult); + SDMCredentials mockSdmCredentials = Mockito.mock(SDMCredentials.class); + when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(headers); + // Use assertThrows to expect a ServiceException and validate the message + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + + // Verify the exception message + assertEquals( + "Cannot upload more than 1 attachments as set up by the application", + thrown.getMessage()); + } + } + @Test public void testMaxCountError() throws IOException { // Initialization of mocks and setup @@ -1392,4 +1621,471 @@ public void throwAttachmetDraftEntityException() throws IOException { handlerSpy.createAttachment(mockContext); }); } + + @Test + public void testCreateAttachment_WithNullContentLength() throws IOException { + // Test scenario where content-length header is null + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + Map emptyHeaders = new HashMap<>(); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(emptyHeaders); + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.empty()); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals(SDMConstants.DRAFT_NOT_FOUND, thrown.getMessage()); + } + + @Test + public void testCreateAttachment_WithEmptyContentLength() throws IOException { + // Test scenario where content-length header is empty string + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + Map headersWithEmpty = new HashMap<>(); + headersWithEmpty.put("content-length", ""); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(headersWithEmpty); + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.empty()); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals(SDMConstants.DRAFT_NOT_FOUND, thrown.getMessage()); + } + + @Test + public void testCreateAttachment_VirusScanEnabledExceedsLimit() throws IOException { + // Test scenario where virus scan is enabled and file size exceeds 400MB limit + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(true); + repoValue.setVersionEnabled(false); + repoValue.setDisableVirusScannerForLargeFile(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + Map largeFileHeaders = new HashMap<>(); + largeFileHeaders.put("content-length", String.valueOf(500 * 1024 * 1024L)); // 500MB + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(largeFileHeaders); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB, thrown.getMessage()); + } + + @Test + public void testCreateAttachment_VirusScanEnabledWithinLimit() throws IOException { + // Test scenario where virus scan is enabled but file size is within 400MB limit + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(true); + repoValue.setVersionEnabled(false); + repoValue.setDisableVirusScannerForLargeFile(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + Map normalFileHeaders = new HashMap<>(); + normalFileHeaders.put("content-length", String.valueOf(100 * 1024 * 1024L)); // 100MB + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(normalFileHeaders); + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.empty()); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals(SDMConstants.DRAFT_NOT_FOUND, thrown.getMessage()); + } + + @Test + public void testCreateAttachment_VirusScanDisabledForLargeFile() throws IOException { + // Test scenario where virus scan is enabled but disabled for large files + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(true); + repoValue.setVersionEnabled(false); + repoValue.setDisableVirusScannerForLargeFile(true); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + Map largeFileHeaders = new HashMap<>(); + largeFileHeaders.put("content-length", String.valueOf(500 * 1024 * 1024L)); // 500MB + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(largeFileHeaders); + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.empty()); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + assertEquals(SDMConstants.DRAFT_NOT_FOUND, thrown.getMessage()); + } + + @Test + public void testMarkAttachmentAsDeleted_WithNullObjectId() throws IOException { + // Test scenario where objectId is null + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + when(deleteContext.getContentId()).thenReturn("null:folderId:entity:subdomain"); + + handlerSpy.markAttachmentAsDeleted(deleteContext); + + verify(deleteContext).setCompleted(); + verify(sdmService, never()).deleteDocument(anyString(), anyString(), anyString()); + } + + @Test + public void testMarkAttachmentAsDeleted_WithInsufficientContextValues() throws IOException { + // Test scenario where contentId has insufficient parts (less than 3) + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + when(deleteContext.getContentId()).thenReturn("only-one-part"); + + // This should throw an ArrayIndexOutOfBoundsException due to the current implementation + assertThrows( + ArrayIndexOutOfBoundsException.class, + () -> { + handlerSpy.markAttachmentAsDeleted(deleteContext); + }); + } + + @Test + public void testMarkAttachmentAsDeleted_WithEmptyString() throws IOException { + // Test scenario where contentId is empty (contextValues.length = 0) + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + when(deleteContext.getContentId()).thenReturn(""); + + // Empty string split results in array of length 1 with empty string, so this will also fail + assertThrows( + ArrayIndexOutOfBoundsException.class, + () -> { + handlerSpy.markAttachmentAsDeleted(deleteContext); + }); + } + + @Test + public void testMarkAttachmentAsDeleted_DeleteFolderWhenNoAttachments() throws IOException { + // Test scenario where no attachments exist for folder, so folder should be deleted + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + DeletionUserInfo deletionUserInfo = mock(DeletionUserInfo.class); + + when(deleteContext.getContentId()).thenReturn("objectId:folderId:entity:subdomain"); + when(deleteContext.getDeletionUserInfo()).thenReturn(deletionUserInfo); + when(deletionUserInfo.getName()).thenReturn("testUser"); + + // Mock empty list for no attachments in folder + when(dbQuery.getAttachmentsForFolder(anyString(), any(), anyString(), any())) + .thenReturn(Collections.emptyList()); + + handlerSpy.markAttachmentAsDeleted(deleteContext); + + verify(sdmService).deleteDocument("deleteTree", "folderId", "testUser"); + verify(deleteContext).setCompleted(); + } + + @Test + public void testMarkAttachmentAsDeleted_DeleteObjectWhenNotPresent() throws IOException { + // Test scenario where objectId is not present in attachments list + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + DeletionUserInfo deletionUserInfo = mock(DeletionUserInfo.class); + + when(deleteContext.getContentId()).thenReturn("objectId:folderId:entity:subdomain"); + when(deleteContext.getDeletionUserInfo()).thenReturn(deletionUserInfo); + when(deletionUserInfo.getName()).thenReturn("testUser"); + + // Mock attachments list without the target objectId + CmisDocument otherDoc = new CmisDocument(); + otherDoc.setObjectId("otherObjectId"); + List attachments = Arrays.asList(otherDoc); + when(dbQuery.getAttachmentsForFolder(anyString(), any(), anyString(), any())) + .thenReturn(attachments); + + handlerSpy.markAttachmentAsDeleted(deleteContext); + + verify(sdmService).deleteDocument("delete", "objectId", "testUser"); + verify(deleteContext).setCompleted(); + } + + @Test + public void testMarkAttachmentAsDeleted_ObjectIdPresent() throws IOException { + // Test scenario where objectId is present in attachments list (should not delete) + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + DeletionUserInfo deletionUserInfo = mock(DeletionUserInfo.class); + + when(deleteContext.getContentId()).thenReturn("objectId:folderId:entity:subdomain"); + when(deleteContext.getDeletionUserInfo()).thenReturn(deletionUserInfo); + when(deletionUserInfo.getName()).thenReturn("testUser"); + + // Mock attachments list with the target objectId + CmisDocument targetDoc = new CmisDocument(); + targetDoc.setObjectId("objectId"); + List attachments = Arrays.asList(targetDoc); + when(dbQuery.getAttachmentsForFolder(anyString(), any(), anyString(), any())) + .thenReturn(attachments); + + handlerSpy.markAttachmentAsDeleted(deleteContext); + + verify(sdmService, never()).deleteDocument(anyString(), anyString(), anyString()); + verify(deleteContext).setCompleted(); + } + + @Test + public void testReadAttachment_ValidContentId() throws IOException { + // Test scenario for successful attachment reading + AttachmentReadEventContext readContext = mock(AttachmentReadEventContext.class); + when(readContext.getContentId()).thenReturn("objectId:folderId:entity"); + when(readContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("dummyToken"); + + SDMCredentials mockSdmCredentials = new SDMCredentials(); + when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); + + handlerSpy.readAttachment(readContext); + + verify(sdmService).readDocument(eq("objectId"), eq(mockSdmCredentials), eq(readContext)); + } + + @Test + public void testReadAttachment_InvalidContentId() throws IOException { + // Test scenario with insufficient contentId parts + AttachmentReadEventContext readContext = mock(AttachmentReadEventContext.class); + when(readContext.getContentId()).thenReturn("invalid"); + when(readContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("dummyToken"); + + SDMCredentials mockSdmCredentials = new SDMCredentials(); + when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); + + // This should work as readAttachment handles the parsing internally + handlerSpy.readAttachment(readContext); + + // Verify the method was called with the full contentId as objectId + verify(sdmService).readDocument(eq("invalid"), eq(mockSdmCredentials), eq(readContext)); + } + + @Test + public void testRestoreAttachment_CompletesSuccessfully() { + // Test scenario for restore attachment (should just complete) + AttachmentRestoreEventContext restoreContext = mock(AttachmentRestoreEventContext.class); + + handlerSpy.restoreAttachment(restoreContext); + + verify(restoreContext).setCompleted(); + } + + @Test + public void testDuplicateCheck_WithEmptyResult() { + // Test scenario with no existing attachments + Result mockResult = mock(Result.class); + when(mockResult.listOf(Map.class)).thenReturn(Collections.emptyList()); + + boolean isDuplicate = handlerSpy.duplicateCheck("test.pdf", "new-id", mockResult); + + assertFalse(isDuplicate); + } + + @Test + public void testCreateAttachment_WithInvalidParameterInfo() throws IOException { + // Test scenario where ParameterInfo is null + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + when(mockContext.getParameterInfo()).thenReturn(null); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + + // This should throw a NullPointerException or be handled gracefully + assertThrows( + Exception.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + } + + @Test + public void testCreateAttachment_WithNullHeaders() throws IOException { + // Test scenario where headers are null + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(null); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + + // This should throw a NullPointerException or be handled gracefully + assertThrows( + Exception.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + } + + @Test + public void testReadAttachment_ExceptionInService() throws IOException { + // Test scenario where sdmService.readDocument throws an exception + AttachmentReadEventContext readContext = mock(AttachmentReadEventContext.class); + when(readContext.getContentId()).thenReturn("objectId:folderId"); + when(readContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("dummyToken"); + + SDMCredentials mockSdmCredentials = new SDMCredentials(); + when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); + + // Mock service to throw exception + doThrow(new RuntimeException("Service error")) + .when(sdmService) + .readDocument( + anyString(), any(SDMCredentials.class), any(AttachmentReadEventContext.class)); + + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.readAttachment(readContext); + }); + + assertEquals("Service error", thrown.getMessage()); + } + + @Test + public void testReadAttachment_WithSinglePartContentId() throws IOException { + // Test scenario with single part content ID (no colon separator) + AttachmentReadEventContext readContext = mock(AttachmentReadEventContext.class); + when(readContext.getContentId()).thenReturn("singleObjectId"); + when(readContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("dummyToken"); + + SDMCredentials mockSdmCredentials = new SDMCredentials(); + when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); + + handlerSpy.readAttachment(readContext); + + // Should call readDocument with the full contentId as objectId + verify(sdmService).readDocument(eq("singleObjectId"), eq(mockSdmCredentials), eq(readContext)); + verify(readContext).setCompleted(); + } + + @Test + public void testMarkAttachmentAsDeleted_MultipleObjectsInFolder() throws IOException { + // Test scenario where multiple attachments exist and target object is among them + AttachmentMarkAsDeletedEventContext deleteContext = + mock(AttachmentMarkAsDeletedEventContext.class); + DeletionUserInfo deletionUserInfo = mock(DeletionUserInfo.class); + + when(deleteContext.getContentId()).thenReturn("targetObjectId:folderId:entity:subdomain"); + when(deleteContext.getDeletionUserInfo()).thenReturn(deletionUserInfo); + when(deletionUserInfo.getName()).thenReturn("testUser"); + + // Mock attachments list with multiple objects including target + CmisDocument targetDoc = new CmisDocument(); + targetDoc.setObjectId("targetObjectId"); + CmisDocument otherDoc = new CmisDocument(); + otherDoc.setObjectId("otherObjectId"); + List attachments = Arrays.asList(targetDoc, otherDoc); + when(dbQuery.getAttachmentsForFolder(anyString(), any(), anyString(), any())) + .thenReturn(attachments); + + handlerSpy.markAttachmentAsDeleted(deleteContext); + + // Should not call delete on either document since target is present + verify(sdmService, never()).deleteDocument(anyString(), anyString(), anyString()); + verify(deleteContext).setCompleted(); + } + + @Test + public void testCreateAttachment_LargeFileVirusScanDisabled() throws IOException { + // Test large file with virus scan disabled should proceed + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("t123"); + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); // Virus scan disabled + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + // Set large file size (600MB) + Map largeFileHeaders = new HashMap<>(); + largeFileHeaders.put("content-length", String.valueOf(600 * 1024 * 1024L)); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(parameterInfo.getHeaders()).thenReturn(largeFileHeaders); + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); + when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); + when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); + when(cdsModel.findEntity(anyString())).thenReturn(Optional.empty()); + + // Should not throw exception for large file when virus scan is disabled + ServiceException thrown = + assertThrows( + ServiceException.class, + () -> { + handlerSpy.createAttachment(mockContext); + }); + // Should fail on draft entity not found, not on virus scan + assertEquals(SDMConstants.DRAFT_NOT_FOUND, thrown.getMessage()); + } } diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java index 851c412f..7cf6e4c6 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java @@ -1,5 +1,6 @@ package unit.com.sap.cds.sdm.service.handler; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -11,6 +12,7 @@ import com.sap.cds.reflect.CdsElement; import com.sap.cds.reflect.CdsEntity; import com.sap.cds.reflect.CdsModel; +import com.sap.cds.sdm.constants.SDMConstants; import com.sap.cds.sdm.handler.TokenHandler; import com.sap.cds.sdm.model.CmisDocument; import com.sap.cds.sdm.model.SDMCredentials; @@ -21,9 +23,12 @@ import com.sap.cds.services.ServiceException; import com.sap.cds.services.draft.DraftService; import com.sap.cds.services.persistence.PersistenceService; +import com.sap.cds.services.request.ParameterInfo; import com.sap.cds.services.request.UserInfo; +import com.sap.cds.services.runtime.CdsRuntime; import java.io.IOException; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; @@ -50,6 +55,8 @@ public class SDMCustomServiceHandlerTest { private static final String FOLDER_ID = "mockFolderId"; private static final String UP_ID = "mockUpId"; private static final String FACET = "mockFacet"; + @Mock private CdsRuntime cdsRuntime; + @Mock ParameterInfo parameterInfo; @BeforeEach void setUp() { @@ -130,8 +137,15 @@ void testCopyAttachments_HappyPathNonLink() throws IOException { void testCopyAttachments_InvalidFacetFormat_ThrowsException() throws IOException { AttachmentCopyEventContext context = mock(AttachmentCopyEventContext.class); when(context.getFacet()).thenReturn("invalid.facet"); // Only 2 parts - // Other mocks not needed as exception is thrown before they're used + // Mock the required methods for localized message handling + when(context.getParameterInfo()).thenReturn(parameterInfo); + when(context.getCdsRuntime()).thenReturn(cdsRuntime); + // Mock localized message to return the key (fallback scenario) + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.FAILED_TO_FETCH_FACET_MSG); + + when(parameterInfo.getLocale()).thenReturn(Locale.ENGLISH); ServiceException ex = assertThrows( ServiceException.class, @@ -141,6 +155,30 @@ void testCopyAttachments_InvalidFacetFormat_ThrowsException() throws IOException assertTrue(ex.getMessage().contains("Invalid facet format")); } + @Test + void testCopyAttachments_InvalidFacetFormat_LocalizedMessage_ThrowsException() + throws IOException { + AttachmentCopyEventContext context = mock(AttachmentCopyEventContext.class); + when(context.getFacet()).thenReturn("invalid.facet"); // Only 2 parts + // Mock the required methods for localized message handling + when(context.getParameterInfo()).thenReturn(parameterInfo); + when(context.getCdsRuntime()).thenReturn(cdsRuntime); + + // Mock localized message to return a different localized message + String localizedMessage = + "Format de facette invalide, impossible d'extraire les informations requises."; + when(cdsRuntime.getLocalizedMessage(any(), any(), any())).thenReturn(localizedMessage); + + when(parameterInfo.getLocale()).thenReturn(Locale.FRENCH); + ServiceException ex = + assertThrows( + ServiceException.class, + () -> { + sdmCustomServiceHandler.copyAttachments(context); + }); + assertEquals(localizedMessage, ex.getMessage()); + } + @Test void testCopyAttachments_FolderDoesNotExist() throws IOException { // Mock SDMCredentials diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java index 177e3b6c..0c25dd07 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java @@ -29,7 +29,9 @@ import com.sap.cds.services.ServiceException; import com.sap.cds.services.draft.DraftService; import com.sap.cds.services.persistence.PersistenceService; +import com.sap.cds.services.request.ParameterInfo; import com.sap.cds.services.request.UserInfo; +import com.sap.cds.services.runtime.CdsRuntime; import java.io.IOException; import java.util.*; import java.util.stream.Stream; @@ -51,6 +53,7 @@ public class SDMServiceGenericHandlerTest { @Mock private CqnSelect cqnSelect; @Mock private CdsEntity cdsEntity; @Mock private CdsEntity draftEntity; + @Mock private CdsRuntime cdsRuntime; private CmisDocument cmisDocument; private SDMCredentials sdmCredentials; @@ -59,6 +62,7 @@ public class SDMServiceGenericHandlerTest { private MockedStatic cqnAnalyzerMock; private MockedStatic sdmUtilsMock; + @Mock ParameterInfo parameterInfo; @BeforeEach void setUp() { @@ -222,6 +226,10 @@ void testCreate_ThrowsServiceException_WhenVersionedRepo() throws IOException { RepoValue repoValue = new RepoValue(); repoValue.setVirusScanEnabled(false); repoValue.setVersionEnabled(true); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.VERSIONED_REPO_ERROR); when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); // Act & Assert @@ -716,6 +724,10 @@ void testCreate_ThrowsServiceExceptionOnUnauthorizedStatus() throws IOException SDMCredentials sdmCredentials = new SDMCredentials(); sdmCredentials.setUrl("http://test-url"); when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.USER_NOT_AUTHORISED_ERROR_LINK); JSONObject createResult = new JSONObject(); createResult.put("status", "unauthorized"); @@ -903,6 +915,10 @@ void testEditLinkFailure() throws IOException { when(cdsModel.findEntity("MyEntity_drafts")).thenReturn(Optional.of(draftEntity)); UserInfo userInfo = Mockito.mock(UserInfo.class); when(mockContext.getUserInfo()).thenReturn(userInfo); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.FAILED_TO_EDIT_LINK_MSG); when(userInfo.isSystemUser()).thenReturn(false); AnalysisResult analysisResult = mock(AnalysisResult.class); @@ -916,6 +932,7 @@ void testEditLinkFailure() throws IOException { when(dbQuery.getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123"))) .thenReturn(cmisDocument); when(mockContext.get("url")).thenReturn("http://badlink.com"); + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); JSONObject failureResponse = new JSONObject(); @@ -928,4 +945,361 @@ void testEditLinkFailure() throws IOException { verify(persistenceService, never()).run(any(Update.class)); verify(mockContext, never()).setCompleted(); } + + @Test + void testOpenAttachment_WithLinkFile() throws Exception { + // Arrange + AttachmentReadContext context = mock(AttachmentReadContext.class); + when(context.getModel()).thenReturn(cdsModel); + when(context.getTarget()).thenReturn(cdsEntity); + when(cdsEntity.getQualifiedName()).thenReturn("MyEntity"); + when(context.get("cqn")).thenReturn(cqnSelect); + when(cdsModel.findEntity("MyEntity_drafts")).thenReturn(Optional.of(draftEntity)); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + when(analysisResult.targetKeyValues()).thenReturn(Map.of("ID", "123")); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + + CmisDocument linkDocument = new CmisDocument(); + linkDocument.setFileName("test.url"); + linkDocument.setMimeType("application/internet-shortcut"); + linkDocument.setUrl("http://test.com"); + when(dbQuery.getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123"))) + .thenReturn(linkDocument); + + // Act + sdmServiceGenericHandler.openAttachment(context); + + // Assert + verify(context).setResult("http://test.com"); + } + + @Test + void testOpenAttachment_WithRegularFile() throws Exception { + // Arrange + AttachmentReadContext context = mock(AttachmentReadContext.class); + when(context.getModel()).thenReturn(cdsModel); + when(context.getTarget()).thenReturn(cdsEntity); + when(cdsEntity.getQualifiedName()).thenReturn("MyEntity"); + when(context.get("cqn")).thenReturn(cqnSelect); + when(cdsModel.findEntity("MyEntity_drafts")).thenReturn(Optional.of(draftEntity)); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + when(analysisResult.targetKeyValues()).thenReturn(Map.of("ID", "123")); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + + CmisDocument regularDocument = new CmisDocument(); + regularDocument.setFileName("test.pdf"); + regularDocument.setMimeType("application/pdf"); + when(dbQuery.getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123"))) + .thenReturn(regularDocument); + + // Act + sdmServiceGenericHandler.openAttachment(context); + + // Assert + verify(context).setResult("None"); + } + + @Test + void testOpenAttachment_FallbackToNonDraftEntity() throws Exception { + // Arrange + AttachmentReadContext context = mock(AttachmentReadContext.class); + when(context.getModel()).thenReturn(cdsModel); + when(context.getTarget()).thenReturn(cdsEntity); + when(cdsEntity.getQualifiedName()).thenReturn("MyEntity"); + when(context.get("cqn")).thenReturn(cqnSelect); + when(cdsModel.findEntity("MyEntity_drafts")).thenReturn(Optional.of(draftEntity)); + when(cdsModel.findEntity("MyEntity")).thenReturn(Optional.of(cdsEntity)); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + when(analysisResult.targetKeyValues()).thenReturn(Map.of("ID", "123")); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + + // First call returns document with empty filename (triggers fallback) + CmisDocument emptyDocument = new CmisDocument(); + emptyDocument.setFileName(""); + when(dbQuery.getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123"))) + .thenReturn(emptyDocument); + + // Second call returns proper document + CmisDocument properDocument = new CmisDocument(); + properDocument.setFileName("test.url"); + properDocument.setMimeType("application/internet-shortcut"); + properDocument.setUrl("http://fallback.com"); + when(dbQuery.getObjectIdForAttachmentID(eq(cdsEntity), eq(persistenceService), eq("123"))) + .thenReturn(properDocument); + + // Act + sdmServiceGenericHandler.openAttachment(context); + + // Assert + verify(context).setResult("http://fallback.com"); + verify(dbQuery).getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123")); + verify(dbQuery).getObjectIdForAttachmentID(eq(cdsEntity), eq(persistenceService), eq("123")); + } + + @Test + void testCreateLink_RepositoryValidationFails() throws IOException { + // Arrange + UserInfo userInfo = mock(UserInfo.class); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("tenant1"); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.VERSIONED_REPO_ERROR_MSG); + + RepoValue repoValue = new RepoValue(); + repoValue.setVersionEnabled(true); // This will trigger validation failure + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + // Act & Assert + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.create(mockContext)); + } + + @Test + void testCreateLink_LocalizedRepositoryValidationMessage() throws IOException { + // Arrange + UserInfo userInfo = mock(UserInfo.class); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("tenant1"); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn("Custom localized message for versioned repository"); + + RepoValue repoValue = new RepoValue(); + repoValue.setVersionEnabled(true); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + // Act & Assert + ServiceException exception = + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.create(mockContext)); + assertEquals("Custom localized message for versioned repository", exception.getMessage()); + } + + @Test + void testCreateLink_AttachmentCountConstraintExceeded() throws IOException { + // Arrange + Result mockResult = mock(Result.class); + UserInfo userInfo = mock(UserInfo.class); + CdsElement mockAssociationElement = mock(CdsElement.class); + CdsAssociationType mockAssociationType = mock(CdsAssociationType.class); + CqnElementRef mockCqnElementRef = mock(CqnElementRef.class); + + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getTarget()).thenReturn(draftEntity); + when(draftEntity.getQualifiedName()).thenReturn("MyService.MyEntity.attachments"); + when(cdsModel.findEntity("MyService.MyEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + when(cdsModel.findEntity("MyService.MyEntity.attachments")).thenReturn(Optional.of(cdsEntity)); + when(mockContext.getEvent()).thenReturn("createLink"); + CqnSelect cqnSelect = mock(CqnSelect.class); + when(mockContext.get("cqn")).thenReturn(cqnSelect); + when(mockContext.get("name")).thenReturn("testURL"); + when(mockContext.get("url")).thenReturn("http://test-url"); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("tenant1"); + when(userInfo.isSystemUser()).thenReturn(false); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + when(analysisResult.rootKeys()).thenReturn(Map.of("ID", "123")); + when(draftEntity.findAssociation("up_")).thenReturn(Optional.of(mockAssociationElement)); + when(mockAssociationElement.getType()).thenReturn(mockAssociationType); + when(mockAssociationType.refs()).thenReturn(Stream.of(mockCqnElementRef)); + when(mockCqnElementRef.path()).thenReturn("ID"); + + when(dbQuery.getAttachmentsForUPID(any(), any(), any(), any())).thenReturn(mockResult); + when(dbQuery.getAttachmentsForUPIDAndRepository(any(), any(), any(), any())) + .thenReturn(mockResult); + when(mockResult.rowCount()).thenReturn(5L); // Exceeds limit + when(mockResult.listOf(Map.class)).thenReturn(Collections.emptyList()); + + sdmUtilsMock + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("3__Maximum attachments exceeded"); // Max 3, current 5 + sdmUtilsMock.when(() -> SDMUtils.isRestrictedCharactersInName(anyString())).thenReturn(false); + + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + // Act & Assert + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.create(mockContext)); + } + + @Test + void testCreateLink_RestrictedCharactersInName() throws IOException { + // Arrange + Result mockResult = mock(Result.class); + UserInfo userInfo = mock(UserInfo.class); + CdsElement mockAssociationElement = mock(CdsElement.class); + CdsAssociationType mockAssociationType = mock(CdsAssociationType.class); + CqnElementRef mockCqnElementRef = mock(CqnElementRef.class); + + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getTarget()).thenReturn(draftEntity); + when(draftEntity.getQualifiedName()).thenReturn("MyService.MyEntity.attachments"); + when(cdsModel.findEntity("MyService.MyEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + when(cdsModel.findEntity("MyService.MyEntity.attachments")).thenReturn(Optional.of(cdsEntity)); + when(mockContext.getEvent()).thenReturn("createLink"); + CqnSelect cqnSelect = mock(CqnSelect.class); + when(mockContext.get("cqn")).thenReturn(cqnSelect); + when(mockContext.get("name")).thenReturn("test/invalid\\name"); + when(mockContext.get("url")).thenReturn("http://test-url"); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(userInfo.getTenant()).thenReturn("tenant1"); + when(userInfo.isSystemUser()).thenReturn(false); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + when(analysisResult.rootKeys()).thenReturn(Map.of("ID", "123")); + when(draftEntity.findAssociation("up_")).thenReturn(Optional.of(mockAssociationElement)); + when(mockAssociationElement.getType()).thenReturn(mockAssociationType); + when(mockAssociationType.refs()).thenReturn(Stream.of(mockCqnElementRef)); + when(mockCqnElementRef.path()).thenReturn("ID"); + + when(dbQuery.getAttachmentsForUPID(any(), any(), any(), any())).thenReturn(mockResult); + when(dbQuery.getAttachmentsForUPIDAndRepository(any(), any(), any(), any())) + .thenReturn(mockResult); + when(mockResult.rowCount()).thenReturn(0L); + when(mockResult.listOf(Map.class)).thenReturn(Collections.emptyList()); + + sdmUtilsMock + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("10__null"); + sdmUtilsMock.when(() -> SDMUtils.isRestrictedCharactersInName(anyString())).thenReturn(true); + + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + + // Act & Assert + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.create(mockContext)); + } + + @Test + void testCreateLink_UnauthorizedError() throws IOException { + // Arrange + Result mockResult = mock(Result.class); + UserInfo userInfo = mock(UserInfo.class); + CdsElement mockAssociationElement = mock(CdsElement.class); + CdsAssociationType mockAssociationType = mock(CdsAssociationType.class); + CqnElementRef mockCqnElementRef = mock(CqnElementRef.class); + + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.getTarget()).thenReturn(draftEntity); + when(draftEntity.getQualifiedName()).thenReturn("MyService.MyEntity.attachments"); + when(cdsModel.findEntity("MyService.MyEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + when(cdsModel.findEntity("MyService.MyEntity.attachments")).thenReturn(Optional.of(cdsEntity)); + when(mockContext.getEvent()).thenReturn("createLink"); + CqnSelect cqnSelect = mock(CqnSelect.class); + when(mockContext.get("cqn")).thenReturn(cqnSelect); + when(mockContext.get("name")).thenReturn("testURL"); + when(mockContext.get("url")).thenReturn("http://test-url"); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.USER_NOT_AUTHORISED_ERROR_LINK_MSG); + when(userInfo.getTenant()).thenReturn("tenant1"); + when(userInfo.isSystemUser()).thenReturn(false); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + AnalysisResult analysisResult = mock(AnalysisResult.class); + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + when(analysisResult.rootKeys()).thenReturn(Map.of("ID", "123")); + when(draftEntity.findAssociation("up_")).thenReturn(Optional.of(mockAssociationElement)); + when(mockAssociationElement.getType()).thenReturn(mockAssociationType); + when(mockAssociationType.refs()).thenReturn(Stream.of(mockCqnElementRef)); + when(mockCqnElementRef.path()).thenReturn("ID"); + + when(dbQuery.getAttachmentsForUPID(any(), any(), any(), any())).thenReturn(mockResult); + when(dbQuery.getAttachmentsForUPIDAndRepository(any(), any(), any(), any())) + .thenReturn(mockResult); + when(mockResult.rowCount()).thenReturn(0L); + when(mockResult.listOf(Map.class)).thenReturn(Collections.emptyList()); + + sdmUtilsMock + .when(() -> SDMUtils.getAttachmentCountAndMessage(anyList(), any())) + .thenReturn("10__null"); + sdmUtilsMock.when(() -> SDMUtils.isRestrictedCharactersInName(anyString())).thenReturn(false); + + RepoValue repoValue = new RepoValue(); + repoValue.setVirusScanEnabled(false); + repoValue.setVersionEnabled(false); + when(sdmService.checkRepositoryType(anyString(), anyString())).thenReturn(repoValue); + when(sdmService.getFolderId(any(), any(), any(), anyBoolean())).thenReturn("folderId123"); + + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url"); + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + JSONObject createResult = new JSONObject(); + createResult.put("status", "unauthorized"); + when(documentService.createDocument( + any(CmisDocument.class), any(SDMCredentials.class), anyBoolean())) + .thenReturn(createResult); + + // Act & Assert + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.create(mockContext)); + } + + @Test + void testEditLink_UnauthorizedError() throws IOException { + // Arrange + when(mockContext.getModel()).thenReturn(cdsModel); + when(mockContext.get("cqn")).thenReturn(cqnSelect); + when(mockContext.getTarget()).thenReturn(cdsEntity); + when(cdsEntity.getQualifiedName()).thenReturn("MyEntity"); + when(cdsModel.findEntity("MyEntity_drafts")).thenReturn(Optional.of(draftEntity)); + UserInfo userInfo = Mockito.mock(UserInfo.class); + when(mockContext.getUserInfo()).thenReturn(userInfo); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.USER_NOT_AUTHORISED_ERROR_MSG); + when(userInfo.isSystemUser()).thenReturn(false); + + AnalysisResult analysisResult = mock(AnalysisResult.class); + when(analysisResult.targetKeyValues()).thenReturn(Map.of("ID", "123")); + + CqnAnalyzer analyzer = mock(CqnAnalyzer.class); + when(analyzer.analyze(any(CqnSelect.class))).thenReturn(analysisResult); + + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(analyzer); + + when(dbQuery.getObjectIdForAttachmentID(eq(draftEntity), eq(persistenceService), eq("123"))) + .thenReturn(cmisDocument); + when(mockContext.get("url")).thenReturn("http://newlink.com"); + + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + JSONObject unauthorizedResponse = new JSONObject(); + unauthorizedResponse.put("status", "unauthorized"); + when(sdmService.editLink(any(CmisDocument.class), any(SDMCredentials.class), eq(false))) + .thenReturn(unauthorizedResponse); + + // Act & Assert + assertThrows(ServiceException.class, () -> sdmServiceGenericHandler.edit(mockContext)); + verify(persistenceService, never()).run(any(Update.class)); + verify(mockContext, never()).setCompleted(); + } } From 8048c4f7d7c3a5153cabc1e8f9a205eaa24fdd7c Mon Sep 17 00:00:00 2001 From: Rashmi Date: Thu, 23 Oct 2025 10:33:14 +0530 Subject: [PATCH 2/7] Update README.md --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1b82756..cb31045d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei - Copy attachments: Provides the capability to copy attachments from one entity to another entity. - Link as attachments: Provides the capability to support link or URL as attachments. - Edit Link-type attachments: Provides the capability to update URL of link-type attachments. - +- Localization of error messages and UI fields: Provides the capability to have the UI fields and error messages translated to the local language of the leading application. ## Table of Contents - [Pre-Requisites](#pre-requisites) @@ -35,6 +35,7 @@ This plugin can be consumed by the CAP application deployed on BTP to store thei - [Support for Copy attachments](#support-for-copy-attachments) - [Support for Link type attachments](#support-for-link-type-attachments) - [Support for Edit of Link type attachments](#support-for-edit-of-link-type-attachments) +- [Support for Localization](#support-for-localization) - [Known Restrictions](#known-restrictions) - [Support, Feedback, Contributing](#support-feedback-contributing) - [Code of Conduct](#code-of-conduct) @@ -859,7 +860,24 @@ annotate Attachments with @Common: {SideEffects #ContentChanged: { - Replace `AdminService` in `Action: 'AdminService.editLink'` with the name of your service. - Repeat for other entities and elements if you have defined multiple `composition of many Attachments`. +## Support for Localization +If the UI fields have to be available in the local language of the leading application ensure to add the below fields in the i18n_[languagecode].properties file under app/_i18n folder. +Default language translations are present in i18n.properties files. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user. + +Example i18n_de.properties for german language. +``` +Attachment=Attachment +Attachments=Attachments +Note= Attachment Note +Filename=File Name + ``` +For the exception messages as well the translation be provided by adding the translation to messages_[languagecode].properties files present under srv/src/main/resources. +Default language translations are present in messages.properties. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user. +Example for german language +``` +SDM.Attachments.maxCountError = Maximum number of attachments reached in German...... + ``` ## Known Restrictions - UI5 Version 1.135.0: This version causes error in upload of attachments. From 68d77a1a3539a216c2c5c9b614692c4048b04764 Mon Sep 17 00:00:00 2001 From: Rashmi Date: Thu, 23 Oct 2025 20:26:44 +0530 Subject: [PATCH 3/7] Changes --- README.md | 2 ++ .../java/com/sap/cds/sdm/constants/SDMConstants.java | 3 --- .../sdm/service/handler/SDMServiceGenericHandler.java | 11 ++++++++++- .../sap/cds/sdm/IntegrationTest_MultipleFacet.java | 8 ++++---- .../com/sap/cds/sdm/IntegrationTest_SingleFacet.java | 6 +++--- .../service/handler/SDMServiceGenericHandlerTest.java | 8 ++++++++ 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cb31045d..bed4551a 100644 --- a/README.md +++ b/README.md @@ -870,6 +870,8 @@ Attachment=Attachment Attachments=Attachments Note= Attachment Note Filename=File Name +linkUrl=Link Url +type=Type ``` For the exception messages as well the translation be provided by adding the translation to messages_[languagecode].properties files present under srv/src/main/resources. Default language translations are present in messages.properties. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user. diff --git a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java index e2e4ff24..c8e4bad9 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java +++ b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java @@ -31,9 +31,6 @@ private SDMConstants() { "You cannot upload files that are larger than 400 MB"; public static final String VIRUS_ERROR = "%s contains potential malware and cannot be uploaded."; public static final String REPOSITORY_ERROR = "Failed to get repository info."; - public static final String NOT_FOUND_ERROR = "Failed to read document."; - public static final String NAME_CONSTRAINT_WARNING_MESSAGE = - "Enter a valid file name for %s. The following characters are not supported: /, \\"; public static final String SDM_MISSING_ROLES_EXCEPTION_MSG = "You do not have the required permissions to update attachments. Kindly contact the admin"; public static final String SDM_ROLES_ERROR_MESSAGE = diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java index 696b0115..96c79974 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMServiceGenericHandler.java @@ -1,5 +1,7 @@ package com.sap.cds.sdm.service.handler; +import static com.sap.cds.sdm.constants.SDMConstants.ATTACHMENT_MAXCOUNT_ERROR_MSG; + import com.sap.cds.Result; import com.sap.cds.feature.attachments.service.AttachmentService; import com.sap.cds.ql.Insert; @@ -262,7 +264,14 @@ private void checkAttachmentConstraints( String message = maxCountArr.length > 1 ? maxCountArr[1] : null; if (maxCount > 0 && rowCount >= maxCount) { if (message != null && !"null".equalsIgnoreCase(message)) { - throw new ServiceException(message); + String errorMessage = + context + .getCdsRuntime() + .getLocalizedMessage( + "SDM.Attachments.maxCountError", null, context.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(ATTACHMENT_MAXCOUNT_ERROR_MSG)) + throw new ServiceException(String.format(SDMConstants.MAX_COUNT_ERROR_MESSAGE, maxCount)); + throw new ServiceException(errorMessage); } throw new ServiceException(String.format(SDMConstants.MAX_COUNT_ERROR_MESSAGE, maxCount)); } diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_MultipleFacet.java b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_MultipleFacet.java index 0fcc83c3..fe576e0e 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_MultipleFacet.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_MultipleFacet.java @@ -2150,7 +2150,7 @@ void testNAttachments_NewEntity() throws IOException { response = api.saveEntityDraft(appUrl, entityName, srvpath, entityID4); if (response.equals("Saved")) { String expectedJson = - "{\"error\":{\"code\":\"500\",\"message\":\"Only 4 attachments allowed.\"}}"; + "{\"error\":{\"code\":\"500\",\"message\":\"Maximum number of attachments reached in English\"}}"; ObjectMapper objectMapper = new ObjectMapper(); JsonNode actualJsonNode = objectMapper.readTree(check); JsonNode expectedJsonNode = objectMapper.readTree(expectedJson); @@ -2198,7 +2198,7 @@ void testUploadNAttachments() throws IOException { System.out.println("Result message for attachment " + i + ": " + resultMessage); String expectedResponse = - "{\"error\":{\"code\":\"500\",\"message\":\"Only 4 attachments allowed.\"}}"; + "{\"error\":{\"code\":\"500\",\"message\":\"Maximum number of attachments reached in English\"}}"; if (resultMessage.equals(expectedResponse)) { ObjectMapper objectMapper = new ObjectMapper(); JsonNode actualJsonNode = objectMapper.readTree(resultMessage); @@ -2953,9 +2953,9 @@ void testCreateLinkFailure() throws IOException { String errorMessage = json.getJSONObject("error").getString("message"); assertEquals("500", errorCode); if (facetName.equals("references")) { - assertEquals("Only 5 attachments allowed.", errorMessage); + assertEquals("Maximum number of attachments reached in English", errorMessage); } else if (facetName.equals("attachments")) { - assertEquals("Only 4 attachments allowed.", errorMessage); + assertEquals("Maximum number of attachments reached in English", errorMessage); } } } diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_SingleFacet.java b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_SingleFacet.java index f073a2cd..8e481be2 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_SingleFacet.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_SingleFacet.java @@ -2093,7 +2093,7 @@ void testNAttachments_NewEntity() throws IOException { response = api.saveEntityDraft(appUrl, entityName, srvpath, entityID4); if (response.equals("Saved")) { String expectedJson = - "{\"error\":{\"code\":\"500\",\"message\":\"Only 4 attachments allowed.\"}}"; + "{\"error\":{\"code\":\"500\",\"message\":\"Maximum number of attachments reached in English\"}}"; ObjectMapper objectMapper = new ObjectMapper(); JsonNode actualJsonNode = objectMapper.readTree(check); JsonNode expectedJsonNode = objectMapper.readTree(expectedJson); @@ -2141,7 +2141,7 @@ void testUploadNAttachments() throws IOException { System.out.println("Result message for attachment " + i + ": " + resultMessage); String expectedResponse = - "{\"error\":{\"code\":\"500\",\"message\":\"Only 4 attachments allowed.\"}}"; + "{\"error\":{\"code\":\"500\",\"message\":\"Maximum number of attachments reached in English\"}}"; if (resultMessage.equals(expectedResponse)) { ObjectMapper objectMapper = new ObjectMapper(); JsonNode actualJsonNode = objectMapper.readTree(resultMessage); @@ -2646,7 +2646,7 @@ void testCreateLinkFailure() throws IOException { String errorCode = json.getJSONObject("error").getString("code"); String errorMessage = json.getJSONObject("error").getString("message"); assertEquals("500", errorCode); - assertEquals("Only 4 attachments allowed.", errorMessage); + assertEquals("Maximum number of attachments reached in English", errorMessage); } String response = api.saveEntityDraft(appUrl, entityName, srvpath, createLinkEntity); if (!response.equals("Saved")) { diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java index 0c25dd07..7033463a 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMServiceGenericHandlerTest.java @@ -260,6 +260,10 @@ void testCreate_ShouldThrowSpecifiedExceptionWhenMaxCountReached() throws IOExce when(mockContext.get("url")).thenReturn("http://test-url"); when(mockContext.getUserInfo()).thenReturn(userInfo); when(userInfo.getTenant()).thenReturn("tenant1"); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn("Maximum two links allowed"); when(userInfo.isSystemUser()).thenReturn(false); CqnAnalyzer analyzer = mock(CqnAnalyzer.class); @@ -1105,6 +1109,10 @@ void testCreateLink_AttachmentCountConstraintExceeded() throws IOException { when(mockContext.get("cqn")).thenReturn(cqnSelect); when(mockContext.get("name")).thenReturn("testURL"); when(mockContext.get("url")).thenReturn("http://test-url"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.ATTACHMENT_MAXCOUNT_ERROR_MSG); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); when(mockContext.getUserInfo()).thenReturn(userInfo); when(userInfo.getTenant()).thenReturn("tenant1"); when(userInfo.isSystemUser()).thenReturn(false); From 821a5adb7c54a8962271742ab9a0baa93fb14e95 Mon Sep 17 00:00:00 2001 From: Rashmi Date: Fri, 24 Oct 2025 16:44:05 +0530 Subject: [PATCH 4/7] Changes --- .../sap/cds/sdm/constants/SDMConstants.java | 2 ++ .../handler/SDMAttachmentsServiceHandler.java | 25 +++++++++++++++++-- .../SDMAttachmentsServiceHandlerTest.java | 14 +++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java index c8e4bad9..01b2e625 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java +++ b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java @@ -29,7 +29,9 @@ private SDMConstants() { "Upload not supported for versioned repositories."; public static final String VIRUS_REPO_ERROR_MORE_THAN_400MB = "You cannot upload files that are larger than 400 MB"; + public static final String VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE = "SDM.VirusRepoErrorMessage"; public static final String VIRUS_ERROR = "%s contains potential malware and cannot be uploaded."; + public static final String VIRUS_ERROR_MESSAGE = "SDM.VirusErrorMessage"; public static final String REPOSITORY_ERROR = "Failed to get repository info."; public static final String SDM_MISSING_ROLES_EXCEPTION_MSG = "You do not have the required permissions to update attachments. Kindly contact the admin"; diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java index 30599f29..69eb61a5 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java @@ -164,7 +164,16 @@ private void validateRepository(AttachmentCreateEventContext eventContext) if (repoValue.getVirusScanEnabled() && contentLen > 400 * 1024 * 1024 && !repoValue.getDisableVirusScannerForLargeFile()) { - throw new ServiceException(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB); + String errorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE, + null, + eventContext.getParameterInfo().getLocale()); + if (errorMessage.equalsIgnoreCase(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE)) + throw new ServiceException(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB); + throw new ServiceException(errorMessage); } } @@ -316,7 +325,19 @@ private void handleCreateDocumentResult( case "duplicate": throw new ServiceException(SDMConstants.getDuplicateFilesError(cmisDocument.getFileName())); case "virus": - throw new ServiceException(SDMConstants.getVirusFilesError(cmisDocument.getFileName())); + Object[] message = new Object[1]; + message[0] = cmisDocument.getFileName(); + String virusErrorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + SDMConstants.VIRUS_ERROR_MESSAGE, + message, + eventContext.getParameterInfo().getLocale()); + if (virusErrorMessage.equalsIgnoreCase(SDMConstants.VIRUS_ERROR_MESSAGE)) + throw new ServiceException(SDMConstants.getVirusFilesError(cmisDocument.getFileName())); + throw new ServiceException(virusErrorMessage); + case "fail": throw new ServiceException(createResult.get("message").toString()); case "unauthorized": diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java index ce5e203c..f0fe95ab 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java @@ -283,6 +283,10 @@ public void testCreateVirusEnabled() throws IOException { when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); when(mockContext.getParameterInfo()).thenReturn(parameterInfo); headers.put("content-length", "900000089999"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); when(parameterInfo.getHeaders()).thenReturn(headers); // Use assertThrows to expect a ServiceException and validate the message ServiceException thrown = @@ -521,7 +525,10 @@ public void testCreateNonVersionedDIVirus() throws IOException { when(tokenHandler.getSDMCredentials()).thenReturn(mockSdmCredentials); when(mockContext.getAttachmentEntity()).thenReturn(mockDraftEntity); when(mockDraftEntity.getQualifiedName()).thenReturn("some.qualified.name"); - + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.getVirusFilesError("sample.pdf")); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); // Use assertThrows to expect a ServiceException and validate the message ServiceException thrown = assertThrows( @@ -1697,7 +1704,10 @@ public void testCreateAttachment_VirusScanEnabledExceedsLimit() throws IOExcepti when(mockContext.getAuthenticationInfo()).thenReturn(mockAuthInfo); when(mockAuthInfo.as(JwtTokenAuthenticationInfo.class)).thenReturn(mockJwtTokenInfo); when(mockJwtTokenInfo.getToken()).thenReturn("mockedJwtToken"); - + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); ServiceException thrown = assertThrows( ServiceException.class, From 4413c59e0982de80576523947b96d1f5d4e6f614 Mon Sep 17 00:00:00 2001 From: Rashmi Date: Fri, 24 Oct 2025 16:47:47 +0530 Subject: [PATCH 5/7] spotless fix --- .../applicationservice/SDMCreateAttachmentsHandler.java | 2 -- .../applicationservice/SDMUpdateAttachmentsHandler.java | 2 -- .../cds/sdm/service/handler/SDMCustomServiceHandlerTest.java | 3 --- 3 files changed, 7 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMCreateAttachmentsHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMCreateAttachmentsHandler.java index 394ac33c..b21a1b3c 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMCreateAttachmentsHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMCreateAttachmentsHandler.java @@ -1,8 +1,6 @@ package com.sap.cds.sdm.handler.applicationservice; import com.sap.cds.CdsData; -import com.sap.cds.reflect.CdsAssociationType; -import com.sap.cds.reflect.CdsElement; import com.sap.cds.reflect.CdsEntity; import com.sap.cds.sdm.caching.CacheConfig; import com.sap.cds.sdm.caching.SecondaryPropertiesKey; diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMUpdateAttachmentsHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMUpdateAttachmentsHandler.java index f6c5a590..34b70204 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMUpdateAttachmentsHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMUpdateAttachmentsHandler.java @@ -1,8 +1,6 @@ package com.sap.cds.sdm.handler.applicationservice; import com.sap.cds.CdsData; -import com.sap.cds.reflect.CdsAssociationType; -import com.sap.cds.reflect.CdsElement; import com.sap.cds.reflect.CdsEntity; import com.sap.cds.sdm.caching.CacheConfig; import com.sap.cds.sdm.caching.SecondaryPropertiesKey; diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java index 00da2f60..9ca075e2 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMCustomServiceHandlerTest.java @@ -1,6 +1,5 @@ package unit.com.sap.cds.sdm.service.handler; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -12,7 +11,6 @@ import com.sap.cds.reflect.CdsElement; import com.sap.cds.reflect.CdsEntity; import com.sap.cds.reflect.CdsModel; -import com.sap.cds.sdm.constants.SDMConstants; import com.sap.cds.sdm.handler.TokenHandler; import com.sap.cds.sdm.model.CmisDocument; import com.sap.cds.sdm.model.SDMCredentials; @@ -28,7 +26,6 @@ import com.sap.cds.services.runtime.CdsRuntime; import java.io.IOException; import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; From d9ae6120ff692ccd69b5ba1d72652bacd3d7be81 Mon Sep 17 00:00:00 2001 From: Rashmi Date: Fri, 24 Oct 2025 16:56:47 +0530 Subject: [PATCH 6/7] Changes --- .../com/sap/cds/sdm/constants/SDMConstants.java | 1 + .../handler/SDMAttachmentsServiceHandler.java | 14 +++++++++++++- .../handler/SDMAttachmentsServiceHandlerTest.java | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java index 956e1f62..500ef4bb 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java +++ b/sdm/src/main/java/com/sap/cds/sdm/constants/SDMConstants.java @@ -32,6 +32,7 @@ private SDMConstants() { public static final String VIRUS_REPO_ERROR_MORE_THAN_400MB_MESSAGE = "SDM.VirusRepoErrorMessage"; public static final String VIRUS_ERROR = "%s contains potential malware and cannot be uploaded."; public static final String VIRUS_ERROR_MESSAGE = "SDM.VirusErrorMessage"; + public static final String SDM_DUPLICATE_ATTACHMENT = "SDM.DuplicateAttachment"; public static final String REPOSITORY_ERROR = "Failed to get repository info."; public static final String SDM_MISSING_ROLES_EXCEPTION_MSG = "You do not have the required permissions to update attachments. Kindly contact the admin"; diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java index 5fb0edd5..3681cfe6 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandler.java @@ -323,7 +323,19 @@ private void handleCreateDocumentResult( switch (status) { case "duplicate": - throw new ServiceException(SDMConstants.getDuplicateFilesError(cmisDocument.getFileName())); + Object[] duplicatemessage = new Object[1]; + duplicatemessage[0] = cmisDocument.getFileName(); + String duplicateErrorMessage = + eventContext + .getCdsRuntime() + .getLocalizedMessage( + SDMConstants.SDM_DUPLICATE_ATTACHMENT, + duplicatemessage, + eventContext.getParameterInfo().getLocale()); + if (duplicateErrorMessage.equalsIgnoreCase(SDMConstants.SDM_DUPLICATE_ATTACHMENT)) + throw new ServiceException( + SDMConstants.getDuplicateFilesError(cmisDocument.getFileName())); + throw new ServiceException(duplicateErrorMessage); case "virus": Object[] message = new Object[1]; message[0] = cmisDocument.getFileName(); diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java index f0fe95ab..5d76393e 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/handler/SDMAttachmentsServiceHandlerTest.java @@ -444,6 +444,10 @@ public void testCreateNonVersionedDIDuplicate() throws IOException { when(mockContext.getAttachmentEntity()).thenReturn(mockDraftEntity); when(mockDraftEntity.getQualifiedName()).thenReturn("some.qualified.name"); + when(mockContext.getCdsRuntime()).thenReturn(cdsRuntime); + when(cdsRuntime.getLocalizedMessage(any(), any(), any())) + .thenReturn(SDMConstants.getDuplicateFilesError("sample.pdf")); + when(mockContext.getParameterInfo()).thenReturn(parameterInfo); // Validate ServiceException for duplicate detection ServiceException thrown = assertThrows( From 077223fee5643bb22f74fb4ed5677db69cf417c8 Mon Sep 17 00:00:00 2001 From: Rashmi Date: Fri, 24 Oct 2025 17:10:12 +0530 Subject: [PATCH 7/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bed4551a..d942a0e6 100644 --- a/README.md +++ b/README.md @@ -873,7 +873,7 @@ Filename=File Name linkUrl=Link Url type=Type ``` -For the exception messages as well the translation be provided by adding the translation to messages_[languagecode].properties files present under srv/src/main/resources. +For the exception messages as well the translation can be done by adding the translation to messages_[languagecode].properties files present under srv/src/main/resources. Default language translations are present in messages.properties. If leading application does not provide any keys and values in their language properties files then default english language messages are shown to the user. Example for german language