From 661871a1427ee02d0f196192a108308e99284937 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 14 Aug 2026 17:12:08 -0700 Subject: [PATCH 1/6] Add audit log for successful LK version changes --- .../tests/upgrade/SystemUpgradeAuditTest.java | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java new file mode 100644 index 0000000000..7fb58cea65 --- /dev/null +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2026 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.test.tests.upgrade; + +import org.junit.Assume; +import org.junit.Before; +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.SelectRowsCommand; +import org.labkey.remoteapi.query.SelectRowsResponse; +import org.labkey.remoteapi.query.Sort; +import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.AuditLogHelper; +import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.PermissionsHelper; +import org.labkey.test.util.TestUser; +import org.labkey.test.util.Version; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Covers the audit event recorded by SystemUpgradeAuditProvider when the server comes up on a new version or build. + * Most assertions hold on any single boot; testVersionChangeRecorded is the reason this is an upgrade test, since a + * recorded version change only exists once a server has booted twice on two different versions. + */ +@Category({}) +public class SystemUpgradeAuditTest extends BaseUpgradeTest +{ + /** The release the provider shipped in. Upgrading from anything earlier leaves no prior event to change from. */ + private static final String FIRST_AUDITED_VERSION = "26.9"; + + private static final String AUDIT_QUERY = "SystemUpgradeAuditEvent"; + private static final String AUDIT_LOG_LABEL = "System Upgrade events"; + + private static final TestUser PROJECT_ADMIN = new TestUser("project_admin@systemupgradeaudit.test"); + + private static final List AUDIT_COLUMNS = List.of( + "RowId", + "Created", + "ChangeType", + "ReleaseVersion", + "PreviousReleaseVersion", + "BuildTime", + "PreviousBuildTime", + "HasSchemaUpgrade", + "HasExternalSchemaUpgrade", + "Comment" + ); + + @Override + protected void doSetup() + { + _containerHelper.createProject(getProjectName(), null); + PROJECT_ADMIN.create(this) + .setInitialPassword() + .addPermission(PermissionsHelper.PROJECT_ADMIN_ROLE, getProjectName()); + } + + @Before + public void preTest() + { + PROJECT_ADMIN.load(this); + } + + @Override + protected String getProjectName() + { + return getClass().getSimpleName() + " Project"; + } + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), afterTest); + _userHelper.deleteUsers(afterTest, PROJECT_ADMIN); + } + + @Override + protected BrowserType bestBrowser() + { + return BrowserType.CHROME; + } + + /** Every server has run at least one boot with this feature, so a baseline event must exist for the running build. */ + @Test + public void testBaselineEventRecorded() throws Exception + { + List> rows = getUpgradeEvents(createDefaultConnection()); + assertFalse("No " + AUDIT_QUERY + " rows were found; the startup listener did not record an event", rows.isEmpty()); + + Map latest = rows.getFirst(); + assertEquals("Latest event should record the version the server is running", + getServerReleaseVersion(), latest.get("ReleaseVersion")); + assertNotNull("ChangeType should be set", latest.get("ChangeType")); + assertNotNull("Comment should summarize the change", latest.get("Comment")); + assertNotNull("HasSchemaUpgrade should be set", latest.get("HasSchemaUpgrade")); + assertNotNull("HasExternalSchemaUpgrade should be set", latest.get("HasExternalSchemaUpgrade")); + } + + /** + * The post-upgrade phase is the only place the interesting case occurs: one server that has booted on two + * different versions, so the event carries both of them. + */ + @Test + @EarliestVersion(FIRST_AUDITED_VERSION) + public void testVersionChangeRecorded() throws Exception + { + Assume.assumeFalse("A version change is only visible after the upgrade", isUpgradeSetupPhase); + assertNotNull("Set webtest.upgradePreviousVersion or labkeyVersion to verify the recorded change", setupVersion); + + Map latest = getUpgradeEvents(createDefaultConnection()).getFirst(); + + String previousReleaseVersion = (String) latest.get("PreviousReleaseVersion"); + assertNotNull("Event should record the version the server upgraded from", previousReleaseVersion); + assertEquals("Event should record the version the setup phase ran on", + setupVersion.trim(2), new Version(previousReleaseVersion).trim(2)); + assertEquals("Event should record the version the server upgraded to", + getServerReleaseVersion(), latest.get("ReleaseVersion")); + assertEquals("Booting on a newer release should be recorded as an upgrade", "Upgrade", latest.get("ChangeType")); + assertNotEquals("A new build should have been deployed", latest.get("PreviousBuildTime"), latest.get("BuildTime")); + + // Every release bumps the core module's SchemaVersion, so crossing a release boundary always runs scripts. + // This is the only phase where the flag can be checked against a known-true expectation. + assertEquals("Upgrading across releases should have run schema scripts", true, latest.get("HasSchemaUpgrade")); + } + + /** + * A restart with no rebuild must not add a second event. Neither phase can restart the server on the same build, + * so this checks the invariant that survives any boot: each version and build pair appears exactly once. + */ + @Test + public void testNoDuplicateEventForCurrentBuild() throws Exception + { + List> rows = getUpgradeEvents(createDefaultConnection()); + Map latest = rows.getFirst(); + + long matching = rows.stream() + .filter(row -> Objects.equals(row.get("ReleaseVersion"), latest.get("ReleaseVersion")) + && Objects.equals(row.get("BuildTime"), latest.get("BuildTime"))) + .count(); + + assertEquals("Exactly one event should be recorded per release version and build time", 1, matching); + } + + @Test + public void testAdminConsoleGrid() + { + DataRegionTable table = new AuditLogHelper(this).goToAuditEventView(AUDIT_LOG_LABEL); + + assertTrue("Admin Console audit grid should show the system upgrade event", table.getDataRowCount() > 0); + assertTrue("Grid should show the running release version", + table.getColumnDataAsText("ReleaseVersion").contains(getServerReleaseVersion())); + } + + /** + * The event lives in the root container, so only users holding CanSeeAuditLogPermission at root can read it - + * a project-scoped admin cannot, even with an allFolders container filter. See the impl plan's section 3.1. + */ + @Test + public void testRootContainerPermissions() throws Exception + { + Connection projectAdmin = PROJECT_ADMIN.getUserConnection(); + ApiPermissionsHelper permissionsHelper = new ApiPermissionsHelper(this); + + assertFalse("Site admin should see the system upgrade event", + getUpgradeEvents(createDefaultConnection()).isEmpty()); + + assertTrue("Project admin without a site-level audit role should not see the root container event", + getUpgradeEventsFromProject(projectAdmin).isEmpty()); + + permissionsHelper.setSiteRoleUserPermissions(PROJECT_ADMIN.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE); + try + { + assertFalse("A user holding the site-level audit role should see the root container event", + getUpgradeEventsFromProject(projectAdmin).isEmpty()); + } + finally + { + permissionsHelper.removeUserRoleAssignment(PROJECT_ADMIN.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE, "/"); + } + } + + /** Newest first, read directly from the root container */ + private List> getUpgradeEvents(Connection connection) throws IOException, CommandException + { + return executeSelect(connection, "/", null); + } + + /** What the app grids do: query from a project with an allFolders container filter */ + private List> getUpgradeEventsFromProject(Connection connection) throws IOException, CommandException + { + return executeSelect(connection, getProjectName(), ContainerFilter.AllFolders); + } + + private List> executeSelect(Connection connection, String containerPath, ContainerFilter containerFilter) throws IOException, CommandException + { + SelectRowsCommand cmd = new SelectRowsCommand("auditLog", AUDIT_QUERY); + cmd.setColumns(AUDIT_COLUMNS); + cmd.setSorts(List.of(new Sort("Created", Sort.Direction.DESCENDING), new Sort("RowId", Sort.Direction.DESCENDING))); + if (containerFilter != null) + cmd.setContainerFilter(containerFilter); + + SelectRowsResponse response = cmd.execute(connection, containerPath); + return response.getRows(); + } + + private String getServerReleaseVersion() + { + // Same value the audit event records: AppProps.getReleaseVersion() + return (String) executeScript("return LABKEY.versionString;"); + } +} From fb19c3d3f5f4e6fb6cc273bd91c18636f7ddbc04 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 14 Aug 2026 17:57:43 -0700 Subject: [PATCH 2/6] Claude review --- src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java index 7fb58cea65..e2969db314 100644 --- a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -68,7 +68,6 @@ public class SystemUpgradeAuditTest extends BaseUpgradeTest "BuildTime", "PreviousBuildTime", "HasSchemaUpgrade", - "HasExternalSchemaUpgrade", "Comment" ); @@ -119,7 +118,6 @@ public void testBaselineEventRecorded() throws Exception assertNotNull("ChangeType should be set", latest.get("ChangeType")); assertNotNull("Comment should summarize the change", latest.get("Comment")); assertNotNull("HasSchemaUpgrade should be set", latest.get("HasSchemaUpgrade")); - assertNotNull("HasExternalSchemaUpgrade should be set", latest.get("HasExternalSchemaUpgrade")); } /** From 916a3749a182ab1bb87935d54c4bd7335bb629d2 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Tue, 18 Aug 2026 15:55:40 -0700 Subject: [PATCH 3/6] Make upgrade test self-contained --- .../test/tests/upgrade/BaseUpgradeTest.java | 18 ++++ .../tests/upgrade/SystemUpgradeAuditTest.java | 102 +++++++++--------- 2 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java index 5faa109585..97f80366ba 100644 --- a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java +++ b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java @@ -45,11 +45,29 @@ * not relevant to the version of LabKey being upgraded from (specified in the {@code webtest.upgradePreviousVersion} * system property).
* The setup steps will be skipped if the {@code webtest.upgradeSetup} system property is set to {@code false}. + *

Writing a new upgrade test

+ *
    + *
  • Use a package name containing {@code upgrade}. Do all setup in {@link #doSetup()} rather than a + * {@code @BeforeClass}, and prefer APIs to the UI. {@code @Test} methods must be read-only or re-runnable, since + * cleanup is skipped after the upgrade.
  • + *
  • The setup phase runs the older branch's copy of the test, so a test that exists only on the newer + * branch never gets setup and anything touching its project or users fails. Commit a matching copy to a feature + * branch on the preceding ESR release ({@code fb_coolUpgrade} plus {@code 26.3_fb_coolUpgrade}); TeamCity pairs + * them. Both copies do the full setup, allowing for API changes between the releases. The older one can + * do minimal validation and just needs one {@code @Test}. Names the newer copy looks up, such as the project and test + * users, must match exactly.
  • + *
  • Guard methods that depend on setup data with {@link EarliestVersion} naming the earliest release that + * carries a copy of the test, which is not necessarily the release the feature shipped in.
  • + *
  • One leg of the pipeline validates a build against itself, where nothing changed and {@link #setupVersion} is + * the running version. {@link #wasSetupBefore(String)} and {@link #wasSetupWithin(String, String)} adjust + * expectations for that leg; the annotations cannot express it.
  • + *
*/ public abstract class BaseUpgradeTest extends BaseWebDriverTest { protected static final boolean isUpgradeSetupPhase = TestProperties.getBooleanProperty("webtest.upgradeSetup", true); + /** The version the setup phase ran, from {@code webtest.upgradePreviousVersion}; the running version if unset. */ protected static final Version setupVersion = isUpgradeSetupPhase ? TestProperties.getProductVersion() : Optional.ofNullable(trimToNull(System.getProperty("webtest.upgradePreviousVersion"))).map(Version::new) .orElse(TestProperties.getProductVersion()); diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java index e2969db314..781eae989e 100644 --- a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -16,7 +16,6 @@ package org.labkey.test.tests.upgrade; import org.junit.Assume; -import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.remoteapi.CommandException; @@ -41,23 +40,26 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** * Covers the audit event recorded by SystemUpgradeAuditProvider when the server comes up on a new version or build. * Most assertions hold on any single boot; testVersionChangeRecorded is the reason this is an upgrade test, since a - * recorded version change only exists once a server has booted twice on two different versions. + * recorded version change only exists once a server has booted twice on two different versions.
+ * Reads from /home and builds what it needs inside each test, so it needs no setup phase - and therefore no matching + * copy on the preceding ESR branch, which the setup phase would otherwise run. */ @Category({}) public class SystemUpgradeAuditTest extends BaseUpgradeTest { - /** The release the provider shipped in. Upgrading from anything earlier leaves no prior event to change from. */ - private static final String FIRST_AUDITED_VERSION = "26.9"; - private static final String AUDIT_QUERY = "SystemUpgradeAuditEvent"; private static final String AUDIT_LOG_LABEL = "System Upgrade events"; - private static final TestUser PROJECT_ADMIN = new TestUser("project_admin@systemupgradeaudit.test"); + private static final String PROJECT_ADMIN_EMAIL = "project_admin@systemupgradeaudit.test"; + + /** Every server has one, so no test-owned project is needed to query with a project-scoped container filter. */ + private static final String HOME_PROJECT = "/home"; private static final List AUDIT_COLUMNS = List.of( "RowId", @@ -71,32 +73,17 @@ public class SystemUpgradeAuditTest extends BaseUpgradeTest "Comment" ); + /** The event is written at startup and each test creates what it needs, so the setup phase has nothing to do. */ @Override protected void doSetup() { - _containerHelper.createProject(getProjectName(), null); - PROJECT_ADMIN.create(this) - .setInitialPassword() - .addPermission(PermissionsHelper.PROJECT_ADMIN_ROLE, getProjectName()); - } - - @Before - public void preTest() - { - PROJECT_ADMIN.load(this); } + /** This test owns no project, so there is nothing for the framework to clean up. */ @Override protected String getProjectName() { - return getClass().getSimpleName() + " Project"; - } - - @Override - protected void doCleanup(boolean afterTest) - { - _containerHelper.deleteProject(getProjectName(), afterTest); - _userHelper.deleteUsers(afterTest, PROJECT_ADMIN); + return null; } @Override @@ -125,26 +112,33 @@ public void testBaselineEventRecorded() throws Exception * different versions, so the event carries both of them. */ @Test - @EarliestVersion(FIRST_AUDITED_VERSION) public void testVersionChangeRecorded() throws Exception { Assume.assumeFalse("A version change is only visible after the upgrade", isUpgradeSetupPhase); assertNotNull("Set webtest.upgradePreviousVersion or labkeyVersion to verify the recorded change", setupVersion); Map latest = getUpgradeEvents(createDefaultConnection()).getFirst(); - String previousReleaseVersion = (String) latest.get("PreviousReleaseVersion"); - assertNotNull("Event should record the version the server upgraded from", previousReleaseVersion); - assertEquals("Event should record the version the setup phase ran on", - setupVersion.trim(2), new Version(previousReleaseVersion).trim(2)); - assertEquals("Event should record the version the server upgraded to", - getServerReleaseVersion(), latest.get("ReleaseVersion")); - assertEquals("Booting on a newer release should be recorded as an upgrade", "Upgrade", latest.get("ChangeType")); - assertNotEquals("A new build should have been deployed", latest.get("PreviousBuildTime"), latest.get("BuildTime")); - // Every release bumps the core module's SchemaVersion, so crossing a release boundary always runs scripts. - // This is the only phase where the flag can be checked against a known-true expectation. - assertEquals("Upgrading across releases should have run schema scripts", true, latest.get("HasSchemaUpgrade")); + if (wasSetupBefore(getServerReleaseVersion())) + { + assertNotNull("Event should record the version the server upgraded from", previousReleaseVersion); + assertEquals("Event should record the version the setup phase ran on", + setupVersion.trim(2), new Version(previousReleaseVersion).trim(2)); + assertEquals("Event should record the version the server upgraded to", + getServerReleaseVersion(), latest.get("ReleaseVersion")); + assertEquals("Booting on a newer release should be recorded as an upgrade", "Upgrade", latest.get("ChangeType")); + assertNotEquals("A new build should have been deployed", latest.get("PreviousBuildTime"), latest.get("BuildTime")); + + // Every release bumps the core module's SchemaVersion, so crossing a release boundary always runs scripts. + // This is the only phase where the flag can be checked against a known-true expectation. + assertEquals("Upgrading across releases should have run schema scripts", true, latest.get("HasSchemaUpgrade")); + } + else + { + // Redeploying the same build records no second event, so the setup boot's baseline is still the newest row. + assertNull("A redeploy of the same release should not record a version change", previousReleaseVersion); + } } /** @@ -182,24 +176,36 @@ public void testAdminConsoleGrid() @Test public void testRootContainerPermissions() throws Exception { - Connection projectAdmin = PROJECT_ADMIN.getUserConnection(); ApiPermissionsHelper permissionsHelper = new ApiPermissionsHelper(this); + TestUser projectAdmin = new TestUser(PROJECT_ADMIN_EMAIL); + projectAdmin.create(this) + .setInitialPassword() + .addPermission(PermissionsHelper.PROJECT_ADMIN_ROLE, HOME_PROJECT); - assertFalse("Site admin should see the system upgrade event", - getUpgradeEvents(createDefaultConnection()).isEmpty()); - - assertTrue("Project admin without a site-level audit role should not see the root container event", - getUpgradeEventsFromProject(projectAdmin).isEmpty()); - - permissionsHelper.setSiteRoleUserPermissions(PROJECT_ADMIN.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE); try { - assertFalse("A user holding the site-level audit role should see the root container event", - getUpgradeEventsFromProject(projectAdmin).isEmpty()); + Connection connection = projectAdmin.getUserConnection(); + + assertFalse("Site admin should see the system upgrade event", + getUpgradeEvents(createDefaultConnection()).isEmpty()); + + assertTrue("Project admin without a site-level audit role should not see the root container event", + getUpgradeEventsFromProject(connection).isEmpty()); + + permissionsHelper.setSiteRoleUserPermissions(projectAdmin.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE); + try + { + assertFalse("A user holding the site-level audit role should see the root container event", + getUpgradeEventsFromProject(connection).isEmpty()); + } + finally + { + permissionsHelper.removeUserRoleAssignment(projectAdmin.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE, "/"); + } } finally { - permissionsHelper.removeUserRoleAssignment(PROJECT_ADMIN.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE, "/"); + projectAdmin.deleteUser(); } } @@ -212,7 +218,7 @@ private List> getUpgradeEvents(Connection connection) throws /** What the app grids do: query from a project with an allFolders container filter */ private List> getUpgradeEventsFromProject(Connection connection) throws IOException, CommandException { - return executeSelect(connection, getProjectName(), ContainerFilter.AllFolders); + return executeSelect(connection, HOME_PROJECT, ContainerFilter.AllFolders); } private List> executeSelect(Connection connection, String containerPath, ContainerFilter containerFilter) throws IOException, CommandException From 1a72c59b53634d2181449421935dfd5e88001aa0 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 20 Aug 2026 11:45:57 -0500 Subject: [PATCH 4/6] consistent label casing between app and LKS --- src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java index 781eae989e..fd010f3d60 100644 --- a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -54,7 +54,7 @@ public class SystemUpgradeAuditTest extends BaseUpgradeTest { private static final String AUDIT_QUERY = "SystemUpgradeAuditEvent"; - private static final String AUDIT_LOG_LABEL = "System Upgrade events"; + private static final String AUDIT_LOG_LABEL = "System Upgrade Events"; private static final String PROJECT_ADMIN_EMAIL = "project_admin@systemupgradeaudit.test"; From b439b4ced1a16985b84107a5186509039980ddfa Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Thu, 20 Aug 2026 11:50:44 -0700 Subject: [PATCH 5/6] Fix version check --- .../test/tests/upgrade/SystemUpgradeAuditTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java index fd010f3d60..5397352e79 100644 --- a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -58,6 +58,9 @@ public class SystemUpgradeAuditTest extends BaseUpgradeTest private static final String PROJECT_ADMIN_EMAIL = "project_admin@systemupgradeaudit.test"; + /** First release that records SystemUpgradeAuditEvent, so the earliest one that can be a recorded previous version. */ + private static final String FIRST_AUDITED_RELEASE = "26.9"; + /** Every server has one, so no test-owned project is needed to query with a project-scoped container filter. */ private static final String HOME_PROJECT = "/home"; @@ -120,7 +123,8 @@ public void testVersionChangeRecorded() throws Exception Map latest = getUpgradeEvents(createDefaultConnection()).getFirst(); String previousReleaseVersion = (String) latest.get("PreviousReleaseVersion"); - if (wasSetupBefore(getServerReleaseVersion())) + // The audit event was added in 26.9, so a setup boot on an earlier release left no event to compare against. + if (wasSetupWithin(FIRST_AUDITED_RELEASE, null) && wasSetupBefore(getServerReleaseVersion())) { assertNotNull("Event should record the version the server upgraded from", previousReleaseVersion); assertEquals("Event should record the version the setup phase ran on", @@ -136,8 +140,10 @@ public void testVersionChangeRecorded() throws Exception } else { - // Redeploying the same build records no second event, so the setup boot's baseline is still the newest row. - assertNull("A redeploy of the same release should not record a version change", previousReleaseVersion); + // Redeploying the same build records no second event, and a pre-26.9 setup boot recorded none at all, so + // either way the newest row is a baseline. + assertNull("Without a prior " + FIRST_AUDITED_RELEASE + " event there should be no recorded version change", + previousReleaseVersion); } } From 98641c440ce9dfccafaeaef43cb825f4187bdff1 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 21 Aug 2026 09:17:32 -0700 Subject: [PATCH 6/6] Simplify --- .../test/tests/upgrade/SystemUpgradeAuditTest.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java index 5397352e79..a80f7965c6 100644 --- a/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java +++ b/src/org/labkey/test/tests/upgrade/SystemUpgradeAuditTest.java @@ -199,15 +199,8 @@ public void testRootContainerPermissions() throws Exception getUpgradeEventsFromProject(connection).isEmpty()); permissionsHelper.setSiteRoleUserPermissions(projectAdmin.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE); - try - { - assertFalse("A user holding the site-level audit role should see the root container event", - getUpgradeEventsFromProject(connection).isEmpty()); - } - finally - { - permissionsHelper.removeUserRoleAssignment(projectAdmin.getEmail(), PermissionsHelper.SEE_AUDIT_LOG_SITE_ROLE, "/"); - } + assertFalse("A user holding the site-level audit role should see the root container event", + getUpgradeEventsFromProject(connection).isEmpty()); } finally {