From f797690fc458dfbc6eeb4c7064bc22616d4f566c Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 26 Sep 2025 13:30:18 -0700 Subject: [PATCH 01/12] Base classes for upgrade pipeline --- src/org/labkey/test/BaseWebDriverTest.java | 7 +- src/org/labkey/test/TestProperties.java | 6 +- .../test/tests/upgrade/BaseUpgradeTest.java | 91 +++++++++++++++++ .../test/tests/upgrade/BasicUpgradeTest.java | 98 +++++++++++++++++++ src/org/labkey/test/util/APIUserHelper.java | 13 +++ src/org/labkey/test/util/TestUser.java | 34 ++++--- 6 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java create mode 100644 src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index d4c0eea4b3..15fe19f10d 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -1337,11 +1337,16 @@ private int getPendingRequestCount(Connection connection) } } + protected boolean skipCleanup(boolean afterTest) + { + return false; + } + private void cleanup(boolean afterTest) { ensureSignedInAsPrimaryTestUser(); - if (!ClassUtils.getAllInterfaces(getClass()).contains(ReadOnlyTest.class) || ((ReadOnlyTest) this).needsSetup()) + if (!skipCleanup(afterTest) && (!(this instanceof ReadOnlyTest readOnlyTest) || readOnlyTest.needsSetup())) { if (afterTest) waitForPendingRequests(WAIT_FOR_PAGE); diff --git a/src/org/labkey/test/TestProperties.java b/src/org/labkey/test/TestProperties.java index 85d3df7bce..651e6d14b1 100644 --- a/src/org/labkey/test/TestProperties.java +++ b/src/org/labkey/test/TestProperties.java @@ -383,7 +383,7 @@ public static File getDumpDir() * @param def Default value * @return value of the specified property */ - private static boolean getBooleanProperty(String key, boolean def) + public static boolean getBooleanProperty(String key, boolean def) { String prop = System.getProperty(key); if (!StringUtils.isBlank(prop)) @@ -403,7 +403,7 @@ private static boolean getBooleanProperty(String key, boolean def) * @param def Default value * @return value of the specified property */ - private static int getIntegerProperty(String key, int def) + public static int getIntegerProperty(String key, int def) { String prop = System.getProperty(key); if (!StringUtils.isBlank(prop)) @@ -427,7 +427,7 @@ private static int getIntegerProperty(String key, int def) * @param def Default value * @return value of the specified property */ - private static double getDoubleProperty(String key, double def) + public static double getDoubleProperty(String key, double def) { String prop = System.getProperty(key); if (!StringUtils.isBlank(prop)) diff --git a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java new file mode 100644 index 0000000000..2105ffcacf --- /dev/null +++ b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java @@ -0,0 +1,91 @@ +package org.labkey.test.tests.upgrade; + +import org.jetbrains.annotations.NotNull; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.TestProperties; +import org.labkey.test.util.TestLogger; + +import java.util.Arrays; +import java.util.List; + +public abstract class BaseUpgradeTest extends BaseWebDriverTest +{ + + protected static final boolean isUpgradeSetupPhase = TestProperties.getBooleanProperty("webtest.upgradeSetup", true); + protected static final double previousVersion = TestProperties.getDoubleProperty("webtest.upgradePreviousVersion", 0.0); + + @Override + protected boolean skipCleanup(boolean afterTest) + { + return afterTest || !isUpgradeSetupPhase; + } + + @BeforeClass + public static void setupProject() throws Exception + { + BaseUpgradeTest currentTest = BaseWebDriverTest.getCurrentTest(); + + if (isUpgradeSetupPhase) + { + currentTest.doSetup(); + } + else + { + TestLogger.info("Skipping setup for %s. Verifying upgrade.". formatted(currentTest.getClass().getSimpleName())); + } + } + + protected abstract void doSetup() throws Exception; + + @Rule + public final TestRule upgradeVersionCheck = new UpgradeVersionCheck(); + + @Override + public List getAssociatedModules() + { + return Arrays.asList(); + } + + protected @interface EariestVersion { + double value(); + } + + protected @interface LatestVersion { + double value(); + } + + private static class UpgradeVersionCheck implements TestRule + { + + @Override + public @NotNull Statement apply(Statement base, Description description) + { + EariestVersion eariestVersion = description.getAnnotation(EariestVersion.class); + LatestVersion latestVersion = description.getAnnotation(LatestVersion.class); + if (isUpgradeSetupPhase || previousVersion <= 0) + { + return base; // Run the test normally + } + + return new Statement() + { + @Override + public void evaluate() throws Throwable + { + Assume.assumeTrue("Test doesn't support upgrading from version: " + previousVersion, + (eariestVersion == null || eariestVersion.value() <= previousVersion) && + (latestVersion == null || previousVersion <= latestVersion.value()) + ); + base.evaluate(); + } + }; + } + } + +} diff --git a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java new file mode 100644 index 0000000000..5e3ccd55bd --- /dev/null +++ b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java @@ -0,0 +1,98 @@ +package org.labkey.test.tests.upgrade; + +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.SelectRowsCommand; +import org.labkey.test.WebTestHelper; +import org.labkey.test.params.FieldInfo; +import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.ApiPermissionsHelper; +import org.labkey.test.util.PasswordUtil; +import org.labkey.test.util.PermissionsHelper; +import org.labkey.test.util.PermissionsHelper.MemberType; +import org.labkey.test.util.TestUser; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.labkey.test.util.PermissionsHelper.READER_ROLE; + +@Category({}) +public class BasicUpgradeTest extends BaseUpgradeTest +{ + private static final TestUser USER = new TestUser("basic_upgrade_reader@sampletypeupgradetest.test"); + private static final String SAMPLE_TYPE = "UpgradeTestSamples"; + + private static final FieldInfo STR_COL = new FieldInfo("String Col1"); + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), afterTest); + _userHelper.deleteUsers(afterTest, USER); + } + + @Override + protected void doSetup() throws Exception + { + _containerHelper.createProject(getProjectName(), null); + USER.create(this); + USER.setInitialPassword(); + USER.addPermission(READER_ROLE, getProjectName()); + + new SampleTypeDefinition(SAMPLE_TYPE) + .setFields(List.of(STR_COL.getFieldDefinition())) + .create(createDefaultConnection(), getProjectName()) + .insertRows(createDefaultConnection(), List.of(Map.of( + "Name", "S-1", + STR_COL.getName(), "Test String Value" + )) ); + } + + @Before + public void preTest() + { + USER.load(this); + } + + @Test + public void testSampleRowsExist() throws Exception + { + // Use primary user to verify data + queryData(createDefaultConnection()); + } + + @Test + public void testUserPassword() throws Exception + { + // Use password authentication for USER + queryData(USER.getUserConnection()); + } + + @Test + public void testUserPermissions() throws Exception + { + // Impersonate to test USER's permission level + queryData(createDefaultConnection().impersonate(USER.getEmail())); + } + + private void queryData(Connection connection) throws IOException, CommandException + { + List> rows = new SelectRowsCommand("samples", SAMPLE_TYPE) + .execute(connection, getProjectName()) + .getRows(); + assertFalse("Expected at least one row", rows.isEmpty()); + } + + @Override + protected String getProjectName() + { + return "SampleTypeUpgradeTest Project"; + } +} diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index ec47d033e8..99f7f692e0 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -217,6 +217,14 @@ public Integer getUserId(String userEmail) return getUserIds(Arrays.asList(userEmail)).get(userEmail); } + public int getUserIdStrict(String userEmail) + { + Integer userId = getUserIds(Arrays.asList(userEmail)).get(userEmail); + if (userId == null) + throw new IllegalStateException("No user with email " + userEmail + " found."); + return userId; + } + @Override protected void _deleteUser(String userEmail) { @@ -242,6 +250,11 @@ protected void _deleteUsers(boolean failIfNotFound, String... userEmails) private static final Pattern regEmailVerification = Pattern.compile("verification=([A-Za-z0-9]+)"); + public String setInitialPassword(String email) + { + return setInitialPassword(getUserIdStrict(email)); + } + @Override public String setInitialPassword(int userId) { diff --git a/src/org/labkey/test/util/TestUser.java b/src/org/labkey/test/util/TestUser.java index 95e451bd4a..b5efafb75e 100644 --- a/src/org/labkey/test/util/TestUser.java +++ b/src/org/labkey/test/util/TestUser.java @@ -14,7 +14,7 @@ public class TestUser { private WebDriverWrapper _test; private final String _email; - private CreateUserResponse _createUserResponse; + private Integer _userId; private String _password; private APIUserHelper _apiUserHelper; private Connection _impersonationConnection; @@ -32,12 +32,25 @@ public TestUser create(WebDriverWrapper test) { _test = test; _apiUserHelper = new APIUserHelper(_test); - _createUserResponse = _apiUserHelper.createUser(_email); + _userId = _apiUserHelper.createUser(_email).getUserId(); _impersonationConnection = null; _password = null; return this; } + /** + * Gets info for an already existing user + */ + public TestUser load(WebDriverWrapper test) + { + _test = test; + _apiUserHelper = new APIUserHelper(_test); + _userId = _apiUserHelper.getUserId(_email); + _impersonationConnection = null; + _password = PasswordUtil.getPassword(); + return this; + } + public void deleteUser() { getApiUserHelper().deleteUsers(false, _email); @@ -45,7 +58,11 @@ public void deleteUser() public Integer getUserId() { - return getCreateUserResponse().getUserId(); + if (_userId == null) + { + throw new IllegalStateException("User" + _email + " has not yet been created"); + } + return _userId; } public String getEmail() @@ -69,7 +86,7 @@ public TestUser setInitialPassword() { if (_password == null) // if null, this is the initial password - we can use the UI to set it now { - _password = _apiUserHelper.setInitialPassword(_createUserResponse.getUserId()); + _password = _apiUserHelper.setInitialPassword(_userId); } else { @@ -162,15 +179,6 @@ public void stopImpersonating(boolean refresh) throws IOException, CommandExcept getWrapper().refresh(); } - private CreateUserResponse getCreateUserResponse() - { - if (_createUserResponse == null) - { - throw new IllegalStateException("User" + _email + " has not yet been created"); - } - return _createUserResponse; - } - private APIUserHelper getApiUserHelper() { if (_apiUserHelper == null) From bf373b42a6cff5d09e7ea440858b92e3aa03d27e Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 29 Sep 2025 12:25:31 -0700 Subject: [PATCH 02/12] Cleanup imports --- src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java index 5e3ccd55bd..8ca8b8d126 100644 --- a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java +++ b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java @@ -6,13 +6,8 @@ import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.query.SelectRowsCommand; -import org.labkey.test.WebTestHelper; import org.labkey.test.params.FieldInfo; import org.labkey.test.params.experiment.SampleTypeDefinition; -import org.labkey.test.util.ApiPermissionsHelper; -import org.labkey.test.util.PasswordUtil; -import org.labkey.test.util.PermissionsHelper; -import org.labkey.test.util.PermissionsHelper.MemberType; import org.labkey.test.util.TestUser; import java.io.IOException; @@ -20,7 +15,6 @@ import java.util.Map; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.labkey.test.util.PermissionsHelper.READER_ROLE; @Category({}) From dd289aec7182c0d03ea7abfd2b339991bd369f19 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 1 Oct 2025 11:33:59 -0700 Subject: [PATCH 03/12] Don't wait an hour for server to start --- src/org/labkey/test/tests/JUnitTest.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index a1fbc0f942..5100c56b9a 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -56,11 +56,14 @@ import org.labkey.test.util.JUnitFooter; import org.labkey.test.util.JUnitHeader; import org.labkey.test.util.LogMethod; +import org.labkey.test.util.TestDateUtils; import org.labkey.test.util.TestLogger; +import org.labkey.test.util.Timer; import java.io.IOException; import java.io.OutputStream; import java.net.SocketTimeoutException; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -181,10 +184,10 @@ public static TestSuite dynamicSuite(Collection categories, Collection> accept, boolean skipInitialUserChecks) throws Exception { - return _suite(accept, skipInitialUserChecks, 0, 0); + return _suite(accept, skipInitialUserChecks, new Timer(Duration.ofMinutes(5)), 0); } - private static TestSuite _suite(Predicate> accept, boolean skipInitialUserChecks, final int startupAttempts, final int upgradeAttempts) throws Exception + private static TestSuite _suite(Predicate> accept, boolean skipInitialUserChecks, final Timer startupTimer, final int upgradeAttempts) throws Exception { if (TestProperties.isPrimaryUserAppAdmin()) { @@ -203,10 +206,10 @@ private static TestSuite _suite(Predicate> accept, boolean sk } catch (IOException ex) { - if (startupAttempts < 60 && upgradeAttempts == 0) + if (!startupTimer.isTimedOut() && upgradeAttempts == 0) { Thread.sleep(1000); - return _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts); + return _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts); } else { @@ -235,12 +238,12 @@ private static TestSuite _suite(Predicate> accept, boolean sk if (responseBody.contains("Start Modules")) { // Server still starting up. We don't need to use the upgradeHelper to sign in. - LOG.info("Remote JUnitTest: Server modules starting up (attempt " + startupAttempts + ") ..."); + LOG.info("Remote JUnitTest: Server modules starting up (remaining " + TestDateUtils.durationString(startupTimer.timeRemaining()) + ") ..."); - if (startupAttempts < 60) + if (!startupTimer.isTimedOut()) { Thread.sleep(1000); - return _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts); + return _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts); } else { @@ -273,7 +276,7 @@ else if (responseBody.contains("Upgrade Status") || TestSuite testSuite; try { - testSuite = _suite(accept, skipInitialUserChecks, startupAttempts + 1, upgradeAttempts + 1); + testSuite = _suite(accept, skipInitialUserChecks, startupTimer, upgradeAttempts + 1); } catch (Exception retryException) { From 73a32a7fb2c5fc44d89e7456253121f2d1b9e212 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 1 Oct 2025 17:08:45 -0700 Subject: [PATCH 04/12] Use Java remote API to connect to starting server --- src/org/labkey/test/BaseWebDriverTest.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/test/BaseWebDriverTest.java b/src/org/labkey/test/BaseWebDriverTest.java index 15fe19f10d..9a87a9a30d 100644 --- a/src/org/labkey/test/BaseWebDriverTest.java +++ b/src/org/labkey/test/BaseWebDriverTest.java @@ -17,7 +17,6 @@ package org.labkey.test; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.time.FastDateFormat; @@ -109,7 +108,6 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; -import org.openqa.selenium.html5.WebStorage; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.UnreachableBrowserException; import org.openqa.selenium.remote.service.DriverService; @@ -780,12 +778,8 @@ private void clearLocalStorage() { // Clears browser localStorage. Needed in order to reset some state such as grid filters/sorts/etc. // which are sticky, but can interfere with what tests expect. - WebDriver driver = getDriver(); - - if (driver instanceof WebStorage webStorage) - { - webStorage.getLocalStorage().clear(); - } + executeScript("window.localStorage.clear();"); + executeScript("window.sessionStorage.clear();"); } @Override From 8b0463195c3072ee0c25d074e48ea00b11dca874 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 1 Oct 2025 17:12:10 -0700 Subject: [PATCH 05/12] wrong file --- src/org/labkey/test/tests/JUnitTest.java | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index 5100c56b9a..65fe53d0e8 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -195,16 +195,17 @@ private static TestSuite _suite(Predicate> accept, boolean sk } HttpContext context = WebTestHelper.getBasicHttpContext(); - CloseableHttpResponse response = null; + CommandResponse response = null; try (CloseableHttpClient client = WebTestHelper.getHttpClient()) { final String url = WebTestHelper.getBaseURL() + "/junit-testlist.view"; HttpGet method = new HttpGet(url); try { - response = client.execute(method, context); + Connection remoteApiConnection = WebTestHelper.getRemoteApiConnection(); + response = new SimplePostCommand("junit", "testlist").execute(remoteApiConnection, "/"); } - catch (IOException ex) + catch (IOException | CommandException ex) { if (!startupTimer.isTimedOut() && upgradeAttempts == 0) { @@ -218,10 +219,10 @@ private static TestSuite _suite(Predicate> accept, boolean sk return failsuite; } } - int status = response.getCode(); + int status = response.getStatusCode(); if (status == HttpStatus.SC_OK) { - final String responseBody = WebTestHelper.getHttpResponseBody(response); + final String responseBody = response.getText(); if (responseBody.isEmpty()) throw new AssertionFailedError("Failed to fetch remote junit test list: empty response"); @@ -339,15 +340,15 @@ else if (responseBody.contains("Upgrade Status") || else { LOG.error("Getting unit test list from server failed with error code " + status + ". Error page content is:"); - final OutputStream streamLogger = IoBuilder.forLogger(LOG).setLevel(Level.ERROR).buildOutputStream(); - response.getEntity().writeTo(streamLogger); - throw new AssertionFailedError("Failed to fetch remote junit test list (" + status + " - " + response.getReasonPhrase() + "): " + url); +// final OutputStream streamLogger = IoBuilder.forLogger(LOG).setLevel(Level.ERROR).buildOutputStream(); + LOG.error(response.getText()); + throw new AssertionFailedError("Failed to fetch remote junit test list (" + status + "): " + url); } } finally { - if (response != null) - EntityUtils.consumeQuietly(response.getEntity()); +// if (response != null) +// EntityUtils.consumeQuietly(response.getEntity()); } } From cf237603e66ddb878d34eaf39257be6bd36fa54b Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 1 Oct 2025 17:34:39 -0700 Subject: [PATCH 06/12] Interpret response correctly --- src/org/labkey/test/tests/JUnitTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index 65fe53d0e8..c7e8bdcb76 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -223,14 +223,12 @@ private static TestSuite _suite(Predicate> accept, boolean sk if (status == HttpStatus.SC_OK) { final String responseBody = response.getText(); - if (responseBody.isEmpty()) - throw new AssertionFailedError("Failed to fetch remote junit test list: empty response"); final JSONObject json; try { - json = new JSONObject(responseBody); + json = new JSONObject(response.getParsedData()); } catch (JSONException e) { From 9dfaf9ba03104b11c206a4e2fe5d9fe39673ff82 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 2 Oct 2025 09:12:29 -0700 Subject: [PATCH 07/12] Back to old HttpClient --- src/org/labkey/test/WebTestHelper.java | 3 +- src/org/labkey/test/tests/JUnitTest.java | 38 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/org/labkey/test/WebTestHelper.java b/src/org/labkey/test/WebTestHelper.java index f231d55fcf..c8c6d9eb78 100644 --- a/src/org/labkey/test/WebTestHelper.java +++ b/src/org/labkey/test/WebTestHelper.java @@ -34,6 +34,7 @@ import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.ssl.SSLContextBuilder; @@ -808,7 +809,7 @@ public static HttpClientContext getBasicHttpContext() return localcontext; } - public static String getHttpResponseBody(CloseableHttpResponse response) + public static String getHttpResponseBody(ClassicHttpResponse response) { StringBuilder builder = new StringBuilder(); try diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index c7e8bdcb76..c891972104 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -195,17 +195,29 @@ private static TestSuite _suite(Predicate> accept, boolean sk } HttpContext context = WebTestHelper.getBasicHttpContext(); - CommandResponse response = null; + CloseableHttpResponse response = null; try (CloseableHttpClient client = WebTestHelper.getHttpClient()) { final String url = WebTestHelper.getBaseURL() + "/junit-testlist.view"; + HttpGet whoAmI = new HttpGet(WebTestHelper.buildURL("login", "whoami.api")); HttpGet method = new HttpGet(url); try { - Connection remoteApiConnection = WebTestHelper.getRemoteApiConnection(); - response = new SimplePostCommand("junit", "testlist").execute(remoteApiConnection, "/"); + client.execute(whoAmI, context, r -> { + try + { + JSONObject jsonObject = new JSONObject(WebTestHelper.getHttpResponseBody(r)); + LOG.info("WhoAmI: {}", jsonObject.getString("email")); + } + catch (JSONException e) + { + LOG.info("WhoAmI: {}", r.getCode()); + } + return null; + }); + response = client.execute(method, context); } - catch (IOException | CommandException ex) + catch (IOException ex) { if (!startupTimer.isTimedOut() && upgradeAttempts == 0) { @@ -219,16 +231,18 @@ private static TestSuite _suite(Predicate> accept, boolean sk return failsuite; } } - int status = response.getStatusCode(); + int status = response.getCode(); if (status == HttpStatus.SC_OK) { - final String responseBody = response.getText(); + final String responseBody = WebTestHelper.getHttpResponseBody(response); + if (responseBody.isEmpty()) + throw new AssertionFailedError("Failed to fetch remote junit test list: empty response"); final JSONObject json; try { - json = new JSONObject(response.getParsedData()); + json = new JSONObject(responseBody); } catch (JSONException e) { @@ -338,15 +352,15 @@ else if (responseBody.contains("Upgrade Status") || else { LOG.error("Getting unit test list from server failed with error code " + status + ". Error page content is:"); -// final OutputStream streamLogger = IoBuilder.forLogger(LOG).setLevel(Level.ERROR).buildOutputStream(); - LOG.error(response.getText()); - throw new AssertionFailedError("Failed to fetch remote junit test list (" + status + "): " + url); + final OutputStream streamLogger = IoBuilder.forLogger(LOG).setLevel(Level.ERROR).buildOutputStream(); + response.getEntity().writeTo(streamLogger); + throw new AssertionFailedError("Failed to fetch remote junit test list (" + status + " - " + response.getReasonPhrase() + "): " + url); } } finally { -// if (response != null) -// EntityUtils.consumeQuietly(response.getEntity()); + if (response != null) + EntityUtils.consumeQuietly(response.getEntity()); } } From bf701cb0632f8397d915ccd2976e26fed7418df4 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 2 Oct 2025 12:12:23 -0700 Subject: [PATCH 08/12] Try ensureLogin --- src/org/labkey/test/tests/JUnitTest.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/test/tests/JUnitTest.java b/src/org/labkey/test/tests/JUnitTest.java index c891972104..5cfa9cdc0e 100644 --- a/src/org/labkey/test/tests/JUnitTest.java +++ b/src/org/labkey/test/tests/JUnitTest.java @@ -199,23 +199,24 @@ private static TestSuite _suite(Predicate> accept, boolean sk try (CloseableHttpClient client = WebTestHelper.getHttpClient()) { final String url = WebTestHelper.getBaseURL() + "/junit-testlist.view"; - HttpGet whoAmI = new HttpGet(WebTestHelper.buildURL("login", "whoami.api")); - HttpGet method = new HttpGet(url); + HttpGet ensureLogin = new HttpGet(WebTestHelper.buildURL("security", "ensureLogin.api")); + HttpGet getTestList = new HttpGet(url); try { - client.execute(whoAmI, context, r -> { + client.execute(ensureLogin, context, r -> { + String ensureLoginResponse = WebTestHelper.getHttpResponseBody(r); try { - JSONObject jsonObject = new JSONObject(WebTestHelper.getHttpResponseBody(r)); - LOG.info("WhoAmI: {}", jsonObject.getString("email")); + JSONObject jsonObject = new JSONObject(ensureLoginResponse); + LOG.info("ensureLogin: {}", jsonObject.getJSONObject("currentUser").getString("email")); } catch (JSONException e) { - LOG.info("WhoAmI: {}", r.getCode()); + LOG.info("ensureLogin: {}", ensureLoginResponse); } return null; }); - response = client.execute(method, context); + response = client.execute(getTestList, context); } catch (IOException ex) { @@ -253,6 +254,7 @@ private static TestSuite _suite(Predicate> accept, boolean sk // Server still starting up. We don't need to use the upgradeHelper to sign in. LOG.info("Remote JUnitTest: Server modules starting up (remaining " + TestDateUtils.durationString(startupTimer.timeRemaining()) + ") ..."); + EntityUtils.consumeQuietly(response.getEntity()); // Consume before possible recursion if (!startupTimer.isTimedOut()) { Thread.sleep(1000); @@ -271,6 +273,7 @@ else if (responseBody.contains("Upgrade Status") || responseBody.contains("Account Setup") || responseBody.contains("This server is being upgraded to a new version of LabKey Server.")) { + EntityUtils.consumeQuietly(response.getEntity()); // Consume before possible recursion LOG.info("Remote JUnitTest: Server needs install or upgrade ..."); if (upgradeAttempts > 3) throw new AssertionFailedError("Failed to update or bootstrap on second attempt: " + responseBody); From 3d0a1cb5c33f6d524bd0cc4f916735a414cd7b6d Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 6 Oct 2025 16:01:29 -0700 Subject: [PATCH 09/12] Update project name --- src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java index 8ca8b8d126..8817d85319 100644 --- a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java +++ b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java @@ -87,6 +87,6 @@ private void queryData(Connection connection) throws IOException, CommandExcepti @Override protected String getProjectName() { - return "SampleTypeUpgradeTest Project"; + return getClass().getSimpleName() + " Project"; } } From cdc53bb0b56fcfc36e2ed32ed4b81af5cb9b8969 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 8 Oct 2025 11:05:54 -0700 Subject: [PATCH 10/12] Reuse code --- src/org/labkey/test/util/APIUserHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/APIUserHelper.java b/src/org/labkey/test/util/APIUserHelper.java index 99f7f692e0..43c610648d 100644 --- a/src/org/labkey/test/util/APIUserHelper.java +++ b/src/org/labkey/test/util/APIUserHelper.java @@ -219,7 +219,7 @@ public Integer getUserId(String userEmail) public int getUserIdStrict(String userEmail) { - Integer userId = getUserIds(Arrays.asList(userEmail)).get(userEmail); + Integer userId = getUserId(userEmail); if (userId == null) throw new IllegalStateException("No user with email " + userEmail + " found."); return userId; From dfb36195e74cf6e99a26ef34cf4f54e5edc73292 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 9 Oct 2025 08:36:57 -0700 Subject: [PATCH 11/12] Update upgrade tests --- src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java index 8817d85319..5cdb8ed7d2 100644 --- a/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java +++ b/src/org/labkey/test/tests/upgrade/BasicUpgradeTest.java @@ -20,7 +20,7 @@ @Category({}) public class BasicUpgradeTest extends BaseUpgradeTest { - private static final TestUser USER = new TestUser("basic_upgrade_reader@sampletypeupgradetest.test"); + private static final TestUser USER = new TestUser("basic_upgrade_reader@basicupgradetest.test"); private static final String SAMPLE_TYPE = "UpgradeTestSamples"; private static final FieldInfo STR_COL = new FieldInfo("String Col1"); From bbdbacdd791542349afec302f672d218fed18110 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 9 Oct 2025 14:47:45 -0700 Subject: [PATCH 12/12] Add comments and fix version range check --- .../test/tests/upgrade/BaseUpgradeTest.java | 52 ++++++-- src/org/labkey/test/util/Version.java | 114 ++++++++++++++++++ src/org/labkey/test/util/VersionRange.java | 34 ++++++ 3 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 src/org/labkey/test/util/Version.java create mode 100644 src/org/labkey/test/util/VersionRange.java diff --git a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java index 2105ffcacf..ffaeacacbc 100644 --- a/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java +++ b/src/org/labkey/test/tests/upgrade/BaseUpgradeTest.java @@ -10,15 +10,33 @@ import org.labkey.test.BaseWebDriverTest; import org.labkey.test.TestProperties; import org.labkey.test.util.TestLogger; +import org.labkey.test.util.Version; +import org.labkey.test.util.VersionRange; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Arrays; import java.util.List; - +import java.util.Optional; + +import static org.apache.commons.lang3.StringUtils.trimToNull; + +/** + * Base test class for tests that setup data and configure a server then verify the persistence or modification of those + * data and configurations after upgrading to a newer version of LabKey.
+ * The {@code EariestVersion} and {@code LatestVersion} annotations can be used to skip particular tests when they are + * 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}. + */ public abstract class BaseUpgradeTest extends BaseWebDriverTest { protected static final boolean isUpgradeSetupPhase = TestProperties.getBooleanProperty("webtest.upgradeSetup", true); - protected static final double previousVersion = TestProperties.getDoubleProperty("webtest.upgradePreviousVersion", 0.0); + protected static final Version previousVersion = Optional.ofNullable(trimToNull(System.getProperty("webtest.upgradePreviousVersion"))) + .map(Version::new).orElse(null); @Override protected boolean skipCleanup(boolean afterTest) @@ -52,12 +70,26 @@ public List getAssociatedModules() return Arrays.asList(); } + /** + * Annotates test methods that should only run when upgrading from particular LabKey versions, as specified in + * {@code webtest.upgradePreviousVersion}.
+ * Specifies the earliest version of the test class that performed the required setup for the annotated method. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) protected @interface EariestVersion { - double value(); + String value(); } + /** + * Annotates test methods that should only run when upgrading from particular LabKey versions, as specified in + * {@code webtest.upgradePreviousVersion}.
+ * Specifies the latest version of the test class that performed the required setup for the annotated method. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) protected @interface LatestVersion { - double value(); + String value(); } private static class UpgradeVersionCheck implements TestRule @@ -66,9 +98,12 @@ private static class UpgradeVersionCheck implements TestRule @Override public @NotNull Statement apply(Statement base, Description description) { - EariestVersion eariestVersion = description.getAnnotation(EariestVersion.class); - LatestVersion latestVersion = description.getAnnotation(LatestVersion.class); - if (isUpgradeSetupPhase || previousVersion <= 0) + String eariestVersion = Optional.ofNullable(description.getAnnotation(EariestVersion.class)) + .map(EariestVersion::value).orElse(null); + String latestVersion = Optional.ofNullable(description.getAnnotation(LatestVersion.class)) + .map(LatestVersion::value).orElse(null); + + if (isUpgradeSetupPhase || previousVersion == null || (eariestVersion == null && latestVersion == null)) { return base; // Run the test normally } @@ -79,8 +114,7 @@ private static class UpgradeVersionCheck implements TestRule public void evaluate() throws Throwable { Assume.assumeTrue("Test doesn't support upgrading from version: " + previousVersion, - (eariestVersion == null || eariestVersion.value() <= previousVersion) && - (latestVersion == null || previousVersion <= latestVersion.value()) + VersionRange.versionRange(eariestVersion, latestVersion).contains(previousVersion) ); base.evaluate(); } diff --git a/src/org/labkey/test/util/Version.java b/src/org/labkey/test/util/Version.java new file mode 100644 index 0000000000..cef81cd705 --- /dev/null +++ b/src/org/labkey/test/util/Version.java @@ -0,0 +1,114 @@ +package org.labkey.test.util; + +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A simple class for parsing and comparing version numbers + */ +public class Version implements Comparable +{ + private final List _version; + + public Version(Integer... version) + { + _version = validate(version); + } + + public Version(String version) + { + this(Arrays.stream(version.split("\\.")).map(Integer::parseInt).toArray(Integer[]::new)); + } + + public Version(Double version) + { + this(version.toString()); + } + + private static List validate(Integer... versionParts) + { + List partList = List.of(versionParts); + if (partList.isEmpty()) + { + throw new IllegalArgumentException("Version must have at least one part"); + } + for (Integer part : partList) + { + if (part < 0) + { + throw new IllegalArgumentException("Version parts must be non-negative"); + } + } + return partList; + } + + @Override + public int compareTo(@NotNull Version o) + { + int i = 0; + for (; i < _version.size() && i < o._version.size(); i++) + { + Integer versionPart = _version.get(i); + Integer otherVersionPart = o._version.get(i); + int result = versionPart.compareTo(otherVersionPart); + if (result != 0) + { + return result; + } + } + // Treat the less specific version as higher + if (i < _version.size()) + return -1; // this version is more specific + if (i < o._version.size()) + return 1; // the other version is more specific + return 0; + } + + @Override + public final boolean equals(Object o) + { + if (!(o instanceof Version version)) return false; + + return _version.equals(version._version); + } + + @Override + public int hashCode() + { + return _version.hashCode(); + } + + @Override + public String toString() + { + return _version.stream().map(Object::toString).collect(Collectors.joining(".")); + } + + + public static class VersionTest + { + @Test + public void testConstructors() + { + Assert.assertEquals("Integer constructor comparison", new Version("25.7"), new Version(25, 7)); + Assert.assertEquals("Integer constructor comparison", new Version("25.7.3"), new Version(25, 7, 3)); + Assert.assertEquals("Double constructor comparison", new Version("25.7"), new Version(25.7)); + } + + @Test + public void testCompareTo() + { + Assert.assertEquals("CompareTo equal version", 0, new Version("25.7").compareTo(new Version("25.7"))); + Assert.assertEquals("CompareTo earlier version", 1, new Version("25.7").compareTo(new Version("25.3"))); + Assert.assertEquals("CompareTo later version", -1, new Version("25.7").compareTo(new Version("25.11"))); + Assert.assertEquals("CompareTo less specific version", -1, new Version("25.7.0").compareTo(new Version("25.7"))); + Assert.assertEquals("CompareTo more specific version", 1, new Version("25.7.0").compareTo(new Version("25.7.0.0"))); + } + } + +} diff --git a/src/org/labkey/test/util/VersionRange.java b/src/org/labkey/test/util/VersionRange.java new file mode 100644 index 0000000000..123b9a987d --- /dev/null +++ b/src/org/labkey/test/util/VersionRange.java @@ -0,0 +1,34 @@ +package org.labkey.test.util; + +public class VersionRange +{ + private final Version eariestVersion; + private final Version latestVersion; + + public VersionRange(Version eariestVersion, Version latestVersion) + { + this.eariestVersion = eariestVersion; + this.latestVersion = latestVersion; + } + + public static VersionRange from(String version) + { + return new VersionRange(new Version(version), null); + } + + public static VersionRange until(String version) + { + return new VersionRange(null, new Version(version)); + } + + public static VersionRange versionRange(String earliestVersion, String latestVersion) + { + return new VersionRange(new Version(earliestVersion), new Version(latestVersion)); + } + + public boolean contains(Version version) + { + return (eariestVersion == null || eariestVersion.compareTo(version) <= 0) && + (latestVersion == null || latestVersion.compareTo(version) >= 0); + } +}