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 b8f92136..176f2026 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 @@ -1113,6 +1113,112 @@ public List> fetchEntityMetadataDraft( } } + @Override + public String downloadSelectedAttachmentsDraft( + String appUrl, String entityName, String facetName, String entityID, List ids) + throws IOException { + String url = + "https://" + + appUrl + + "/odata/v4/" + + serviceName + + "/" + + entityName + + "(ID=" + + entityID + + ",IsActiveEntity=false)" + + "/" + + facetName + + "(up__ID=" + + entityID + + ",ID=" + + ids.get(0) + + ",IsActiveEntity=false)" + + "/" + + serviceName + + ".downloadSelectedAttachments"; + + String idsParam = String.join(",", ids); + String jsonPayload = "{\"ids\": \"" + idsParam + "\"}"; + + RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonPayload); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + if (!response.isSuccessful()) { + throw new IOException( + "Could not download attachments: " + + response.code() + + " - " + + response.body().string()); + } + String responseBody = response.body().string(); + Map responseMap = objectMapper.readValue(responseBody, Map.class); + if (responseMap.containsKey("value")) { + return responseMap.get("value").toString(); + } + return responseBody; + } catch (IOException e) { + System.out.println("Error while downloading attachments: " + e.getMessage()); + throw new IOException(e); + } + } + + @Override + public String downloadSelectedAttachments( + String appUrl, String entityName, String facetName, String entityID, List ids) + throws IOException { + String url = + "https://" + + appUrl + + "/odata/v4/" + + serviceName + + "/" + + entityName + + "(ID=" + + entityID + + ",IsActiveEntity=true)" + + "/" + + facetName + + "(up__ID=" + + entityID + + ",ID=" + + ids.get(0) + + ",IsActiveEntity=true)" + + "/" + + serviceName + + ".downloadSelectedAttachments"; + + String idsParam = String.join(",", ids); + String jsonPayload = "{\"ids\": \"" + idsParam + "\"}"; + + RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonPayload); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + if (!response.isSuccessful()) { + throw new IOException( + "Could not download attachments: " + + response.code() + + " - " + + response.body().string()); + } + String responseBody = response.body().string(); + Map responseMap = objectMapper.readValue(responseBody, Map.class); + if (responseMap.containsKey("value")) { + return responseMap.get("value").toString(); + } + return responseBody; + } catch (IOException e) { + System.out.println("Error while downloading attachments: " + e.getMessage()); + throw new IOException(e); + } + } + public Map fetchChangelog( String appUrl, String entityName, String facetName, String entityID, String ID) throws IOException { 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 b41acae8..034841d8 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 @@ -121,4 +121,12 @@ public String openAttachment( public Map fetchChangelog( String appUrl, String entityName, String facetName, String entityID, String ID) throws IOException; + + public String downloadSelectedAttachments( + String appUrl, String entityName, String facetName, String entityID, List ids) + throws IOException; + + public String downloadSelectedAttachmentsDraft( + String appUrl, String entityName, String facetName, String entityID, List ids) + 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 d4c380bc..8a88e2df 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 @@ -1051,6 +1051,104 @@ public List> fetchEntityMetadataDraft( } } + @Override + public String downloadSelectedAttachmentsDraft( + String appUrl, String entityName, String facetName, String entityID, List ids) + throws IOException { + String url = + "https://" + + appUrl + + "/api/admin/" + + entityName + + "(ID=" + + entityID + + ",IsActiveEntity=false)" + + "/" + + facetName + + "(up__ID=" + + entityID + + ",ID=" + + ids.get(0) + + ",IsActiveEntity=false)" + + "/AdminService.downloadSelectedAttachments"; + + String idsParam = String.join(",", ids); + String jsonPayload = "{\"ids\": \"" + idsParam + "\"}"; + + RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonPayload); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + if (!response.isSuccessful()) { + throw new IOException( + "Could not download attachments: " + + response.code() + + " - " + + response.body().string()); + } + String responseBody = response.body().string(); + Map responseMap = objectMapper.readValue(responseBody, Map.class); + if (responseMap.containsKey("value")) { + return responseMap.get("value").toString(); + } + return responseBody; + } catch (IOException e) { + System.out.println("Error while downloading attachments: " + e.getMessage()); + throw new IOException(e); + } + } + + @Override + public String downloadSelectedAttachments( + String appUrl, String entityName, String facetName, String entityID, List ids) + throws IOException { + String url = + "https://" + + appUrl + + "/api/admin/" + + entityName + + "(ID=" + + entityID + + ",IsActiveEntity=true)" + + "/" + + facetName + + "(up__ID=" + + entityID + + ",ID=" + + ids.get(0) + + ",IsActiveEntity=true)" + + "/AdminService.downloadSelectedAttachments"; + + String idsParam = String.join(",", ids); + String jsonPayload = "{\"ids\": \"" + idsParam + "\"}"; + + RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonPayload); + + Request request = + new Request.Builder().url(url).post(body).addHeader("Authorization", token).build(); + + try (Response response = executeWithRetry(request)) { + if (!response.isSuccessful()) { + throw new IOException( + "Could not download attachments: " + + response.code() + + " - " + + response.body().string()); + } + String responseBody = response.body().string(); + Map responseMap = objectMapper.readValue(responseBody, Map.class); + if (responseMap.containsKey("value")) { + return responseMap.get("value").toString(); + } + return responseBody; + } catch (IOException e) { + System.out.println("Error while downloading attachments: " + e.getMessage()); + throw new IOException(e); + } + } + public Map fetchChangelog( String appUrl, String entityName, String facetName, String entityID, String ID) throws IOException { diff --git a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_Chapters_MultipleFacet.java b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_Chapters_MultipleFacet.java index abf2b7f3..664a6043 100644 --- a/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_Chapters_MultipleFacet.java +++ b/sdm/src/test/java/integration/com/sap/cds/sdm/IntegrationTest_Chapters_MultipleFacet.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import okhttp3.*; import okio.ByteString; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.*; @@ -2407,9 +2408,9 @@ void testUpdateInvalidSecondaryProperty_afterBookIsSaved_multipleChapterAttachme } } - // Tests 28 and 29 removed - chapters have no attachment limit + // // Tests 28 and 29 removed - chapters have no attachment limit - // Tests 28-29 skipped - chapters have no attachment limit + // // Tests 28-29 skipped - chapters have no attachment limit @Test @Order(30) @@ -2483,8 +2484,8 @@ void testDiscardBookDraftWithChapterAttachments() throws IOException { } } - // Tests 32-34 covered in tests 19, 23, 24 - // Tests 37-41 skipped - copy with notes/secondary properties not applicable + // // Tests 32-34 covered in tests 19, 23, 24 + // // Tests 37-41 skipped - copy with notes/secondary properties not applicable @Test @Order(42) @@ -3957,7 +3958,7 @@ void testCopyAttachmentsToExistingChapter() throws IOException { api.deleteEntity(appUrl, bookEntityName, targetBookID); } - // ============= LINK RENAME TESTS (47-49) ============= + // // ============= LINK RENAME TESTS (47-49) ============= @Test @Order(47) @@ -4174,7 +4175,7 @@ void testRenameLinkUnsupportedCharacters() throws IOException { api.deleteEntity(appUrl, bookEntityName, testBookID); } - // ============= LINK EDIT TESTS (50-53) ============= + // // ============= LINK EDIT TESTS (50-53) ============= @Test @Order(50) @@ -4445,7 +4446,7 @@ void testEditLinkNoSDMRoles() throws IOException { api.deleteEntity(appUrl, bookEntityName, testBookID); } - // ============= COPY LINK TESTS (54-58) ============= + // // ============= COPY LINK TESTS (54-58) ============= @Test @Order(54) @@ -4833,7 +4834,7 @@ void testCopyLinkFromDraftChapter() throws IOException { api.deleteEntity(appUrl, bookEntityName, targetBookID); } - // ============= COPY ATTACHMENTS DRAFT MODE (59) ============= + // // ============= COPY ATTACHMENTS DRAFT MODE (59) ============= @Test @Order(59) @@ -4962,7 +4963,7 @@ void testCopyAttachmentsSuccessNewChapterDraft() throws IOException { api.deleteEntity(appUrl, bookEntityName, targetBookID); } - // ============= CHANGELOG TESTS (60-64) ============= + // // ============= CHANGELOG TESTS (60-64) ============= @Test @Order(60) @@ -5354,7 +5355,7 @@ void testChangelogForNewChapter() throws IOException { api.deleteEntityDraft(appUrl, bookEntityName, testBookID); } - // ============= MOVE ATTACHMENT TESTS (65-75) ============= + // // ============= MOVE ATTACHMENT TESTS (65-75) ============= @Test @Order(65) @@ -6574,4 +6575,481 @@ void testRenameChapterAttachmentWithExtensionChange_BeforeSave() throws IOExcept api.deleteEntity(appUrl, bookEntityName, newBookID); } } + + @Test + @Order(78) + void testDownloadMultipleAttachmentsInDraftState() throws IOException { + System.out.println( + "Test (76): Create book+chapter, upload pdf/txt/exe per facet in draft state, download" + + " before saving"); + + String draftBookID = api.createEntityDraft(appUrl, bookEntityName, entityName2, srvpath); + if (draftBookID.equals("Could not create entity")) { + fail("Could not create book"); + return; + } + String draftChapterID = + api.createEntityDraft(appUrl, chapterEntityName, entityName2, srvpath, draftBookID); + if (draftChapterID.equals("Could not create entity")) { + api.deleteEntityDraft(appUrl, bookEntityName, draftBookID); + fail("Could not create chapter"); + return; + } + + ClassLoader classLoader = getClass().getClassLoader(); + Map> facetAttachmentIds = new HashMap<>(); + int facetIndex = 0; + for (String facetName : facet) { + List ids = new ArrayList<>(); + Map postData = new HashMap<>(); + postData.put("up__ID", draftChapterID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + postData.put("mimeType", "application/pdf"); + File pdfOrig = new File(classLoader.getResource("sample.pdf").getFile()); + File pdfFile = File.createTempFile("sample_ch_" + facetIndex + "_pdf_", ".pdf"); + Files.copy(pdfOrig.toPath(), pdfFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + List r1 = + api.createAttachment( + appUrl, chapterEntityName, facetName, draftChapterID, srvpath, postData, pdfFile); + pdfFile.delete(); + if (!r1.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, draftBookID); + fail("Could not upload sample.pdf for facet " + facetName); + return; + } + ids.add(r1.get(1)); + + postData.put("mimeType", "application/txt"); + File txtOrig = new File(classLoader.getResource("sample.txt").getFile()); + File txtFile = File.createTempFile("sample_ch_" + facetIndex + "_txt_", ".txt"); + Files.copy(txtOrig.toPath(), txtFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + List r2 = + api.createAttachment( + appUrl, chapterEntityName, facetName, draftChapterID, srvpath, postData, txtFile); + txtFile.delete(); + if (!r2.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, draftBookID); + fail("Could not upload sample.txt for facet " + facetName); + return; + } + ids.add(r2.get(1)); + + postData.put("mimeType", "application/exe"); + File exeOrig = new File(classLoader.getResource("sample.exe").getFile()); + File exeFile = File.createTempFile("sample_ch_" + facetIndex + "_exe_", ".exe"); + Files.copy(exeOrig.toPath(), exeFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + List r3 = + api.createAttachment( + appUrl, chapterEntityName, facetName, draftChapterID, srvpath, postData, exeFile); + exeFile.delete(); + if (!r3.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, draftBookID); + fail("Could not upload sample.exe for facet " + facetName); + return; + } + ids.add(r3.get(1)); + facetAttachmentIds.put(facetName, ids); + facetIndex++; + } + + for (String facetName : facet) { + List ids = facetAttachmentIds.get(facetName); + + String singleResult = + api.downloadSelectedAttachmentsDraft( + appUrl, chapterEntityName, facetName, draftChapterID, List.of(ids.get(0))); + JSONArray singleArray = new JSONArray(singleResult); + assertEquals(1, singleArray.length(), "Expected 1 result for facet " + facetName); + assertEquals( + "success", + singleArray.getJSONObject(0).getString("status"), + "Download button should be enabled in draft state for facet " + facetName); + assertTrue( + singleArray.getJSONObject(0).has("content"), + "Attachment should have content field for facet " + facetName); + + String multiResult = + api.downloadSelectedAttachmentsDraft( + appUrl, chapterEntityName, facetName, draftChapterID, ids); + JSONArray multiArray = new JSONArray(multiResult); + assertEquals(3, multiArray.length(), "Expected 3 results for facet " + facetName); + for (int j = 0; j < multiArray.length(); j++) { + assertEquals( + "success", + multiArray.getJSONObject(j).getString("status"), + "Attachment " + (j + 1) + " should download successfully for facet " + facetName); + assertTrue( + multiArray.getJSONObject(j).has("content"), + "Attachment " + (j + 1) + " should have content field for facet " + facetName); + } + } + + api.deleteEntityDraft(appUrl, bookEntityName, draftBookID); + } + + @Test + @Order(79) + void testDownloadButtonDisabledWithLinkInDraftState() throws IOException { + System.out.println( + "Test (77): Upload pdf and link per facet to chapter, save book, edit (draft state)," + + " pdf download enabled, pdf+link download disabled"); + + String testBookID = api.createEntityDraft(appUrl, bookEntityName, entityName2, srvpath); + if (testBookID.equals("Could not create entity")) { + fail("Could not create book"); + return; + } + String testChapterID = + api.createEntityDraft(appUrl, chapterEntityName, entityName2, srvpath, testBookID); + if (testChapterID.equals("Could not create entity")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not create chapter"); + return; + } + + ClassLoader classLoader = getClass().getClassLoader(); + Map facetPdfId = new HashMap<>(); + Map facetLinkId = new HashMap<>(); + for (String facetName : facet) { + Map postData = new HashMap<>(); + postData.put("up__ID", testChapterID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List pdfResponse = + api.createAttachment( + appUrl, chapterEntityName, facetName, testChapterID, srvpath, postData, pdfFile); + if (!pdfResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not upload pdf for facet " + facetName); + return; + } + facetPdfId.put(facetName, pdfResponse.get(1)); + + String linkResp = + api.createLink( + appUrl, + chapterEntityName, + facetName, + testChapterID, + "TestLink", + "https://www.example.com"); + if (!linkResp.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not create link for facet " + facetName); + return; + } + + List> draftMeta = + api.fetchEntityMetadataDraft(appUrl, chapterEntityName, facetName, testChapterID); + String linkId = + draftMeta.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkId == null) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not find link attachment in draft metadata for facet " + facetName); + return; + } + facetLinkId.put(facetName, linkId); + } + + String saveResponse = api.saveEntityDraft(appUrl, bookEntityName, srvpath, testBookID); + if (!saveResponse.equals("Saved")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not save book: " + saveResponse); + return; + } + + String editResponse = api.editEntityDraft(appUrl, bookEntityName, srvpath, testBookID); + if (!editResponse.equals("Entity in draft mode")) { + api.deleteEntity(appUrl, bookEntityName, testBookID); + fail("Could not put book into edit/draft mode: " + editResponse); + return; + } + + for (String facetName : facet) { + String pdfId = facetPdfId.get(facetName); + String linkId = facetLinkId.get(facetName); + + String pdfResult = + api.downloadSelectedAttachmentsDraft( + appUrl, chapterEntityName, facetName, testChapterID, List.of(pdfId)); + JSONArray pdfArray = new JSONArray(pdfResult); + assertEquals(1, pdfArray.length(), "Expected 1 result for pdf-only for facet " + facetName); + assertEquals( + "success", + pdfArray.getJSONObject(0).getString("status"), + "Download button should be enabled for pdf in draft state, facet " + facetName); + + String mixedResult = + api.downloadSelectedAttachmentsDraft( + appUrl, chapterEntityName, facetName, testChapterID, List.of(pdfId, linkId)); + JSONArray mixedArray = new JSONArray(mixedResult); + assertEquals( + 2, mixedArray.length(), "Expected 2 results for pdf+link for facet " + facetName); + JSONObject linkResult = null; + for (int j = 0; j < mixedArray.length(); j++) { + JSONObject item = mixedArray.getJSONObject(j); + if (linkId.equals(item.getString("id"))) { + linkResult = item; + break; + } + } + assertNotNull(linkResult, "Link result should be present for facet " + facetName); + assertEquals( + "error", + linkResult.getString("status"), + "Download button should be disabled: link should return error for facet " + facetName); + assertEquals( + "Download is not supported for link attachments", + linkResult.getString("message"), + "Error message should match for facet " + facetName); + } + + api.deleteEntity(appUrl, bookEntityName, testBookID); + } + + @Test + @Order(80) + void testDownloadMultipleAttachmentsInActiveState() throws IOException { + System.out.println( + "Test (78): Create book+chapter, upload pdf/txt/exe per facet, save, download in active" + + " state"); + + String testBookID = api.createEntityDraft(appUrl, bookEntityName, entityName2, srvpath); + if (testBookID.equals("Could not create entity")) { + fail("Could not create book"); + return; + } + String testChapterID = + api.createEntityDraft(appUrl, chapterEntityName, entityName2, srvpath, testBookID); + if (testChapterID.equals("Could not create entity")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not create chapter"); + return; + } + + ClassLoader classLoader = getClass().getClassLoader(); + Map> facetAttachmentIds = new HashMap<>(); + for (String facetName : facet) { + List ids = new ArrayList<>(); + Map postData = new HashMap<>(); + postData.put("up__ID", testChapterID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + postData.put("mimeType", "application/pdf"); + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List r1 = + api.createAttachment( + appUrl, chapterEntityName, facetName, testChapterID, srvpath, postData, pdfFile); + if (!r1.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not upload sample.pdf for facet " + facetName); + return; + } + ids.add(r1.get(1)); + + postData.put("mimeType", "application/txt"); + File txtFile = new File(classLoader.getResource("sample.txt").getFile()); + List r2 = + api.createAttachment( + appUrl, chapterEntityName, facetName, testChapterID, srvpath, postData, txtFile); + if (!r2.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not upload sample.txt for facet " + facetName); + return; + } + ids.add(r2.get(1)); + + postData.put("mimeType", "application/exe"); + File exeFile = new File(classLoader.getResource("sample.exe").getFile()); + List r3 = + api.createAttachment( + appUrl, chapterEntityName, facetName, testChapterID, srvpath, postData, exeFile); + if (!r3.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not upload sample.exe for facet " + facetName); + return; + } + ids.add(r3.get(1)); + facetAttachmentIds.put(facetName, ids); + } + + String saveResponse = api.saveEntityDraft(appUrl, bookEntityName, srvpath, testBookID); + if (!saveResponse.equals("Saved")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not save book: " + saveResponse); + return; + } + + for (String facetName : facet) { + List ids = facetAttachmentIds.get(facetName); + + String singleResult = + api.downloadSelectedAttachments( + appUrl, chapterEntityName, facetName, testChapterID, List.of(ids.get(0))); + JSONArray singleArray = new JSONArray(singleResult); + assertEquals(1, singleArray.length(), "Expected 1 result for facet " + facetName); + assertEquals( + "success", + singleArray.getJSONObject(0).getString("status"), + "Download button should be enabled: single download should succeed for facet " + + facetName); + assertTrue( + singleArray.getJSONObject(0).has("content"), + "Downloaded attachment should have a content field for facet " + facetName); + + String multiResult = + api.downloadSelectedAttachments(appUrl, chapterEntityName, facetName, testChapterID, ids); + JSONArray multiArray = new JSONArray(multiResult); + assertEquals(3, multiArray.length(), "Expected 3 results for facet " + facetName); + for (int j = 0; j < multiArray.length(); j++) { + assertEquals( + "success", + multiArray.getJSONObject(j).getString("status"), + "Attachment " + (j + 1) + " should download successfully for facet " + facetName); + assertTrue( + multiArray.getJSONObject(j).has("content"), + "Attachment " + (j + 1) + " should have content field for facet " + facetName); + } + } + + api.deleteEntity(appUrl, bookEntityName, testBookID); + } + + @Test + @Order(81) + void testDownloadButtonDisabledWithLinkInActiveState() throws IOException { + System.out.println( + "Test (79): Upload pdf and link per facet to chapter, save book, pdf download enabled," + + " pdf+link download disabled in active state"); + + String testBookID = api.createEntityDraft(appUrl, bookEntityName, entityName2, srvpath); + if (testBookID.equals("Could not create entity")) { + fail("Could not create book"); + return; + } + String testChapterID = + api.createEntityDraft(appUrl, chapterEntityName, entityName2, srvpath, testBookID); + if (testChapterID.equals("Could not create entity")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not create chapter"); + return; + } + + ClassLoader classLoader = getClass().getClassLoader(); + File origPdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + Map facetPdfId = new HashMap<>(); + int facetIdx = 0; + for (String facetName : facet) { + Map postData = new HashMap<>(); + postData.put("up__ID", testChapterID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File tempPdf = File.createTempFile("sample_ch_link_" + facetIdx + "_", ".pdf"); + Files.copy(origPdfFile.toPath(), tempPdf.toPath(), StandardCopyOption.REPLACE_EXISTING); + List pdfResponse = + api.createAttachment( + appUrl, chapterEntityName, facetName, testChapterID, srvpath, postData, tempPdf); + tempPdf.delete(); + if (!pdfResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not upload pdf for facet " + facetName); + return; + } + facetPdfId.put(facetName, pdfResponse.get(1)); + facetIdx++; + + String linkResp = + api.createLink( + appUrl, + chapterEntityName, + facetName, + testChapterID, + "TestLink", + "https://www.example.com"); + if (!linkResp.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not create link for facet " + facetName); + return; + } + } + + String saveResponse = api.saveEntityDraft(appUrl, bookEntityName, srvpath, testBookID); + if (!saveResponse.equals("Saved")) { + api.deleteEntityDraft(appUrl, bookEntityName, testBookID); + fail("Could not save book: " + saveResponse); + return; + } + + for (String facetName : facet) { + String pdfId = facetPdfId.get(facetName); + + List> activeMetadata = + api.fetchEntityMetadata(appUrl, chapterEntityName, facetName, testChapterID); + String linkId = + activeMetadata.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkId == null) { + api.deleteEntity(appUrl, bookEntityName, testBookID); + fail("Could not find link attachment in active metadata for facet " + facetName); + return; + } + + String pdfOnlyResult = + api.downloadSelectedAttachments( + appUrl, chapterEntityName, facetName, testChapterID, List.of(pdfId)); + JSONArray pdfArray = new JSONArray(pdfOnlyResult); + assertEquals(1, pdfArray.length(), "Expected 1 result for pdf-only for facet " + facetName); + assertEquals( + "success", + pdfArray.getJSONObject(0).getString("status"), + "Download button should be enabled: pdf download should succeed for facet " + facetName); + + String mixedResult = + api.downloadSelectedAttachments( + appUrl, chapterEntityName, facetName, testChapterID, List.of(pdfId, linkId)); + JSONArray mixedArray = new JSONArray(mixedResult); + assertEquals( + 2, mixedArray.length(), "Expected 2 results for pdf+link for facet " + facetName); + JSONObject linkResult = null; + for (int j = 0; j < mixedArray.length(); j++) { + JSONObject item = mixedArray.getJSONObject(j); + if (linkId.equals(item.getString("id"))) { + linkResult = item; + break; + } + } + assertNotNull(linkResult, "Link result should be present for facet " + facetName); + assertEquals( + "error", + linkResult.getString("status"), + "Download button should be disabled: link should return error for facet " + facetName); + assertEquals( + "Download is not supported for link attachments", + linkResult.getString("message"), + "Error message should match for facet " + facetName); + } + + api.deleteEntity(appUrl, bookEntityName, testBookID); + } } 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 08f04a51..fd0e0d7b 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 @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import okhttp3.*; import okio.ByteString; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.*; @@ -7060,6 +7061,407 @@ void testRenameAttachmentWithExtensionChange_BeforeSave() throws IOException { } } + @Test + @Order(78) + void testDownloadAttachmentsAcrossMultipleFacets() throws IOException { + System.out.println( + "Test (76): Create entity, upload attachments to each facet, and verify" + + " download works across all facets"); + + // Step 1: Create entity + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String downloadTestEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + Map postData = new HashMap<>(); + postData.put("up__ID", downloadTestEntityID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + postData.put("mimeType", "application/pdf"); + + // Step 2: Upload one pdf attachment to each of the 3 facets + File originalPdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + String[] facetAttachmentIDs = new String[facet.length]; + for (int i = 0; i < facet.length; i++) { + File tempFacetFile = File.createTempFile("sample_mf_facet" + i + "_", ".pdf"); + Files.copy( + originalPdfFile.toPath(), tempFacetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + List createResp = + api.createAttachment( + appUrl, entityName, facet[i], downloadTestEntityID, srvpath, postData, tempFacetFile); + tempFacetFile.delete(); + if (!createResp.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not upload pdf to facet: " + facet[i]); + return; + } + facetAttachmentIDs[i] = createResp.get(1); + } + + // Step 3: Save entity draft + response = api.saveEntityDraft(appUrl, entityName, srvpath, downloadTestEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Step 4: Verify download works from each facet independently + for (int i = 0; i < facet.length; i++) { + String downloadResult = + api.downloadSelectedAttachments( + appUrl, entityName, facet[i], downloadTestEntityID, List.of(facetAttachmentIDs[i])); + JSONArray resultArray = new JSONArray(downloadResult); + assertEquals(1, resultArray.length(), "Expected 1 result from facet: " + facet[i]); + JSONObject result = resultArray.getJSONObject(0); + assertEquals( + "success", result.getString("status"), "Download should succeed for facet: " + facet[i]); + assertTrue( + result.has("content"), + "Downloaded attachment in facet " + facet[i] + " should have a content field"); + } + + // Step 5: Edit entity back to draft, upload 2 more attachments to facet[0], save, and verify + // multi-download + String editResponse = api.editEntityDraft(appUrl, entityName, srvpath, downloadTestEntityID); + if (!editResponse.equals("Entity in draft mode")) { + api.deleteEntity(appUrl, entityName, downloadTestEntityID); + fail("Could not edit entity back to draft mode: " + editResponse); + return; + } + + List multiIDs = new ArrayList<>(); + multiIDs.add(facetAttachmentIDs[0]); + File[] extraFiles = { + new File(classLoader.getResource("sample1.pdf").getFile()), + new File(classLoader.getResource("sample2.pdf").getFile()) + }; + for (int i = 0; i < 2; i++) { + List extraResp = + api.createAttachment( + appUrl, entityName, facet[0], downloadTestEntityID, srvpath, postData, extraFiles[i]); + if (!extraResp.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not upload extra attachment to facet: " + facet[0]); + return; + } + multiIDs.add(extraResp.get(1)); + } + + response = api.saveEntityDraft(appUrl, entityName, srvpath, downloadTestEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not save entity draft after extra uploads: " + response); + return; + } + + String multiResult = + api.downloadSelectedAttachments( + appUrl, entityName, facet[0], downloadTestEntityID, multiIDs); + JSONArray multiArray = new JSONArray(multiResult); + assertEquals(3, multiArray.length(), "Expected 3 results from multi-download in " + facet[0]); + for (int i = 0; i < multiArray.length(); i++) { + assertEquals( + "success", + multiArray.getJSONObject(i).getString("status"), + "Attachment " + (i + 1) + " in " + facet[0] + " should download successfully"); + } + + // Clean up + api.deleteEntity(appUrl, entityName, downloadTestEntityID); + } + + @Test + @Order(79) + void testDownloadButtonDisabledWhenLinkSelectedAcrossFacets() throws IOException { + System.out.println( + "Test (77): Upload pdf to one facet and link to another; verify download" + + " button enabled for pdf facet only, disabled when link-facet item selected"); + + // Step 1: Create entity + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String testEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload pdf to facet[0] (attachments) + Map postData = new HashMap<>(); + postData.put("up__ID", testEntityID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List createResponse = + api.createAttachment( + appUrl, entityName, facet[0], testEntityID, srvpath, postData, pdfFile); + if (!createResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not upload pdf to facet: " + facet[0]); + return; + } + String pdfAttachmentID = createResponse.get(1); + + // Step 3: Create a link in facet[1] (references) + String linkResponse = + api.createLink( + appUrl, entityName, facet[1], testEntityID, "TestLink", "https://www.example.com"); + if (!linkResponse.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not create link in facet: " + facet[1]); + return; + } + + // Step 4: Save entity + response = api.saveEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Fetch link attachment ID from facet[1] metadata + List> facet1Attachments = + api.fetchEntityMetadata(appUrl, entityName, facet[1], testEntityID); + String linkAttachmentID = + facet1Attachments.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkAttachmentID == null) { + api.deleteEntity(appUrl, entityName, testEntityID); + fail("Could not find link attachment in facet: " + facet[1]); + return; + } + + // Step 5: Download pdf from facet[0] - Download button should be enabled + String pdfOnlyResult = + api.downloadSelectedAttachments( + appUrl, entityName, facet[0], testEntityID, List.of(pdfAttachmentID)); + JSONArray pdfOnlyArray = new JSONArray(pdfOnlyResult); + assertEquals(1, pdfOnlyArray.length(), "Expected 1 result for pdf download from " + facet[0]); + assertEquals( + "success", + pdfOnlyArray.getJSONObject(0).getString("status"), + "Download button should be enabled: pdf in " + facet[0] + " should download successfully"); + + // Step 6: Attempt to download link from facet[1] - Download button should be disabled + String linkResult = + api.downloadSelectedAttachments( + appUrl, entityName, facet[1], testEntityID, List.of(linkAttachmentID)); + JSONArray linkArray = new JSONArray(linkResult); + assertEquals( + 1, linkArray.length(), "Expected 1 result for link download attempt from " + facet[1]); + JSONObject linkItem = linkArray.getJSONObject(0); + assertEquals( + "error", + linkItem.getString("status"), + "Download button should be disabled: link in " + facet[1] + " should return error"); + assertEquals( + "Download is not supported for link attachments", + linkItem.getString("message"), + "Error message for link download in " + facet[1] + " should match"); + + // Clean up + api.deleteEntity(appUrl, entityName, testEntityID); + } + + @Test + @Order(80) + void testDownloadAttachmentsAcrossMultipleFacetsInDraftState() throws IOException { + System.out.println( + "Test (78): Create entity in draft state, upload attachments to each" + + " facet, download before saving"); + + // Step 1: Create entity draft (do NOT save) + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String draftEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + Map postData = new HashMap<>(); + postData.put("up__ID", draftEntityID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + postData.put("mimeType", "application/pdf"); + + // Step 2: Upload one pdf to each of the 3 facets (entity stays in draft state) + File origPdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + String[] draftFacetAttachmentIDs = new String[facet.length]; + for (int i = 0; i < facet.length; i++) { + File tempFacetFile = File.createTempFile("sample_mf_draft_facet" + i + "_", ".pdf"); + Files.copy(origPdfFile.toPath(), tempFacetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + List createResp = + api.createAttachment( + appUrl, entityName, facet[i], draftEntityID, srvpath, postData, tempFacetFile); + tempFacetFile.delete(); + if (!createResp.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + fail("Could not upload pdf to facet in draft state: " + facet[i]); + return; + } + draftFacetAttachmentIDs[i] = createResp.get(1); + } + + // Step 3: Verify download works from each facet in draft state + for (int i = 0; i < facet.length; i++) { + String downloadResult = + api.downloadSelectedAttachmentsDraft( + appUrl, entityName, facet[i], draftEntityID, List.of(draftFacetAttachmentIDs[i])); + JSONArray resultArray = new JSONArray(downloadResult); + assertEquals( + 1, resultArray.length(), "Expected 1 result from facet in draft state: " + facet[i]); + JSONObject result = resultArray.getJSONObject(0); + assertEquals( + "success", + result.getString("status"), + "Download should succeed in draft state for facet: " + facet[i]); + assertTrue( + result.has("content"), + "Downloaded attachment in draft facet " + facet[i] + " should have a content field"); + } + + // Clean up - entity was never saved, so delete the draft + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + } + + @Test + @Order(81) + void testDownloadButtonWithPdfAndLinkAcrossFacetsInDraftState() throws IOException { + System.out.println( + "Test (79): Upload pdf to one facet and link to another, save entity, edit" + + " (draft state), verify download button enabled for pdf facet, disabled for link" + + " facet"); + + // Step 1: Create entity draft + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String testEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload pdf to facet[0] (attachments) + Map postData = new HashMap<>(); + postData.put("up__ID", testEntityID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File origPdf = new File(classLoader.getResource("sample.pdf").getFile()); + File tempPdf = File.createTempFile("sample_mf_draftlink_", ".pdf"); + Files.copy(origPdf.toPath(), tempPdf.toPath(), StandardCopyOption.REPLACE_EXISTING); + List createResponse = + api.createAttachment( + appUrl, entityName, facet[0], testEntityID, srvpath, postData, tempPdf); + tempPdf.delete(); + if (!createResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not upload pdf to facet: " + facet[0]); + return; + } + String pdfAttachmentID = createResponse.get(1); + + // Step 3: Create a link in facet[1] (references) + String linkResponse = + api.createLink( + appUrl, entityName, facet[1], testEntityID, "TestLink", "https://www.example.com"); + if (!linkResponse.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not create link in facet: " + facet[1]); + return; + } + + // Fetch link attachment ID from draft metadata of facet[1] + List> draftFacet1Attachments = + api.fetchEntityMetadataDraft(appUrl, entityName, facet[1], testEntityID); + String linkAttachmentID = + draftFacet1Attachments.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkAttachmentID == null) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not find link attachment in draft facet: " + facet[1]); + return; + } + + // Step 4: Save entity + response = api.saveEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Step 5: Edit entity - puts it back into draft state + String editResponse = api.editEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!editResponse.equals("Entity in draft mode")) { + api.deleteEntity(appUrl, entityName, testEntityID); + fail("Could not put entity into edit/draft mode: " + editResponse); + return; + } + + // Step 6: Select pdf from facet[0] in draft state - Download button should be enabled + String pdfOnlyResult = + api.downloadSelectedAttachmentsDraft( + appUrl, entityName, facet[0], testEntityID, List.of(pdfAttachmentID)); + JSONArray pdfOnlyArray = new JSONArray(pdfOnlyResult); + assertEquals( + 1, + pdfOnlyArray.length(), + "Expected 1 result for pdf download from " + facet[0] + " in draft state"); + assertEquals( + "success", + pdfOnlyArray.getJSONObject(0).getString("status"), + "Download button should be enabled in draft state: pdf in " + facet[0] + " should succeed"); + + // Step 7: Select link from facet[1] in draft state - Download button should be disabled + String linkOnlyResult = + api.downloadSelectedAttachmentsDraft( + appUrl, entityName, facet[1], testEntityID, List.of(linkAttachmentID)); + JSONArray linkOnlyArray = new JSONArray(linkOnlyResult); + assertEquals( + 1, + linkOnlyArray.length(), + "Expected 1 result for link download attempt from " + facet[1] + " in draft state"); + JSONObject linkItem = linkOnlyArray.getJSONObject(0); + assertEquals( + "error", + linkItem.getString("status"), + "Download button should be disabled in draft state: link in " + + facet[1] + + " should return error"); + assertEquals( + "Download is not supported for link attachments", + linkItem.getString("message"), + "Error message for link download in " + facet[1] + " in draft state should match"); + + // Clean up + api.deleteEntity(appUrl, entityName, testEntityID); + } + // @Test // @Order(77) // void testUploadAttachmentExceedingMaximumFileSize() throws IOException { @@ -7104,7 +7506,7 @@ void testRenameAttachmentWithExtensionChange_BeforeSave() throws IOException { // fail("Failed to parse error response for references facet: " + e.getMessage()); // } // } else { - // fail("Attachment got created in references facet with file size exceeding maximum + // fail("Attachment got created in references facet with file size exceeding maximum // // limit"); // } // } else { 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 ab0dbee5..444a6384 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 @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import okhttp3.*; import okio.ByteString; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.*; @@ -6535,6 +6536,468 @@ void testRenameAttachmentWithExtensionChange_WhileUpload() throws IOException { api.deleteEntity(appUrl, entityName, newEntityID); } + @Test + @Order(78) + void testDownloadMultipleAttachments() throws IOException { + System.out.println( + "Test (76): Create entity, upload 3 attachments (pdf, txt, exe), and download all"); + boolean testStatus = false; + + // Step 1: Create entity + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String downloadTestEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload pdf, txt, exe in one draft session + Map postData = new HashMap<>(); + postData.put("up__ID", downloadTestEntityID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Upload pdf + postData.put("mimeType", "application/pdf"); + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List createResponse1 = + api.createAttachment( + appUrl, entityName, facetName, downloadTestEntityID, srvpath, postData, pdfFile); + if (!createResponse1.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not upload sample.pdf"); + return; + } + String downloadAttachmentID1 = createResponse1.get(1); + + // Upload txt + postData.put("mimeType", "application/txt"); + File txtFile = new File(classLoader.getResource("sample.txt").getFile()); + List createResponse2 = + api.createAttachment( + appUrl, entityName, facetName, downloadTestEntityID, srvpath, postData, txtFile); + if (!createResponse2.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not upload sample.txt"); + return; + } + String downloadAttachmentID2 = createResponse2.get(1); + + // Upload exe + postData.put("mimeType", "application/exe"); + File exeFile = new File(classLoader.getResource("sample.exe").getFile()); + List createResponse3 = + api.createAttachment( + appUrl, entityName, facetName, downloadTestEntityID, srvpath, postData, exeFile); + if (!createResponse3.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not upload sample.exe"); + return; + } + String downloadAttachmentID3 = createResponse3.get(1); + + // Step 3: Save entity draft + response = api.saveEntityDraft(appUrl, entityName, srvpath, downloadTestEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, downloadTestEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Step 4: Select first attachment - Download button should be enabled + // Verify download works with a single attachment selection + String singleDownloadResult = + api.downloadSelectedAttachments( + appUrl, entityName, facetName, downloadTestEntityID, List.of(downloadAttachmentID1)); + JSONArray singleResultArray = new JSONArray(singleDownloadResult); + assertEquals(1, singleResultArray.length(), "Expected 1 result in download response"); + JSONObject singleResult = singleResultArray.getJSONObject(0); + assertEquals( + "success", + singleResult.getString("status"), + "Download button should be enabled: single attachment download should succeed"); + assertTrue(singleResult.has("content"), "Downloaded attachment should have a content field"); + + // Step 5: Select all 3 and click download + String multiDownloadResult = + api.downloadSelectedAttachments( + appUrl, + entityName, + facetName, + downloadTestEntityID, + List.of(downloadAttachmentID1, downloadAttachmentID2, downloadAttachmentID3)); + JSONArray multiResultArray = new JSONArray(multiDownloadResult); + assertEquals(3, multiResultArray.length(), "Expected 3 results in download response"); + for (int i = 0; i < multiResultArray.length(); i++) { + JSONObject result = multiResultArray.getJSONObject(i); + assertEquals( + "success", + result.getString("status"), + "Attachment " + (i + 1) + " should download successfully"); + assertTrue( + result.has("content"), + "Attachment " + (i + 1) + " should have a content field in the response"); + } + testStatus = true; + + // Clean up + api.deleteEntity(appUrl, entityName, downloadTestEntityID); + + if (!testStatus) { + fail("Multiple attachment download test failed"); + } + } + + @Test + @Order(79) + void testDownloadButtonDisabledWhenLinkSelected() throws IOException { + System.out.println( + "Test (77): Download button enabled for pdf only; disabled when link is also selected"); + + // Step 1: Create entity (already in draft mode) + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String testEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload one pdf attachment (entity is already in draft mode) + Map postData = new HashMap<>(); + postData.put("up__ID", testEntityID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, testEntityID, srvpath, postData, pdfFile); + if (!createResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not upload sample.pdf"); + return; + } + String pdfAttachmentID = createResponse.get(1); + + // Step 3: Create a link attachment (entity still in draft mode) + String linkResponse = + api.createLink( + appUrl, entityName, facetName, testEntityID, "TestLink", "https://www.example.com"); + if (!linkResponse.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not create link attachment"); + return; + } + + // Save entity draft + response = api.saveEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Fetch metadata to find the link attachment ID (mimeType = "application/internet-shortcut") + List> allAttachments = + api.fetchEntityMetadata(appUrl, entityName, facetName, testEntityID); + String linkAttachmentID = + allAttachments.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkAttachmentID == null) { + api.deleteEntity(appUrl, entityName, testEntityID); + fail("Could not find link attachment in entity metadata"); + return; + } + + // Step 4: Select only the pdf - Download button should be enabled (succeeds) + String pdfOnlyResult = + api.downloadSelectedAttachments( + appUrl, entityName, facetName, testEntityID, List.of(pdfAttachmentID)); + JSONArray pdfOnlyArray = new JSONArray(pdfOnlyResult); + assertEquals(1, pdfOnlyArray.length(), "Expected 1 result when only pdf is selected"); + assertEquals( + "success", + pdfOnlyArray.getJSONObject(0).getString("status"), + "Download button should be enabled: pdf-only download should succeed"); + + // Step 5: Select both pdf and link - Download button should be disabled + // (link attachment returns error status, disabling the download) + String mixedResult = + api.downloadSelectedAttachments( + appUrl, + entityName, + facetName, + testEntityID, + List.of(pdfAttachmentID, linkAttachmentID)); + JSONArray mixedArray = new JSONArray(mixedResult); + assertEquals(2, mixedArray.length(), "Expected 2 results when pdf and link are selected"); + + // Find the result for the link attachment and assert it has error status + JSONObject linkResult = null; + for (int i = 0; i < mixedArray.length(); i++) { + JSONObject item = mixedArray.getJSONObject(i); + if (linkAttachmentID.equals(item.getString("id"))) { + linkResult = item; + break; + } + } + assertNotNull(linkResult, "Result for link attachment should be present"); + assertEquals( + "error", + linkResult.getString("status"), + "Download button should be disabled: link attachment download should return error"); + assertEquals( + "Download is not supported for link attachments", + linkResult.getString("message"), + "Error message for link attachment download should match"); + + // Clean up + api.deleteEntity(appUrl, entityName, testEntityID); + } + + @Test + @Order(80) + void testDownloadMultipleAttachmentsInDraftState() throws IOException { + System.out.println( + "Test (78): Create entity in draft state, upload 3 attachments (pdf, txt, exe), and" + + " download before saving"); + boolean testStatus = false; + + // Step 1: Create entity draft (do NOT save) + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String draftEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload pdf, txt, exe while entity remains in draft state + Map postData = new HashMap<>(); + postData.put("up__ID", draftEntityID); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + // Upload pdf + postData.put("mimeType", "application/pdf"); + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List createResponse1 = + api.createAttachment( + appUrl, entityName, facetName, draftEntityID, srvpath, postData, pdfFile); + if (!createResponse1.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + fail("Could not upload sample.pdf"); + return; + } + String draftAttachmentID1 = createResponse1.get(1); + + // Upload txt + postData.put("mimeType", "application/txt"); + File txtFile = new File(classLoader.getResource("sample.txt").getFile()); + List createResponse2 = + api.createAttachment( + appUrl, entityName, facetName, draftEntityID, srvpath, postData, txtFile); + if (!createResponse2.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + fail("Could not upload sample.txt"); + return; + } + String draftAttachmentID2 = createResponse2.get(1); + + // Upload exe + postData.put("mimeType", "application/exe"); + File exeFile = new File(classLoader.getResource("sample.exe").getFile()); + List createResponse3 = + api.createAttachment( + appUrl, entityName, facetName, draftEntityID, srvpath, postData, exeFile); + if (!createResponse3.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + fail("Could not upload sample.exe"); + return; + } + String draftAttachmentID3 = createResponse3.get(1); + + // Step 3: Select first attachment - Download button should be enabled even in draft state + String singleDownloadResult = + api.downloadSelectedAttachmentsDraft( + appUrl, entityName, facetName, draftEntityID, List.of(draftAttachmentID1)); + JSONArray singleResultArray = new JSONArray(singleDownloadResult); + assertEquals(1, singleResultArray.length(), "Expected 1 result in download response"); + JSONObject singleResult = singleResultArray.getJSONObject(0); + assertEquals( + "success", + singleResult.getString("status"), + "Download button should be enabled in draft state: single attachment download should" + + " succeed"); + assertTrue(singleResult.has("content"), "Downloaded attachment should have a content field"); + + // Step 4: Select all 3 and download while entity is still in draft state + String multiDownloadResult = + api.downloadSelectedAttachmentsDraft( + appUrl, + entityName, + facetName, + draftEntityID, + List.of(draftAttachmentID1, draftAttachmentID2, draftAttachmentID3)); + JSONArray multiResultArray = new JSONArray(multiDownloadResult); + assertEquals(3, multiResultArray.length(), "Expected 3 results in download response"); + for (int i = 0; i < multiResultArray.length(); i++) { + JSONObject result = multiResultArray.getJSONObject(i); + assertEquals( + "success", + result.getString("status"), + "Attachment " + (i + 1) + " should download successfully in draft state"); + assertTrue( + result.has("content"), + "Attachment " + (i + 1) + " should have a content field in the response"); + } + testStatus = true; + + // Clean up - entity was never saved, so delete the draft + api.deleteEntityDraft(appUrl, entityName, draftEntityID); + + if (!testStatus) { + fail("Multiple attachment download in draft state test failed"); + } + } + + @Test + @Order(81) + void testDownloadButtonWithPdfAndLinkInDraftState() throws IOException { + System.out.println( + "Test (79): Upload pdf and link, save entity, edit entity (draft state)," + + " download button enabled for pdf only, disabled when link also selected"); + + // Step 1: Create entity draft + String response = api.createEntityDraft(appUrl, entityName, entityName2, srvpath); + if (response.equals("Could not create entity")) { + fail("Could not create entity"); + return; + } + String testEntityID = response; + + ClassLoader classLoader = getClass().getClassLoader(); + + // Step 2: Upload one pdf attachment (entity in draft state) + Map postData = new HashMap<>(); + postData.put("up__ID", testEntityID); + postData.put("mimeType", "application/pdf"); + postData.put("createdAt", new Date().toString()); + postData.put("createdBy", "test@test.com"); + postData.put("modifiedBy", "test@test.com"); + + File pdfFile = new File(classLoader.getResource("sample.pdf").getFile()); + List createResponse = + api.createAttachment( + appUrl, entityName, facetName, testEntityID, srvpath, postData, pdfFile); + if (!createResponse.get(0).equals("Attachment created")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not upload sample.pdf"); + return; + } + // Capture pdf attachment ID directly from upload response (draft state, reliable) + String pdfAttachmentID = createResponse.get(1); + + // Step 3: Create a link attachment (entity still in draft state) + String linkResponse = + api.createLink( + appUrl, entityName, facetName, testEntityID, "TestLink", "https://www.example.com"); + if (!linkResponse.equals("Link created successfully")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not create link attachment"); + return; + } + + // Fetch link attachment ID from draft metadata while still in draft state + List> draftAttachments = + api.fetchEntityMetadataDraft(appUrl, entityName, facetName, testEntityID); + String linkAttachmentID = + draftAttachments.stream() + .filter( + a -> "application/internet-shortcut".equalsIgnoreCase((String) a.get("mimeType"))) + .map(a -> (String) a.get("ID")) + .findFirst() + .orElse(null); + if (linkAttachmentID == null) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not find link attachment in draft entity metadata"); + return; + } + + // Step 4: Save entity + response = api.saveEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!response.equals("Saved")) { + api.deleteEntityDraft(appUrl, entityName, testEntityID); + fail("Could not save entity draft: " + response); + return; + } + + // Step 5: Edit entity - puts it back into draft state + String editResponse = api.editEntityDraft(appUrl, entityName, srvpath, testEntityID); + if (!editResponse.equals("Entity in draft mode")) { + api.deleteEntity(appUrl, entityName, testEntityID); + fail("Could not put entity into edit/draft mode: " + editResponse); + return; + } + + // Step 7: Select only pdf - Download button should be enabled (succeeds) + String pdfOnlyResult = + api.downloadSelectedAttachmentsDraft( + appUrl, entityName, facetName, testEntityID, List.of(pdfAttachmentID)); + JSONArray pdfOnlyArray = new JSONArray(pdfOnlyResult); + assertEquals(1, pdfOnlyArray.length(), "Expected 1 result when only pdf is selected"); + assertEquals( + "success", + pdfOnlyArray.getJSONObject(0).getString("status"), + "Download button should be enabled in draft state: pdf-only download should succeed"); + + // Step 8: Select pdf + link - Download button should be disabled + // (link attachment returns error status, disabling the download) + String mixedResult = + api.downloadSelectedAttachmentsDraft( + appUrl, + entityName, + facetName, + testEntityID, + List.of(pdfAttachmentID, linkAttachmentID)); + JSONArray mixedArray = new JSONArray(mixedResult); + assertEquals(2, mixedArray.length(), "Expected 2 results when pdf and link are selected"); + + JSONObject linkResult = null; + for (int i = 0; i < mixedArray.length(); i++) { + JSONObject item = mixedArray.getJSONObject(i); + if (linkAttachmentID.equals(item.getString("id"))) { + linkResult = item; + break; + } + } + assertNotNull(linkResult, "Result for link attachment should be present"); + assertEquals( + "error", + linkResult.getString("status"), + "Download button should be disabled in draft state: link attachment should return error"); + assertEquals( + "Download is not supported for link attachments", + linkResult.getString("message"), + "Error message for link attachment download should match"); + + // Clean up + api.deleteEntity(appUrl, entityName, testEntityID); + } + // @Test // @Order(76) // void testUploadAttachmentExceedingMaximumFileSize() throws IOException {