diff --git a/.github/workflows/cfdeploy.yml b/.github/workflows/cfdeploy.yml index 4dd2527d..417f44c8 100644 --- a/.github/workflows/cfdeploy.yml +++ b/.github/workflows/cfdeploy.yml @@ -55,12 +55,12 @@ jobs: - name: Verify and Checkout Deploy Branch 🔄 run: | git fetch origin - echo "📂 Verifying 'local_deploy' branch..." - if git rev-parse --verify origin/local_deploy; then - git checkout local_deploy + echo "📂 Verifying 'changesForChangeLogSupport' branch..." + if git rev-parse --verify origin/changesForChangeLogSupport; then + git checkout changesForChangeLogSupport echo "✅ Branch checked out successfully!" else - echo "❌ Branch 'local_deploy' not found. Please verify the branch name." + echo "❌ Branch 'changesForChangeLogSupport' not found. Please verify the branch name." exit 1 fi diff --git a/pom.xml b/pom.xml index fdec1f3c..d5a84cfb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.6.2-SNAPSHOT + 1.0.0-RC1 17 ${java.version} ${java.version} 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 b1f79db0..30993d19 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 @@ -202,12 +202,6 @@ private void processAttachment( List noSDMRoles) throws IOException { String id = (String) attachment.get("ID"); - String fileNameInDB; - fileNameInDB = - dbQuery.getAttachmentForID( - attachmentEntity.get(), - persistenceService, - id); // Fetching the name of the file from DB String filenameInRequest = (String) attachment.get("fileName"); // Fetching the name of the file from request String objectId = (String) attachment.get("objectId"); @@ -241,56 +235,64 @@ private void processAttachment( persistenceService, secondaryTypeProperties, propertiesInDB); - if (SDMUtils.hasRestrictedCharactersInName(filenameInRequest)) { - fileNameWithRestrictedCharacters.add(filenameInRequest); - } CmisDocument cmisDocument = new CmisDocument(); cmisDocument.setFileName(filenameInRequest); cmisDocument.setObjectId(objectId); - if (fileNameInDB == null || !fileNameInDB.equals(filenameInRequest)) { + + if (filenameInRequest == null) { + throw new ServiceException("Filename cannot be empty"); + } else if (!filenameInRequest.equals( + fileNameInSDM)) { // If the file name in DB is not equal to the file name in + // request, it means that the file name has been modified updatedSecondaryProperties.put("filename", filenameInRequest); } + if (!updatedSecondaryProperties.isEmpty()) { + try { + int responseCode = + sdmService.updateAttachments( + sdmCredentials, + cmisDocument, + updatedSecondaryProperties, + secondaryPropertiesWithInvalidDefinitions, + context.getUserInfo().isSystemUser()); + switch (responseCode) { + case 403: + // SDM Roles for user are missing + noSDMRoles.add(fileNameInSDM); + replacePropertiesInAttachment( + attachment, fileNameInSDM, propertiesInDB, secondaryTypeProperties); + break; + case 409: + duplicateFileNameList.add(filenameInRequest); + replacePropertiesInAttachment( + attachment, fileNameInSDM, propertiesInDB, secondaryTypeProperties); + break; + case 404: + filesNotFound.add(filenameInRequest); + replacePropertiesInAttachment( + attachment, filenameInRequest, propertiesInDB, secondaryTypeProperties); + break; + case 200: + case 201: + // Success cases, do nothing + break; - try { - int responseCode = - sdmService.updateAttachments( - sdmCredentials, - cmisDocument, - updatedSecondaryProperties, - secondaryPropertiesWithInvalidDefinitions, - context.getUserInfo().isSystemUser()); - switch (responseCode) { - case 403: - // SDM Roles for user are missing - noSDMRoles.add(fileNameInSDM); + default: + throw new ServiceException(SDMConstants.SDM_ROLES_ERROR_MESSAGE, null); + } + } catch (ServiceException e) { + // This exception is thrown when there are unsupported properties in the request + if (e.getMessage().startsWith(SDMConstants.UNSUPPORTED_PROPERTIES)) { + String unsupportedDetails = + e.getMessage().substring(SDMConstants.UNSUPPORTED_PROPERTIES.length()).trim(); + filesWithUnsupportedProperties.add(unsupportedDetails); replacePropertiesInAttachment( attachment, fileNameInSDM, propertiesInDB, secondaryTypeProperties); - break; - case 404: - filesNotFound.add(filenameInRequest); + } else { + badRequest.put(filenameInRequest, e.getMessage()); replacePropertiesInAttachment( attachment, filenameInRequest, propertiesInDB, secondaryTypeProperties); - break; - case 200: - case 201: - // Success cases, do nothing - break; - - default: - throw new ServiceException(SDMConstants.SDM_ROLES_ERROR_MESSAGE, null); - } - } catch (ServiceException e) { - // This exception is thrown when there are unsupported properties in the request - if (e.getMessage().startsWith(SDMConstants.UNSUPPORTED_PROPERTIES)) { - String unsupportedDetails = - e.getMessage().substring(SDMConstants.UNSUPPORTED_PROPERTIES.length()).trim(); - filesWithUnsupportedProperties.add(unsupportedDetails); - replacePropertiesInAttachment( - attachment, fileNameInSDM, propertiesInDB, secondaryTypeProperties); - } else { - badRequest.put(filenameInRequest, e.getMessage()); - replacePropertiesInAttachment( - attachment, filenameInRequest, propertiesInDB, secondaryTypeProperties); + } } } } diff --git a/sdm/src/main/java/com/sap/cds/sdm/model/AttachmentLogContext.java b/sdm/src/main/java/com/sap/cds/sdm/model/AttachmentLogContext.java new file mode 100644 index 00000000..c2ef0d38 --- /dev/null +++ b/sdm/src/main/java/com/sap/cds/sdm/model/AttachmentLogContext.java @@ -0,0 +1,17 @@ +package com.sap.cds.sdm.model; + +import com.sap.cds.services.EventContext; +import com.sap.cds.services.EventName; +import org.json.JSONObject; + +@EventName("changelog") +public interface AttachmentLogContext extends EventContext { + + static AttachmentLogContext create() { + return (AttachmentLogContext) EventContext.create(AttachmentLogContext.class, null); + } + + void setResult(JSONObject res); + + JSONObject getResult(); +} diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMService.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMService.java index 605309dc..c2815cfd 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMService.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMService.java @@ -66,4 +66,7 @@ public List copyAttachment( public JSONObject editLink( CmisDocument cmisDocument, SDMCredentials sdmCredentials, boolean isSystemUser) throws IOException; + + public JSONObject getChangeLog( + String objectId, SDMCredentials sdmCredentials, boolean isSystemUser); } 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 f1c76502..6e97cf24 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 @@ -3,6 +3,8 @@ import static com.sap.cds.sdm.constants.SDMConstants.NAMED_USER_FLOW; import static com.sap.cds.sdm.constants.SDMConstants.TECHNICAL_USER_FLOW; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.sap.cds.Result; import com.sap.cds.feature.attachments.service.model.servicehandler.AttachmentReadEventContext; import com.sap.cds.sdm.caching.*; @@ -274,17 +276,23 @@ public int updateAttachments( // Prepare the request body parts Map updateRequestBody = new HashMap<>(); updateRequestBody.put("cmisaction", "update"); - updateRequestBody.put( - "propertyId[0]", - "cmis:secondaryObjectTypeIds"); // Creating request body for update properties - for (int index = 0; index < secondaryTypes.size(); index++) { + boolean isFilenameUpdated = secondaryProperties.containsKey("filename"); + boolean isSecondaryPropertiesUpdated = secondaryProperties.size() > (isFilenameUpdated ? 1 : 0); + if (isSecondaryPropertiesUpdated) { updateRequestBody.put( - "propertyValue[0][" + index + "]", - secondaryTypes.get(index)); // Adding Secondary Types to the request body + "propertyId[0]", + "cmis:secondaryObjectTypeIds"); // Creating request body for update properties + + for (int index = 0; index < secondaryTypes.size(); index++) { + updateRequestBody.put( + "propertyValue[0][" + index + "]", + secondaryTypes.get(index)); // Adding Secondary Types to the request body + } } - SDMUtils.prepareSecondaryProperties(updateRequestBody, secondaryProperties, fileName); + SDMUtils.prepareSecondaryProperties( + updateRequestBody, secondaryProperties, isSecondaryPropertiesUpdated); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); SDMUtils.assembleRequestBodySecondaryTypes( builder, updateRequestBody, objectId); // Adding Secondary Properties to the request body @@ -702,4 +710,59 @@ public List copyAttachment( throw new ServiceException(SDMConstants.FAILED_TO_COPY_ATTACHMENT, e); } } + + private String getRepositoryId(String jsonString) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + JsonNode rootNode = objectMapper.readTree(jsonString); + JsonNode repoInfos = rootNode.path("repoAndConnectionInfos"); + + // Iterate through the array to find the correct externalId and retrieve the id + for (JsonNode repoInfo : repoInfos) { + JsonNode repository = repoInfo.path("repository"); + if (repository.path("externalId") != null + && repository.path("externalId").asText().equals(SDMConstants.REPOSITORY_ID)) { + return repository.path("id").asText(); + } + } + } catch (Exception e) { + throw new ServiceException(String.format(e.getMessage())); + } + return null; + } + + @Override + public JSONObject getChangeLog( + String objectId, SDMCredentials sdmCredentials, boolean isSystemUser) { + String grantType = isSystemUser ? TECHNICAL_USER_FLOW : NAMED_USER_FLOW; + logger.info("This is a :" + grantType + " flow"); + var httpClient = tokenHandler.getHttpClient(binding, connectionPool, null, grantType); + String sdmUrl = sdmCredentials.getUrl() + SDMConstants.REST_V2_REPOSITORIES + "/"; + HttpGet getRepos = new HttpGet(sdmUrl); + String repoId = ""; + try (var response = (CloseableHttpResponse) httpClient.execute(getRepos)) { + repoId = getRepositoryId(EntityUtils.toString(response.getEntity())); + } catch (IOException e) { + logger.error("Error in offboarding repository : " + e.getMessage()); + throw new ServiceException("Error in offboarding ", e.getMessage()); + } + sdmUrl = + sdmUrl + + (repoId == null ? SDMConstants.REPOSITORY_ID : repoId) + + "/objects/" + + objectId + + "/changeLogs?includeAll=true"; + + HttpGet getChangeLogRequest = new HttpGet(sdmUrl); + try (var response = (CloseableHttpResponse) httpClient.execute(getChangeLogRequest)) { + if (response.getStatusLine().getStatusCode() != 200) { + return null; + } + String responseString = EntityUtils.toString(response.getEntity()); + return new JSONObject(responseString); + + } catch (IOException e) { + throw new ServiceException(SDMConstants.ATTACHMENT_NOT_FOUND, e); + } + } } 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 1bec4714..04ccefd1 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 @@ -74,6 +74,71 @@ public SDMServiceGenericHandler( this.tokenHandler = tokenHandler; } + @On(event = "changelog") + public void changelog(AttachmentLogContext context) throws IOException { + logger.info("Starting changelog event handler"); + CdsModel cdsModel = context.getModel(); + logger.info("CDS Model fetched in changelog: " + cdsModel); + logger.info("Target entity qualified name: " + context.getTarget().getQualifiedName()); + + CqnAnalyzer cqnAnalyzer = CqnAnalyzer.create(cdsModel); + logger.info("CqnAnalyzer created successfully"); + + Optional attachmentEntity = + cdsModel.findEntity(context.getTarget().getQualifiedName() + "_drafts"); + logger.info("Draft attachment entity found: " + attachmentEntity.isPresent()); + + Map targetKeys = + cqnAnalyzer.analyze((CqnSelect) context.get("cqn")).targetKeyValues(); + logger.info("Target keys extracted: " + targetKeys); + + // get the objectId against the Id + String id = targetKeys.get("ID").toString(); + logger.info("Attachment ID: " + id); + + CmisDocument cmisDocument = + dbQuery.getObjectIdForAttachmentID(attachmentEntity.get(), persistenceService, id); + logger.info( + "CMIS document retrieved - ObjectId: " + + cmisDocument.getObjectId() + + ", FileName: " + + cmisDocument.getFileName()); + + if (cmisDocument.getFileName() == null || cmisDocument.getFileName().isEmpty()) { + logger.info("Filename is null or empty, fetching from non-draft entity"); + // open attachment is triggered on non-draft entity + attachmentEntity = cdsModel.findEntity(context.getTarget().getQualifiedName()); + logger.info("Active attachment entity found: " + attachmentEntity.isPresent()); + + cmisDocument = + dbQuery.getObjectIdForAttachmentID(attachmentEntity.get(), persistenceService, id); + logger.info( + "CMIS document retrieved from active entity - ObjectId: " + + cmisDocument.getObjectId() + + ", FileName: " + + cmisDocument.getFileName()); + } + + logger.info("Retrieving SDM credentials"); + SDMCredentials sdmCredentials = tokenHandler.getSDMCredentials(); + logger.info("SDM credentials retrieved successfully"); + + logger.info("Is system user: " + context.getUserInfo().isSystemUser()); + logger.info( + "Calling SDM service to get change log for objectId: " + cmisDocument.getObjectId()); + + JSONObject jsonObject = + sdmService.getChangeLog( + cmisDocument.getObjectId(), sdmCredentials, context.getUserInfo().isSystemUser()); + logger.info("Change log retrieved from SDM service"); + + jsonObject.put("filename", cmisDocument.getFileName()); + logger.info("Filename added to JSON response: " + cmisDocument.getFileName()); + + context.setResult(jsonObject); + logger.info("Changelog event handler completed successfully"); + } + @On(event = "copyAttachments") public void copyAttachments(EventContext context) throws IOException { String upID = context.get("up__ID").toString(); diff --git a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java index 339cc3ff..0a1a391e 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java +++ b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java @@ -109,10 +109,12 @@ public static boolean hasRestrictedCharactersInName(String cmisName) { } public static void prepareSecondaryProperties( - Map requestBody, Map secondaryProperties, String fileName) { + Map requestBody, + Map secondaryProperties, + boolean isSecondaryPropertiesUpdated) { Iterator> iterator = secondaryProperties.entrySet().iterator(); - int index = 1; + int index = isSecondaryPropertiesUpdated ? 1 : 0; while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if ("filename".equals(entry.getKey())) { diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java index 1f60e33a..f8a5d692 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/service/SDMServiceImplTest.java @@ -1593,6 +1593,361 @@ public void testCopyAttachment_IOException() throws Exception { assertTrue(ex.getCause() instanceof IOException); } + @Test + public void testGetRepositoryId_Success() { + String jsonString = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"" + + SDMConstants.REPOSITORY_ID + + "\",\n" + + " \"id\": \"internal-repo-123\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"other-repo\",\n" + + " \"id\": \"other-internal-id\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + // Use reflection to call the private method + try { + java.lang.reflect.Method method = + SDMServiceImpl.class.getDeclaredMethod("getRepositoryId", String.class); + method.setAccessible(true); + String result = (String) method.invoke(sdmServiceImpl, jsonString); + + assertEquals("internal-repo-123", result); + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } + + @Test + public void testGetRepositoryId_NotFound() { + String jsonString = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"different-repo\",\n" + + " \"id\": \"different-internal-id\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + try { + java.lang.reflect.Method method = + SDMServiceImpl.class.getDeclaredMethod("getRepositoryId", String.class); + method.setAccessible(true); + String result = (String) method.invoke(sdmServiceImpl, jsonString); + + assertNull(result); + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } + + @Test + public void testGetRepositoryId_EmptyArray() { + String jsonString = "{\n" + " \"repoAndConnectionInfos\": []\n" + "}"; + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + try { + java.lang.reflect.Method method = + SDMServiceImpl.class.getDeclaredMethod("getRepositoryId", String.class); + method.setAccessible(true); + String result = (String) method.invoke(sdmServiceImpl, jsonString); + + assertNull(result); + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } + + @Test + public void testGetRepositoryId_InvalidJson() { + String invalidJsonString = "invalid json"; + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + try { + java.lang.reflect.Method method = + SDMServiceImpl.class.getDeclaredMethod("getRepositoryId", String.class); + method.setAccessible(true); + + java.lang.reflect.InvocationTargetException exception = + assertThrows( + java.lang.reflect.InvocationTargetException.class, + () -> { + method.invoke(sdmServiceImpl, invalidJsonString); + }); + + assertTrue(exception.getCause() instanceof ServiceException); + ServiceException serviceException = (ServiceException) exception.getCause(); + assertTrue(serviceException.getMessage().contains("Unrecognized token")); + } catch (Exception e) { + fail("Exception occurred: " + e.getMessage()); + } + } + + @Test + public void testGetChangeLog_Success() throws IOException { + String repositoryResponse = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"" + + SDMConstants.REPOSITORY_ID + + "\",\n" + + " \"id\": \"internal-repo-123\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + String changeLogResponse = + "{\n" + + " \"changeLogs\": [\n" + + " {\n" + + " \"changeType\": \"created\",\n" + + " \"changeTime\": \"2023-01-01T00:00:00Z\",\n" + + " \"user\": \"test-user\"\n" + + " }\n" + + " ]\n" + + "}"; + + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url/"); + String objectId = "test-object-id"; + + CloseableHttpResponse repositoryResponse1 = mock(CloseableHttpResponse.class); + CloseableHttpResponse changeLogResponse1 = mock(CloseableHttpResponse.class); + HttpEntity repositoryEntity = mock(HttpEntity.class); + HttpEntity changeLogEntity = mock(HttpEntity.class); + StatusLine changeLogStatusLine = mock(StatusLine.class); + + when(tokenHandler.getHttpClient(any(), any(), any(), eq("TECHNICAL_CREDENTIALS_FLOW"))) + .thenReturn(httpClient); + + // Mock first call (repository info) + when(httpClient.execute(any(HttpGet.class))) + .thenReturn(repositoryResponse1) + .thenReturn(changeLogResponse1); + + when(repositoryResponse1.getEntity()).thenReturn(repositoryEntity); + when(changeLogResponse1.getStatusLine()).thenReturn(changeLogStatusLine); + when(changeLogResponse1.getEntity()).thenReturn(changeLogEntity); + when(changeLogStatusLine.getStatusCode()).thenReturn(200); + + try (MockedStatic entityUtilsMock = mockStatic(EntityUtils.class)) { + entityUtilsMock + .when(() -> EntityUtils.toString(repositoryEntity)) + .thenReturn(repositoryResponse); + entityUtilsMock + .when(() -> EntityUtils.toString(changeLogEntity)) + .thenReturn(changeLogResponse); + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + JSONObject result = sdmServiceImpl.getChangeLog(objectId, sdmCredentials, true); + + assertNotNull(result); + assertTrue(result.has("changeLogs")); + JSONArray changeLogs = result.getJSONArray("changeLogs"); + assertEquals(1, changeLogs.length()); + assertEquals("created", changeLogs.getJSONObject(0).getString("changeType")); + } + } + + @Test + public void testGetChangeLog_RepositoryNotFound() throws IOException { + String repositoryResponse = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"different-repo\",\n" + + " \"id\": \"different-internal-id\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + String changeLogResponse = "{\n" + " \"changeLogs\": []\n" + "}"; + + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url/"); + String objectId = "test-object-id"; + + CloseableHttpResponse repositoryResponse1 = mock(CloseableHttpResponse.class); + CloseableHttpResponse changeLogResponse1 = mock(CloseableHttpResponse.class); + HttpEntity repositoryEntity = mock(HttpEntity.class); + HttpEntity changeLogEntity = mock(HttpEntity.class); + StatusLine changeLogStatusLine = mock(StatusLine.class); + + when(tokenHandler.getHttpClient(any(), any(), any(), eq("TOKEN_EXCHANGE"))) + .thenReturn(httpClient); + + when(httpClient.execute(any(HttpGet.class))) + .thenReturn(repositoryResponse1) + .thenReturn(changeLogResponse1); + + when(repositoryResponse1.getEntity()).thenReturn(repositoryEntity); + when(changeLogResponse1.getStatusLine()).thenReturn(changeLogStatusLine); + when(changeLogResponse1.getEntity()).thenReturn(changeLogEntity); + when(changeLogStatusLine.getStatusCode()).thenReturn(200); + + try (MockedStatic entityUtilsMock = mockStatic(EntityUtils.class)) { + entityUtilsMock + .when(() -> EntityUtils.toString(repositoryEntity)) + .thenReturn(repositoryResponse); + entityUtilsMock + .when(() -> EntityUtils.toString(changeLogEntity)) + .thenReturn(changeLogResponse); + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + JSONObject result = sdmServiceImpl.getChangeLog(objectId, sdmCredentials, false); + + assertNotNull(result); + assertTrue(result.has("changeLogs")); + JSONArray changeLogs = result.getJSONArray("changeLogs"); + assertEquals(0, changeLogs.length()); + } + } + + @Test + public void testGetChangeLog_ChangeLogRequestFails() throws IOException { + String repositoryResponse = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"" + + SDMConstants.REPOSITORY_ID + + "\",\n" + + " \"id\": \"internal-repo-123\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url/"); + String objectId = "test-object-id"; + + CloseableHttpResponse repositoryResponse1 = mock(CloseableHttpResponse.class); + CloseableHttpResponse changeLogResponse1 = mock(CloseableHttpResponse.class); + HttpEntity repositoryEntity = mock(HttpEntity.class); + StatusLine changeLogStatusLine = mock(StatusLine.class); + + when(tokenHandler.getHttpClient(any(), any(), any(), eq("TECHNICAL_CREDENTIALS_FLOW"))) + .thenReturn(httpClient); + + when(httpClient.execute(any(HttpGet.class))) + .thenReturn(repositoryResponse1) + .thenReturn(changeLogResponse1); + + when(repositoryResponse1.getEntity()).thenReturn(repositoryEntity); + when(changeLogResponse1.getStatusLine()).thenReturn(changeLogStatusLine); + when(changeLogStatusLine.getStatusCode()).thenReturn(404); + + try (MockedStatic entityUtilsMock = mockStatic(EntityUtils.class)) { + entityUtilsMock + .when(() -> EntityUtils.toString(repositoryEntity)) + .thenReturn(repositoryResponse); + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + JSONObject result = sdmServiceImpl.getChangeLog(objectId, sdmCredentials, true); + + assertNull(result); + } + } + + @Test + public void testGetChangeLog_RepositoryIOException() throws IOException { + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url/"); + String objectId = "test-object-id"; + + when(tokenHandler.getHttpClient(any(), any(), any(), eq("TOKEN_EXCHANGE"))) + .thenReturn(httpClient); + + when(httpClient.execute(any(HttpGet.class))).thenThrow(new IOException("Network error")); + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + ServiceException exception = + assertThrows( + ServiceException.class, + () -> { + sdmServiceImpl.getChangeLog(objectId, sdmCredentials, false); + }); + + assertEquals("Error in offboarding ", exception.getMessage()); + } + + @Test + public void testGetChangeLog_ChangeLogIOException() throws IOException { + String repositoryResponse = + "{\n" + + " \"repoAndConnectionInfos\": [\n" + + " {\n" + + " \"repository\": {\n" + + " \"externalId\": \"" + + SDMConstants.REPOSITORY_ID + + "\",\n" + + " \"id\": \"internal-repo-123\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + SDMCredentials sdmCredentials = new SDMCredentials(); + sdmCredentials.setUrl("http://test-url/"); + String objectId = "test-object-id"; + + CloseableHttpResponse repositoryResponse1 = mock(CloseableHttpResponse.class); + HttpEntity repositoryEntity = mock(HttpEntity.class); + + when(tokenHandler.getHttpClient(any(), any(), any(), eq("TECHNICAL_CREDENTIALS_FLOW"))) + .thenReturn(httpClient); + + when(httpClient.execute(any(HttpGet.class))) + .thenReturn(repositoryResponse1) + .thenThrow(new IOException("Network error on changelog")); + + when(repositoryResponse1.getEntity()).thenReturn(repositoryEntity); + + try (MockedStatic entityUtilsMock = mockStatic(EntityUtils.class)) { + entityUtilsMock + .when(() -> EntityUtils.toString(repositoryEntity)) + .thenReturn(repositoryResponse); + + SDMServiceImpl sdmServiceImpl = new SDMServiceImpl(binding, connectionPool, tokenHandler); + + ServiceException exception = + assertThrows( + ServiceException.class, + () -> { + sdmServiceImpl.getChangeLog(objectId, sdmCredentials, true); + }); + + assertEquals(SDMConstants.ATTACHMENT_NOT_FOUND, exception.getMessage()); + } + } + @Test public void testEditLink_technicalUserFlow() throws IOException { String mockResponseBody = "{\"succinctProperties\": {\"cmis:objectId\": \"objectId\"}}"; 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 151c6463..a35bf3f7 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 @@ -103,6 +103,262 @@ void tearDown() { sdmUtilsMock.close(); } + @Test + void testChangelogSuccess() throws IOException { + // Arrange + AttachmentLogContext mockLogContext = mock(AttachmentLogContext.class); + CqnAnalyzer mockCqnAnalyzer = mock(CqnAnalyzer.class); + AnalysisResult mockAnalysisResult = mock(AnalysisResult.class); + UserInfo mockUserInfo = mock(UserInfo.class); + CdsEntity mockTarget = mock(CdsEntity.class); + + Map targetKeys = new HashMap<>(); + targetKeys.put("ID", "test-id-123"); + + JSONObject mockChangeLogResult = new JSONObject(); + mockChangeLogResult.put("changes", "change data"); + mockChangeLogResult.put("version", "1.0"); + + cmisDocument.setFileName("test-document.pdf"); + cmisDocument.setObjectId("object-123"); + + // Mock the context + when(mockLogContext.getModel()).thenReturn(cdsModel); + when(mockLogContext.getTarget()).thenReturn(mockTarget); + when(mockTarget.getQualifiedName()).thenReturn("MyService.MyEntity.attachments"); + when(mockLogContext.get("cqn")).thenReturn(cqnSelect); + when(mockLogContext.getUserInfo()).thenReturn(mockUserInfo); + when(mockUserInfo.isSystemUser()).thenReturn(false); + + // Mock the model and entity + when(cdsModel.findEntity("MyService.MyEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + + // Mock CqnAnalyzer + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(mockCqnAnalyzer); + when(mockCqnAnalyzer.analyze(cqnSelect)).thenReturn(mockAnalysisResult); + when(mockAnalysisResult.targetKeyValues()).thenReturn(targetKeys); + + // Mock DB query + when(dbQuery.getObjectIdForAttachmentID(draftEntity, persistenceService, "test-id-123")) + .thenReturn(cmisDocument); + + // Mock token handler + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + // Mock SDM service + when(sdmService.getChangeLog("object-123", sdmCredentials, false)) + .thenReturn(mockChangeLogResult); + + // Act + sdmServiceGenericHandler.changelog(mockLogContext); + + // Assert + verify(mockLogContext) + .setResult( + argThat( + result -> { + JSONObject jsonResult = (JSONObject) result; + return jsonResult.has("filename") + && "test-document.pdf".equals(jsonResult.getString("filename")) + && jsonResult.has("changes") + && jsonResult.has("version"); + })); + + verify(dbQuery).getObjectIdForAttachmentID(draftEntity, persistenceService, "test-id-123"); + verify(tokenHandler).getSDMCredentials(); + verify(sdmService).getChangeLog("object-123", sdmCredentials, false); + } + + @Test + void testChangelogWithSystemUser() throws IOException { + // Arrange + AttachmentLogContext mockLogContext = mock(AttachmentLogContext.class); + CqnAnalyzer mockCqnAnalyzer = mock(CqnAnalyzer.class); + AnalysisResult mockAnalysisResult = mock(AnalysisResult.class); + UserInfo mockUserInfo = mock(UserInfo.class); + CdsEntity mockTarget = mock(CdsEntity.class); + + Map targetKeys = new HashMap<>(); + targetKeys.put("ID", "system-id-456"); + + JSONObject mockChangeLogResult = new JSONObject(); + mockChangeLogResult.put("systemChanges", "system change data"); + + cmisDocument.setFileName("system-document.pdf"); + cmisDocument.setObjectId("system-object-456"); + + // Mock the context + when(mockLogContext.getModel()).thenReturn(cdsModel); + when(mockLogContext.getTarget()).thenReturn(mockTarget); + when(mockTarget.getQualifiedName()).thenReturn("SystemService.SystemEntity.attachments"); + when(mockLogContext.get("cqn")).thenReturn(cqnSelect); + when(mockLogContext.getUserInfo()).thenReturn(mockUserInfo); + when(mockUserInfo.isSystemUser()).thenReturn(true); + + // Mock the model and entity + when(cdsModel.findEntity("SystemService.SystemEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + + // Mock CqnAnalyzer + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(mockCqnAnalyzer); + when(mockCqnAnalyzer.analyze(cqnSelect)).thenReturn(mockAnalysisResult); + when(mockAnalysisResult.targetKeyValues()).thenReturn(targetKeys); + + // Mock DB query + when(dbQuery.getObjectIdForAttachmentID(draftEntity, persistenceService, "system-id-456")) + .thenReturn(cmisDocument); + + // Mock token handler + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + // Mock SDM service + when(sdmService.getChangeLog("system-object-456", sdmCredentials, true)) + .thenReturn(mockChangeLogResult); + + // Act + sdmServiceGenericHandler.changelog(mockLogContext); + + // Assert + verify(mockLogContext) + .setResult( + argThat( + result -> { + JSONObject jsonResult = (JSONObject) result; + return jsonResult.has("filename") + && "system-document.pdf".equals(jsonResult.getString("filename")) + && jsonResult.has("systemChanges"); + })); + + verify(sdmService).getChangeLog("system-object-456", sdmCredentials, true); + } + + @Test + void testChangelogEntityNotFound() throws IOException { + // Arrange + AttachmentLogContext mockLogContext = mock(AttachmentLogContext.class); + CdsEntity mockTarget = mock(CdsEntity.class); + + when(mockLogContext.getModel()).thenReturn(cdsModel); + when(mockLogContext.getTarget()).thenReturn(mockTarget); + when(mockTarget.getQualifiedName()).thenReturn("NonExistent.Entity.attachments"); + + // Mock entity not found + when(cdsModel.findEntity("NonExistent.Entity.attachments_drafts")).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows( + RuntimeException.class, + () -> { + sdmServiceGenericHandler.changelog(mockLogContext); + }); + } + + @Test + void testChangelogServiceException() throws IOException { + // Arrange + AttachmentLogContext mockLogContext = mock(AttachmentLogContext.class); + CqnAnalyzer mockCqnAnalyzer = mock(CqnAnalyzer.class); + AnalysisResult mockAnalysisResult = mock(AnalysisResult.class); + UserInfo mockUserInfo = mock(UserInfo.class); + CdsEntity mockTarget = mock(CdsEntity.class); + + Map targetKeys = new HashMap<>(); + targetKeys.put("ID", "error-id-789"); + + cmisDocument.setFileName("error-document.pdf"); + cmisDocument.setObjectId("error-object-789"); + + // Mock the context + when(mockLogContext.getModel()).thenReturn(cdsModel); + when(mockLogContext.getTarget()).thenReturn(mockTarget); + when(mockTarget.getQualifiedName()).thenReturn("ErrorService.ErrorEntity.attachments"); + when(mockLogContext.get("cqn")).thenReturn(cqnSelect); + when(mockLogContext.getUserInfo()).thenReturn(mockUserInfo); + when(mockUserInfo.isSystemUser()).thenReturn(false); + + // Mock the model and entity + when(cdsModel.findEntity("ErrorService.ErrorEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + + // Mock CqnAnalyzer + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(mockCqnAnalyzer); + when(mockCqnAnalyzer.analyze(cqnSelect)).thenReturn(mockAnalysisResult); + when(mockAnalysisResult.targetKeyValues()).thenReturn(targetKeys); + + // Mock DB query + when(dbQuery.getObjectIdForAttachmentID(draftEntity, persistenceService, "error-id-789")) + .thenReturn(cmisDocument); + + // Mock token handler + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + // Mock SDM service to throw ServiceException (runtime exception) + when(sdmService.getChangeLog("error-object-789", sdmCredentials, false)) + .thenThrow(new ServiceException("Network error")); + + // Act & Assert + assertThrows( + ServiceException.class, + () -> { + sdmServiceGenericHandler.changelog(mockLogContext); + }); + + verify(sdmService).getChangeLog("error-object-789", sdmCredentials, false); + } + + @Test + void testChangelogWithNullObjectId() throws IOException { + // Arrange + AttachmentLogContext mockLogContext = mock(AttachmentLogContext.class); + CqnAnalyzer mockCqnAnalyzer = mock(CqnAnalyzer.class); + AnalysisResult mockAnalysisResult = mock(AnalysisResult.class); + UserInfo mockUserInfo = mock(UserInfo.class); + CdsEntity mockTarget = mock(CdsEntity.class); + + Map targetKeys = new HashMap<>(); + targetKeys.put("ID", "null-object-id"); + + CmisDocument nullObjectIdDocument = new CmisDocument(); + nullObjectIdDocument.setFileName("null-object-document.pdf"); + nullObjectIdDocument.setObjectId(null); + + // Mock the context + when(mockLogContext.getModel()).thenReturn(cdsModel); + when(mockLogContext.getTarget()).thenReturn(mockTarget); + when(mockTarget.getQualifiedName()).thenReturn("NullService.NullEntity.attachments"); + when(mockLogContext.get("cqn")).thenReturn(cqnSelect); + when(mockLogContext.getUserInfo()).thenReturn(mockUserInfo); + when(mockUserInfo.isSystemUser()).thenReturn(false); + + // Mock the model and entity + when(cdsModel.findEntity("NullService.NullEntity.attachments_drafts")) + .thenReturn(Optional.of(draftEntity)); + + // Mock CqnAnalyzer + cqnAnalyzerMock.when(() -> CqnAnalyzer.create(cdsModel)).thenReturn(mockCqnAnalyzer); + when(mockCqnAnalyzer.analyze(cqnSelect)).thenReturn(mockAnalysisResult); + when(mockAnalysisResult.targetKeyValues()).thenReturn(targetKeys); + + // Mock DB query to return document with null objectId + when(dbQuery.getObjectIdForAttachmentID(draftEntity, persistenceService, "null-object-id")) + .thenReturn(nullObjectIdDocument); + + // Mock token handler + when(tokenHandler.getSDMCredentials()).thenReturn(sdmCredentials); + + // Mock SDM service + when(sdmService.getChangeLog(null, sdmCredentials, false)) + .thenThrow(new IllegalArgumentException("ObjectId cannot be null")); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> { + sdmServiceGenericHandler.changelog(mockLogContext); + }); + } + @Test void testCopyAttachments_shouldCopyAttachment() throws IOException { when(mockContext.get("up__ID")).thenReturn("123"); diff --git a/sdm/src/test/java/unit/com/sap/cds/sdm/utilities/SDMUtilsTest.java b/sdm/src/test/java/unit/com/sap/cds/sdm/utilities/SDMUtilsTest.java index 6d839397..599b60fa 100644 --- a/sdm/src/test/java/unit/com/sap/cds/sdm/utilities/SDMUtilsTest.java +++ b/sdm/src/test/java/unit/com/sap/cds/sdm/utilities/SDMUtilsTest.java @@ -155,7 +155,7 @@ public void prepareSecondaryPropertiesTest_withFilenameKey() { Map secondaryProperties = new HashMap<>(); secondaryProperties.put("filename", "myfile.txt"); - SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, "myfile.txt"); + SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, true); assertEquals("cmis:name", requestBody.get("propertyId[1]")); assertEquals("myfile.txt", requestBody.get("propertyValue[1]")); @@ -168,7 +168,7 @@ public void testPrepareSecondaryProperties_withOtherKeys() { secondaryProperties.put("author", "test user"); secondaryProperties.put("subject", "JUnit Testing"); - SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, "testfile.txt"); + SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, true); assertEquals("author", requestBody.get("propertyId[1]")); assertEquals("test user", requestBody.get("propertyValue[1]")); @@ -181,7 +181,7 @@ public void testPrepareSecondaryProperties_emptySecondaryProperties() { Map requestBody = new HashMap<>(); Map secondaryProperties = new HashMap<>(); - SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, "emptyfile.txt"); + SDMUtils.prepareSecondaryProperties(requestBody, secondaryProperties, true); assertTrue(requestBody.isEmpty()); }