diff --git a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java index c92abe4c4e..6e037894e6 100644 --- a/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java +++ b/src/org/labkey/test/components/ui/domainproperties/EntityTypeDesigner.java @@ -255,6 +255,21 @@ public Optional optionalWarningAlert() return elementCache().optionalWarningAlert(); } + public void setActionComment(String comment) + { + elementCache().commentInput.sendKeys(comment); + } + + public void clearActionComment() + { + elementCache().commentInput.clear(); + } + + public boolean isCommentInputPresent() + { + return elementCache().commentInputLocator.findOptionalElement(getDriver()).isPresent(); + } + /** * Dialog that allows the user to set the genId value. @@ -419,6 +434,9 @@ public final WebElement helpTarget(String divLabelText) final Locator uniqueIdMsgLoc = Locator.tagWithClass("div", "uniqueid-msg"); + public Locator.XPathLocator commentInputLocator = Locator.tagWithId("textarea", "actionComments"); + public WebElement commentInput = commentInputLocator.refindWhenNeeded(getDriver()); + public List parentAliases() { return Input.Input(Locator.name("alias"), getDriver()).findAll(propertiesPanel); diff --git a/src/org/labkey/test/tests/AuditLogTest.java b/src/org/labkey/test/tests/AuditLogTest.java index f2407751d0..31a2da9680 100644 --- a/src/org/labkey/test/tests/AuditLogTest.java +++ b/src/org/labkey/test/tests/AuditLogTest.java @@ -18,18 +18,13 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; -import org.json.JSONException; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; -import org.labkey.remoteapi.query.ContainerFilter; -import org.labkey.remoteapi.query.Filter; import org.labkey.remoteapi.query.InsertRowsCommand; import org.labkey.remoteapi.query.SaveRowsResponse; -import org.labkey.remoteapi.query.SelectRowsCommand; -import org.labkey.remoteapi.query.SelectRowsResponse; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.TestFileUtils; @@ -48,7 +43,6 @@ import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.Log4jUtils; -import org.labkey.test.util.Maps; import org.labkey.test.util.PermissionsHelper; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.UIUserHelper; @@ -94,9 +88,12 @@ public class AuditLogTest extends BaseWebDriverTest private static final String AUDIT_TEST_SUBFOLDER = "AuditVerifyTest_Subfolder"; private static final String AUDIT_PROPERTY_EVENTS_PROJECT = "AuditDomainPropertyEvents"; + final String DOMAIN_PROPERTY_LOG_NAME = "Domain property events"; + public static final String COMMENT_COLUMN = "Comment"; private final ApiPermissionsHelper permissionsHelper = new ApiPermissionsHelper(this); + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); @Override public List getAssociatedModules() @@ -576,9 +573,6 @@ public void testDomainPropertyEvents() final String FIELD03_NAME = "Field03"; final String FIELD03_LABEL = "Field 03 Lookup"; - final ColumnType FIELD03_TYPE = ColumnType.Integer; - - final String DOMAIN_PROPERTY_LOG_NAME = "Domain property events"; _containerHelper.createProject(AUDIT_PROPERTY_EVENTS_PROJECT, null); @@ -601,34 +595,30 @@ public void testDomainPropertyEvents() createList(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, null, listColumns); - List> domainPropertyEventRows = getDomainPropertyEventsFromDomainEvents(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, null); - - // Add the list of the event ids to an ignore list so future tests don't look at them again. - List ignoreIds = new ArrayList<>(getDomainEventIdsFromPropertyEvents(domainPropertyEventRows)); - - if(domainPropertyEventRows.size() != 3) - { - // We are going to fail, so navigate to the Domain Property Events Audit Log so the screen shot shows the log. - // I do the navigation because the log validation is happening by the API, so if there is a failure in the log - // we may be on a page that will add no value to the screen shot artifact. - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - Assert.assertEquals("The number of entries in the domain audit log were not as expected.", 3, domainPropertyEventRows.size()); - } - log("Validate that the expected rows are there."); - Map field01ExpectedColumns = Maps.of("action", "Created"); - Map field01ExpectedComment = Maps.of("Name", FIELD01_NAME,"Label", FIELD01_LABEL,"Type", "String","Description", FIELD01_DESCRIPTION); - boolean pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD01_NAME, field01ExpectedColumns, field01ExpectedComment); - Map field02ExpectedColumns = Maps.of("action", "Created"); - Map field02ExpectedComment = Maps.of("Name", FIELD02_NAME,"Label", FIELD02_LABEL,"Type", FIELD02_TYPE.getLabel(),"Description", FIELD02_DESCRIPTION); - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD02_NAME, field02ExpectedColumns, field02ExpectedComment) && pass; - - // We are going to fail, so navigate to the Domain Property Events Audit Log. - if(!pass) - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - - Assert.assertTrue("The values logged for the 'Created' events were not as expected. See log for details.", pass); + String eventInitValue = "Name=ChangeMyColumns&AllowDelete=true&AllowUpload=true&AllowExport=true&DiscussionSetting=0" + + "&EntireListIndexSetting=0&EntireListBodySetting=0&EachItemBodySetting=0&EntireListIndex=false&EachItemIndex=false&FileAttachmentIndex=false"; + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, LIST_CHECK_LOG, null, + "The domain " + LIST_CHECK_LOG + " was created. The column(s) of domain " + LIST_CHECK_LOG + " were modified.", + null, null, eventInitValue, null); + String field1InitValue = "Name=Field01&Label=This%20is%20Field%2001&Type=String&Scale=4000&Description=Simple%20String%20field." + + "&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false" + + "&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false&DefaultValueType=Editable%20default"; + AuditLogHelper.DetailedAuditEventRow field1ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD01_NAME, "Created", + null, null, null, field1InitValue, null); + + String field2InitValue = "Name=Field02&Label=This%20is%20Field%2002&Type=Integer&Description=Simple%20Integer%20field." + + "&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=true&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false" + + "&ExcludedFromShifting=false&Scannable=false&DefaultValueType=Editable%20default"; + AuditLogHelper.DetailedAuditEventRow field2ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD02_NAME, "Created", + null, null, null, field2InitValue, null); + + AuditLogHelper.DetailedAuditEventRow keyFieldExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, "Key", "Created", + null, null, null, null, null); + validateLastDomainAuditEvents(LIST_CHECK_LOG, "The values logged for the 'Created' events were not as expected.", expectedDomainEvent, Map.of("Key", keyFieldExpectedEvent, FIELD01_NAME, field1ExpectedEvent, FIELD02_NAME, field2ExpectedEvent)); log("Looks like the created events were as expected. Now modify some column/field attributes."); goToProjectHome(AUDIT_PROPERTY_EVENTS_PROJECT); @@ -653,37 +643,23 @@ public void testDomainPropertyEvents() listDefinitionPage.clickSave(); - log("Get a list of ids from the Domain Events Audit Log again but this time remove from the list the ids from the created events."); - domainPropertyEventRows = getDomainPropertyEventsFromDomainEvents(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, ignoreIds); - - // Add the list of the event ids to an ignore list so future tests don't look at them again. - ignoreIds.addAll(getDomainEventIdsFromPropertyEvents(domainPropertyEventRows)); - - if(domainPropertyEventRows.size() != 2) - { - // We are going to fail, so navigate to the Domain Property Events Audit Log. - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - Assert.assertEquals("The number of entries in the domain audit log were not as expected.", 2, domainPropertyEventRows.size()); - } + expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, LIST_CHECK_LOG, null, + "The column(s) of domain " + LIST_CHECK_LOG + " were modified.", + "", eventInitValue, eventInitValue, null); + + String field1UpdateValue = "Name=Field01&Label=This%20is%20Update%20Label%20for%20Field%2001&Type=String&Scale=4000&Description=This%20should%20be%20a%20new%20description%20for%20the%20field." + + "&PHI=Restricted%20PHI&DefaultScale=Linear&Required=true&Hidden=false&MvEnabled=false&Measure=false&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true" + + "&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false&DefaultValueType=Editable%20default"; + field1ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD01_NAME, "Modified", + "The following properties were updated: Label, Description, PHI, Required", "", field1InitValue, field1UpdateValue, null); + String field2UpdateValue = "Name=Field02&Label=This%20is%20Field%2002&Type=Integer&Description=Simple%20Integer%20field.&Format=%23!&PHI=Not%20PHI" + + "&DefaultScale=Log&Required=false&Hidden=false&MvEnabled=false&Measure=true&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true" + + "&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false&DefaultValueType=Editable%20default&ConditionalFormat=format.column~eq%3D5%3A%20"; + field2ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD02_NAME, "Modified", + "The following properties were updated: DefaultScale, Format, ConditionalFormat", "", field2InitValue, field2UpdateValue, null); log("Validate that the expected rows after the update are in the log."); - field01ExpectedColumns = Maps.of("action", "Modified"); - field01ExpectedComment = Maps.of("Label", FIELD01_LABEL + " -> " + FIELD01_UPDATED_LABEL, - "Description", FIELD01_DESCRIPTION + " -> " + FIELD01_UPDATED_DESCRIPTION, - "PHI", "Not PHI -> Restricted PHI", - "Required", "false -> true"); - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD01_NAME, field01ExpectedColumns, field01ExpectedComment); - - field02ExpectedColumns = Maps.of("action", "Modified"); - field02ExpectedComment = Maps.of("ConditionalFormats", "old: , new: 1", - "DefaultScale", "Linear -> Log"); - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD02_NAME, field02ExpectedColumns, field02ExpectedComment) && pass; - - // We are going to fail, so navigate to the Domain Property Events Audit Log. - if(!pass) - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - - Assert.assertTrue("The values logged for the 'Modified' events were not as expected. See log for details.", pass); + validateLastDomainAuditEvents(LIST_CHECK_LOG, "The values logged for the 'Modified' events were not as expected.", expectedDomainEvent, Map.of(FIELD01_NAME, field1ExpectedEvent, FIELD02_NAME, field2ExpectedEvent)); log("The modified events were logged as expected. Now add a lookup field."); goToProjectHome(AUDIT_PROPERTY_EVENTS_PROJECT); @@ -695,32 +671,14 @@ public void testDomainPropertyEvents() .setLabel(FIELD03_LABEL)); listDefinitionPage.clickSave(); - log("Validate that a 'Create' event was logged for the new filed."); - domainPropertyEventRows = getDomainPropertyEventsFromDomainEvents(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, ignoreIds); - - // Add the list of the event ids to an ignore list so future tests don't look at them again. - ignoreIds.addAll(getDomainEventIdsFromPropertyEvents(domainPropertyEventRows)); + String field3CreateValue = "Name=Field03&Label=Field%2003%20Lookup&Type=Integer&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false" + + "&Measure=false&Dimension=true&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + + "&DefaultValueType=Editable%20default&Lookup=%7B%22queryName%22%3A%22LookUp01%22%2C%22schemaName%22%3A%22lists%22%7D"; + AuditLogHelper.DetailedAuditEventRow field3ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD03_NAME, "Created", + null, "", null, field3CreateValue, null); - if(domainPropertyEventRows.size() != 1) - { - // We are going to fail, so navigate to the Domain Property Events Audit Log. - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - Assert.assertEquals("The number of entries in the domain audit log were not as expected.", 1, domainPropertyEventRows.size()); - } - - log("Validate that the expected row is there for the newly created field."); - Map field03ExpectedColumns = Maps.of("action", "Created"); - Map field03ExpectedComment = Maps.of("Name", FIELD03_NAME, - "Label", FIELD03_LABEL, - "Type", FIELD03_TYPE.getLabel(), - "Lookup", "[Schema: lists, Query: " + LOOK_UP_LIST01 + "]"); - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD03_NAME, field03ExpectedColumns, field03ExpectedComment); - - // We are going to fail, so navigate to the Domain Property Events Audit Log. - if(!pass) - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - - Assert.assertTrue("The values logged for the 'Created' event for the lookup field were not as expected. See log for details.", pass); + log("Validate that the expected rows after the update are in the log."); + validateLastDomainAuditEvents(LIST_CHECK_LOG, "The values logged for the 'Created' event for the lookup field were not as expected.", expectedDomainEvent, Map.of(FIELD03_NAME, field3ExpectedEvent)); log("The 'Created' event was logged as expected. Now modify the field to point to a new list in the lookup field."); goToProjectHome(AUDIT_PROPERTY_EVENTS_PROJECT); @@ -733,17 +691,13 @@ public void testDomainPropertyEvents() .setLookup(new FieldDefinition.IntLookup(null, "lists", LOOK_UP_LIST02)); listDefinitionPage.clickSave(); - log("Validate that the expected row is there for the after modifying the Lookup field."); - field03ExpectedColumns = Maps.of("action", "Modified"); - field03ExpectedComment = Maps.of("Lookup", "[Query: old: " + LOOK_UP_LIST01 + ", new: " + LOOK_UP_LIST02 + "]"); - - log("Get a list of ids from the Domain Events Audit Log again but remove from the list the ids from all of the previous events."); - domainPropertyEventRows = getDomainPropertyEventsFromDomainEvents(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, ignoreIds); - - // Add the list of the event ids to an ignore list so future tests don't look at them again. - ignoreIds.addAll(getDomainEventIdsFromPropertyEvents(domainPropertyEventRows)); - - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD03_NAME, field03ExpectedColumns, field03ExpectedComment); + log("Validate that the expected rows after the update are in the log."); + String field3UpdateValue = "Name=Field03&Label=Field%2003%20Lookup&Type=Integer&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false" + + "&Measure=false&Dimension=true&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false" + + "&Scannable=false&DefaultValueType=Editable%20default&Lookup=%7B%22queryName%22%3A%22LookUp02%22%2C%22schemaName%22%3A%22lists%22%7D"; + field3ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD03_NAME, "Modified", + "The following property was updated: Lookup", "", field3CreateValue, field3UpdateValue, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(LIST_CHECK_LOG, AUDIT_PROPERTY_EVENTS_PROJECT, expectedDomainEvent, Map.of(FIELD03_NAME, field3ExpectedEvent)); // We are going to fail, so navigate to the Domain Property Events Audit Log. if(!pass) @@ -758,221 +712,20 @@ public void testDomainPropertyEvents() listDefinitionPage.getFieldsPanel().getField(3).clickRemoveField(true); listDefinitionPage.clickSave(); - log("Validate that the expected row is there after deleting the Lookup field."); - field03ExpectedColumns = Maps.of("action", "Deleted"); - - log("Get a list of ids from the Domain Events Audit Log again but remove from the list the ids from all of the previous events."); - domainPropertyEventRows = getDomainPropertyEventsFromDomainEvents(AUDIT_PROPERTY_EVENTS_PROJECT, LIST_CHECK_LOG, ignoreIds); - - pass = validateExpectedRowInDomainPropertyAuditLog(domainPropertyEventRows, FIELD03_NAME, field03ExpectedColumns, null); - - // We are going to fail, so navigate to the Domain Property Events Audit Log. - if(!pass) - goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - - Assert.assertTrue("The values logged for the 'Deleted' events for the lookup field were not as expected. See log for details.", pass); + field3ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, FIELD03_NAME, "Deleted", + "", "", null, null, null); + validateLastDomainAuditEvents(LIST_CHECK_LOG, "The values logged for the 'Deleted' events for the lookup field were not as expected.", expectedDomainEvent, Map.of(FIELD03_NAME, field3ExpectedEvent)); log("Ok, it looks like everything was logged as expected. Yipeee!"); } - private boolean validateExpectedRowInDomainPropertyAuditLog(List> domainPropertyEventRows, String propertyName, Map expectedColumns, @Nullable Map expectedComment) + private void validateLastDomainAuditEvents(String domainName, String failComment, AuditLogHelper.DetailedAuditEventRow domainEvent, Map propertyEvents) { - boolean pass = true; - - for(Map row : domainPropertyEventRows) - { - - if(getLogColumnValue(row, "propertyname").equals(propertyName)) - { - log("Validate the columns for property '" + propertyName + "'."); - for(String fieldName : expectedColumns.keySet()) - { - if(!getLogColumnValue(row, fieldName).equals(expectedColumns.get(fieldName))) - { - pass = false; - log("************** For field '" + fieldName + "' expected value '" + expectedColumns.get(fieldName) + "' found '" + row.get(fieldName) + "' **************"); - } - } - - if(null != expectedComment) - { - log("Validate that the Comment field is as expected."); - Map commentFieldValues = getDomainPropertyEventComment(row); - pass = validateCommentHasExpectedValues(commentFieldValues, expectedComment) && pass; - } - } - - } - - return pass; - } - - private boolean validateCommentHasExpectedValues(Map comment, Map expected) - { - boolean pass = true; - - for(String key : expected.keySet()) - { - if(!expected.get(key).equals(comment.get(key))) - { - log("************** Comment value does not contain expected value for field '" + key + "'. Expected '" + expected.get(key) + "' found '" + comment.get(key) + "'. **************"); - pass = false; - } - } - - return pass; - } - - private List> getDomainPropertyEventsFromDomainEvents(String projectName, String domainName, @Nullable List ignoreIds) - { - List domainEventIds = getDomainEventIds(projectName, domainName); - - if(null != ignoreIds) - { - log("Removing the ignore ids from the list."); - domainEventIds.removeAll(ignoreIds); - } - - log("Get all of the Domain Property Events for '" + domainName + "' that are linked to the domain events."); - List> domainPropertyEventRows = getDomainPropertyEventLog(domainName, domainEventIds); - log("Number of 'Domain Property Event' log entries: " + domainPropertyEventRows.size()); - - return domainPropertyEventRows; - } - - private List getDomainEventIds(String projectName, String domainName) - { - log("Get a list of the Domain Events for project '" + projectName + "'. "); - List> domainAuditEventAllRows = getDomainEventLog(projectName); - log("Number of 'Domain Event' log entries for '" + projectName + "': " + domainAuditEventAllRows.size()); - - log("Filter the list to look only at '" + domainName + "'."); - List> domainAuditEventRows = new ArrayList<>(); - - for(Map row : domainAuditEventAllRows) - { - if(getLogColumnValue(row, "domainname").toLowerCase().trim().equals(domainName.toLowerCase().trim())) - domainAuditEventRows.add(row); - } - - List domainEventIds = new ArrayList<>(); - domainAuditEventRows.forEach((event)->domainEventIds.add(getLogColumnValue(event, "rowid"))); - - log("Number of 'Domain Event' log entries for '" + domainName + "': " + domainEventIds.size()); - - return domainEventIds; - } - - private List getDomainEventIdsFromPropertyEvents(List> domainPropertyEventRows) - { - List domainEventIds = new ArrayList<>(); - - for(Map row : domainPropertyEventRows) - { - domainEventIds.add(getLogColumnValue(row, "domaineventid")); - } - - return domainEventIds; - } - - private List> getDomainEventLog(String projectName) - { - Connection cn = WebTestHelper.getRemoteApiConnection(); - SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "DomainAuditEvent"); - cmd.setRequiredVersion(9.1); - cmd.setColumns(Arrays.asList("rowid", "created", "createdby", "impersonatedby", "projectid", "domainuri", "domainname", "comment")); - cmd.addFilter("projectid/DisplayName", projectName, Filter.Operator.EQUAL); - cmd.setContainerFilter(ContainerFilter.AllFolders); - - return executeSelectCommand(cn, cmd); - } - - private List> getDomainPropertyEventLog(String domainName, @Nullable List eventIds) - { - Connection cn = WebTestHelper.getRemoteApiConnection(); - SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "DomainPropertyAuditEvent"); - cmd.setRequiredVersion(9.1); - cmd.setColumns(Arrays.asList("Created", "CreatedBy", "ImpersonatedBy", "propertyname", "action", "domainname", "domaineventid", "Comment")); - cmd.addFilter("domainname", domainName, Filter.Operator.EQUAL); - - if(null != eventIds) - { - StringBuilder stringBuilder = new StringBuilder(); - eventIds.forEach((id)->{ - if(!stringBuilder.isEmpty()) - stringBuilder.append(";"); - stringBuilder.append(id); - }); - cmd.addFilter("domaineventid/rowid", stringBuilder, Filter.Operator.IN); - } - - cmd.setContainerFilter(ContainerFilter.AllFolders); - - return executeSelectCommand(cn, cmd); - } - - private List> executeSelectCommand(Connection cn, SelectRowsCommand cmd) - { - List> rowsReturned = new ArrayList<>(); - try - { - SelectRowsResponse response = cmd.execute(cn, "/"); - log("Number of rows: " + response.getRowCount()); - rowsReturned.addAll(response.getRows()); - } - catch(IOException | CommandException ex) - { - // Just fail here, don't toss the exception up the stack. - fail("There was a command exception when getting the log: " + ex); - } - - return rowsReturned; - } - - private Map getDomainPropertyEventComment(Map row) - { - String comment = getLogColumnValue(row, "Comment"); - - String[] commentAsArray = comment.split(";"); - - Map fieldComments = new HashMap<>(); - - for (String s : commentAsArray) - { - String[] fieldValue = s.split(":"); - - // If the split on the ':' produced more than two entries in the array it most likely means that the - // comment for that property had a : in it. So treat the first entry as the field name and then concat the - // other fields together. - // For example the ConditionalFormats field will log the following during an update: - // ConditionalFormats: old: , new: 1; - // And a create of a Lookup will log as: - // Lookup: [Schema: lists, Query: LookUp01]; - StringBuilder sb = new StringBuilder(); - sb.append(fieldValue[1].trim()); - - for (int j = 2; j < fieldValue.length; j++) - { - sb.append(":"); - sb.append(fieldValue[j]); - } - - fieldComments.put(fieldValue[0].trim(), sb.toString()); - } + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(domainName, AUDIT_PROPERTY_EVENTS_PROJECT, domainEvent, propertyEvents); + if(!pass) + goToAuditEventView(this, DOMAIN_PROPERTY_LOG_NAME); - return fieldComments; + Assert.assertTrue(failComment + " See log for details.", pass); } - private String getLogColumnValue(Map rowEntry, String columnName) - { - try - { - return ((Map) rowEntry.get(columnName)).get("value").toString(); - } - catch(JSONException je) - { - // Just fail here, don't toss the exception up the stack. - throw new IllegalArgumentException(je); - } - } } diff --git a/src/org/labkey/test/tests/DomainDesignerTest.java b/src/org/labkey/test/tests/DomainDesignerTest.java index 757a332d9b..fcc76afee7 100644 --- a/src/org/labkey/test/tests/DomainDesignerTest.java +++ b/src/org/labkey/test/tests/DomainDesignerTest.java @@ -40,6 +40,7 @@ import org.labkey.test.pages.experiment.CreateSampleTypePage; import org.labkey.test.pages.list.EditListDefinitionPage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.TestDataGenerator; @@ -53,6 +54,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -71,6 +73,9 @@ @Category({BVT.class}) public class DomainDesignerTest extends BaseWebDriverTest { + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + final String DOMAIN_PROPERTY_LOG_NAME = "Domain property events"; + @Override protected void doCleanup(boolean afterTest) throws TestTimeoutException { @@ -281,6 +286,13 @@ public void testInvalidLookupDomainField() throws IOException, CommandException EditListDefinitionPage editListDefinitionPage = new EditListDefinitionPage(getDriver()); editListDefinitionPage.setName(editedListName) .clickSave(); + + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The name of the list domain 'InvalidLookUpNameList' was changed to 'InvalidLookUpNameList_edited'. The descriptor of domain InvalidLookUpNameList_edited was updated.", + "", null, null, "Name: " + listName + " > " + editedListName); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(editedListName, getProjectName(), expectedDomainEvent, Collections.emptyMap()); + checker().verifyTrue("The comment logged for the list renaming was not as expected", pass); + domainDesignerPage = DomainDesignerPage.beginAt(this, getProjectName(), "exp.materials", sampleType); assertEquals("Look up should be updated after list renaming", "Current Folder > lists > " + editedListName, domainDesignerPage.fieldsPanel().getField("lookUpField").detailsMessage()); @@ -1335,18 +1347,19 @@ public void testConditionalFormat() throws Exception public void testRegexValidator() throws Exception { String listName = "regexValidatorList"; - + String fieldNameWithReg = "favoriteSnack"; FieldDefinition.LookupInfo lookupInfo = new FieldDefinition.LookupInfo(getProjectName(), "lists", listName); TestDataGenerator dgen = new TestDataGenerator(lookupInfo) .withColumns(List.of( new FieldDefinition("name", FieldDefinition.ColumnType.String), new FieldDefinition("favoriteIceCream", FieldDefinition.ColumnType.String), - new FieldDefinition("favoriteSnack", FieldDefinition.ColumnType.String), + new FieldDefinition(fieldNameWithReg, FieldDefinition.ColumnType.String), new FieldDefinition("size", FieldDefinition.ColumnType.Integer))); DomainResponse createResponse = dgen.createDomain(createDefaultConnection(), "IntList", Map.of("keyName", "Key")); + DomainDesignerPage domainDesignerPage = DomainDesignerPage.beginAt(this, getProjectName(), "lists", listName); DomainFormPanel domainFormPanel = domainDesignerPage.fieldsPanel(); - DomainFieldRow favoriteSnack = domainFormPanel.getField("favoriteSnack"); + DomainFieldRow favoriteSnack = domainFormPanel.getField(fieldNameWithReg); RegexValidatorDialog validatorDialog = favoriteSnack.clickRegexButton(); RegexValidatorPanel panel = validatorDialog.getValidationPanel(); @@ -1371,6 +1384,23 @@ public void testRegexValidator() throws Exception assertEquals("expected error message should be on the field", "favorite snack cannot be twizzlers, yo", specialCharsValidator.get("errorMessage")); + log("Validate that the expected rows after the update are in the log."); + String fieldOldValues = "Name=favoriteSnack&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false" + + "&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false" + + "&Scannable=false&DefaultValueType=Editable%20default"; + String fieldNewValues = fieldOldValues + "&Validator=neverTwizzlers%2C%20twizzler%20is%20not%20a%20snack%2C%20twizzler%2C%20failOnMatch%3Dtrue%2C%20favorite%20snack%20cannot%20be%20twizzlers%2C%20yo%2C%20Regular%20Expression"; + AuditLogHelper.DetailedAuditEventRow fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, fieldNameWithReg, "Modified", + "The following property was updated: Validator", "", fieldOldValues, fieldNewValues, null); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The column(s) of domain " + listName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, getProjectName(), expectedDomainEvent, Map.of(fieldNameWithReg, fieldEvent)); + + if(!pass) + _auditLogHelper.goToAuditEventView(DOMAIN_PROPERTY_LOG_NAME); + + Assert.assertTrue("The values logged for the updating domain field regex event were not as expected. See log for details.", pass); + // this test does not verify that attempts to insert values that match will get an error } diff --git a/src/org/labkey/test/tests/LinkedSchemaTest.java b/src/org/labkey/test/tests/LinkedSchemaTest.java index d7cba04578..4a3bcce5f7 100644 --- a/src/org/labkey/test/tests/LinkedSchemaTest.java +++ b/src/org/labkey/test/tests/LinkedSchemaTest.java @@ -35,7 +35,9 @@ import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.ListDefinition; import org.labkey.test.params.list.VarListDefinition; +import org.labkey.test.util.APIContainerHelper; import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.LogMethod; import org.labkey.test.util.SchemaHelper; @@ -45,6 +47,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -109,6 +112,7 @@ public class LinkedSchemaTest extends BaseWebDriverTest private static final String STUDY_FOLDER = "StudyFolder"; // Folder used to validate fix for issues 32454 & 32456 private static final String MOTHER = "Mother"; private static final String READER_USER = "reader@linkedschema.test"; + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); public static final String LIST_NAME = "LinkedSchemaTestPeople"; public static final String LIST_DATA = "Name\tAge\tCrazy\tP\tQ\tR\tS\tT\tU\tV\tW\tX\tY\tZ\n" + @@ -426,7 +430,7 @@ void deleteLinkedSchema() } @Test - public void lookupTest() + public void lookupTest() throws CommandException { String sourceContainerPath = "/" + getProjectName() + "/" + SOURCE_FOLDER; _schemaHelper.createLinkedSchema(getProjectName() + "/" + TARGET_FOLDER, "BasicLinkedSchema", sourceContainerPath, null, "lists", "NIMHDemographics,NIMHPortions", null); @@ -441,11 +445,23 @@ public void lookupTest() //Make sure that the lookup columns propagated properly into the linked schema assertLookupsWorking(TARGET_FOLDER, "BasicLinkedSchema", "NIMHDemographics", true, "Mother", "Father"); + String sourceFolderId = ((APIContainerHelper) _containerHelper).getContainerId(getProjectName() + "/" + SOURCE_FOLDER); + String otherFolderId = ((APIContainerHelper) _containerHelper).getContainerId(getProjectName() + "/" + OTHER_FOLDER); + // Linked schemas disallow lookups to other folders outside of the current folder. //Change the Mother column lookup to point to the other folder, then ensure that the mother lookup is no longer propagating - changelistLookup(SOURCE_FOLDER, "NIMHDemographics", MOTHER, + String fieldOldValues = "Name=Mother&Label=Mother&Type=Integer&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + + "&Lookup=%7B%22queryName%22%3A%22NIMHDemographics%22%2C%22schemaName%22%3A%22lists%22%7D"; + String fieldUpdateValues = "Name=Mother&Label=Mother&Type=Integer&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + + "&Lookup=%7B%22queryName%22%3A%22NIMHDemographics%22%2C%22schemaName%22%3A%22lists%22%2C%22containerId%22%3A%22" + + otherFolderId + "%22%7D"; + + changelistLookup( new FieldDefinition.LookupInfo("/" + PROJECT_NAME + "/" + OTHER_FOLDER, "lists", "NIMHDemographics") - .setTableType(FieldDefinition.ColumnType.Integer)); + .setTableType(FieldDefinition.ColumnType.Integer), + fieldOldValues, fieldUpdateValues); assertLookupsWorking(TARGET_FOLDER, "BasicLinkedSchema", "NIMHDemographics", true, "Father"); assertLookupsWorking(TARGET_FOLDER, "BasicLinkedSchema", "NIMHDemographics", false, "Mother"); @@ -457,10 +473,14 @@ public void lookupTest() assertLookupsWorking(TARGET_FOLDER, "QueryLinkedSchema", "QueryOverLookup", true, "Father"); assertLookupsWorking(TARGET_FOLDER, "QueryLinkedSchema", "QueryOverLookup", false, "Mother"); + String fieldUpdateValues2 = "Name=Mother&Label=Mother&Type=Integer&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false&ShownInInsert=true" + + "&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false&Lookup=%7B%22queryName%22%3A%22QueryOverLookup%22%2C%22schemaName%22%3A%22lists%22%2C%22containerId%22%3A%22" + + sourceFolderId + "%22%7D"; //Change the Mother column lookup to point to the query, and then make sure that the table has lookups appropriately. - changelistLookup(SOURCE_FOLDER, "NIMHDemographics", MOTHER, + changelistLookup( new FieldDefinition.LookupInfo("/" + PROJECT_NAME + "/" + SOURCE_FOLDER, "lists", "QueryOverLookup") - .setTableType(FieldDefinition.ColumnType.Integer)); + .setTableType(FieldDefinition.ColumnType.Integer), + fieldUpdateValues, fieldUpdateValues2); assertLookupsWorking(TARGET_FOLDER, "QueryLinkedSchema", "NIMHDemographics", true, "Mother", "Father"); } @@ -923,7 +943,7 @@ public void testAuditTableLinkedSchema() goToSchemaBrowser(); table = viewQueryData(linkedSchemaName, "DomainAuditEvent"); - checker().verifyEquals("Incorrect number of rows in DomainAuditEvent", 33, table.getDataRowCount()); + checker().verifyEquals("Incorrect number of rows in DomainAuditEvent", 23, table.getDataRowCount()); } protected void goToSchemaBrowserTable(String schemaName, String tableName) @@ -991,16 +1011,26 @@ protected void assertLookupsWorking(String sourceFolder, String schemaName, Stri } } - protected void changelistLookup(String sourceFolder, String tableName, String fieldName, FieldDefinition.LookupInfo info) + protected void changelistLookup(FieldDefinition.LookupInfo info, String fieldOldValues, String fieldUpdateValues) { - clickFolder(sourceFolder); + clickFolder(LinkedSchemaTest.SOURCE_FOLDER); goToManageLists(); - EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(tableName); + EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign("NIMHDemographics"); listDefinitionPage.getFieldsPanel() - .getField(fieldName).setLookup(info); + .getField(LinkedSchemaTest.MOTHER).setLookup(info); listDefinitionPage.clickSave(); + + + log("Validate domain and domain property audit log."); + AuditLogHelper.DetailedAuditEventRow fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, LinkedSchemaTest.MOTHER, "Modified", + "The following property was updated: Lookup", "", fieldOldValues, fieldUpdateValues, null); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, "NIMHDemographics", null, + "The column(s) of domain " + "NIMHDemographics" + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents("NIMHDemographics", getProjectName(), expectedDomainEvent, Map.of(LinkedSchemaTest.MOTHER, fieldEvent)); + checker().verifyTrue("Domain audit long not as expected after changing lookup expected", pass); } protected void createLinkedSchemaQuery(String sourceFolder, String schemaName, String queryName, String tableName) diff --git a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java index 71d21e438e..39dac35804 100644 --- a/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java +++ b/src/org/labkey/test/tests/SampleTypeNameExpressionTest.java @@ -33,6 +33,7 @@ import org.labkey.test.pages.experiment.UpdateSampleTypePage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.PortalHelper; @@ -82,6 +83,8 @@ public class SampleTypeNameExpressionTest extends BaseWebDriverTest private static final File PARENT_EXCEL = TestFileUtils.getSampleData("samples/ParentSamples.xlsx"); + protected final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + @Override public List getAssociatedModules() { @@ -1283,6 +1286,13 @@ public void testSetAndResetOfGenId() idDialog.setGenId(Integer.toString(nextGenId)); idDialog.dismiss("Update"); + // check audit log + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleType, null, + "The genId for domain Test_Set_GenId has been updated to " + (nextGenId - 1) + ".", null, null, + null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(sampleType, getProjectName(), expectedDomainEvent, null); + checker().verifyTrue("Result Domain event not as expected after updating genId", pass); + log("Validate that the banner has been updated."); checker() @@ -1335,17 +1345,17 @@ public void testSetAndResetOfGenId() waitFor(updatePage::isResetGenIdVisible, "The 'Reset GenId' button should now be visible if the sample type is empty. Fatal error.", 500); - ModalDialog deleteDialog = updatePage.clickResetGenId(); + ModalDialog genIdDialog = updatePage.clickResetGenId(); String expectedMsg = String.format("The current genId is at %d. Resetting will reset genId back to 1 and cannot be undone.", nextGenId); checker() .withScreenshot("Reset_GenId_Dialog_Error") .verifyEquals("Message in the reset confirm dialog is not as expected.", - expectedMsg, deleteDialog.getBodyText()); + expectedMsg, genIdDialog.getBodyText()); log("Click 'Cancel' and verify banner/genId does not change."); - deleteDialog.dismiss("Cancel"); + genIdDialog.dismiss("Cancel"); checker() .withScreenshot("Reset_GenId_Cancel_Error") @@ -1354,8 +1364,15 @@ public void testSetAndResetOfGenId() log("Click 'Rest GenId' again and this time reset the genId."); - deleteDialog = updatePage.clickResetGenId(); - deleteDialog.dismiss("Reset"); + genIdDialog = updatePage.clickResetGenId(); + genIdDialog.dismiss("Reset"); + + expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleType, null, + "The genId for domain " + sampleType + " has been updated to 0.", null, null, + null, null); + pass = _auditLogHelper.validateLastDomainAuditEvents(sampleType, getProjectName(), expectedDomainEvent, null); + checker().verifyTrue("Result Domain event not as expected after resetting genId", pass); + nextGenId = 1; checker() diff --git a/src/org/labkey/test/tests/SampleTypeParentColumnTest.java b/src/org/labkey/test/tests/SampleTypeParentColumnTest.java index c3da61d38a..004b52037d 100644 --- a/src/org/labkey/test/tests/SampleTypeParentColumnTest.java +++ b/src/org/labkey/test/tests/SampleTypeParentColumnTest.java @@ -18,6 +18,7 @@ import org.labkey.test.pages.experiment.UpdateSampleTypePage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.ExcelHelper; import org.labkey.test.util.PortalHelper; @@ -29,6 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -65,6 +67,8 @@ public class SampleTypeParentColumnTest extends BaseWebDriverTest COL_DESCRIPTION_CAPTION, COL_DESCRIPTION_NAME, COL_NAME_CAPTION, COL_NAME_NAME); + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + @Override public List getAssociatedModules() { @@ -605,6 +609,15 @@ public void testUseThenRemoveAnAliasParentColumn() updatePage.removeParentAlias(PARENT_COLUMN); updatePage.clickSave(); + log("Validate domain audit log."); + String oldValues = "Name=SimpleSampleType07&ImportAlias=P7(materialInputs%2FSimpleSampleType07%2Crequired%3Dfalse)"; + String newValues = "Name=SimpleSampleType07&AliquotNameExpression=%24%7B%24%7BAliquotedFrom%7D-%3AwithCounter%7D"; + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, SAMPLE_TYPE_NAME, null, + "The descriptor of domain " + SAMPLE_TYPE_NAME + " was updated.", + "", oldValues, newValues, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(SAMPLE_TYPE_NAME, PROJECT_NAME, expectedDomainEvent, Collections.emptyMap()); + checker().verifyTrue("Domain audit long not as expected after removing the parent alias column", pass); + log("Import some more samples using the alias column and make sure it doesn't work."); sampleText = "Name\t" + PARENT_COLUMN + "\n" + "SG_03\tSG_01\n"; @@ -702,6 +715,15 @@ public void testAliasNameConflictsWithFieldName() updatePage.addParentAlias(GOOD_PARENT_NAME, SampleTypeDesigner.CURRENT_SAMPLE_TYPE); updatePage.clickSave(); + log("Validate domain audit log."); + String oldValues = "Name=SimpleSampleType08&AliquotNameExpression=%24%7B%24%7BAliquotedFrom%7D-%3AwithCounter%7D"; + String newValues = "Name=SimpleSampleType08&AliquotNameExpression=%24%7B%24%7BAliquotedFrom%7D-%3AwithCounter%7D&ImportAlias=P8(materialInputs%2FSimpleSampleType08%2Crequired%3Dfalse)"; + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, SAMPLE_TYPE_NAME, null, + "The descriptor of domain " + SAMPLE_TYPE_NAME + " was updated.", + "", oldValues, newValues, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(SAMPLE_TYPE_NAME, PROJECT_NAME, expectedDomainEvent, Collections.emptyMap()); + checker().verifyTrue("Domain audit long not as expected after adding a parent alias column", pass); + clickFolder(SUB_FOLDER_NAME); updatePage = sampleHelper.goToEditSampleType(SAMPLE_TYPE_NAME); Assert.assertEquals("Expected parent alias name not found.", GOOD_PARENT_NAME, updatePage.getParentAlias(0)); diff --git a/src/org/labkey/test/tests/StudyBaseTest.java b/src/org/labkey/test/tests/StudyBaseTest.java index c248fb8fba..db50748835 100644 --- a/src/org/labkey/test/tests/StudyBaseTest.java +++ b/src/org/labkey/test/tests/StudyBaseTest.java @@ -31,6 +31,7 @@ import org.labkey.test.params.FieldDefinition.PhiSelectType; import org.labkey.test.util.APITestHelper; import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.StudyHelper; @@ -73,6 +74,8 @@ protected void setupRequestStatuses() new SpecimenHelper(this).setupRequestStatuses(); } + protected final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + @Override public List getAssociatedModules() { diff --git a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java index c44a54e052..ca6fd7aafe 100644 --- a/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java +++ b/src/org/labkey/test/tests/TextChoiceSampleTypeTest.java @@ -1,5 +1,6 @@ package org.labkey.test.tests; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -16,6 +17,7 @@ import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.FieldDefinition.ColumnType; import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.SampleTypeHelper; @@ -28,13 +30,16 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Category({Daily.class}) public class TextChoiceSampleTypeTest extends BaseWebDriverTest { + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); @Override public BrowserType bestBrowser() @@ -326,6 +331,10 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm TestDataGenerator dataGenerator = createSampleType(sampleTypeName, namePrefix, textChoiceFieldName, expectedUnLockedValues); + // Add the list of the event ids to an ignore list so future tests don't look at them again. + Set ignoreIds = new HashSet<>(); + ignoreIds.addAll(_auditLogHelper.getDomainEventIds(getProjectName(), sampleTypeName, null)); + log("Create some samples that have TextChoice values set."); // Only assign a few of the values to samples (i.e. lock them). @@ -447,6 +456,22 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm updatePage.clickSave(); + log("Validate that the expected rows after the update are in the log."); + String fieldOldValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + + "&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20%C3%85%5C%7C%C3%85%7CBB%7CCC%7CDD%7CE%20E%20E%7C%C2%83%C2%83%7CGG%7CH%2C%20Text%20Choice%20Validator"; + String fieldUpdateValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" + + "&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" + + "&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator"; + AuditLogHelper.DetailedAuditEventRow fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified", + "The following property was updated: Validator", "", fieldOldValues, fieldUpdateValues, null); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleTypeName, null, + "The column(s) of domain " + sampleTypeName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(sampleTypeName, getProjectName(), expectedDomainEvent, Map.of(textChoiceFieldName, fieldEvent)); + checker().verifyTrue("Audit event is not as expected after text choice after", pass); + + // Construct a list of samples that have TextChoice set and what they are expected to be. List> expectedSamples = new ArrayList<>(); @@ -496,7 +521,7 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm log("Click save, there should be no errors."); updatePage.clickSave(); - log("Finally update a value and click the save button for the sample type without clicking 'Apply'."); + log("Next update a value and click the save button for the sample type without clicking 'Apply'."); waitAndClickAndWait(Locator.lkButton("Edit Type")); updatePage = new UpdateSampleTypePage(getDriver()); @@ -525,8 +550,22 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm checker().verifyTrue(String.format("Expected value '%s' is not in the list of values.", valueToUpdate), actualValues.contains(valueToUpdate)); + log("Finally update a value and click the save button for the sample type and clicking 'Apply'."); + fieldRow.updateTextChoiceValue(valueToUpdate, updatedValue); + updatePage.clickSave(); + + log("Validate that the expected rows after the update are in the log."); + String fieldUpdateValues2 = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false" + + "&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false" + + "&Scannable=false&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%20no%20change%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator"; + fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified", + "The following property was updated: Validator", "", fieldUpdateValues, fieldUpdateValues2, null); + pass = _auditLogHelper.validateLastDomainAuditEvents(sampleTypeName, getProjectName(), expectedDomainEvent, Map.of(textChoiceFieldName, fieldEvent)); + checker().verifyTrue("Audit event is not as expected", pass); + } + /** *

* Validate the TextChoice values when editing/create a sample. @@ -560,6 +599,22 @@ public void testSetTextChoiceValueForSample() throws IOException, CommandExcepti TestDataGenerator dataGenerator = createSampleType(sampleTypeName, namePrefix, textChoiceFieldName, tcValues); + // audit log for creating new sample type + String fieldValueMap = "Name=TextChoice_Field&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false" + + "&Measure=false&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false" + + "&ExcludedFromShifting=false&Scannable=false&DefaultValueType=Editable%20default" + + "&Validator=Text%20Choice%20Validator%2C%20%C3%86%5C%7C%5C%7C%C3%86%7CBB%7CC%5C%7C%5C%7CC%2C%20Text%20Choice%20Validator"; + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleTypeName, null, + "The domain " + sampleTypeName + " was created. The column(s) of domain " + sampleTypeName + " were modified.", + null, null, "NameExpression=TCSM_%24%7BgenId%7D", null); + AuditLogHelper.DetailedAuditEventRow field1ExpectedEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Created", + null, null, null, fieldValueMap, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(sampleTypeName, getProjectName(), expectedDomainEvent, + Map.of(textChoiceFieldName, field1ExpectedEvent, + "Str", new AuditLogHelper.DetailedAuditEventRow(null, "Str", "Created", + null, null, null, null, null))); + checker().verifyTrue("Audit log not as expected for creating a new sample type with text choice field.", pass); + log("Create some samples int the sample type. None of the samples will have a TextChoice value."); List availableSamples = new ArrayList<>(); diff --git a/src/org/labkey/test/tests/list/ListDateAndTimeTest.java b/src/org/labkey/test/tests/list/ListDateAndTimeTest.java index 140542c360..211181d651 100644 --- a/src/org/labkey/test/tests/list/ListDateAndTimeTest.java +++ b/src/org/labkey/test/tests/list/ListDateAndTimeTest.java @@ -27,6 +27,7 @@ import org.labkey.test.pages.core.admin.LookAndFeelSettingsPage; import org.labkey.test.pages.list.EditListDefinitionPage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionExportHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.ExcelHelper; @@ -50,6 +51,8 @@ public class ListDateAndTimeTest extends BaseWebDriverTest private static SimpleDateFormat _defaultTimeFormat = null; private static SimpleDateFormat _defaultDateTimeFormat = null; + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + @Override public List getAssociatedModules() { @@ -1222,6 +1225,16 @@ public void testDateAndTimeFormat() listDefinitionPage.clickSave(); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The column(s) of domain " + listName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, PROJECT_NAME, expectedDomainEvent, + Map.of(timeCol, new AuditLogHelper.DetailedAuditEventRow(null, timeCol, "Modified","The following property was updated: Format",null, null, null, "Format: hh:mm a > HH:mm:ss"), + dateCol, new AuditLogHelper.DetailedAuditEventRow(null, dateCol, "Modified","The following property was updated: Format",null, null, null, "Format: yyyy-MM-dd > ddMMMyy"), + dateTimeCol, new AuditLogHelper.DetailedAuditEventRow(null, dateTimeCol, "Modified","The following property was updated: Format",null, null, null, "Format: yyyy-MM-dd hh:mm a > dd-MMM-yyyy HH:mm:ss")) + ); + checker().verifyTrue("Domain audit log not as expected after updating formats", pass); + expectedData = new ArrayList<>(); for(Date date : dates) @@ -1350,6 +1363,15 @@ public void testConvertDateTimeField() throws IOException, CommandException listDefinitionPage.clickSave(); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The column(s) of domain " + listName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, PROJECT_NAME, expectedDomainEvent, + Map.of(dateTimeToDateCol, new AuditLogHelper.DetailedAuditEventRow(null, dateTimeToDateCol, "Modified","The following properties were updated: Type, Format",null, null, null, "Type: DateTime > Date\nFormat: MMMM dd yyyy HH:mm > "), + dateTimeToTimeCol, new AuditLogHelper.DetailedAuditEventRow(null, dateTimeToTimeCol, "Modified","The following properties were updated: Type, Format",null, null, null, "Type: DateTime > Time\nFormat: yyyy-MMM-dd HH:mm:ss > ")) + ); + checker().verifyTrue("Domain audit log not as expected after changing data type", pass); + // Update default format after changing the types. DATE_FORMAT dateFormat = DATE_FORMAT.Default; TIME_FORMAT timeFormat = TIME_FORMAT.Default; diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index c00b99fba4..a5b62e0ec1 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -56,6 +56,7 @@ import org.labkey.test.params.FieldDefinition.StringLookup; import org.labkey.test.tests.AuditLogTest; import org.labkey.test.util.AbstractDataRegionExportOrSignHelper.ColumnHeaderType; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionExportHelper; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.EscapeUtil; @@ -192,6 +193,8 @@ public class ListTest extends BaseWebDriverTest private final File TSV_SAMPLE_FILE = TestFileUtils.getSampleData("fileTypes/tsv_sample.tsv"); private final String TSV_LIST_NAME = "Fruits from TSV"; + private final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); + @Override public List getAssociatedModules() { @@ -635,14 +638,17 @@ public void testCustomViews() clickAndWait(Locator.linkWithText("view history")); checker().wrapAssertion(()->assertTextPresent(":History")); checker().wrapAssertion(()->assertTextPresent("record was modified", 2)); // An existing list record was modified - checker().wrapAssertion(()->assertTextPresent("were modified", 8)); // The column(s) of LIST_NAME_COLORS domain were modified + + checker().wrapAssertion(()->assertTextPresent(" was created. The column(s) of domain ", 1));// Create domain and update columns combined into a single event + checker().wrapAssertion(()->assertTextPresent(" were modified.", 7)); // The column(s) of LIST_NAME_COLORS domain were modified + checker().wrapAssertion(()->assertTextPresent("The descriptor of domain", 1)); // The description LIST_NAME_COLORS domain were modified checker().wrapAssertion(()->assertTextPresent("Bulk inserted", 2)); checker().wrapAssertion(()->assertTextPresent("A new list record was inserted", 1)); checker().wrapAssertion(()->assertTextPresent("was created", 2)); // Once for the list, once for the domain // List insert/update events should each have a link to the list item that was modified, but the other events won't have a link - checker().wrapAssertion(()->assertEquals("details Links", 6, DataRegionTable.detailsLinkLocator().findElements(getDriver()).size())); - checker().wrapAssertion(()->assertEquals("Project Links", 18, DataRegionTable.Locators.table().append(Locator.linkWithText(PROJECT_VERIFY)).findElements(getDriver()).size())); - checker().wrapAssertion(()->assertEquals("List Links", 18, DataRegionTable.Locators.table().append(Locator.linkWithText(LIST_NAME_COLORS)).findElements(getDriver()).size())); + checker().wrapAssertion(()->assertEquals("details Links", 6/*List Events*/ + 8/*Domain Audit*/, DataRegionTable.detailsLinkLocator().findElements(getDriver()).size())); + checker().wrapAssertion(()->assertEquals("Project Links", 17, DataRegionTable.Locators.table().append(Locator.linkWithText(PROJECT_VERIFY)).findElements(getDriver()).size())); + checker().wrapAssertion(()->assertEquals("List Links", 17, DataRegionTable.Locators.table().append(Locator.linkWithText(LIST_NAME_COLORS)).findElements(getDriver()).size())); DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); dataRegionTable.clickRowDetails(0); checker().wrapAssertion(()->assertTextPresent("List Item Details")); @@ -1227,6 +1233,15 @@ private void customFormattingTest() listDefinitionPage.clickSave(); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, TSV_LIST_NAME, null, + "The column(s) of domain " + TSV_LIST_NAME + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(TSV_LIST_NAME, getProjectName(), expectedDomainEvent, + Map.of("IntCol", new AuditLogHelper.DetailedAuditEventRow(null, "IntCol", "Modified","The following property was updated: ConditionalFormat",null, null, null, "ConditionalFormat: > format.column~gt=7: text-decoration: line-through;, format.column~gt=5: font-weight: bold;"), + "BoolCol", new AuditLogHelper.DetailedAuditEventRow(null, "BoolCol", "Modified","The following property was updated: ConditionalFormat",null, null, null, "ConditionalFormat: > format.column~eq=true: text-decoration: line-through;font-weight: bold;font-style: italic;color: #68ccca;background-color: #d33115 !important;")) + ); + checker().verifyTrue("Domain audit comment not as expected after changing conditional format", pass); + // Verify conditional format of boolean column // look for cells that do not match the assertTextPresent(TSV_LIST_NAME); @@ -1294,6 +1309,13 @@ public void doRenameFieldsTest() .setLabel(newFieldName); listDefinitionPage.clickSave(); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The column(s) of domain " + listName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, getProjectName(), expectedDomainEvent, + Map.of(newFieldName, new AuditLogHelper.DetailedAuditEventRow(null, newFieldName, "Modified","The following properties were updated: Name, Label",null, null, null, "Name: "+ origFieldName + " > " + newFieldName + "\nLabel: " + origFieldName + " > " + newFieldName))); + checker().verifyTrue("Domain audit comment not as expected after renaming a field", pass); + assertTextPresent(newFieldName); assertTextNotPresent(origFieldName); @@ -1331,6 +1353,16 @@ public void exportPhiFileColumn() throws Exception listDefinitionPage.setColumnPhiLevel("RestrictedPhiColumn", FieldDefinition.PhiSelectType.Restricted); listDefinitionPage.clickSave(); + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The column(s) of domain " + listName + " were modified.", + "", null, null, null); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, getProjectName(), expectedDomainEvent, + Map.of("LimitedPhiColumn", new AuditLogHelper.DetailedAuditEventRow(null, "LimitedPhiColumn", "Modified","The following property was updated: PHI",null, null, null, "PHI: Not PHI > Limited PHI"), + "PhiColumn", new AuditLogHelper.DetailedAuditEventRow(null, "PhiColumn", "Modified","The following property was updated: PHI",null, null, null, "PHI: Not PHI > Full PHI"), + "RestrictedPhiColumn", new AuditLogHelper.DetailedAuditEventRow(null, "RestrictedPhiColumn", "Modified","The following property was updated: PHI",null, null, null, "PHI: Not PHI > Restricted PHI")) + ); + checker().verifyTrue("Domain audit comment not as expected after changing PHI setting", pass); + goToProjectHome(); clickAndWait(Locator.linkWithText(listName)); @@ -1502,6 +1534,13 @@ public void testFieldUniqueConstraint() .getField(fieldName2).expand().clickAdvancedSettings().setUniqueConstraint(true) .apply(); listDefinitionPage.clickSave(); + + AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The descriptor of domain " + listName + " was updated.", + "", null, null, "Indices: > [field Name1, unique: true, fieldName_2, unique: true]"); + boolean pass = _auditLogHelper.validateLastDomainAuditEvents(listName, getProjectName(), expectedDomainEvent, Collections.emptyMap()); + checker().verifyTrue("Domain audit comment not as expected after updating field unique constraint", pass); + viewRawTableMetadata(listName); verifyTableIndices("unique_constraint_list_", List.of("field_name1", "fieldname_2")); assertTextNotPresent("unique_constraint_list_fieldname_3"); @@ -1515,6 +1554,13 @@ public void testFieldUniqueConstraint() .getField(fieldName3).expand().clickAdvancedSettings().setUniqueConstraint(true) .apply(); listDefinitionPage.clickSave(); + + expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, listName, null, + "The descriptor of domain " + listName + " was updated.", + "", null, null, "Indices: [field name1, unique: true, fieldname_2, unique: true] > [FieldName@3, unique: true, field Name1, unique: true]"); + pass = _auditLogHelper.validateLastDomainAuditEvents(listName, getProjectName(), expectedDomainEvent, Collections.emptyMap()); + checker().verifyTrue("Domain audit comment not as expected after updating field unique constraint", pass); + viewRawTableMetadata(listName); verifyTableIndices("unique_constraint_list_", List.of("field_name1", "fieldname_3")); assertTextNotPresent("unique_constraint_list_fieldname_2"); diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 4064181740..685f3fd789 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -1,6 +1,8 @@ package org.labkey.test.util; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; +import org.json.JSONException; import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.query.ContainerFilter; @@ -10,18 +12,27 @@ import org.labkey.remoteapi.query.Sort; import org.labkey.test.Locator; import org.labkey.test.WebDriverWrapper; +import org.labkey.test.WebTestHelper; import org.labkey.test.pages.core.admin.ShowAdminPage; import org.labkey.test.pages.core.admin.ShowAuditLogPage; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; +import static java.lang.Integer.parseInt; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class AuditLogHelper { @@ -188,4 +199,330 @@ public interface ConnectionSupplier { Connection get() throws IOException, CommandException; } + + public boolean validateDetailAuditLog(DetailedAuditEventRow expectedAuditDetail, DetailedAuditEventRow actualAuditDetail) + { + boolean pass = true; + for (String prop : propertyAuditColumns) + { + String expectedValue = expectedAuditDetail.getColumn(prop); + if (expectedValue != null) + { + String actualValue = actualAuditDetail.getColumn(prop); + + if (StringUtils.isEmpty(expectedValue) && actualValue == null) + continue; + + if (!expectedValue.equals(actualValue)) + { + pass = false; + TestLogger.log(prop + " is not as expected. Expected: " + expectedValue + ", Actual: " + actualValue); + } + } + } + return pass; + } + + public boolean validateDomainPropertiesAuditLog(String domainName, Integer domainEventId, Map expectedAuditDetails) + { + if (expectedAuditDetails == null) + return true; + Map actualAuditDetails = getDomainPropertyEvents(domainName, domainEventId); + boolean pass = true; + if (expectedAuditDetails.keySet().size() != actualAuditDetails.keySet().size()) + { + pass = false; + TestLogger.log("Number of DomainPropertyAuditEvent events not as expected."); + } + for (String key : expectedAuditDetails.keySet()) + { + DetailedAuditEventRow expectedAuditDetail = expectedAuditDetails.get(key); + DetailedAuditEventRow actualAuditDetail = actualAuditDetails.get(key); + if (actualAuditDetail == null) + { + pass = false; + TestLogger.log("Field " + key + " is missing DomainPropertyAuditEvent."); + } + else + pass = pass && validateDetailAuditLog(expectedAuditDetail, actualAuditDetail); + } + + return pass; + } + + public boolean validateLastDomainAuditEvents(String domainName, String projectName, DetailedAuditEventRow expectedDomainEvent, Map expectedDomainPropertyEvents) + { + DetailedAuditEventRow latestDomainEvent = getLastDomainEvent(projectName, domainName); + boolean pass = validateDetailAuditLog(expectedDomainEvent, latestDomainEvent); + return pass && validateDomainPropertiesAuditLog(domainName, latestDomainEvent.rowId, expectedDomainPropertyEvents); + } + + public List getDomainEventIds(String projectName, String domainName, @Nullable Collection ignoreIds) + { + List domainAuditEventAllRows = getDomainEventLog(projectName, domainName, ignoreIds); + + List domainEventIds = new ArrayList<>(); + domainAuditEventAllRows.forEach((event)->domainEventIds.add(event.rowId)); + + TestLogger.log("Number of 'Domain Event' log entries for '" + domainName + "': " + domainEventIds.size()); + + return domainEventIds; + } + + public DetailedAuditEventRow getLastDomainEvent(String projectName, String domainName) + { + return getDomainEventLog(projectName, domainName, null).get(0); + } + + public Integer getLastDomainEventId(String projectName, String domainName) + { + return getLastDomainEvent(projectName, domainName).rowId; + } + + public static List propertyAuditColumns = List.of("type", "comment", "usercomment", "oldvalues", "newvalues", "datachanges"); + public record DetailedAuditEventRow(Integer rowId, String keyValue, String type, String comment, String userComment, String oldValues, String newValues, String dataChanges) + { + public String getColumn(String columnName) + { + String columnname = columnName.toLowerCase(); + return switch (columnname) + { + case "keyvalue" -> keyValue; + case "rowid" -> rowId + ""; + case "type" -> type; + case "comment" -> comment; + case "usercomment" -> userComment; + case "oldvalues" -> oldValues; + case "newvalues" -> newValues; + case "datachanges" -> dataChanges; + default -> null; + }; + } + + public String getLogString() + { + return "Comment: " + comment + "\nOldValue:" + oldValues + "\nNewValue:" + newValues; + } + } + + public Map getDomainPropertyEvents(String domainName, Integer domainEventId) + { + List> allRows = getDomainPropertyEventLog(domainName, Collections.singletonList(domainEventId)); + Map domainPropEventComments = new HashMap<>(); + allRows.forEach((event)->{ + Integer rowId = getLogColumnIntValue(event, "RowId"); + String propertyName = getLogColumnValue(event, "PropertyName"); + String action = getLogColumnValue(event, "Action"); + String comment = getLogColumnValue(event, "Comment"); + String userComment = getLogColumnValue(event, "UserComment"); + String oldValue = getLogColumnValue(event, "oldValues"); + String newValue = getLogColumnValue(event, "newValues"); + String dataChanges = getLogColumnDisplayValue(event, "dataChanges"); + domainPropEventComments.put(propertyName, new DetailedAuditEventRow(rowId, propertyName, action, comment, userComment, oldValue, newValue, dataChanges)); + }); + return domainPropEventComments; + + } + + public Map getLastDomainPropertyEvents(String projectName, String domainName) + { + Integer lastDomainEventId = getLastDomainEventId(projectName, domainName); + return getDomainPropertyEvents(domainName, lastDomainEventId); + } + + public List getLastDomainPropertyValues(String projectName, String domainName, String columnName) + { + return getLastDomainPropertyEvents(projectName, domainName).values().stream().map(values -> values.getColumn(columnName)).toList(); + } + + public List getDomainEventComments(String projectName, String domainName, @Nullable Collection ignoreIds) + { + List domainAuditEventAllRows = getDomainEventLog(projectName, domainName, ignoreIds); + + List domainEventComments = new ArrayList<>(); + domainAuditEventAllRows.forEach((event)->domainEventComments.add(event.comment)); + return domainEventComments; + } + + public Set getDomainEventIdsFromPropertyEvents(List> domainPropertyEventRows) + { + Set domainEventIds = new HashSet<>(); + + for(Map row : domainPropertyEventRows) + { + domainEventIds.add(getLogColumnIntValue(row, "domaineventid")); + } + + return domainEventIds; + } + + private List getDomainEventLog(String projectName, String domainName, @Nullable Collection ignoreIds) + { + TestLogger.log("Get a list of the Domain Events for project '" + projectName + "'. "); + + Connection cn = WebTestHelper.getRemoteApiConnection(); + SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "DomainAuditEvent"); + cmd.setRequiredVersion(9.1); + cmd.setColumns(Arrays.asList("rowid", "domainuri", "domainname", "comment", "usercomment", "oldvalues", "newvalues", "datachanges")); + cmd.addFilter("projectid/DisplayName", projectName, Filter.Operator.EQUAL); + if(null != ignoreIds) + { + StringBuilder stringBuilder = new StringBuilder(); + ignoreIds.forEach((id)->{ + if(!stringBuilder.isEmpty()) + stringBuilder.append(";"); + stringBuilder.append(id); + }); + cmd.addFilter("rowId", stringBuilder, Filter.Operator.NOT_IN); + } + cmd.setContainerFilter(ContainerFilter.AllFolders); + cmd.setSorts(Arrays.asList(new Sort("RowId", Sort.Direction.DESCENDING))); + + List> domainAuditEventAllRows = executeSelectCommand(cn, cmd); + TestLogger.log("Number of 'Domain Event' log entries for '" + projectName + "': " + domainAuditEventAllRows.size()); + + TestLogger.log("Filter the list to look only at '" + domainName + "'."); + List domainAuditEventRows = new ArrayList<>(); + + for(Map row : domainAuditEventAllRows) + { + String domainName_ = getLogColumnValue(row, "domainname"); + + if(domainName_.trim().equalsIgnoreCase(domainName.trim())) + { + Integer rowId = getLogColumnIntValue(row, "rowid"); + String comment = getLogColumnValue(row, "comment"); + String userComment = getLogColumnValue(row, "usercomment"); + String oldValue = getLogColumnValue(row, "oldvalues"); + String newValue = getLogColumnValue(row, "newvalues"); + String dataChanges = getLogColumnDisplayValue(row, "dataChanges"); + domainAuditEventRows.add(new DetailedAuditEventRow(rowId, domainName, null, comment, userComment, oldValue, newValue, dataChanges)); + } + } + + return domainAuditEventRows; + } + + private List> getDomainPropertyEventLog(String domainName, @Nullable List eventIds) + { + Connection cn = WebTestHelper.getRemoteApiConnection(); + SelectRowsCommand cmd = new SelectRowsCommand("auditLog", "DomainPropertyAuditEvent"); + cmd.setRequiredVersion(9.1); + cmd.setColumns(Arrays.asList("Created", "CreatedBy", "ImpersonatedBy", "propertyname", "action", "domainname", "domaineventid", "Comment", "UserComment", "oldvalues", "newvalues", "datachanges")); + cmd.addFilter("domainname", domainName, Filter.Operator.EQUAL); + + if(null != eventIds) + { + StringBuilder stringBuilder = new StringBuilder(); + eventIds.forEach((id)->{ + if(!stringBuilder.isEmpty()) + stringBuilder.append(";"); + stringBuilder.append(id); + }); + cmd.addFilter("domaineventid/rowid", stringBuilder, Filter.Operator.IN); + } + + cmd.setContainerFilter(ContainerFilter.AllFolders); + + return executeSelectCommand(cn, cmd); + } + + private List> executeSelectCommand(Connection cn, SelectRowsCommand cmd) + { + List> rowsReturned = new ArrayList<>(); + try + { + SelectRowsResponse response = cmd.execute(cn, "/"); + TestLogger.log("Number of rows: " + response.getRowCount()); + rowsReturned.addAll(response.getRows()); + } + catch(IOException | CommandException ex) + { + // Just fail here, don't toss the exception up the stack. + fail("There was a command exception when getting the log: " + ex); + } + + return rowsReturned; + } + + private Map getDomainPropertyEventComment(Map row) + { + String comment = getLogColumnValue(row, "Comment"); + if (comment != null) + return null; + + String[] commentAsArray = comment.split(";"); + + Map fieldComments = new HashMap<>(); + + for (String s : commentAsArray) + { + String[] fieldValue = s.split(":"); + + // If the split on the ':' produced more than two entries in the array it most likely means that the + // comment for that property had a : in it. So treat the first entry as the field name and then concat the + // other fields together. + // For example the ConditionalFormats field will log the following during an update: + // ConditionalFormats: old: , new: 1; + // And a create of a Lookup will log as: + // Lookup: [Schema: lists, Query: LookUp01]; + StringBuilder sb = new StringBuilder(); + sb.append(fieldValue[1].trim()); + + for (int j = 2; j < fieldValue.length; j++) + { + sb.append(":"); + sb.append(fieldValue[j]); + } + + fieldComments.put(fieldValue[0].trim(), sb.toString()); + } + + return fieldComments; + } + + private String getLogColumnValue(Map rowEntry, String columnName, String valueType) + { + try + { + Map val = ((Map) rowEntry.get(columnName)); + if (val == null) + return null; + Object value = val.get(valueType); + if (value == null) + return null; + return value.toString(); + } + catch(JSONException je) + { + // Just fail here, don't toss the exception up the stack. + throw new IllegalArgumentException(je); + } + } + + private String getLogColumnValue(Map rowEntry, String columnName) + { + return getLogColumnValue(rowEntry, columnName, "value"); + } + + private String getLogColumnDisplayValue(Map rowEntry, String columnName) + { + return getLogColumnValue(rowEntry, columnName, "displayValue"); + } + + private Integer getLogColumnIntValue(Map rowEntry, String columnName) + { + try + { + String strVal = getLogColumnValue(rowEntry, columnName); + if (strVal == null) + return null; + return parseInt(strVal); + } + catch(JSONException je) + { + // Just fail here, don't toss the exception up the stack. + throw new IllegalArgumentException(je); + } + } }