diff --git a/src/org/labkey/test/tests/SpecimenTest.java b/src/org/labkey/test/tests/SpecimenTest.java index 2afd5888ba..d2f0d4bcc7 100644 --- a/src/org/labkey/test/tests/SpecimenTest.java +++ b/src/org/labkey/test/tests/SpecimenTest.java @@ -18,7 +18,13 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hc.core5.http.HttpStatus; +import org.json.JSONObject; +import org.junit.Assert; import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.CommandResponse; +import org.labkey.remoteapi.Connection; +import org.labkey.remoteapi.SimplePostCommand; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.Locators; @@ -36,6 +42,7 @@ import org.labkey.test.util.LogMethod; import org.labkey.test.util.LoggedParam; import org.labkey.test.util.PasswordUtil; +import org.labkey.test.util.PermissionsHelper; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.StudyHelper; import org.labkey.test.util.TextSearcher; @@ -51,6 +58,7 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.List; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -59,6 +67,7 @@ import static org.junit.Assert.assertTrue; import static org.labkey.test.pages.study.specimen.ManageNotificationsPage.SpecimensAttachment; import static org.labkey.test.util.DataRegionTable.DataRegion; +import static org.labkey.test.util.PermissionsHelper.READER_ROLE; @Category({Daily.class, Specimen.class}) @BaseWebDriverTest.ClassTimeout(minutes = 20) @@ -124,11 +133,12 @@ protected void setupRequestabilityRules() @Override @LogMethod - protected void doVerifySteps() throws IOException + protected void doVerifySteps() throws IOException, CommandException { verifyActorDetails(); verifySpecimenEventsRedirect(); createRequest(); + createRequestWithApi(); verifyViews(); verifyAdditionalRequestFields(); verifyNotificationEmails(); @@ -435,6 +445,110 @@ private void createRequest() waitForElement(Locator.css(".labkey-message").withText("Complete")); } + @LogMethod + private void createRequestWithApi() throws IOException, CommandException + { + // Create an empty specimen request as admin user + goToSpecimenData(); + click(Locator.tag("img").withAttributeContaining("alt", "[New Request Icon]")); + selectOptionByText(Locator.name("destinationLocation"), DESTINATION_SITE); + setFormElement(Locator.id("input0"), "Assay Plan"); + setFormElement(Locator.id("input1"), "Shipping"); + setFormElement(Locator.id("input3"), "Comments"); + clickButton("Create and View Details"); + int requestId = Integer.parseInt(getUrlParam("id")); + + // Create commands to add vials, remove vials, add specimens, and cancel the request + JSONObject vialParameters = new JSONObject( + Map.of( + "requestId", requestId, + "idType", "GlobalUniqueId", + "vialIds", new String[]{"ABH00LT8-01"} + ) + ); + SimplePostCommand addVialsCommand = new SimplePostCommand("specimen-api", "addVialsToRequest"); + addVialsCommand.setJsonObject(vialParameters); + SimplePostCommand removeVialsCommand = new SimplePostCommand("specimen-api", "removeVialsFromRequest"); + removeVialsCommand.setJsonObject(vialParameters); + SimplePostCommand addSpecimenCommand = new SimplePostCommand("specimen-api", "addSpecimensToRequest"); + addSpecimenCommand.setJsonObject(new JSONObject( + Map.of( + "requestId", requestId, + "specimenHashes", new String[]{"FakeSpecimenHashThatDoesntMatter"} + ) + )); + SimplePostCommand cancelRequestCommand = new SimplePostCommand("specimen-api", "cancelRequest"); + cancelRequestCommand.setJsonObject(new JSONObject( + Map.of( + "requestId", requestId + ) + )); + + // Assign "Specimen Requester" role to USER1 + String containerPath = getCurrentContainerPath(); + log(containerPath); + ApiPermissionsHelper helper = new ApiPermissionsHelper(containerPath); + helper.uncheckInheritedPermissions(); // Uses UI to un-inherit and save... + clickButton("Cancel"); // ...so click button to return to specimen page + helper.addMemberToRole(USER1, READER_ROLE, PermissionsHelper.MemberType.user, containerPath); + helper.addMemberToRole(USER1, "Specimen Requester", PermissionsHelper.MemberType.user, containerPath); + Connection conn = createDefaultConnection(); + + // Impersonate USER1. Specimen Requester role who doesn't own the request shouldn't be able to modify it. + impersonate(USER1); + Assert.assertThrows("Request " + requestId + " was not found or the current user does not have permissions to access it.", + CommandException.class, + () -> addVialsCommand.execute(conn, containerPath) + ); + Assert.assertThrows("Request " + requestId + " was not found or the current user does not have permissions to access it.", + CommandException.class, + () -> addSpecimenCommand.execute(conn, containerPath) + ); + Assert.assertThrows("Request " + requestId + " was not found or the current user does not have permissions to access it.", + CommandException.class, + () -> cancelRequestCommand.execute(conn, containerPath) + ); + stopImpersonating(false); + + // Verify that the owner can add a vial + impersonateRoles(READER_ROLE, "Specimen Requester"); + CommandResponse response = addVialsCommand.execute(conn, containerPath); + assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + //noinspection unchecked + assertEquals(1, ((List)response.getParsedData().get("vials")).size()); + stopImpersonating(false); + + // Attempt to remove the just-added vial as non-owner + impersonate(USER1); + Assert.assertThrows("Request " + requestId + " was not found or the current user does not have permissions to access it.", + CommandException.class, + () -> removeVialsCommand.execute(conn, containerPath) + ); + stopImpersonating(false); + + // Owner can remove vials and add via specimen hash + impersonateRoles(READER_ROLE, "Specimen Requester"); + response = removeVialsCommand.execute(conn, containerPath); + assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + //noinspection unchecked + assertEquals(0, ((List)response.getParsedData().get("vials")).size()); + response = addSpecimenCommand.execute(conn, containerPath); + assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + stopImpersonating(false); + + // Now give USER1 the "Specimen Coordinator" role and verify they can modify the request, including cancel + helper.addMemberToRole(USER1, "Specimen Coordinator", PermissionsHelper.MemberType.user, containerPath); + impersonate(USER1); + assertEquals(HttpStatus.SC_OK, addVialsCommand.execute(conn, containerPath).getStatusCode()); + assertEquals(HttpStatus.SC_OK, removeVialsCommand.execute(conn, containerPath).getStatusCode()); + assertEquals(HttpStatus.SC_OK, addSpecimenCommand.execute(conn, containerPath).getStatusCode()); + assertEquals(HttpStatus.SC_OK, cancelRequestCommand.execute(conn, containerPath).getStatusCode()); + stopImpersonating(false); + + // Restore permissions back to inherit + helper.checkInheritedPermissions(); + } + @LogMethod private void verifyViews() { @@ -584,8 +698,8 @@ private void verifyNotificationEmails() assertTrue(emailMessages1.getFirst().getBody().contains(_specimen_McMichael)); assertNotNull("No message found", emailMessages1); String messageBody = emailMessages2.getFirst().getBody().replaceFirst("-*=_Part_\\d{3}_\\d*.\\d*\\n",""); - messageBody = messageBody.replaceAll("Content-Type: text\\/html; charset=UTF-8\n",""); - messageBody = messageBody.replaceAll("Content-Transfer-Encoding: 7bit\n", ""); + messageBody = messageBody.replace("Content-Type: text/html; charset=UTF-8\n",""); + messageBody = messageBody.replace("Content-Transfer-Encoding: 7bit\n", ""); assertTrue("Notification was not as expected.\nExpected:\n" + notification + "\n\nActual:\n" + messageBody, messageBody.contains(notification)); String attachment1 = getAttribute(Locator.linkWithText(ATTACHMENT1), "href");