From e131e41702687a5982136244bba91bd5acaaf961 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 29 Aug 2025 10:34:06 -0700 Subject: [PATCH 1/5] Fix domain/field name validation for trial tests --- .../labkey/test/util/TestDataGenerator.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 27f0f41cfb..8f2a5afa72 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -37,6 +37,7 @@ import org.labkey.remoteapi.query.SelectRowsResponse; import org.labkey.remoteapi.query.Sort; import org.labkey.serverapi.reader.TabLoader; +import org.labkey.test.TestProperties; import org.labkey.test.WebTestHelper; import org.labkey.test.params.FieldDefinition; import org.labkey.test.util.data.ColumnNameMapper; @@ -594,7 +595,8 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Integ } // Multiple spaces in the UI are collapsed into a single space. If we need to test for handling of multiple spaces, we'll not use this generator - domainName = domainName.replaceAll("\\s+", " "); + // Capitalize to make it easier to match grid header labels and audit events + domainName = StringUtils.capitalize(domainName).replaceAll("\\s+", " "); TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName; @@ -644,6 +646,31 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num private static boolean isDomainAndFieldNameInvalid(DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { + if (TestProperties.isTrialServer()) // WebTestHelper.getRemoteApiConnection() won't work against trial server + { + if (domainName != null) + { + int maxLength = switch (domainKind) + { + case Assay -> 200 - 13; // Make room for "{$domainName} Batch Fields" domain + case SampleSet -> 100; + default -> 200; // Sources, lists, and datasets allow 200 character names + }; + if (domainName.length() > maxLength) + return true; + } + if (fieldName != null) + { + if (fieldName.length() > 200 || Pattern.matches(".*:[a-zA-Z]{3}.*", fieldName)) // Avoid illegal patterns like ":Date" + { + return true; + } + } + else + { + return false; + } + } SimplePostCommand command = new SimplePostCommand("property", "validateDomainAndFieldNames"); JSONObject domainDesign = new JSONObject(); if (domainName != null) From f0ef9ecedd21bffd8cc9e482a26eec076287db9d Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 29 Aug 2025 13:39:54 -0700 Subject: [PATCH 2/5] Update comment --- src/org/labkey/test/util/TestDataGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 8f2a5afa72..b4cb04c1c2 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -646,7 +646,7 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num private static boolean isDomainAndFieldNameInvalid(DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { - if (TestProperties.isTrialServer()) // WebTestHelper.getRemoteApiConnection() won't work against trial server + if (TestProperties.isTrialServer()) // WebTestHelper.getRemoteApiConnection() won't work against trial server before logging in via UI { if (domainName != null) { From f94fda5130032687fad5b1ed5497f4ad4e19b3e1 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 5 Sep 2025 11:27:20 -0700 Subject: [PATCH 3/5] Avoid known issues with random field name generation --- src/org/labkey/test/TestProperties.java | 5 ++ .../labkey/test/tests/DomainDesignerTest.java | 2 +- .../test/tests/query/QueryLookupTest.java | 2 +- src/org/labkey/test/util/DomainUtils.java | 17 ++++ .../labkey/test/util/TestDataGenerator.java | 79 ++++++++++++------- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/src/org/labkey/test/TestProperties.java b/src/org/labkey/test/TestProperties.java index 83d56286fc..88fb9ad949 100644 --- a/src/org/labkey/test/TestProperties.java +++ b/src/org/labkey/test/TestProperties.java @@ -257,6 +257,11 @@ public static boolean isTrialServer() return getBooleanProperty("webtest.server.trial", false); } + public static boolean isRemoteNameValidationEnabled() + { + return getBooleanProperty("webtest.remote.domain.validation", false); + } + public static boolean isCheckerFatal() { return "true".equals(System.getProperty("webtest.checker.fatal")); diff --git a/src/org/labkey/test/tests/DomainDesignerTest.java b/src/org/labkey/test/tests/DomainDesignerTest.java index 040e53438b..a8e2e78a7c 100644 --- a/src/org/labkey/test/tests/DomainDesignerTest.java +++ b/src/org/labkey/test/tests/DomainDesignerTest.java @@ -316,7 +316,7 @@ public void testInvalidLookupDomainField() throws IOException, CommandException public void testInvalidSampleFieldFromDelete() throws Exception { String listName = TestDataGenerator.randomDomainName("Sample Lookups List", DomainUtils.DomainKind.IntList); - String listKey = TestDataGenerator.randomFieldName("Id", null, 10, null, DomainUtils.DomainKind.IntList); + String listKey = DomainUtils.DomainKind.IntList.randomFieldName("Key"); String sampleType1 = TestDataGenerator.randomDomainName("Sample Type 1"); String sampleType2 = TestDataGenerator.randomDomainName("Sample Type 2"); SampleTypeAPIHelper.createEmptySampleType(getProjectName(), new SampleTypeDefinition(sampleType1)); diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index dec685a921..0e1aad74b3 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -29,7 +29,7 @@ public class QueryLookupTest extends BaseWebDriverTest private static final String PROJECT_NAME = "QueryLookupTest" + TRICKY_CHARACTERS_FOR_PROJECT_NAMES; private static final String LIST_NAME = "l&ist q"; - private static final FieldInfo NAME_COLUMN = FieldInfo.random("Name", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); + private static final FieldInfo NAME_COLUMN = FieldInfo.random("Key", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); private static final FieldInfo TSHIRT_COLUMN = FieldInfo.random("TShirt", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); @Override diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 9266495c22..790cd2afa3 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -6,6 +6,8 @@ import org.labkey.remoteapi.domain.DropDomainCommand; import org.labkey.remoteapi.domain.GetDomainDetailsCommand; import org.labkey.test.WebTestHelper; +import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldInfo; import java.io.IOException; @@ -108,5 +110,20 @@ public String randomName(String namePart) { return TestDataGenerator.randomDomainName(namePart, this); } + + public String randomFieldName(String namePart) + { + return randomField(namePart).getName(); + } + + public FieldInfo randomField(String namePart) + { + return randomField(namePart, null); + } + + public FieldInfo randomField(String namePart, FieldDefinition.ColumnType columnType) + { + return FieldInfo.random(namePart, columnType, this); + } } } diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index b4cb04c1c2..b7d60f9f1e 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -40,6 +40,7 @@ import org.labkey.test.TestProperties; import org.labkey.test.WebTestHelper; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.util.DomainUtils.DomainKind; import org.labkey.test.util.data.ColumnNameMapper; import org.labkey.test.util.data.TestDataUtils; import org.labkey.test.util.query.QueryApiHelper; @@ -567,7 +568,7 @@ public static String randomInvalidDomainName(@Nullable String namePart, int numS return domainName; } - public static String randomDomainName(@Nullable String namePart, @Nullable DomainUtils.DomainKind domainKind) + public static String randomDomainName(@Nullable String namePart, @Nullable DomainKind domainKind) { return randomDomainName(namePart, null, null, domainKind); } @@ -580,10 +581,10 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Domai * @param numEndChars Number of random characters at end of name * @return name containing the given name part and appended random characters that should be a valid domain name */ - public static String randomDomainName(@Nullable String namePart, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable DomainUtils.DomainKind domainKind) + public static String randomDomainName(@Nullable String namePart, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable DomainKind domainKind) { String _namePart = namePart == null ? "" : namePart; - DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; + DomainKind _domainKind = domainKind == null ? DomainKind.SampleSet : domainKind; String charSet = ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; int currentTries = 0; String domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); @@ -617,14 +618,14 @@ public static String randomFieldName(String part, @Nullable String exclusion) return randomFieldName(part, exclusion, null); } - public static String randomFieldName(String part, @Nullable String exclusion, DomainUtils.DomainKind domainKind) + public static String randomFieldName(String part, @Nullable String exclusion, DomainKind domainKind) { return randomFieldName(part, null, null, exclusion, domainKind); } - public static String randomFieldName(@NotNull String part, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable String exclusion, @Nullable DomainUtils.DomainKind domainKind) + public static String randomFieldName(@NotNull String part, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable String exclusion, @Nullable DomainKind domainKind) { - DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; + DomainKind _domainKind = domainKind == null ? DomainKind.SampleSet : domainKind; // use the characters that we know are encoded in fieldKeys plus characters that we know clients are using // Issue 53197: Field name with double byte character can cause client side exception in Firefox when trying to customize grid view. @@ -644,33 +645,20 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num return randomFieldName; } - private static boolean isDomainAndFieldNameInvalid(DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + private static boolean isDomainAndFieldNameInvalid(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { - if (TestProperties.isTrialServer()) // WebTestHelper.getRemoteApiConnection() won't work against trial server before logging in via UI + if (TestProperties.isRemoteNameValidationEnabled()) { - if (domainName != null) - { - int maxLength = switch (domainKind) - { - case Assay -> 200 - 13; // Make room for "{$domainName} Batch Fields" domain - case SampleSet -> 100; - default -> 200; // Sources, lists, and datasets allow 200 character names - }; - if (domainName.length() > maxLength) - return true; - } - if (fieldName != null) - { - if (fieldName.length() > 200 || Pattern.matches(".*:[a-zA-Z]{3}.*", fieldName)) // Avoid illegal patterns like ":Date" - { - return true; - } - } - else - { - return false; - } + return isNameInvalidRemote(domainKind, domainName, fieldName); } + else + { + return isNameInvalidLocal(domainKind, domainName, fieldName); + } + } + + private static boolean isNameInvalidRemote(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + { SimplePostCommand command = new SimplePostCommand("property", "validateDomainAndFieldNames"); JSONObject domainDesign = new JSONObject(); if (domainName != null) @@ -706,6 +694,37 @@ private static boolean isDomainAndFieldNameInvalid(DomainUtils.DomainKind domain } } + public static boolean isNameInvalidLocal(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + { + if (domainName != null) + { + if (!Character.isLetterOrDigit(domainName.charAt(0))) + return true; // domain needs to start with alphanumeric char + if (Pattern.matches("(.*\\s--[^ ].*)|(.*\\s-[^- ].*)", domainName)) + return true; // domain name must not contain space followed by dash. (command like: Issue 49161) + + int maxLength = switch (domainKind) + { + case Assay -> 200 - 13; // Make room for "{$domainName} Batch Fields" domain + case SampleSet -> 100; + default -> 200; // Sources, lists, and datasets allow 200 character names + }; + if (domainName.length() > maxLength) + return true; + } + if (fieldName != null) + { + if (fieldName.length() > 200) + return true; + if (Pattern.matches(".*:[a-zA-Z]{3}.*", fieldName)) // Avoid illegal patterns like ":Date" + return true; + if (fieldName.toLowerCase().contains("key")) // Not guaranteed but likely a list key + return true; // Issue 53706: List key field name length is limited to 64 characters + } + + return false; + } + public static T randomChoice(List choices) { return choices.get(randomInt(0, choices.size() - 1)); From 82da4cef481ffdc2d6c5c7cac7e27544ff648f56 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 5 Sep 2025 11:45:50 -0700 Subject: [PATCH 4/5] Fix key length check --- src/org/labkey/test/util/TestDataGenerator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index b7d60f9f1e..8838f00316 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -647,6 +647,9 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num private static boolean isDomainAndFieldNameInvalid(DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { + if (fieldName != null && fieldName.length() > 64 && fieldName.toLowerCase().contains("key")) // Not guaranteed but likely a list key + return true; // Issue 53706: List key field name length is limited to 64 characters + if (TestProperties.isRemoteNameValidationEnabled()) { return isNameInvalidRemote(domainKind, domainName, fieldName); @@ -718,8 +721,6 @@ public static boolean isNameInvalidLocal(DomainKind domainKind, @Nullable String return true; if (Pattern.matches(".*:[a-zA-Z]{3}.*", fieldName)) // Avoid illegal patterns like ":Date" return true; - if (fieldName.toLowerCase().contains("key")) // Not guaranteed but likely a list key - return true; // Issue 53706: List key field name length is limited to 64 characters } return false; From e3a09616679aa7aa8b29495d34faef1cab1a720e Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 5 Sep 2025 13:22:43 -0700 Subject: [PATCH 5/5] Don't capitalize domain names --- src/org/labkey/test/util/TestDataGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 8838f00316..e53ac7b0aa 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -596,8 +596,7 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Integ } // Multiple spaces in the UI are collapsed into a single space. If we need to test for handling of multiple spaces, we'll not use this generator - // Capitalize to make it easier to match grid header labels and audit events - domainName = StringUtils.capitalize(domainName).replaceAll("\\s+", " "); + domainName = domainName.replaceAll("\\s+", " "); TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName;