diff --git a/src/org/labkey/test/LabKeySiteWrapper.java b/src/org/labkey/test/LabKeySiteWrapper.java index 62b26da729..d277990120 100644 --- a/src/org/labkey/test/LabKeySiteWrapper.java +++ b/src/org/labkey/test/LabKeySiteWrapper.java @@ -321,22 +321,31 @@ protected void bypassSecondaryAuthentication() } } - protected void acceptTermsOfUse(String termsText, boolean clickAgree) + protected boolean acceptTermsOfUse(String termsText, boolean clickAgree) { + boolean signed = false; Optional optionalCheckbox = Locators.termsOfUseCheckbox().findOptionalElement(getDriver()); - optionalCheckbox.ifPresent(termsCheckbox -> + + if (optionalCheckbox.isPresent()) { - if (termsCheckbox.isDisplayed()) + + if (optionalCheckbox.get().isDisplayed()) { - checkCheckbox(termsCheckbox); + checkCheckbox(optionalCheckbox.get()); if (null != termsText) { assertTextPresent(termsText); } if (clickAgree) + { clickButton("Agree"); + signed = true; + } } - }); + } + + return signed; + } @LogMethod diff --git a/src/org/labkey/test/tests/BaseTermsOfUseTest.java b/src/org/labkey/test/tests/BaseTermsOfUseTest.java index d33917ec4c..691539183e 100644 --- a/src/org/labkey/test/tests/BaseTermsOfUseTest.java +++ b/src/org/labkey/test/tests/BaseTermsOfUseTest.java @@ -16,18 +16,25 @@ package org.labkey.test.tests; import org.junit.BeforeClass; +import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.query.Filter; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.WikiHelper; +import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import static org.labkey.api.security.UserManager.USER_AUDIT_EVENT; import static org.labkey.test.util.PermissionsHelper.EDITOR_ROLE; import static org.labkey.test.util.PermissionsHelper.READER_ROLE; @@ -43,6 +50,7 @@ public class BaseTermsOfUseTest extends BaseWebDriverTest protected final PortalHelper _portalHelper = new PortalHelper(this); protected WikiHelper _wikiHelper = new WikiHelper(this); protected final ApiPermissionsHelper _permissionsHelper = new ApiPermissionsHelper(this); + protected final AuditLogHelper _auditLogHelper = new AuditLogHelper(this); protected static final String WIKI_TERMS_TITLE = "Terms of Use"; protected static final String PROJECT_TERMS_SNIPPET = "fight club"; @@ -112,8 +120,10 @@ protected void createProjectWithTermsOfUse(String name, String termsText, boolea } } - protected void createTermsOfUsePage(String projectName, String body) + protected boolean createTermsOfUsePage(String projectName, String body) { + boolean signed = false; + String message; if (null != projectName) { @@ -134,8 +144,51 @@ protected void createTermsOfUsePage(String projectName, String body) setFormElement(Locator.name("title"), WIKI_TERMS_TITLE); setFormElement(Locator.name("body"), body); _wikiHelper.saveWikiPage(); - acceptTermsOfUse(null, true); + signed = acceptTermsOfUse(null, true); } + + return signed; + } + + protected void validateAuditLogEntries(String path, int minRowId, List> expected) throws IOException, CommandException + { + log(String.format("Validating %s user events in the audit log for path / project '%s' and above row id of %d", + USER_AUDIT_EVENT, path, minRowId)); + + List> actualLog = getAuditLogEntries(path, minRowId); + + checker().verifyEquals("Number of audit log entries not as expected.", + expected.size(), actualLog.size()); + + List> unmatched = expected.stream() + .filter(expectedRow -> { + Set keysOfInterest = expectedRow.keySet(); + return actualLog.stream().noneMatch(actualRow -> { + Map actualFiltered = actualRow.entrySet().stream() + .filter(e -> keysOfInterest.contains(e.getKey())) + .collect(HashMap::new, + (m, e) -> m.put(e.getKey(), e.getValue()), + HashMap::putAll); + return actualFiltered.equals(expectedRow); + }); + }) + .toList(); + + checker().verifyTrue("Expected rows with no match in actual: " + unmatched, unmatched.isEmpty()); + + } + + private List> getAuditLogEntries(String path, int minRowId) throws IOException, CommandException + { + log(String.format("Get %s audit log entries for path '%s' where RowId is greater than %d.", + USER_AUDIT_EVENT, path, minRowId)); + + List filters = List.of( + new Filter("Comment", "Agreed to terms of use", Filter.Operator.EQUAL), + new Filter("RowId", minRowId, Filter.Operator.getOperator("GREATER_THAN"))); + + return _auditLogHelper.getAuditLogsFromLKS(path, null, AuditLogHelper.AuditEvent.USER_AUDIT_EVENT, + List.of("RowId", "Date", "CreatedBy", "ImpersonatedBy", "User", "Comment"), filters, null, null).getRows(); } @Override diff --git a/src/org/labkey/test/tests/ProjectTermsOfUseTest.java b/src/org/labkey/test/tests/ProjectTermsOfUseTest.java index cd6fefee9c..2397e8b789 100644 --- a/src/org/labkey/test/tests/ProjectTermsOfUseTest.java +++ b/src/org/labkey/test/tests/ProjectTermsOfUseTest.java @@ -17,14 +17,23 @@ import org.junit.Test; import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.Locators; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; import org.labkey.test.util.Maps; +import org.labkey.test.util.PasswordUtil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.labkey.api.security.UserManager.USER_AUDIT_EVENT; @Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 6) @@ -40,8 +49,11 @@ protected void doSetup() } @Test - public void projectTermsOfUseTest() + public void projectTermsOfUseTest() throws IOException, CommandException { + // RowId is a single sequence across every container, so one baseline covers every path checked below. + int initialRowId = _auditLogHelper.getLatestAuditRowId(USER_AUDIT_EVENT); + log("Terms don't come into play until you log out"); clickProject(NON_PUBLIC_TERMS_PROJECT2_NAME); assertTextNotPresent("fight club"); @@ -121,6 +133,28 @@ public void projectTermsOfUseTest() clickAndWait(Locator.linkWithText("Edit")); deleteWikiPage(); assertTextNotPresent(WIKI_TERMS_TITLE); + + log("Validate the audit logs show 'agreement to terms' entry."); + + // No terms for this project. + validateAuditLogEntries("/" + PUBLIC_NO_TERMS_PROJECT_NAME, initialRowId, new ArrayList<>()); + + // The remaining three projects should have the same audit log entries. + int defaultUserId = _userHelper.getUserId(PasswordUtil.getUsername()); + int impersonatedUserId = _userHelper.getUserId(USER); + List> expected = new ArrayList<>(); + expected.add(Map.of("CreatedBy", impersonatedUserId, + "User", impersonatedUserId, + "ImpersonatedBy", defaultUserId)); + Map row = new HashMap<>(); + row.put("CreatedBy", defaultUserId); + row.put("User", defaultUserId); + row.put("ImpersonatedBy", null); + expected.add(row); + + validateAuditLogEntries("/" + PUBLIC_TERMS_PROJECT_NAME, initialRowId, expected); + validateAuditLogEntries("/" + NON_PUBLIC_TERMS_PROJECT2_NAME, initialRowId, expected); + validateAuditLogEntries("/" + NON_PUBLIC_TERMS_PROJECT_NAME, initialRowId, expected); } protected void deleteWikiPage() @@ -129,5 +163,6 @@ protected void deleteWikiPage() clickButton("Delete Page"); clickButton("Delete"); } + } diff --git a/src/org/labkey/test/tests/SiteWideTermsOfUseTest.java b/src/org/labkey/test/tests/SiteWideTermsOfUseTest.java index 47f865c896..07a03f8b97 100644 --- a/src/org/labkey/test/tests/SiteWideTermsOfUseTest.java +++ b/src/org/labkey/test/tests/SiteWideTermsOfUseTest.java @@ -31,11 +31,14 @@ import org.openqa.selenium.WebElement; import java.io.IOException; +import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.labkey.api.security.UserManager.USER_AUDIT_EVENT; @Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 7) @@ -45,13 +48,31 @@ public class SiteWideTermsOfUseTest extends BaseTermsOfUseTest protected static final String NON_PUBLIC_NO_TERMS_PROJECT_NAME = "Non-public No Terms Project"; @Before - public void preTest() + public void preTest() throws IOException, CommandException { - assureSiteWideTermsOfUsePage(); + + // Get the initial audit log row id, for sitewide terms. Use the row id as a filter when checking the log. + int logId = _auditLogHelper.getLatestAuditRowId(USER_AUDIT_EVENT); + + int defaultUserId = _userHelper.getUserId(PasswordUtil.getUsername()); + Map defaultUserAuditLogEntry = new HashMap<>(); + defaultUserAuditLogEntry.put("CreatedBy", defaultUserId); + defaultUserAuditLogEntry.put("User", defaultUserId); + defaultUserAuditLogEntry.put("ImpersonatedBy", null); + + if (assureSiteWideTermsOfUsePage()) + { + validateAuditLogEntries("", logId, List.of(defaultUserAuditLogEntry)); + } + if(!isElementPresent(Locator.linkWithText(getProjectName()))) { goToHome(); - acceptTermsOfUse(null, true); + if (acceptTermsOfUse(null, true)) + { + validateAuditLogEntries("", logId, List.of(defaultUserAuditLogEntry)); + } + } } @@ -94,10 +115,11 @@ public void editTermsAdminConsoleLinkTest() Locators.bodyTitle().findElement(getDriver()).getText()); } - protected void assureSiteWideTermsOfUsePage() + protected boolean assureSiteWideTermsOfUsePage() { - createTermsOfUsePage(null, SITE_WIDE_TERMS_TEXT); + boolean signed = createTermsOfUsePage(null, SITE_WIDE_TERMS_TEXT); setFrequency(0); + return signed; } // Test that the site-wide terms appear when you log out, even if you've accepted the terms when logged in diff --git a/src/org/labkey/test/util/AuditLogHelper.java b/src/org/labkey/test/util/AuditLogHelper.java index 187a319f32..fe2aef692d 100644 --- a/src/org/labkey/test/util/AuditLogHelper.java +++ b/src/org/labkey/test/util/AuditLogHelper.java @@ -122,7 +122,8 @@ public enum AuditEvent SAMPLE_TIMELINE_EVENT("SampleTimelineEvent"), SAMPLE_WORKFLOW_AUDIT_EVENT("SamplesWorkflowAuditEvent"), SOURCES_AUDIT_EVENT("SourcesAuditEvent"), // available with SampleManagement module - TRANSACTION_AUDIT_EVENT("TransactionAuditEvent"); + TRANSACTION_AUDIT_EVENT("TransactionAuditEvent"), + USER_AUDIT_EVENT("UserAuditEvent"); private final String _name; @@ -210,12 +211,17 @@ public SelectRowsResponse getAuditLogsFromLKS(String containerPath, AuditEvent a return getAuditLogsFromLKS(containerPath, _wrapper.getCurrentProject(), auditEvent, columnNames, filters, maxRows, containerFilter); } - public SelectRowsResponse getAuditLogsFromLKS(String containerPath, @NotNull String projectName, AuditEvent auditEvent, List columnNames, + /** + * @param projectName Name of the project to filter to. If null or empty, no project filter is applied (e.g. for + * events logged against the root container, which don't belong to any project). + */ + public SelectRowsResponse getAuditLogsFromLKS(String containerPath, @Nullable String projectName, AuditEvent auditEvent, List columnNames, @Nullable List filters, @Nullable Integer maxRows, @Nullable ContainerFilter containerFilter) throws IOException, CommandException { SelectRowsCommand cmd = new SelectRowsCommand("auditLog", auditEvent.getName()); cmd.setColumns(columnNames); - cmd.addFilter("ProjectId/Name", projectName, Filter.Operator.EQUAL); + if (StringUtils.isNotEmpty(projectName)) + cmd.addFilter("ProjectId/Name", projectName, Filter.Operator.EQUAL); if (filters != null) filters.forEach(cmd::addFilter); if (maxRows != null)