From 618509120b91c8826fba418387fa53ca6ece46c3 Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:42:05 +0530 Subject: [PATCH 1/7] move attachments Integration tests --- .../java/integration/com/sap/cds/sdm/Api.java | 61 + .../com/sap/cds/sdm/ApiInterface.java | 10 + .../integration/com/sap/cds/sdm/ApiMT.java | 58 + .../sdm/IntegrationTest_MultipleFacet.java | 1669 ++++++++++++++- .../cds/sdm/IntegrationTest_SingleFacet.java | 1790 ++++++++++++++++- 5 files changed, 3575 insertions(+), 13 deletions(-) diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/Api.java b/sdm/src/test/java/integration/com/sap/cds/sdm/Api.java index f1e9b84f..10829e16 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/Api.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/Api.java @@ -728,6 +728,67 @@ public String copyAttachment( } } + public Map moveAttachment( + String appUrl, + String entityName, + String facetName, + String targetEntityID, + String sourceFolderId, + List objectIds, + String sourceFacet) + throws IOException { + String objectIdsString = String.join(",", objectIds); + String url = + "https://" + + appUrl + + "/odata/v4/" + + serviceName + + "/" + + entityName + + "(ID=" + + targetEntityID + + ",IsActiveEntity=false)/" + + facetName + + "/" + + serviceName + + ".moveAttachments"; + + MediaType mediaType = MediaType.parse("application/json"); + + StringBuilder jsonPayload = new StringBuilder(); + jsonPayload.append("{"); + jsonPayload.append("\"sourceFolderId\": \"").append(sourceFolderId).append("\","); + jsonPayload.append("\"up__ID\": \"").append(targetEntityID).append("\","); + jsonPayload.append("\"objectIds\": \"").append(objectIdsString).append("\""); + + if (sourceFacet != null && !sourceFacet.isEmpty()) { + jsonPayload.append(",\"sourceFacet\": \"").append(sourceFacet).append("\""); + } + + jsonPayload.append("}"); + + RequestBody body = RequestBody.create(jsonPayload.toString(), mediaType); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + String responseBody = response.body().string(); + + if (!response.isSuccessful()) { + throw new IOException( + "Could not move attachments: " + response.code() + " - " + responseBody); + } + + @SuppressWarnings("unchecked") + Map result = objectMapper.readValue(responseBody, Map.class); + return result; + } catch (IOException e) { + System.out.println("Error while moving attachments: " + e.getMessage()); + throw new IOException(e); + } + } + public String createLink( String appUrl, String entityName, diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/ApiInterface.java b/sdm/src/test/java/integration/com/sap/cds/sdm/ApiInterface.java index dcd40636..333328a6 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/ApiInterface.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/ApiInterface.java @@ -66,6 +66,16 @@ public String copyAttachment( List sourceObjectIds) throws IOException; + public Map moveAttachment( + String appUrl, + String entityName, + String facetName, + String targetEntityID, + String sourceFolderId, + List objectIds, + String sourceFacet) + throws IOException; + public Map fetchMetadata( String appUrl, String entityName, String facetName, String entityID, String ID) throws IOException; diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/ApiMT.java b/sdm/src/test/java/integration/com/sap/cds/sdm/ApiMT.java index 872a8999..4fc5d083 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/ApiMT.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/ApiMT.java @@ -687,6 +687,64 @@ public String copyAttachment( } } + public Map moveAttachment( + String appUrl, + String entityName, + String facetName, + String targetEntityID, + String sourceFolderId, + List objectIds, + String sourceFacet) + throws IOException { + String objectIdsString = String.join(",", objectIds); + String url = + "https://" + + appUrl + + "/api/admin/" + + entityName + + "(ID=" + + targetEntityID + + ",IsActiveEntity=false)/" + + facetName + + "/" + + "AdminService.moveAttachments"; + + MediaType mediaType = MediaType.parse("application/json"); + + StringBuilder jsonPayload = new StringBuilder(); + jsonPayload.append("{"); + jsonPayload.append("\"sourceFolderId\": \"").append(sourceFolderId).append("\","); + jsonPayload.append("\"up__ID\": \"").append(targetEntityID).append("\","); + jsonPayload.append("\"objectIds\": \"").append(objectIdsString).append("\""); + + if (sourceFacet != null && !sourceFacet.isEmpty()) { + jsonPayload.append(",\"sourceFacet\": \"").append(sourceFacet).append("\""); + } + + jsonPayload.append("}"); + + RequestBody body = RequestBody.create(jsonPayload.toString(), mediaType); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + String responseBody = response.body().string(); + + if (!response.isSuccessful()) { + throw new IOException( + "Could not move attachments: " + response.code() + " - " + responseBody); + } + + @SuppressWarnings("unchecked") + Map result = objectMapper.readValue(responseBody, Map.class); + return result; + } catch (IOException e) { + System.out.println("Error while moving attachments: " + e.getMessage()); + throw new IOException(e); + } + } + public String createLink( String appUrl, String entityName, 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 10241a57..c8bf7f93 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 @@ -7,14 +7,8 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import okhttp3.*; -import okio.ByteString; -import org.json.JSONObject; import org.junit.jupiter.api.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -64,6 +58,10 @@ class IntegrationTest_MultipleFacet { private static List successfullyRenamedAttachments = new ArrayList<>(); private static String[] changelogEntityID = new String[3]; private static String[] changelogAttachmentID = new String[3]; + private static String moveSourceEntity; + private static String moveTargetEntity; + private static List moveObjectIds = new ArrayList<>(); + private static String moveSourceFolderId; @BeforeAll static void setup() throws IOException { @@ -5163,4 +5161,1663 @@ void testChangelogForSavedAttachmentWithoutModification() throws IOException { api.deleteEntity(appUrl, entityName, newEntityID); } } + + @Test + @Order(65) + void testMoveAttachmentsWithSourceFacet() throws IOException { + System.out.println( + "Test (65): Move attachments from Source Entity to Target Entity with sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + files.add(new File(classLoader.getResource("WDIRSCodeList.csv").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity"); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move"); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals( + sourceAttachmentIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all attachments after move"); + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + 0, sourceMetadataAfterMove.size(), "Source entity should have no attachments after move"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(66) + public void testMoveAttachmentsToEntityWithDuplicateWithSourceFacet() throws Exception { + System.out.println( + "Test (66): Move attachments to entity with duplicate attachment with sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity"); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + Map targetPostData = new HashMap<>(); + targetPostData.put("up__ID", moveTargetEntity); + targetPostData.put("mimeType", "application/pdf"); + targetPostData.put("createdAt", new Date().toString()); + targetPostData.put("createdBy", "test@test.com"); + targetPostData.put("modifiedBy", "test@test.com"); + + File duplicateFile = new File(classLoader.getResource("sample.pdf").getFile()); + List targetCreateResponse = + api.createAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + srvpath, + targetPostData, + duplicateFile); + + if (!targetCreateResponse.get(0).equals("Attachment created")) { + fail("Could not create attachment on target entity"); + } + + String saveTargetBeforeMoveResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetBeforeMoveResponse.equals("Saved")) { + fail("Could not save target entity before move"); + } + + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + int targetCountBeforeMove = targetMetadataBeforeMove.size(); + + String editTargetResponse = + api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveMoveResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveMoveResponse.equals("Saved")) { + fail("Could not save target entity after move"); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + + int expectedTargetCount = targetCountBeforeMove + (sourceAttachmentIds.size() - 1); + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target should have duplicate skipped, other attachments moved"); + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + int expectedSourceCount = + sourceAttachmentIds.size() - (targetMetadataAfterMove.size() - targetCountBeforeMove); + assertEquals( + expectedSourceCount, + sourceMetadataAfterMove.size(), + "Source should have duplicate attachment remaining"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(67) + public void testMoveAttachmentsWithNotesAndSecondaryProperties() throws Exception { + System.out.println( + "Test (67): Move attachments with notes and secondary properties with sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String notesValue = "Test note for verification"; + MediaType mediaType = MediaType.parse("application/json"); + String jsonPayload = "{\"note\": \"" + notesValue + "\"}"; + RequestBody updateNotesBody = RequestBody.create(jsonPayload, mediaType); + + for (String attachmentId : sourceAttachmentIds) { + String updateNotesResponse = + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, attachmentId, updateNotesBody); + if (!updateNotesResponse.equals("Updated")) { + fail("Could not update notes for attachment: " + attachmentId); + } + } + + Integer customProperty2Value = 54321; + RequestBody bodyInt = + RequestBody.create( + "{\"customProperty2\": " + customProperty2Value + "}", + MediaType.parse("application/json")); + + for (String attachmentId : sourceAttachmentIds) { + String updateCustomPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, attachmentId, bodyInt); + if (!updateCustomPropertyResponse.equals("Updated")) { + fail("Could not update custom property for attachment: " + attachmentId); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals( + sourceAttachmentIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all attachments after move"); + + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveTargetEntity, targetAttachmentId); + + if (detailedMetadata.containsKey("note")) { + assertEquals( + notesValue, + detailedMetadata.get("note"), + "Notes should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Notes property missing after move for attachment: " + targetAttachmentId); + } + + if (detailedMetadata.containsKey("customProperty2")) { + assertEquals( + customProperty2Value, + detailedMetadata.get("customProperty2"), + "Custom property should be preserved after move for attachment: " + + targetAttachmentId); + } else { + fail("Custom property missing after move for attachment: " + targetAttachmentId); + } + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + 0, sourceMetadataAfterMove.size(), "Source entity has no attachments after move"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(68) + public void testMoveAttachmentsWithoutSourceFacet() throws Exception { + System.out.println( + "Test (68): Move valid attachments from Source Entity to Target Entity without sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } else { + fail("Attachment metadata does not contain objectId"); + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals( + moveObjectIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all moved attachments"); + + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + String readResponse = + api.readAttachment(appUrl, entityName, facet[i], moveTargetEntity, targetAttachmentId); + if (!readResponse.equals("OK")) { + fail("Could not read moved attachment from target entity"); + } + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + moveObjectIds.size(), + sourceMetadataAfterMove.size(), + "Source entity should still have attachments in UI when sourceFacet is not specified"); + + for (Map metadata : sourceMetadataAfterMove) { + String objectId = (String) metadata.get("objectId"); + assertTrue( + moveObjectIds.contains(objectId), + "Source entity should still show attachment with objectId: " + objectId); + } + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(69) + public void testMoveAttachmentsToEntityWithDuplicateWithoutSourceFacet() throws Exception { + System.out.println( + "Test (69): Move attachments into existing Target Entity when duplicate exists without sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } else { + fail("Attachment metadata does not contain objectId"); + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + Map targetPostData = new HashMap<>(); + targetPostData.put("up__ID", moveTargetEntity); + targetPostData.put("mimeType", "application/pdf"); + targetPostData.put("createdAt", new Date().toString()); + targetPostData.put("createdBy", "test@test.com"); + targetPostData.put("modifiedBy", "test@test.com"); + + List createTargetResponse = + api.createAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + srvpath, + targetPostData, + files.get(0)); + if (!createTargetResponse.get(0).equals("Attachment created")) { + fail("Could not create duplicate attachment in target entity"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity: " + saveTargetResponse); + } + + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + int initialTargetCount = targetMetadataBeforeMove.size(); + + String editTargetResponse = + api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + + int nonDuplicateCount = moveObjectIds.size() - 1; + int expectedTargetCount = initialTargetCount + nonDuplicateCount; + + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target entity should have initial attachments plus non-duplicate moved attachments"); + + assertTrue( + targetMetadataAfterMove.size() > initialTargetCount, + "Target should have more attachments after move (non-duplicates added)"); + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + moveObjectIds.size(), + sourceMetadataAfterMove.size(), + "Source entity should still have all attachments in UI when sourceFacet is not specified"); + + List sourceObjectIds = new ArrayList<>(); + for (Map metadata : sourceMetadataAfterMove) { + sourceObjectIds.add((String) metadata.get("objectId")); + } + for (String objectId : moveObjectIds) { + assertTrue( + sourceObjectIds.contains(objectId), + "Source entity should still show attachment with objectId: " + objectId); + } + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(70) + public void testMoveAttachmentsWithNotesAndSecondaryPropertiesWithoutSourceFacet() + throws Exception { + System.out.println( + "Test (70): Move attachments with notes and secondary properties without sourceFacet"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String notesValue = "Test note for migration verification"; + MediaType mediaType = MediaType.parse("application/json"); + String jsonPayload = "{\"note\": \"" + notesValue + "\"}"; + RequestBody updateNotesBody = RequestBody.create(jsonPayload, mediaType); + + for (String attachmentId : sourceAttachmentIds) { + String updateNotesResponse = + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, attachmentId, updateNotesBody); + if (!updateNotesResponse.equals("Updated")) { + fail("Could not update notes for attachment: " + attachmentId); + } + } + + Integer customProperty2Value = 54321; + RequestBody bodyInt = + RequestBody.create( + "{\"customProperty2\": " + customProperty2Value + "}", + MediaType.parse("application/json")); + + for (String attachmentId : sourceAttachmentIds) { + String updateCustomPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, attachmentId, bodyInt); + if (!updateCustomPropertyResponse.equals("Updated")) { + fail("Could not update custom property for attachment: " + attachmentId); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + List> sourceMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + int sourceCountBeforeMove = sourceMetadataBeforeMove.size(); + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + int targetCountBeforeMove = targetMetadataBeforeMove.size(); + + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + int expectedTargetCount = targetCountBeforeMove + sourceAttachmentIds.size(); + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target entity should have " + expectedTargetCount + " attachments after move"); + + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveTargetEntity, targetAttachmentId); + + if (detailedMetadata.containsKey("note")) { + assertEquals( + notesValue, + detailedMetadata.get("note"), + "Notes should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Notes property missing after move for attachment: " + targetAttachmentId); + } + + if (detailedMetadata.containsKey("customProperty2")) { + assertEquals( + customProperty2Value, + detailedMetadata.get("customProperty2"), + "Custom property should be preserved after move for attachment: " + + targetAttachmentId); + } else { + fail("Custom property missing after move for attachment: " + targetAttachmentId); + } + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + sourceCountBeforeMove, + sourceMetadataAfterMove.size(), + "Source entity should still have " + + sourceCountBeforeMove + + " attachments (without sourceFacet)"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(71) + public void testMoveAttachmentsWithInvalidOrUndefinedSecondaryProperties() throws Exception { + System.out.println( + "Test (71): Move attachments with invalid or undefined secondary properties"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + files.add(new File(classLoader.getResource("WDIRSCodeList.csv").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String validAttachmentId = sourceAttachmentIds.get(0); + Integer validCustomProperty2Value = 12345; + RequestBody validPropertyBody = + RequestBody.create( + "{\"customProperty2\": " + validCustomProperty2Value + "}", + MediaType.parse("application/json")); + + String validPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, validAttachmentId, validPropertyBody); + if (!validPropertyResponse.equals("Updated")) { + fail("Could not update valid property for attachment: " + validAttachmentId); + } + + String invalidAttachmentId = sourceAttachmentIds.get(1); + RequestBody invalidPropertyBody = + RequestBody.create( + "{\"nonExistentProperty\": \"invalid\"}", MediaType.parse("application/json")); + + api.updateSecondaryProperty( + appUrl, entityName, facet[i], moveSourceEntity, invalidAttachmentId, invalidPropertyBody); + + String undefinedAttachmentId = sourceAttachmentIds.get(2); + RequestBody undefinedPropertyBody = + RequestBody.create( + "{\"undefinedField\": \"test\", \"anotherUndefined\": 999}", + MediaType.parse("application/json")); + + api.updateSecondaryProperty( + appUrl, + entityName, + facet[i], + moveSourceEntity, + undefinedAttachmentId, + undefinedPropertyBody); + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + List> sourceMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + int sourceCountBeforeMove = sourceMetadataBeforeMove.size(); + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + + assertTrue( + targetMetadataAfterMove.size() > 0, "Target entity should have attachments after move"); + assertEquals( + sourceCountBeforeMove, + targetMetadataAfterMove.size(), + "All attachments should move (invalid properties are ignored)"); + + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveTargetEntity, targetAttachmentId); + + if (detailedMetadata.containsKey("customProperty2") + && detailedMetadata.get("customProperty2") != null) { + assertEquals( + validCustomProperty2Value, + detailedMetadata.get("customProperty2"), + "Valid customProperty2 should be preserved"); + } + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterMove.size(), + "Source entity should have no attachments after move with sourceFacet"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(72) + public void testMoveAttachmentsFromSourceEntityInDraftMode() throws Exception { + System.out.println( + "Test (72): Move attachments from Source Entity when Source Entity is in draft mode"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + files.add(new File(classLoader.getResource("WDIRSCodeList.csv").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + int sourceCountBeforeMove = sourceAttachmentIds.size(); + assertTrue(sourceCountBeforeMove > 0, "Source entity should have attachments before move"); + assertEquals( + files.size(), + sourceCountBeforeMove, + "Source should have " + files.size() + " attachments"); + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + String editSourceResponse = + api.editEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!editSourceResponse.equals("Entity in draft mode")) { + fail("Could not edit source entity back to draft mode"); + } + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity: " + saveTargetResponse); + } + + String editTargetResponse = + api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + facet[i]); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertTrue( + targetMetadataAfterMove.size() > 0, "Target entity should have attachments after move"); + assertEquals( + sourceCountBeforeMove, + targetMetadataAfterMove.size(), + "Target should have " + sourceCountBeforeMove + " attachments after move"); + + Set targetFileNames = + targetMetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + targetFileNames.contains(file.getName()), + "Target should contain attachment: " + file.getName()); + } + + String saveSourceAfterMoveResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceAfterMoveResponse.equals("Saved")) { + fail("Could not save source entity after move: " + saveSourceAfterMoveResponse); + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + sourceCountBeforeMove, + sourceMetadataAfterMove.size(), + "Source entity in draft mode retains attachments after move (copy behavior)"); + + Set sourceFileNamesAfterMove = + sourceMetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + sourceFileNamesAfterMove.contains(file.getName()), + "Source (draft) should still contain attachment: " + file.getName()); + } + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(73) + public void testEditAttachmentFileNameAndMoveToTarget() throws Exception { + System.out.println( + "Test (73): Edit attachment file name in Source Entity and move it to Target Entity"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + File originalFile = new File(classLoader.getResource("sample.txt").getFile()); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "text/plain"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, originalFile); + if (!createResponse.get(0).equals("Attachment created")) { + fail("Could not create attachment in source entity"); + } + + String attachmentId = createResponse.get(1); + assertNotNull(attachmentId, "Attachment ID should not be null"); + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + List> metadataBeforeRename = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals(1, metadataBeforeRename.size(), "Source should have 1 attachment"); + assertEquals( + "sample.txt", + metadataBeforeRename.get(0).get("fileName"), + "Original filename should be sample.txt"); + + String editSourceResponse = + api.editEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!editSourceResponse.equals("Entity in draft mode")) { + fail("Could not edit source entity to draft mode"); + } + + String newFileName = "testEdited.txt"; + String renameResponse = + api.renameAttachment( + appUrl, entityName, facet[i], moveSourceEntity, attachmentId, newFileName); + assertEquals("Renamed", renameResponse, "Attachment should be renamed successfully"); + + saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity after rename: " + saveSourceResponse); + } + + List> metadataAfterRename = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals(1, metadataAfterRename.size(), "Source should still have 1 attachment"); + assertEquals( + newFileName, + metadataAfterRename.get(0).get("fileName"), + "Filename should be updated to " + newFileName); + + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + String objectId = metadata.get("objectId").toString(); + moveSourceFolderId = metadata.get("folderId").toString(); + assertNotNull(objectId, "Object ID should not be null"); + assertNotNull(moveSourceFolderId, "Folder ID should not be null"); + + moveObjectIds = new ArrayList<>(); + moveObjectIds.add(objectId); + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + String saveTargetResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals(1, targetMetadataAfterMove.size(), "Target should have 1 attachment after move"); + assertEquals( + newFileName, + targetMetadataAfterMove.get(0).get("fileName"), + "Target should have attachment with renamed filename: " + newFileName); + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterMove.size(), + "Source entity should have no attachments after move with sourceFacet"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(74) + public void testChainMoveAttachmentsFromSourceToTarget1ToTarget2() throws Exception { + System.out.println( + "Test (74): Move attachments from Source Entity to Target Entity 1 and then to Target Entity 2"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + int sourceCountInitial = sourceAttachmentIds.size(); + assertTrue(sourceCountInitial > 0, "Source should have attachments"); + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity 1"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult1 = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult1 == null) { + fail("Move operation from source to target 1 returned null result"); + } + + String saveTarget1Response = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTarget1Response.equals("Saved")) { + fail("Could not save target entity 1 after move: " + saveTarget1Response); + } + + List> target1MetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertTrue( + target1MetadataAfterMove.size() > 0, + "Target entity 1 should have attachments after move"); + assertEquals( + sourceCountInitial, + target1MetadataAfterMove.size(), + "Target 1 should have " + sourceCountInitial + " attachments"); + + Set target1FileNames = + target1MetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + target1FileNames.contains(file.getName()), + "Target 1 should contain attachment: " + file.getName()); + } + + List> sourceMetadataAfterFirstMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterFirstMove.size(), + "Source entity should have no attachments after move to target 1"); + + String moveTargetEntity2 = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity2.equals("Could not create entity")) { + fail("Could not create target entity 2"); + } + + List target1AttachmentIds = new ArrayList<>(); + for (Map metadata : target1MetadataAfterMove) { + String attachmentId = metadata.get("ID").toString(); + target1AttachmentIds.add(attachmentId); + } + + moveObjectIds = new ArrayList<>(); + String target1FolderId = null; + for (String attachmentId : target1AttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveTargetEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (target1FolderId == null && metadata.containsKey("folderId")) { + target1FolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata from target 1: " + e.getMessage()); + } + } + + assertNotNull(target1FolderId, "Target 1 folder ID should not be null"); + + Map moveResult2 = + api.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity2, + target1FolderId, + moveObjectIds, + sourceFacet); + + if (moveResult2 == null) { + fail("Move operation from target 1 to target 2 returned null result"); + } + + String saveTarget2Response = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity2); + if (!saveTarget2Response.equals("Saved")) { + fail("Could not save target entity 2 after move: " + saveTarget2Response); + } + + List> target2MetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity2); + assertTrue( + target2MetadataAfterMove.size() > 0, + "Target entity 2 should have attachments after move"); + assertEquals( + sourceCountInitial, + target2MetadataAfterMove.size(), + "Target 2 should have " + sourceCountInitial + " attachments"); + + Set target2FileNames = + target2MetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + target2FileNames.contains(file.getName()), + "Target 2 should contain attachment: " + file.getName()); + } + + List> target1MetadataAfterSecondMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals( + 0, + target1MetadataAfterSecondMove.size(), + "Target entity 1 should have no attachments after move to target 2"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity2); + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } + + @Test + @Order(75) + public void testMoveAttachmentsWithoutSDMRole() throws Exception { + System.out.println("Test (75): Move attachments when user does not have SDM Role"); + + for (int i = 0; i < facet.length; i++) { + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facet[i], moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + String saveSourceResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + int sourceCountInitial = sourceAttachmentIds.size(); + assertTrue(sourceCountInitial > 0, "Source should have attachments"); + + moveObjectIds = new ArrayList<>(); + moveSourceFolderId = null; + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facet[i], moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + moveTargetEntity = apiNoRoles.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity with no SDM role"); + } + + String sourceFacet = serviceName + "." + entityName + "." + facet[i]; + Map moveResult = null; + boolean moveOperationFailed = false; + String errorMessage = null; + + try { + moveResult = + apiNoRoles.moveAttachment( + appUrl, + entityName, + facet[i], + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + moveOperationFailed = true; + errorMessage = "Move operation returned null"; + } else if (moveResult.containsKey("error")) { + moveOperationFailed = true; + errorMessage = moveResult.get("error").toString(); + } + } catch (Exception e) { + moveOperationFailed = true; + errorMessage = e.getMessage(); + } + + assertTrue( + moveOperationFailed, "Move operation should fail when user does not have SDM role"); + assertNotNull(errorMessage, "Error message should be present when move operation fails"); + System.out.println("Move operation failed as expected. Error: " + errorMessage); + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveSourceEntity); + assertEquals( + sourceCountInitial, + sourceMetadataAfterMove.size(), + "Source should still have all attachments after failed move"); + + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facet[i], moveTargetEntity); + assertEquals( + 0, targetMetadataAfterMove.size(), "Target should have no attachments after failed move"); + + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + } } 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 516380ff..67accbd6 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 @@ -2,19 +2,12 @@ import static org.junit.jupiter.api.Assertions.*; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import okhttp3.*; -import okio.ByteString; -import org.json.JSONObject; import org.junit.jupiter.api.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @@ -66,6 +59,10 @@ class IntegrationTest_SingleFacet { private static String editLinkEntity; private static List sourceObjectIds = new ArrayList<>(); private static List targetAttachmentIds = new ArrayList<>(); + private static String moveSourceEntity; + private static String moveTargetEntity; + private static List moveObjectIds = new ArrayList<>(); + private static String moveSourceFolderId; private static IntegrationTestUtils integrationTestUtils; @@ -4576,4 +4573,1783 @@ void testChangelogForSavedAttachmentWithoutModification() throws IOException { // Clean up the new entity api.deleteEntity(appUrl, entityName, newEntityID); } + + @Test + @Order(65) + void testMoveAttachmentsWithSourceFacet() throws IOException { + System.out.println( + "Test (65): Move attachments from Source Entity to Target Entity with sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } else { + fail("Attachment metadata does not contain objectId"); + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Move attachments from source to target with sourceFacet + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // All attachments moved to target entity in SDM & UI + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals( + sourceAttachmentIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all attachments after move"); + + // Verify attachments can be read from target entity + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + String readResponse = + api.readAttachment(appUrl, entityName, facetName, moveTargetEntity, targetAttachmentId); + if (!readResponse.equals("OK")) { + fail("Could not read moved attachment from target entity"); + } + } + + // All attachments removed from source entity in SDM & UI + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + 0, sourceMetadataAfterMove.size(), "Source entity should have 0 attachments after move"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(66) + public void testMoveAttachmentsToEntityWithDuplicateWithSourceFacet() throws Exception { + System.out.println( + "Test (66): Move attachments to entity with duplicate attachment with sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + // Create target entity and add attachment + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + Map targetPostData = new HashMap<>(); + targetPostData.put("up__ID", moveTargetEntity); + targetPostData.put("mimeType", "application/pdf"); + targetPostData.put("createdAt", new Date().toString()); + targetPostData.put("createdBy", "test@test.com"); + targetPostData.put("modifiedBy", "test@test.com"); + + File duplicateFile = new File(classLoader.getResource("sample.pdf").getFile()); + List targetCreateResponse = + api.createAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + srvpath, + targetPostData, + duplicateFile); + + if (!targetCreateResponse.get(0).equals("Attachment created")) { + fail("Could not create attachment on target entity"); + } + + // Save target entity to persist the attachment + String saveTargetBeforeMoveResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetBeforeMoveResponse.equals("Saved")) { + fail("Could not save target entity before move: " + saveTargetBeforeMoveResponse); + } + + // Fetch target metadata before move (target entity is now saved with 1 attachment) + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + int targetCountBeforeMove = targetMetadataBeforeMove.size(); + + // Edit target entity to put it back in draft mode for move operation + String editTargetResponse = api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + // Move attachments from source to target with sourceFacet + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + "AdminService.Books.attachments"); + + // Save target entity after move + String saveMoveResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveMoveResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveMoveResponse); + } + + // Verify target has duplicate skipped, other attachments moved + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + + // Expected: original attachments + non-duplicate moved attachments + int expectedTargetCount = targetCountBeforeMove + (sourceAttachmentIds.size() - 1); + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target should have duplicate skipped, other attachments moved"); + + // Verify source entity has only the duplicate attachment remaining + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + // Calculate expected source count: number of duplicates that couldn't be moved + int expectedSourceCount = + sourceAttachmentIds.size() - (targetMetadataAfterMove.size() - targetCountBeforeMove); + assertEquals( + expectedSourceCount, + sourceMetadataAfterMove.size(), + "Source should have duplicate attachment remaining"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(67) + public void testMoveAttachmentsWithNotesAndSecondaryProperties() throws Exception { + System.out.println( + "Test (67): Move attachments with notes and secondary properties with sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Add notes to attachments + String notesValue = "Test note for verification"; + MediaType mediaType = MediaType.parse("application/json"); + String jsonPayload = "{\"note\": \"" + notesValue + "\"}"; + RequestBody updateNotesBody = RequestBody.create(jsonPayload, mediaType); + + for (String attachmentId : sourceAttachmentIds) { + String updateNotesResponse = + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, attachmentId, updateNotesBody); + if (!updateNotesResponse.equals("Updated")) { + fail("Could not update notes for attachment: " + attachmentId); + } + } + + // Add custom property to attachments + Integer customProperty2Value = 54321; + RequestBody bodyInt = + RequestBody.create( + "{\"customProperty2\": " + customProperty2Value + "}", + MediaType.parse("application/json")); + + for (String attachmentId : sourceAttachmentIds) { + String updateCustomPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, attachmentId, bodyInt); + if (!updateCustomPropertyResponse.equals("Updated")) { + fail("Could not update custom property for attachment: " + attachmentId); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Move attachments from source to target with sourceFacet + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify all attachments moved to target + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals( + sourceAttachmentIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all attachments after move"); + + // Verify notes and secondary properties are preserved + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facetName, moveTargetEntity, targetAttachmentId); + + // Verify notes are preserved + if (detailedMetadata.containsKey("note")) { + assertEquals( + notesValue, + detailedMetadata.get("note"), + "Notes should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Notes property missing after move for attachment: " + targetAttachmentId); + } + + // Verify custom property is preserved + if (detailedMetadata.containsKey("customProperty2")) { + assertEquals( + customProperty2Value, + detailedMetadata.get("customProperty2"), + "Custom property should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Custom property missing after move for attachment: " + targetAttachmentId); + } + } + + // Verify source entity has no attachments (all moved with sourceFacet) + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals(0, sourceMetadataAfterMove.size(), "Source entity has no attachments after move"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(68) + public void testMoveAttachmentsWithoutSourceFacet() throws Exception { + System.out.println( + "Test (68): Move valid attachments from Source Entity to Target Entity without sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID from first attachment + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } else { + fail("Attachment metadata does not contain objectId"); + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Move attachments without sourceFacet (pass null for sourceFacet parameter) + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify attachments are in target entity + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals( + moveObjectIds.size(), + targetMetadataAfterMove.size(), + "Target entity should have all moved attachments"); + + // Verify attachments can be read from target entity + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + String readResponse = + api.readAttachment(appUrl, entityName, facetName, moveTargetEntity, targetAttachmentId); + if (!readResponse.equals("OK")) { + fail("Could not read moved attachment from target entity"); + } + } + + // Expected Behavior: Attachments remain in source entity UI (without sourceFacet) + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + moveObjectIds.size(), + sourceMetadataAfterMove.size(), + "Source entity should still have attachments in UI when sourceFacet is not specified"); + + // Verify the same objectIds are still visible in source + for (Map metadata : sourceMetadataAfterMove) { + String objectId = (String) metadata.get("objectId"); + assertTrue( + moveObjectIds.contains(objectId), + "Source entity should still show attachment with objectId: " + objectId); + } + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(69) + public void testMoveAttachmentsToEntityWithDuplicateWithoutSourceFacet() throws Exception { + System.out.println( + "Test (69): Move attachments into existing Target Entity when duplicate exists without sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID from first attachment + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } else { + fail("Attachment metadata does not contain objectId"); + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + // Create target entity and add duplicate attachment (sample.pdf) + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Add the same first file (sample.pdf) to target entity to create duplicate + Map targetPostData = new HashMap<>(); + targetPostData.put("up__ID", moveTargetEntity); + targetPostData.put("mimeType", "application/pdf"); + targetPostData.put("createdAt", new Date().toString()); + targetPostData.put("createdBy", "test@test.com"); + targetPostData.put("modifiedBy", "test@test.com"); + + List createTargetResponse = + api.createAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + srvpath, + targetPostData, + files.get(0)); // Add same file (sample.pdf) + if (!createTargetResponse.get(0).equals("Attachment created")) { + fail("Could not create duplicate attachment in target entity"); + } + + // Save target entity before move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity: " + saveTargetResponse); + } + + // Get initial target metadata count + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + int initialTargetCount = targetMetadataBeforeMove.size(); + + // Edit target entity to put it back in draft mode for move operation + String editTargetResponse = api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + // Step 3: Move attachments without sourceFacet (duplicate should be skipped) + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Expected Behavior - Verify duplicate was skipped, other attachments moved + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + + int nonDuplicateCount = moveObjectIds.size() - 1; + int expectedTargetCount = initialTargetCount + nonDuplicateCount; + + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target entity should have initial attachments plus non-duplicate moved attachments"); + + // Verify at least one non-duplicate attachment was moved + assertTrue( + targetMetadataAfterMove.size() > initialTargetCount, + "Target should have more attachments after move (non-duplicates added)"); + + // Verify all attachments still remain in source entity UI (without sourceFacet) + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + moveObjectIds.size(), + sourceMetadataAfterMove.size(), + "Source entity should still have all attachments in UI when sourceFacet is not specified"); + + // Verify all original objectIds are still visible in source + List sourceObjectIds = new ArrayList<>(); + for (Map metadata : sourceMetadataAfterMove) { + sourceObjectIds.add((String) metadata.get("objectId")); + } + for (String objectId : moveObjectIds) { + assertTrue( + sourceObjectIds.contains(objectId), + "Source entity should still show attachment with objectId: " + objectId); + } + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(70) + public void testMoveAttachmentsWithNotesAndSecondaryPropertiesWithoutSourceFacet() + throws Exception { + System.out.println( + "Test (70): Move attachments with notes and secondary properties without sourceFacet"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Add notes to attachments + String notesValue = "Test note for migration verification"; + MediaType mediaType = MediaType.parse("application/json"); + String jsonPayload = "{\"note\": \"" + notesValue + "\"}"; + RequestBody updateNotesBody = RequestBody.create(jsonPayload, mediaType); + + for (String attachmentId : sourceAttachmentIds) { + String updateNotesResponse = + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, attachmentId, updateNotesBody); + if (!updateNotesResponse.equals("Updated")) { + fail("Could not update notes for attachment: " + attachmentId); + } + } + + // Add custom property to attachments + Integer customProperty2Value = 54321; + RequestBody bodyInt = + RequestBody.create( + "{\"customProperty2\": " + customProperty2Value + "}", + MediaType.parse("application/json")); + + for (String attachmentId : sourceAttachmentIds) { + String updateCustomPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, attachmentId, bodyInt); + if (!updateCustomPropertyResponse.equals("Updated")) { + fail("Could not update custom property for attachment: " + attachmentId); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + // Get source attachment count before move + List> sourceMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + int sourceCountBeforeMove = sourceMetadataBeforeMove.size(); + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Get target attachment count before move + List> targetMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + int targetCountBeforeMove = targetMetadataBeforeMove.size(); + + // Move attachments from source to target WITHOUT sourceFacet + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + null); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify expected number of attachments moved to target + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + int expectedTargetCount = targetCountBeforeMove + sourceAttachmentIds.size(); + assertEquals( + expectedTargetCount, + targetMetadataAfterMove.size(), + "Target entity should have " + expectedTargetCount + " attachments after move"); + + // Verify notes and secondary properties are preserved + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facetName, moveTargetEntity, targetAttachmentId); + + // Verify notes are preserved + if (detailedMetadata.containsKey("note")) { + assertEquals( + notesValue, + detailedMetadata.get("note"), + "Notes should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Notes property missing after move for attachment: " + targetAttachmentId); + } + + // Verify custom property is preserved + if (detailedMetadata.containsKey("customProperty2")) { + assertEquals( + customProperty2Value, + detailedMetadata.get("customProperty2"), + "Custom property should be preserved after move for attachment: " + targetAttachmentId); + } else { + fail("Custom property missing after move for attachment: " + targetAttachmentId); + } + } + + // Verify source entity still has all attachments (without sourceFacet) + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + sourceCountBeforeMove, + sourceMetadataAfterMove.size(), + "Source entity should still have " + + sourceCountBeforeMove + + " attachments (without sourceFacet)"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(71) + public void testMoveAttachmentsWithInvalidOrUndefinedSecondaryProperties() throws Exception { + System.out.println( + "Test (71): Move attachments with invalid or undefined secondary properties"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + files.add(new File(classLoader.getResource("WDIRSCodeList.csv").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Add valid secondary properties to first attachment (customProperty2) + String validAttachmentId = sourceAttachmentIds.get(0); + Integer validCustomProperty2Value = 12345; + RequestBody validPropertyBody = + RequestBody.create( + "{\"customProperty2\": " + validCustomProperty2Value + "}", + MediaType.parse("application/json")); + + String validPropertyResponse = + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, validAttachmentId, validPropertyBody); + if (!validPropertyResponse.equals("Updated")) { + fail("Could not update valid property for attachment: " + validAttachmentId); + } + + // add invalid secondary properties to second attachment (non-existent property) + String invalidAttachmentId = sourceAttachmentIds.get(1); + RequestBody invalidPropertyBody = + RequestBody.create( + "{\"nonExistentProperty\": \"invalid\"}", MediaType.parse("application/json")); + + api.updateSecondaryProperty( + appUrl, entityName, facetName, moveSourceEntity, invalidAttachmentId, invalidPropertyBody); + + // add undefined properties to third attachment + String undefinedAttachmentId = sourceAttachmentIds.get(2); + RequestBody undefinedPropertyBody = + RequestBody.create( + "{\"undefinedField\": \"test\", \"anotherUndefined\": 999}", + MediaType.parse("application/json")); + + api.updateSecondaryProperty( + appUrl, + entityName, + facetName, + moveSourceEntity, + undefinedAttachmentId, + undefinedPropertyBody); + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (Exception e) { + fail("Could not fetch metadata for attachment: " + attachmentId); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch all objectIds from source entity"); + } + + // Get source attachment count before move + List> sourceMetadataBeforeMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + int sourceCountBeforeMove = sourceMetadataBeforeMove.size(); + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Move attachments from source to target with sourceFacet + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify attachments moved to target + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + + assertTrue( + targetMetadataAfterMove.size() > 0, "Target entity should have attachments after move"); + assertEquals( + sourceCountBeforeMove, + targetMetadataAfterMove.size(), + "All attachments should move (invalid properties are ignored)"); + + // Verify only allowed properties are populated in target + for (Map metadata : targetMetadataAfterMove) { + String targetAttachmentId = (String) metadata.get("ID"); + assertNotNull(targetAttachmentId, "Target attachment ID should not be null"); + + // Fetch detailed metadata to verify properties + Map detailedMetadata = + api.fetchMetadata(appUrl, entityName, facetName, moveTargetEntity, targetAttachmentId); + + // Check if this is the attachment with valid customProperty2 + if (detailedMetadata.containsKey("customProperty2") + && detailedMetadata.get("customProperty2") != null) { + assertEquals( + validCustomProperty2Value, + detailedMetadata.get("customProperty2"), + "Valid customProperty2 should be preserved"); + } + } + + // Verify source entity has no attachments + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterMove.size(), + "Source entity should have no attachments after move with sourceFacet"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(72) + public void testMoveAttachmentsFromSourceEntityInDraftMode() throws Exception { + System.out.println( + "Test (72): Move attachments from Source Entity when Source Entity is in draft mode"); + + // Create source entity and keep it in draft mode + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + files.add(new File(classLoader.getResource("WDIRSCodeList.csv").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Verify attachments are added to source entity + int sourceCountBeforeMove = sourceAttachmentIds.size(); + assertTrue(sourceCountBeforeMove > 0, "Source entity should have attachments before move"); + assertEquals( + files.size(), sourceCountBeforeMove, "Source should have " + files.size() + " attachments"); + + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID from first attachment + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + String editSourceResponse = api.editEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!editSourceResponse.equals("Entity in draft mode")) { + fail("Could not edit source entity back to draft mode"); + } + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity: " + saveTargetResponse); + } + + String editTargetResponse = api.editEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!editTargetResponse.equals("Entity in draft mode")) { + fail("Could not edit target entity for move operation"); + } + + // Move attachments from draft source to target using sourceFacet + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + facetName); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify attachments moved to target + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertTrue( + targetMetadataAfterMove.size() > 0, "Target entity should have attachments after move"); + assertEquals( + sourceCountBeforeMove, + targetMetadataAfterMove.size(), + "Target should have " + sourceCountBeforeMove + " attachments after move"); + + // Verify all expected attachments are in target + Set targetFileNames = + targetMetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + targetFileNames.contains(file.getName()), + "Target should contain attachment: " + file.getName()); + } + + // Now save the source entity + String saveSourceAfterMoveResponse = + api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceAfterMoveResponse.equals("Saved")) { + fail("Could not save source entity after move: " + saveSourceAfterMoveResponse); + } + + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + sourceCountBeforeMove, + sourceMetadataAfterMove.size(), + "Source entity in draft mode retains attachments after move (copy behavior)"); + + Set sourceFileNamesAfterMove = + sourceMetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + sourceFileNamesAfterMove.contains(file.getName()), + "Source (draft) should still contain attachment: " + file.getName()); + } + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(73) + public void testEditAttachmentFileNameAndMoveToTarget() throws Exception { + System.out.println( + "Test (73): Edit attachment file name in Source Entity and move it to Target Entity"); + + // Create source entity and add attachment + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Add attachment with original name (sample.txt) + ClassLoader classLoader = getClass().getClassLoader(); + File originalFile = new File(classLoader.getResource("sample.txt").getFile()); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "text/plain"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, originalFile); + if (!createResponse.get(0).equals("Attachment created")) { + fail("Could not create attachment in source entity"); + } + + String attachmentId = createResponse.get(1); + assertNotNull(attachmentId, "Attachment ID should not be null"); + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Verify original filename + List> metadataBeforeRename = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals(1, metadataBeforeRename.size(), "Source should have 1 attachment"); + assertEquals( + "sample.txt", + metadataBeforeRename.get(0).get("fileName"), + "Original filename should be sample.txt"); + + // Edit source entity back to draft mode + String editSourceResponse = api.editEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!editSourceResponse.equals("Entity in draft mode")) { + fail("Could not edit source entity to draft mode"); + } + + // Rename the attachment to testEdited.txt + String newFileName = "testEdited.txt"; + String renameResponse = + api.renameAttachment( + appUrl, entityName, facetName, moveSourceEntity, attachmentId, newFileName); + assertEquals("Renamed", renameResponse, "Attachment should be renamed successfully"); + + // Save source entity after rename + saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity after rename: " + saveSourceResponse); + } + + // Verify renamed filename in source + List> metadataAfterRename = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals(1, metadataAfterRename.size(), "Source should still have 1 attachment"); + assertEquals( + newFileName, + metadataAfterRename.get(0).get("fileName"), + "Filename should be updated to " + newFileName); + + // Get objectId and folderId for move operation + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + String objectId = metadata.get("objectId").toString(); + moveSourceFolderId = metadata.get("folderId").toString(); + assertNotNull(objectId, "Object ID should not be null"); + assertNotNull(moveSourceFolderId, "Folder ID should not be null"); + + moveObjectIds.clear(); + moveObjectIds.add(objectId); + + // Create target entity + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity"); + } + + // Move attachment from source to target with sourceFacet + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + fail("Move operation returned null result"); + } + + // Save target entity after move + String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTargetResponse.equals("Saved")) { + fail("Could not save target entity after move: " + saveTargetResponse); + } + + // Verify attachment moved to target with renamed filename + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals(1, targetMetadataAfterMove.size(), "Target should have 1 attachment after move"); + assertEquals( + newFileName, + targetMetadataAfterMove.get(0).get("fileName"), + "Target should have attachment with renamed filename: " + newFileName); + + // Verify attachment removed from source + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterMove.size(), + "Source entity should have no attachments after move with sourceFacet"); + + // Clean up - delete both entities + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(74) + public void testChainMoveAttachmentsFromSourceToTarget1ToTarget2() throws Exception { + System.out.println( + "Test (74): Move attachments from Source Entity to Target Entity 1 and then to Target Entity 2"); + + // Create source entity and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Get count of attachments in source + int sourceCountInitial = sourceAttachmentIds.size(); + assertTrue(sourceCountInitial > 0, "Source should have attachments"); + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID from first attachment + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + // Create Target Entity 1 + moveTargetEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity 1"); + } + + // Move attachments from source to Target Entity 1 with sourceFacet + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult1 = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult1 == null) { + fail("Move operation from source to target 1 returned null result"); + } + + // Save target entity 1 after move + String saveTarget1Response = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); + if (!saveTarget1Response.equals("Saved")) { + fail("Could not save target entity 1 after move: " + saveTarget1Response); + } + + // Verify attachments moved to Target Entity 1 + List> target1MetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertTrue( + target1MetadataAfterMove.size() > 0, "Target entity 1 should have attachments after move"); + assertEquals( + sourceCountInitial, + target1MetadataAfterMove.size(), + "Target 1 should have " + sourceCountInitial + " attachments"); + + // Verify all expected files are in Target Entity 1 + Set target1FileNames = + target1MetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + target1FileNames.contains(file.getName()), + "Target 1 should contain attachment: " + file.getName()); + } + + // Verify attachments removed from source + List> sourceMetadataAfterFirstMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + 0, + sourceMetadataAfterFirstMove.size(), + "Source entity should have no attachments after move to target 1"); + + // Create Target Entity 2 + String moveTargetEntity2 = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity2.equals("Could not create entity")) { + fail("Could not create target entity 2"); + } + + // Get new object IDs and folder ID from Target Entity 1 for second move + List target1AttachmentIds = new ArrayList<>(); + for (Map metadata : target1MetadataAfterMove) { + String attachmentId = metadata.get("ID").toString(); + target1AttachmentIds.add(attachmentId); + } + + moveObjectIds.clear(); + String target1FolderId = null; + for (String attachmentId : target1AttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveTargetEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get folder ID from first attachment + if (target1FolderId == null && metadata.containsKey("folderId")) { + target1FolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata from target 1: " + e.getMessage()); + } + } + + assertNotNull(target1FolderId, "Target 1 folder ID should not be null"); + + // Move attachments from Target Entity 1 to Target Entity 2 with sourceFacet + Map moveResult2 = + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity2, + target1FolderId, + moveObjectIds, + sourceFacet); + + if (moveResult2 == null) { + fail("Move operation from target 1 to target 2 returned null result"); + } + + // Save target entity 2 after move + String saveTarget2Response = + api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity2); + if (!saveTarget2Response.equals("Saved")) { + fail("Could not save target entity 2 after move: " + saveTarget2Response); + } + + // Verify attachments moved to Target Entity 2 + List> target2MetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity2); + assertTrue( + target2MetadataAfterMove.size() > 0, "Target entity 2 should have attachments after move"); + assertEquals( + sourceCountInitial, + target2MetadataAfterMove.size(), + "Target 2 should have " + sourceCountInitial + " attachments"); + + // Verify all expected files are in Target Entity 2 + Set target2FileNames = + target2MetadataAfterMove.stream() + .map(m -> (String) m.get("fileName")) + .collect(java.util.stream.Collectors.toSet()); + + for (File file : files) { + assertTrue( + target2FileNames.contains(file.getName()), + "Target 2 should contain attachment: " + file.getName()); + } + + // Verify attachments removed from Target Entity 1 + List> target1MetadataAfterSecondMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals( + 0, + target1MetadataAfterSecondMove.size(), + "Target entity 1 should have no attachments after move to target 2"); + + // Clean up - delete all three entities + api.deleteEntity(appUrl, entityName, moveTargetEntity2); + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } + + @Test + @Order(75) + public void testMoveAttachmentsWithoutSDMRole() throws Exception { + System.out.println("Test (75): Move attachments when user does not have SDM Role"); + + // Create source entity with SDM role and add attachments + moveSourceEntity = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveSourceEntity.equals("Could not create entity")) { + fail("Could not create source entity"); + } + + // Prepare sample files + ClassLoader classLoader = getClass().getClassLoader(); + List files = new ArrayList<>(); + files.add(new File(classLoader.getResource("sample.pdf").getFile())); + files.add(new File(classLoader.getResource("sample.txt").getFile())); + + Map postData = new HashMap<>(); + postData.put("up__ID", moveSourceEntity); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Create attachments in source entity with SDM role + List sourceAttachmentIds = new ArrayList<>(); + for (File file : files) { + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, moveSourceEntity, srvpath, postData, file); + if (createResponse.get(0).equals("Attachment created")) { + sourceAttachmentIds.add(createResponse.get(1)); + } else { + fail("Could not create attachment in source entity"); + } + } + + // Save source entity with SDM role + String saveSourceResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveSourceEntity); + if (!saveSourceResponse.equals("Saved")) { + fail("Could not save source entity: " + saveSourceResponse); + } + + // Get count of attachments in source + int sourceCountInitial = sourceAttachmentIds.size(); + assertTrue(sourceCountInitial > 0, "Source should have attachments"); + + // Fetch object IDs from source entity + moveObjectIds.clear(); + for (String attachmentId : sourceAttachmentIds) { + try { + Map metadata = + api.fetchMetadata(appUrl, entityName, facetName, moveSourceEntity, attachmentId); + if (metadata.containsKey("objectId")) { + moveObjectIds.add(metadata.get("objectId").toString()); + // Get source folder ID from first attachment + if (moveSourceFolderId == null && metadata.containsKey("folderId")) { + moveSourceFolderId = metadata.get("folderId").toString(); + } + } + } catch (IOException e) { + fail("Could not fetch attachment metadata: " + e.getMessage()); + } + } + + if (moveObjectIds.size() != sourceAttachmentIds.size()) { + fail("Could not fetch object IDs for all attachments"); + } + + assertNotNull(moveSourceFolderId, "Source folder ID should not be null"); + + // Create target entity with no SDM role + moveTargetEntity = apiNoRoles.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (moveTargetEntity.equals("Could not create entity")) { + fail("Could not create target entity with no SDM role"); + } + + // Try to move attachments from source to target using user without SDM role + String sourceFacet = serviceName + "." + entityName + "." + facetName; + Map moveResult = null; + boolean moveOperationFailed = false; + String errorMessage = null; + + try { + moveResult = + apiNoRoles.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); + + if (moveResult == null) { + moveOperationFailed = true; + errorMessage = "Move operation returned null"; + } else if (moveResult.containsKey("error")) { + moveOperationFailed = true; + errorMessage = moveResult.get("error").toString(); + } + } catch (Exception e) { + moveOperationFailed = true; + errorMessage = e.getMessage(); + } + + // Verify move operation failed + assertTrue(moveOperationFailed, "Move operation should fail when user does not have SDM role"); + assertNotNull(errorMessage, "Error message should be present when move operation fails"); + System.out.println("Move operation failed as expected. Error: " + errorMessage); + + // Verify attachments are still in source entity (not moved) + List> sourceMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveSourceEntity); + assertEquals( + sourceCountInitial, + sourceMetadataAfterMove.size(), + "Source should still have all attachments after failed move"); + + // Verify target entity has no attachments + List> targetMetadataAfterMove = + api.fetchEntityMetadata(appUrl, entityName, facetName, moveTargetEntity); + assertEquals( + 0, targetMetadataAfterMove.size(), "Target should have no attachments after failed move"); + + // Clean up - delete both entities using SDM role + api.deleteEntity(appUrl, entityName, moveTargetEntity); + api.deleteEntity(appUrl, entityName, moveSourceEntity); + } } From 714c5c6c3165de3f653a8d4c311bf5f5d6fe1a8f Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:44:39 +0530 Subject: [PATCH 2/7] pom change --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4e0361fe..d5a84cfb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.6.3-SNAPSHOT + 1.0.0-RC1 17 ${java.version} ${java.version} From f5e6ba3c0d644690dbb45369690ee06145193569 Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 12:46:33 +0530 Subject: [PATCH 3/7] gemini review changes --- .../cds/sdm/IntegrationTest_SingleFacet.java | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) 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 67accbd6..489a28ef 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 @@ -4650,15 +4650,14 @@ void testMoveAttachmentsWithSourceFacet() throws IOException { // Move attachments from source to target with sourceFacet String sourceFacet = serviceName + "." + entityName + "." + facetName; - Map moveResult = - api.moveAttachment( - appUrl, - entityName, - facetName, - moveTargetEntity, - moveSourceFolderId, - moveObjectIds, - sourceFacet); + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + sourceFacet); // Save target entity after move String saveTargetResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); @@ -4807,15 +4806,14 @@ public void testMoveAttachmentsToEntityWithDuplicateWithSourceFacet() throws Exc } // Move attachments from source to target with sourceFacet - Map moveResult = - api.moveAttachment( - appUrl, - entityName, - facetName, - moveTargetEntity, - moveSourceFolderId, - moveObjectIds, - "AdminService.Books.attachments"); + api.moveAttachment( + appUrl, + entityName, + facetName, + moveTargetEntity, + moveSourceFolderId, + moveObjectIds, + "AdminService.Books.attachments"); // Save target entity after move String saveMoveResponse = api.saveEntityDraft(appUrl, entityName, srvpath, moveTargetEntity); From a2133cfcd696be75947ae89cbbaf75838cb16dba Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:04:34 +0530 Subject: [PATCH 4/7] gemini review changes --- .../com/sap/cds/sdm/IntegrationTest_MultipleFacet.java | 6 ++++++ .../com/sap/cds/sdm/IntegrationTest_SingleFacet.java | 6 ++++++ 2 files changed, 12 insertions(+) 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 c8bf7f93..67fe63ac 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 @@ -7,8 +7,14 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; import okhttp3.*; +import okio.ByteString; +import org.json.JSONObject; import org.junit.jupiter.api.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 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 489a28ef..132ae67a 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 @@ -6,8 +6,14 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; import okhttp3.*; +import okio.ByteString; +import org.json.JSONObject; import org.junit.jupiter.api.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) From 2e95e92d1eb7f65dc9d8d60059b610f78ccc8eb3 Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:07:22 +0530 Subject: [PATCH 5/7] formatting fix --- .../integration/com/sap/cds/sdm/IntegrationTest_SingleFacet.java | 1 + 1 file changed, 1 insertion(+) 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 132ae67a..59e02436 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 @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; From f4ca13c9fb9c9d48c8f878560fae5b9d3acfe580 Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Wed, 7 Jan 2026 14:49:05 +0530 Subject: [PATCH 6/7] pom change reverted --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5a84cfb..4e0361fe 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.0.0-RC1 + 1.6.3-SNAPSHOT 17 ${java.version} ${java.version} From c5c977a4ad2064498bf59ed3c49aa86b06b8fcb7 Mon Sep 17 00:00:00 2001 From: N S Deepika Singh <110279487+deepikaSingh2711@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:08:18 +0530 Subject: [PATCH 7/7] entity cleanup --- .../com/sap/cds/sdm/IntegrationTest_MultipleFacet.java | 7 +++++-- .../com/sap/cds/sdm/IntegrationTest_SingleFacet.java | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) 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 67fe63ac..5fa5ed43 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 @@ -2479,7 +2479,7 @@ void testUploadAttachmentWithoutSDMRole() throws IOException { } } } - + api.deleteEntityDraft(appUrl, entityName, entityID7); if (!testStatus) { fail("Attachment uploaded without SDM role for one or more facets"); } @@ -3294,7 +3294,8 @@ void testCopyAttachmentWithNotesAndSecondaryPropertiesField() throws IOException testStatus = true; } } - + api.deleteEntity(appUrl, entityName, copyCustomSourceEntity); + api.deleteEntity(appUrl, entityName, copyCustomTargetEntity); if (!testStatus) { fail( "Could not verify that notes field and all secondary properties were copied from source to target attachment for all facets"); @@ -4728,6 +4729,8 @@ void testCopyAttachmentsSuccessNewEntityDraft() throws IOException { } else { fail("Could not create entities"); } + api.deleteEntityDraft(appUrl, entityName, copyAttachmentSourceEntity); + api.deleteEntity(appUrl, entityName, copyAttachmentTargetEntity); } @Test 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 59e02436..6fb7f057 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 @@ -2999,6 +2999,8 @@ void testCopyAttachmentWithNotesAndSecondaryPropertiesField() throws IOException fail( "Could not verify that notes field and all secondary properties were copied from source to target attachment"); } + api.deleteEntity(appUrl, entityName, copyCustomSourceEntity); + api.deleteEntity(appUrl, entityName, copyCustomTargetEntity); } @Test @@ -3752,6 +3754,7 @@ void testEditLinkNoSDMRoles() throws IOException { if (!testStatus) { fail("Link got edited without SDM roles"); } + api.deleteEntity(appUrl, entityName, editLinkEntity); } @Test @@ -4178,6 +4181,8 @@ void testCopyAttachmentsSuccessNewEntityDraft() throws IOException { } else { fail("Could not create entities"); } + api.deleteEntityDraft(appUrl, entityName, copyAttachmentSourceEntity); + api.deleteEntity(appUrl, entityName, copyAttachmentTargetEntity); } @Test