From 145f646061a21938a624cfeee897a067793e11c6 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 10 Jul 2025 11:45:36 -0500 Subject: [PATCH 01/19] new FieldInfo(TestDataGenerator.randomFieldName() -> FieldInfo.random() --- .../test/tests/DomainFieldTypeChangeTest.java | 10 +++++----- src/org/labkey/test/tests/SampleTypeTest.java | 8 ++++---- .../test/tests/component/GridPanelTest.java | 6 +++--- .../test/tests/query/QueryLookupTest.java | 19 +++++++++---------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java b/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java index 3112fd56d0..c861fa8c01 100644 --- a/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java +++ b/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java @@ -62,11 +62,11 @@ public void testProvisionedDomainFieldChanges() throws IOException, CommandExcep { log("Creating list with variety of data fields"); String listName = TestDataGenerator.randomDomainName("SampleListWithAllDataTypes"); - FieldInfo stringField = new FieldInfo(TestDataGenerator.randomFieldName("name"), FieldDefinition.ColumnType.String); - FieldInfo integerField = new FieldInfo(TestDataGenerator.randomFieldName("Test/Integer"), FieldDefinition.ColumnType.Integer); - FieldInfo decimalField = new FieldInfo(TestDataGenerator.randomFieldName("Test/Decimal"), FieldDefinition.ColumnType.Decimal); - FieldInfo dateField = new FieldInfo(TestDataGenerator.randomFieldName("Test/Date"), FieldDefinition.ColumnType.DateAndTime); - FieldInfo booleanField = new FieldInfo(TestDataGenerator.randomFieldName("Test'/\"Boolean"), FieldDefinition.ColumnType.Boolean); // GH Issue #755 + FieldInfo stringField = FieldInfo.random("name", FieldDefinition.ColumnType.String); + FieldInfo integerField = FieldInfo.random("Test/Integer", FieldDefinition.ColumnType.Integer); + FieldInfo decimalField = FieldInfo.random("Test/Decimal", FieldDefinition.ColumnType.Decimal); + FieldInfo dateField = FieldInfo.random("Test/Date", FieldDefinition.ColumnType.DateAndTime); + FieldInfo booleanField = FieldInfo.random("Test'/\"Boolean", FieldDefinition.ColumnType.Boolean); // GH Issue #755 TestDataGenerator dgen = new TestDataGenerator("lists", listName, getProjectName()) .withColumns(List.of( stringField.getFieldDefinition(), diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index 3c687fdaaf..4ae1fc4762 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -249,11 +249,11 @@ public void testCreateSampleTypeNoExpression() public void testCustomProperties() { final String sampleTypeName = "SampleTypeCustomProps" + DOMAIN_TRICKY_CHARACTERS; - FieldInfo stringCol1 = new FieldInfo(TestDataGenerator.randomFieldName("StringColPlain"), ColumnType.String); - FieldInfo stringCol2 = new FieldInfo(TestDataGenerator.randomFieldName("StringCol%"), ColumnType.String); + FieldInfo stringCol1 = FieldInfo.random("StringColPlain", ColumnType.String); + FieldInfo stringCol2 = FieldInfo.random("StringCol%", ColumnType.String); // Used to make sure the details page shows properties with null values - FieldInfo stringCol3 = new FieldInfo(TestDataGenerator.randomFieldName("StringColNull"), ColumnType.String); - FieldInfo calcCol = new FieldInfo(TestDataGenerator.randomFieldName("CalcCol"), ColumnType.Calculation); + FieldInfo stringCol3 = FieldInfo.random("StringColNull", ColumnType.String); + FieldInfo calcCol = FieldInfo.random("CalcCol", ColumnType.Calculation); final List fields = List.of( stringCol1.getFieldDefinition(), stringCol2.getFieldDefinition(), diff --git a/src/org/labkey/test/tests/component/GridPanelTest.java b/src/org/labkey/test/tests/component/GridPanelTest.java index 99711eba1f..00aa25652a 100644 --- a/src/org/labkey/test/tests/component/GridPanelTest.java +++ b/src/org/labkey/test/tests/component/GridPanelTest.java @@ -67,9 +67,9 @@ public class GridPanelTest extends GridPanelBaseTest private static final String FILTER_EXPDATE_COL = "Expiration Date"; private static final FieldInfo FILTER_STRING_COL = new FieldInfo("Str\uD83D\uDC7E]|*안", FieldDefinition.ColumnType.String); private static final FieldInfo FILTER_INT_COL = new FieldInfo("Int&`~_@", FieldDefinition.ColumnType.Integer); - private static final FieldInfo FILTER_EXTEND_CHAR_COL = new FieldInfo(TestDataGenerator.randomFieldName("\u0106\u00D8\u0139", 0, 5), FieldDefinition.ColumnType.String); - private static final FieldInfo FILTER_BOOL_COL = new FieldInfo(TestDataGenerator.randomFieldName("Bool", 0, 5), FieldDefinition.ColumnType.Boolean); - private static final FieldInfo FILTER_DATE_COL = new FieldInfo(TestDataGenerator.randomFieldName("Date", 0, 5), FieldDefinition.ColumnType.DateAndTime); + private static final FieldInfo FILTER_EXTEND_CHAR_COL = FieldInfo.random("\u0106\u00D8\u0139", FieldDefinition.ColumnType.String); + private static final FieldInfo FILTER_BOOL_COL = FieldInfo.random("Bool", FieldDefinition.ColumnType.Boolean); + private static final FieldInfo FILTER_DATE_COL = FieldInfo.random("Date", FieldDefinition.ColumnType.DateAndTime); private static final String FILTER_STORED_AMOUNT_COL = "Amount"; // Views and columns used in the views. The views are only applied to the small sample type (Small_SampleType). diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index 2bb9f73069..33e7852e37 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -28,10 +28,8 @@ 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 = - new FieldInfo("Name", FieldDefinition.ColumnType.String); - private static final FieldInfo TSHIRT_COLUMN = - new FieldInfo("TShirt", FieldDefinition.ColumnType.String); + private static final FieldInfo NAME_COLUMN = FieldInfo.random("Name", FieldDefinition.ColumnType.String); + private static final FieldInfo TSHIRT_COLUMN = FieldInfo.random("TShirt", FieldDefinition.ColumnType.String); @Override protected void doCleanup(boolean afterTest) @@ -64,16 +62,18 @@ private void doSetup() throws Exception public void testLookupToQueryColumn() throws Exception { var insertedRows = executeSelectRowCommand("lists", LIST_NAME).getRows(); - var itemNames = insertedRows.stream().map(a-> a.get("name").toString()).toList(); + var itemNames = insertedRows.stream().map(a-> a.get(NAME_COLUMN.getName()).toString()).toList(); String secondList = "secondList"; // create a query from LIST_NAME list, with a key defined in the query xml String queryName = "query from list"; String querySql = """ - SELECT [list_name].Name, - [list_name].TShirt + SELECT [list_name].[name_column] AS Name, + [list_name].[tshirt_column] AS TShirt FROM [list_name] - """.replace("[list_name]", EscapeUtil.getSqlQuotedValue(LIST_NAME)); + """.replace("[list_name]", EscapeUtil.getSqlQuotedValue(LIST_NAME)) + .replace("[name_column]", EscapeUtil.getSqlQuotedValue(NAME_COLUMN.getName())) + .replace("[tshirt_column]", EscapeUtil.getSqlQuotedValue(TSHIRT_COLUMN.getName())); String queryXml = """ @@ -84,8 +84,7 @@ public void testLookupToQueryColumn() throws Exception
- """.replace("[query_name]", EscapeUtil.getMarkupEscapedValue(queryName)) - .replace("[list_key]", EscapeUtil.getMarkupEscapedValue(NAME_COLUMN.getName())); + """.replace("[query_name]", EscapeUtil.getMarkupEscapedValue(queryName)); // create a query on the list goToSchemaBrowser(); createQuery(getProjectName(), queryName, "lists", querySql, queryXml, false); From 4e407bb39844a9f1c9b31060ed0fb511680895b0 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 10 Jul 2025 11:46:52 -0500 Subject: [PATCH 02/19] TestDataGenerator randomString support for REPEAT_PLACEHOLDER to add a repeated character between 2 and 15 times --- src/org/labkey/test/util/TestDataGenerator.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index a35446b0fc..9a3e86e57e 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -72,6 +72,7 @@ public class TestDataGenerator { private static final String WIDE_CHAR = "\uD83D\uDC7E"; // 👾 private static final char WIDE_PLACEHOLDER = '\u03A0'; // 'Π' - Wide character can't be picked from the string with 'charAt' + private static final char REPEAT_PLACEHOLDER = '\u22EF'; // '⋯' - Used to indicate that the char will be repeated private static final String NON_LATIN_STRING = "\u0438\uC548\u306F"; // "и안は" // chose a Character random from this String public static final String CHARSET_STRING = "ABCDEFG01234abcdefvxyz~!@#$%^&*()-+=_{}[]|:;\"',.<>" + NON_LATIN_STRING + WIDE_PLACEHOLDER; @@ -474,7 +475,14 @@ public static String randomString(int size, @Nullable String exclusion, @Nullabl { int randIndex = (int)(charSetFrom.length() * Math.random()); char c = charSetFrom.charAt(randIndex); - if (c == WIDE_PLACEHOLDER) + if (c == REPEAT_PLACEHOLDER) + { + randIndex = (int)(charSetFrom.length() * Math.random()); + c = charSetFrom.charAt(randIndex); + int repeatCount = randomInt(2, 15); // repeat between 2 and 15 times + val.append(StringUtils.repeat(c, repeatCount)); + } + else if (c == WIDE_PLACEHOLDER) val.append(WIDE_CHAR); else val.append(c); @@ -496,7 +504,7 @@ public static String randomMultiLineString(int size, @Nullable String exclusion) * Creates a String containing the given part with random characters from charSet surrounding it. The name * will have leading and trailing spaces removed and multiple internal spaces collapsed to a single space * in order to be compatible with UI display. Because of this space treatment, there may be fewer than the - * sepcified number of charcters before and after the given part. + * specified number of characters before and after the given part. * * @param part the part that is to be included between random strings * @param numStartChars maximum number of random characters from charSet @@ -577,7 +585,7 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in { // 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. - String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + WIDE_PLACEHOLDER ; + String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER; String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); From 81af9522348e56472b4f0a0846d08d9c17acc082 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 10 Jul 2025 12:10:43 -0500 Subject: [PATCH 03/19] TestDataGenerator randomString support for ALL_CHARS_PLACEHOLDER to add all of the charSet to the generated string --- src/org/labkey/test/util/TestDataGenerator.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 9a3e86e57e..abaeb0aea1 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -73,6 +73,7 @@ public class TestDataGenerator private static final String WIDE_CHAR = "\uD83D\uDC7E"; // 👾 private static final char WIDE_PLACEHOLDER = '\u03A0'; // 'Π' - Wide character can't be picked from the string with 'charAt' private static final char REPEAT_PLACEHOLDER = '\u22EF'; // '⋯' - Used to indicate that the char will be repeated + private static final char ALL_CHARS_PLACEHOLDER = '\u2211'; // '∑' - Used to indicate that all characters from the charset should be used private static final String NON_LATIN_STRING = "\u0438\uC548\u306F"; // "и안は" // chose a Character random from this String public static final String CHARSET_STRING = "ABCDEFG01234abcdefvxyz~!@#$%^&*()-+=_{}[]|:;\"',.<>" + NON_LATIN_STRING + WIDE_PLACEHOLDER; @@ -481,6 +482,12 @@ public static String randomString(int size, @Nullable String exclusion, @Nullabl c = charSetFrom.charAt(randIndex); int repeatCount = randomInt(2, 15); // repeat between 2 and 15 times val.append(StringUtils.repeat(c, repeatCount)); + break; // we don't want to hit another REPEAT_PLACEHOLDER or ALL_CHARS_PLACEHOLDER in this for loop + } + else if (c == ALL_CHARS_PLACEHOLDER) + { + val.append(charSetFrom); + break; // we've added them all, no need to continue, and we don't want to hit another REPEAT_PLACEHOLDER or ALL_CHARS_PLACEHOLDER in this for loop } else if (c == WIDE_PLACEHOLDER) val.append(WIDE_CHAR); @@ -585,7 +592,8 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in { // 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. - String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER; + String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER + ALL_CHARS_PLACEHOLDER; String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); From 3c29c4f357a093af174e1269bd2b0a0ab2f588c5 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 10 Jul 2025 16:37:46 -0500 Subject: [PATCH 04/19] DataClassAPIHelper.dataClassTestFields update to use FieldInfo.random() --- src/org/labkey/test/params/FieldDefinition.java | 12 ++++++++++++ src/org/labkey/test/params/FieldInfo.java | 14 +++++++++++++- .../params/experiment/DataClassDefinition.java | 10 ++++++++++ .../tests/DataClassFolderExportImportTest.java | 16 ++++++++++------ .../tests/ExportOptionsMetadataOnlyTest.java | 4 ++-- .../tests/SampleTypeFolderExportImportTest.java | 6 +++--- .../labkey/test/util/exp/DataClassAPIHelper.java | 14 ++++++++------ 7 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/org/labkey/test/params/FieldDefinition.java b/src/org/labkey/test/params/FieldDefinition.java index 2c0f043f12..ed29808db6 100644 --- a/src/org/labkey/test/params/FieldDefinition.java +++ b/src/org/labkey/test/params/FieldDefinition.java @@ -58,6 +58,8 @@ public class FieldDefinition extends PropertyDescriptor // Collection of JSON properties not explicitly known by 'PropertyDescriptor' private final Map _extraFieldProperties = new HashMap<>(); + private String _namePart; + /** * Define a non-lookup field of the specified type * @param name field name @@ -467,6 +469,16 @@ public void setAliquotOption(ExpSchema.DerivationDataScopeType aliquotOption) _aliquotOption = aliquotOption; } + public void setNamePart(String namePart) + { + _namePart = namePart; + } + + public boolean isNamePartMatch(String namePart) + { + return _namePart != null && _namePart.equals(namePart); + } + public enum RangeType { Equals("Equals", Filter.Operator.EQUAL), diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index 7e5ce8b71f..f962aa8241 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -20,6 +20,7 @@ public class FieldInfo implements CharSequence, WrapsFieldKey private final String _label; private final ColumnType _columnType; private final Consumer _fieldDefinitionMutator; + private String _namePart; // used for random field generation to track the name part used private FieldInfo(FieldKey fieldKey, String label, ColumnType columnType, Consumer fieldDefinitionMutator) { @@ -54,7 +55,9 @@ public FieldInfo(String name) */ public static FieldInfo random(String namePart, ColumnType columnType) { - return new FieldInfo(TestDataGenerator.randomFieldName(namePart), columnType); + FieldInfo field = new FieldInfo(TestDataGenerator.randomFieldName(namePart), columnType); + field.setNamePart(namePart); + return field; } /** @@ -165,9 +168,18 @@ private FieldDefinition getFieldDefinition(ColumnType columnType) { _fieldDefinitionMutator.accept(fieldDefinition); } + if (_namePart != null) + { + fieldDefinition.setNamePart(_namePart); + } return fieldDefinition; } + private void setNamePart(String namePart) + { + _namePart = namePart; + } + @Override public int length() { diff --git a/src/org/labkey/test/params/experiment/DataClassDefinition.java b/src/org/labkey/test/params/experiment/DataClassDefinition.java index 2fc6717222..30fffcb10d 100644 --- a/src/org/labkey/test/params/experiment/DataClassDefinition.java +++ b/src/org/labkey/test/params/experiment/DataClassDefinition.java @@ -168,4 +168,14 @@ public TestDataGenerator getTestDataGenerator(String containerPath) { return super.getTestDataGenerator(containerPath).withColumns(List.of(new FieldDefinition("Name", FieldDefinition.ColumnType.String))); } + + public FieldDefinition getFieldByNamePart(String namePart) + { + for (FieldDefinition field : getFields()) + { + if (field.isNamePartMatch(namePart)) + return field; + } + throw new IllegalArgumentException("No field found with name part: " + namePart); + } } diff --git a/src/org/labkey/test/tests/DataClassFolderExportImportTest.java b/src/org/labkey/test/tests/DataClassFolderExportImportTest.java index 8859969749..da3cb09146 100644 --- a/src/org/labkey/test/tests/DataClassFolderExportImportTest.java +++ b/src/org/labkey/test/tests/DataClassFolderExportImportTest.java @@ -99,14 +99,18 @@ public void testExportImportSimpleDataClass() throws Exception _containerHelper.createSubfolder(getProjectName(), subfolder); DataClassDefinition testType = new DataClassDefinition(testDataClass).setFields(DataClassAPIHelper.dataClassTestFields()); + String intColumnName = testType.getFieldByNamePart("intColumn").getName(); + String decimalColumnName = testType.getFieldByNamePart("decimalColumn").getName(); + String stringColumnName = testType.getFieldByNamePart("stringColumn").getName(); + String attachmentColumnName = testType.getFieldByNamePart("attachmentColumn").getName(); TestDataGenerator testDgen = DataClassAPIHelper.createEmptyDataClass(subfolderPath, testType); - testDgen.addCustomRow(Map.of("Name", "class1", "intColumn", 7771, "decimalColumn", 1.1, "stringColumn", "one")); - testDgen.addCustomRow(Map.of("Name", "class2", "intColumn", 7772, "decimalColumn", 2.2, "stringColumn", "two")); - testDgen.addCustomRow(Map.of("Name", "class3", "intColumn", 7773, "decimalColumn", 3.3, "stringColumn", "three")); - testDgen.addCustomRow(Map.of("Name", "class4", "intColumn", 7774, "decimalColumn", 4.4, "stringColumn", "four")); - testDgen.addCustomRow(Map.of("Name", "class5", "intColumn", 7775, "decimalColumn", 5.5, "stringColumn", "five")); + testDgen.addCustomRow(Map.of("Name", "class1", intColumnName, 7771, decimalColumnName, 1.1, stringColumnName, "one")); + testDgen.addCustomRow(Map.of("Name", "class2", intColumnName, 7772, decimalColumnName, 2.2, stringColumnName, "two")); + testDgen.addCustomRow(Map.of("Name", "class3", intColumnName, 7773, decimalColumnName, 3.3, stringColumnName, "three")); + testDgen.addCustomRow(Map.of("Name", "class4", intColumnName, 7774, decimalColumnName, 4.4, stringColumnName, "four")); + testDgen.addCustomRow(Map.of("Name", "class5", intColumnName, 7775, decimalColumnName, 5.5, stringColumnName, "five")); testDgen.insertRows(); PortalHelper portalHelper = new PortalHelper(this); @@ -125,7 +129,7 @@ public void testExportImportSimpleDataClass() throws Exception // issue https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=42191 tracks this // until it is fixed we will have to add attachments via the UI, like this sourceTable.clickEditRow(i); - setFormElement(Locator.input("quf_attachmentColumn"), _attachments.get(i)); + setFormElement(Locator.input("quf_" + attachmentColumnName), _attachments.get(i)); clickButton("Submit"); } List> sourceRowData = sourceTable.getTableData(); diff --git a/src/org/labkey/test/tests/ExportOptionsMetadataOnlyTest.java b/src/org/labkey/test/tests/ExportOptionsMetadataOnlyTest.java index ed9d9538d9..aa65a21a56 100644 --- a/src/org/labkey/test/tests/ExportOptionsMetadataOnlyTest.java +++ b/src/org/labkey/test/tests/ExportOptionsMetadataOnlyTest.java @@ -67,8 +67,8 @@ public void testDataClassExportOptions() throws IOException, CommandException DataClassDefinition testType = new DataClassDefinition(dataClassName).setFields(DataClassAPIHelper.dataClassTestFields()); TestDataGenerator testDgen = DataClassAPIHelper.createEmptyDataClass(getProjectName(), testType); - testDgen.addCustomRow(Map.of("Name", "class1", "intColumn", 1, "decimalColumn", 1.1, "stringColumn", "one")); - testDgen.addCustomRow(Map.of("Name", "class2", "intColumn", 2, "decimalColumn", 2.2, "stringColumn", "two")); + testDgen.addCustomRow(Map.of("Name", "class1")); + testDgen.addCustomRow(Map.of("Name", "class2")); testDgen.insertRows(); log("Export data class design only"); diff --git a/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java b/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java index 3739f2eee2..20afe88380 100644 --- a/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java +++ b/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java @@ -443,9 +443,9 @@ public void testExportImportDerivedSamples() throws Exception .addParentAlias("SelfParent"); // to derive from samles in the current type TestDataGenerator dataClassDgen = DataClassAPIHelper.createEmptyDataClass(subfolderPath, dataClassType); - dataClassDgen.addCustomRow(Map.of("Name", "data1", "intColumn", 1, "stringColumn", "one")); - dataClassDgen.addCustomRow(Map.of("Name", "data2", "intColumn", 2, "stringColumn", "two")); - dataClassDgen.addCustomRow(Map.of("Name", "data3", "intColumn", 3, "stringColumn", "three")); + dataClassDgen.addCustomRow(Map.of("Name", "data1")); + dataClassDgen.addCustomRow(Map.of("Name", "data2")); + dataClassDgen.addCustomRow(Map.of("Name", "data3")); dataClassDgen.insertRows(); TestDataGenerator parentDgen = SampleTypeAPIHelper.createEmptySampleType(subfolderPath, parentType); diff --git a/src/org/labkey/test/util/exp/DataClassAPIHelper.java b/src/org/labkey/test/util/exp/DataClassAPIHelper.java index 53ab3aef05..8ac77d5059 100644 --- a/src/org/labkey/test/util/exp/DataClassAPIHelper.java +++ b/src/org/labkey/test/util/exp/DataClassAPIHelper.java @@ -9,6 +9,7 @@ import org.labkey.remoteapi.query.SelectRowsResponse; import org.labkey.test.WebTestHelper; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.experiment.DataClassDefinition; import org.labkey.test.util.DomainUtils; import org.labkey.test.util.TestDataGenerator; @@ -54,12 +55,13 @@ static public TestDataGenerator createEmptyDataClass(String containerPath, DataC public static List dataClassTestFields() { return Arrays.asList( - new FieldDefinition("intColumn", FieldDefinition.ColumnType.Integer), - new FieldDefinition("decimalColumn", FieldDefinition.ColumnType.Decimal), - new FieldDefinition("stringColumn", FieldDefinition.ColumnType.String), - new FieldDefinition("sampleDate", FieldDefinition.ColumnType.DateAndTime), - new FieldDefinition("boolColumn", FieldDefinition.ColumnType.Boolean), - new FieldDefinition("attachmentColumn", FieldDefinition.ColumnType.Attachment)); + FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer).getFieldDefinition(), + FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal).getFieldDefinition(), + FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String).getFieldDefinition(), + FieldInfo.random("sampleDate", FieldDefinition.ColumnType.DateAndTime).getFieldDefinition(), + FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean).getFieldDefinition(), + FieldInfo.random("attachmentColumn", FieldDefinition.ColumnType.Attachment).getFieldDefinition() + ); } /** From a33701e160a2a8e579ee0f3fd66af6ee7f7ffb82 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 15 Jul 2025 09:03:13 -0500 Subject: [PATCH 05/19] CrossFolderListTest to use FieldInfo.random() --- .../test/params/list/ListDefinition.java | 29 +++++---- .../test/tests/list/CrossFolderListTest.java | 63 +++++++++++++------ 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/org/labkey/test/params/list/ListDefinition.java b/src/org/labkey/test/params/list/ListDefinition.java index 290ded5b2f..4c1345b2f1 100644 --- a/src/org/labkey/test/params/list/ListDefinition.java +++ b/src/org/labkey/test/params/list/ListDefinition.java @@ -1,15 +1,11 @@ package org.labkey.test.params.list; import org.jetbrains.annotations.NotNull; -import org.labkey.remoteapi.CommandException; -import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.domain.Domain; -import org.labkey.remoteapi.domain.InferDomainCommand; import org.labkey.remoteapi.domain.PropertyDescriptor; +import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.property.DomainProps; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -19,7 +15,7 @@ public abstract class ListDefinition extends DomainProps { private String _name; private String _description; - private List _fields = new ArrayList<>(); + private List _fields = new ArrayList<>(); private String _keyName; // API Options private String _titleColumn; @@ -67,7 +63,7 @@ public List getFields() return new ArrayList<>(_fields); // return a copy } - public ListDefinition setFields(List fields) + public ListDefinition setFields(List fields) { if (!fields.isEmpty() && getKeyName() == null) { @@ -78,14 +74,7 @@ public ListDefinition setFields(List fields) return this; } - public ListDefinition inferFields(File dataFile, Connection connection) throws IOException, CommandException - { - return setFields(new InferDomainCommand(dataFile, getKind()) - .execute(connection, "/") - .getFields()); - } - - public ListDefinition addField(@NotNull PropertyDescriptor field) + public ListDefinition addField(@NotNull FieldDefinition field) { if (getKeyName() == null) { @@ -157,4 +146,14 @@ protected Map getOptions() } protected abstract String getKeyType(); + + public FieldDefinition getFieldByNamePart(String namePart) + { + for (FieldDefinition field : _fields) + { + if (field.isNamePartMatch(namePart)) + return field; + } + throw new IllegalArgumentException("No field found with name part: " + namePart); + } } diff --git a/src/org/labkey/test/tests/list/CrossFolderListTest.java b/src/org/labkey/test/tests/list/CrossFolderListTest.java index eb5191000b..4c05310a00 100644 --- a/src/org/labkey/test/tests/list/CrossFolderListTest.java +++ b/src/org/labkey/test/tests/list/CrossFolderListTest.java @@ -13,6 +13,7 @@ import org.labkey.test.pages.LabkeyErrorPage; import org.labkey.test.pages.list.GridPage; import org.labkey.test.params.FieldDefinition; +import org.labkey.test.params.FieldInfo; import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.ListDefinition; import org.labkey.test.util.DataRegionTable; @@ -133,8 +134,11 @@ public void testCanDeleteTestInSeparateFolder() throws Exception helper.beginAtList(getProjectName(), SHARED_LIST); // insert a record into the shared list, in the current folder + String intColumnName = sharedListDef.getFieldByNamePart("intColumn").getName(); + String decimalColumnName = sharedListDef.getFieldByNamePart("decimalColumn").getName(); + String stringColumnName = sharedListDef.getFieldByNamePart("stringColumn").getName(); DataRegionTable.DataRegion(getDriver()).find().clickInsertNewRow() - .update(Map.of("intColumn", 1, "decimalColumn", "2.2", "stringColumn", "stringy")); + .update(Map.of(intColumnName, 1, decimalColumnName, "2.2", stringColumnName, "stringy")); // navigate to the shared list but view it in the project helper.beginAtList(getProjectName(), SHARED_LIST); @@ -179,12 +183,20 @@ public void testAddDataInSubfolderToTopLevelList() throws Exception dGen.withGeneratedRows(3); dGen.insertRows(); + String intColumnName = listDef.getFieldByNamePart("intColumn").getName(); + String decimalColumnName = listDef.getFieldByNamePart("decimalColumn").getName(); + String stringColumnName = listDef.getFieldByNamePart("stringColumn").getName(); + String dateColumnName = listDef.getFieldByNamePart("dateColumn").getName(); + String boolColumnName = listDef.getFieldByNamePart("boolColumn").getName(); + // make a couple rows of data for the subfolder List> rowsToInsert = List.of( - Map.of("intColumn", 1, "decimalColumn", 1.1, - "stringColumn", "stringy", "dateColumn", "11/11/2023", "boolColumn", true), - Map.of("intColumn", 2, "decimalColumn", 2.2, - "stringColumn", "chewy", "dateColumn", "11/12/2023", "boolColumn", false) + Map.of(intColumnName, 1, decimalColumnName, 1.1, + stringColumnName, "stringy", dateColumnName, "11/11/2023", + boolColumnName, true), + Map.of(intColumnName, 2, decimalColumnName, 2.2, + stringColumnName, "chewy", dateColumnName, "11/12/2023", + boolColumnName, false) ); // insert 2 records into the list, in the subfolder @@ -195,7 +207,7 @@ public void testAddDataInSubfolderToTopLevelList() throws Exception var displayedData = subFolderListPage.getGrid().getTableData(); assertEquals("expect only subfolder data to be shown here by default", rowsToInsert.size(), displayedData.size()); - assertThat(subFolderListPage.getGrid().getColumnDataAsText("String Column")) + assertThat(subFolderListPage.getGrid().getColumnDataAsText(stringColumnName)) .as("expect only the data inserted here to be shown").contains("stringy", "chewy"); } @@ -209,10 +221,20 @@ public void testListMetadataIsAvailableInSubfolder() throws Exception dGen.withGeneratedRows(2); dGen.insertRows(); + String intColumnName = listDef.getFieldByNamePart("intColumn").getName(); + String decimalColumnName = listDef.getFieldByNamePart("decimalColumn").getName(); + String stringColumnName = listDef.getFieldByNamePart("stringColumn").getName(); + String dateColumnName = listDef.getFieldByNamePart("dateColumn").getName(); + String boolColumnName = listDef.getFieldByNamePart("boolColumn").getName(); + // make a row of data for the subfolder List> rowToInsert = List.of( - Map.of("intColumn", 3, "decimalColumn", 3.3, - "stringColumn", "meta", "dateColumn", "11/14/2023", "boolColumn", true)); + Map.of(intColumnName, 3, + decimalColumnName, 3.3, + stringColumnName, "meta", + dateColumnName, "11/14/2023", + boolColumnName, true + )); // insert 1 record into the list, in the subfolder var qah = new QueryApiHelper(createDefaultConnection(), SUBFOLDER_A_PATH, LIST_SCHEMA, listName); @@ -266,30 +288,32 @@ public void testListsWithSameNameInTopAndSubfolder() throws Exception // same name in the same scope. // create a simple list in subfolder, insert some values, and grab a column's worth of data from it - ListDefinition listDef = createListDef(listName, testFields()); + List fields = testFields(); + ListDefinition listDef = createListDef(listName, fields); var dGen = listDef.create(createDefaultConnection(), SUBFOLDER_A_PATH); dGen.withGeneratedRows(2); dGen.insertRows(); - var subfolderData = dGen.getRows().stream().map(a-> a.get("stringColumn").toString()).collect(Collectors.toList()); + String stringColumnName = listDef.getFieldByNamePart("stringColumn").getName(); + var subfolderData = dGen.getRows().stream().map(a-> a.get(stringColumnName).toString()).toList(); // create another list with the same name at the project level, insert a little different data and capture the string values - ListDefinition listDef2 = createListDef(listName, testFields()); + ListDefinition listDef2 = createListDef(listName, fields); var dgen2 = listDef2.create(createDefaultConnection(), getProjectName()); dgen2.withGeneratedRows(3); dgen2.insertRows(); - var topFolderData = dgen2.getRows().stream().map(a-> a.get("stringColumn").toString()).collect(Collectors.toList()); + var topFolderData = dgen2.getRows().stream().map(a-> a.get(stringColumnName).toString()).toList(); // navigate to the top folder, open the container filter to include the subfolder and ensure only data from this list is shown var topPage = GridPage.beginAt(this, getProjectName(), listName); topPage.getGrid().setContainerFilter(DataRegionTable.ContainerFilterType.CURRENT_AND_SUBFOLDERS_PLUS_SHARED); assertEquals("expect the view in the top folder to only show current list data even with different subfolder list by the same name", - new HashSet<>(topFolderData), new HashSet<>(topPage.getGrid().getColumnDataAsText("String Column"))); + new HashSet<>(topFolderData), new HashSet<>(topPage.getGrid().getColumnDataAsText(stringColumnName))); // now view the list from the subfolder and ensure the list contents aren't mixed despite name ambiguity var subfolderPage = GridPage.beginAt(this, SUBFOLDER_A_PATH, listName); subfolderPage.getGrid().setContainerFilter(DataRegionTable.ContainerFilterType.CURRENT_PLUS_PROJECT_AND_SHARED); assertEquals("expect the view in the top folder to only show current list data even with different subfolder list by the same name", - new HashSet<>(subfolderData), new HashSet<>(subfolderPage.getGrid().getColumnDataAsText("String Column"))); + new HashSet<>(subfolderData), new HashSet<>(subfolderPage.getGrid().getColumnDataAsText(stringColumnName))); } @Test // Issue 52501 @@ -364,11 +388,12 @@ private ListDefinition createListDef(String listName, List list private List testFields() { return Arrays.asList( - new FieldDefinition("intColumn", FieldDefinition.ColumnType.Integer), - new FieldDefinition("decimalColumn", FieldDefinition.ColumnType.Decimal), - new FieldDefinition("stringColumn", FieldDefinition.ColumnType.String), - new FieldDefinition("dateColumn", FieldDefinition.ColumnType.DateAndTime), - new FieldDefinition("boolColumn", FieldDefinition.ColumnType.Boolean)); + FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer).getFieldDefinition(), + FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal).getFieldDefinition(), + FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String).getFieldDefinition(), + FieldInfo.random("dateColumn", FieldDefinition.ColumnType.DateAndTime).getFieldDefinition(), + FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean).getFieldDefinition() + ); } @Override From e267d72e06e0a22fba49bb638234e7039923b487 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 15 Jul 2025 10:10:26 -0500 Subject: [PATCH 06/19] SampleTypeTest to use FieldInfo.random() --- src/org/labkey/test/tests/SampleTypeTest.java | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index 546f96d866..931776ead2 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -214,9 +214,9 @@ public void testDateAndTimeValueUpdates() throws Exception public void testCreateSampleTypeNoExpression() { final String sampleTypeName = "SimpleCreateNoExp"; - final List fields = List.of( - new FieldDefinition("StringValue", ColumnType.String), - new FieldDefinition("IntValue", ColumnType.Integer)); + FieldInfo stringCol = FieldInfo.random("StringValue", ColumnType.String); + FieldInfo intCol = FieldInfo.random("IntValue", ColumnType.Integer); + final List fields = List.of(stringCol.getFieldDefinition(), intCol.getFieldDefinition()); SampleTypeDefinition sampleTypeDefinition = new SampleTypeDefinition(sampleTypeName).setFields(fields); @@ -228,19 +228,22 @@ public void testCreateSampleTypeNoExpression() sampleTypeHelper.verifyFields(fields); log("Add a single row to the sample type"); - Map fieldMap = Map.of("Name", "S-1", "StringValue", "Ess", "IntValue", "1"); + Map fieldMap = Map.of("Name", "S-1", stringCol.getName(), "Ess", intCol.getName(), "1"); sampleTypeHelper.insertRow(fieldMap); log("Verify values were saved"); + fieldMap = Map.of("Name", "S-1", stringCol.toString(), "Ess", intCol.toString(), "1"); sampleTypeHelper.verifyDataValues(Collections.singletonList(fieldMap)); List> data = new ArrayList<>(); - data.add(Map.of("Name", "S-2", "StringValue", "Tee", "IntValue", "2")); - data.add(Map.of("Name", "S-3", "StringValue", "Ewe", "IntValue", "3")); + data.add(Map.of("Name", "S-2", stringCol.getName(), "Tee", intCol.getName(), "2")); + data.add(Map.of("Name", "S-3", stringCol.getName(), "Ewe", intCol.getName(), "3")); sampleTypeHelper.bulkImport(data); assertEquals("Number of samples not as expected", 3, sampleTypeHelper.getSampleCount()); - + data = new ArrayList<>(); + data.add(Map.of("Name", "S-2", stringCol.toString(), "Tee", intCol.toString(), "2")); + data.add(Map.of("Name", "S-3", stringCol.toString(), "Ewe", intCol.toString(), "3")); sampleTypeHelper.verifyDataValues(data); } @@ -335,11 +338,13 @@ public void testMeFilterOnSampleType() USER_FOR_FILTERTEST.create(this) .addPermission("Folder Administrator", getProjectName()); String sampleType = "meFilterSamples"; + FieldInfo sizeField = FieldInfo.random("size", ColumnType.Integer); + FieldInfo userField = FieldInfo.random("user", ColumnType.User); var domainDesigner = CreateSampleTypePage.beginAt(this, getProjectName()); domainDesigner.setName(sampleType) - .addField(new FieldDefinition("size", ColumnType.Integer)) - .addField(new FieldDefinition("user", ColumnType.User)); - var formatDialog = domainDesigner.getFieldsPanel().getField("user").clickConditionalFormatButton(); + .addField(sizeField.getFieldDefinition()) + .addField(userField.getFieldDefinition()); + var formatDialog = domainDesigner.getFieldsPanel().getField(userField.getName()).clickConditionalFormatButton(); formatDialog.getOpenFormatPanel() .setFirstCondition(Filter.Operator.EQUAL) .setFirstValue("~me~") @@ -352,13 +357,13 @@ public void testMeFilterOnSampleType() var insertPage = sampleHelper.getSamplesDataRegionTable().clickInsertNewRow(); insertPage.setField("Name", "me") - .setField("size", 2) - .setField("user", OptionSelect.SelectOption.textOption(getDisplayName())) + .setField(sizeField.getName(), 2) + .setField(userField.getName(), OptionSelect.SelectOption.textOption(getDisplayName())) .submit(); insertPage = sampleHelper.getSamplesDataRegionTable().clickInsertNewRow(); insertPage.setField("Name", "not me") - .setField("size", 3) - .setField("user", OptionSelect.SelectOption.textOption(USER_FOR_FILTERTEST.getUserDisplayName())) + .setField(sizeField.getName(), 3) + .setField(userField.getName(), OptionSelect.SelectOption.textOption(USER_FOR_FILTERTEST.getUserDisplayName())) .submit(); var meCell = Locator.tag("td").withChild(Locator.tagWithText("a", getDisplayName())) @@ -574,7 +579,7 @@ public void testDeleteMultipleSamplesNoDependencies() clickProject(PROJECT_NAME); SampleTypeHelper sampleHelper = new SampleTypeHelper(this); sampleHelper.createSampleType(new SampleTypeDefinition(SAMPLE_TYPE_NAME) - .setFields(List.of(new FieldDefinition("Field01", ColumnType.String))), + .setFields(List.of(FieldInfo.random("Field01").getFieldDefinition())), sampleData); DataRegionTable drtSamples = sampleHelper.getSamplesDataRegionTable(); @@ -781,6 +786,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException final String DESC_UPDATE_1 = "New description when one did not exist before."; final String FLAG_UPDATE_2 = "Flag Value Updated After Add"; final String DESC_UPDATE_2 = "Updated description after adding a description."; + FieldInfo field01 = FieldInfo.random("Field01", ColumnType.String); log("Validate that update and delete works correctly with the Comment and Flag fields."); @@ -789,38 +795,38 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException // Using Map.of() creates an immutable collection I want to be able to update these data/collection items. Map descriptionUpdate = new HashMap<>(); descriptionUpdate.put("Name", SAMPLE_DESC_UPDATE); - descriptionUpdate.put("Field01", "cc"); + descriptionUpdate.put(field01.getName(), "cc"); descriptionUpdate.put("Description", "Here is the second description."); descriptionUpdate.put("Flag", ""); Map flagUpdate = new HashMap<>(); flagUpdate.put("Name", SAMPLE_FLAG_UPDATE); - flagUpdate.put("Field01", "bb"); + flagUpdate.put(field01.getName(), "bb"); flagUpdate.put("Description", ""); flagUpdate.put("Flag", "Flag Value 2"); Map updateBoth = new HashMap<>(); updateBoth.put("Name", SAMPLE_UPDATE_BOTH); - updateBoth.put("Field01", "dd"); + updateBoth.put(field01.getName(), "dd"); updateBoth.put("Description", ""); updateBoth.put("Flag", ""); Map deleteSample = new HashMap<>(); deleteSample.put("Name", SAMPLE_NAME_TO_DELETE); - deleteSample.put("Field01", "aa"); + deleteSample.put(field01.getName(), "aa"); deleteSample.put("Description", "This is description number 1."); deleteSample.put("Flag", "Flag Value 1"); // Some extra samples not really sure I will need them. Map canarySample01 = new HashMap<>(); canarySample01.put("Name", "ud05"); - canarySample01.put("Field01", "ee"); + canarySample01.put(field01.getName(), "ee"); canarySample01.put("Description", "This is description for sample 5."); canarySample01.put("Flag", "Flag Value 5"); Map canarySample02 = new HashMap<>(); canarySample02.put("Name", "ud06"); - canarySample02.put("Field01", "ff"); + canarySample02.put(field01.getName(), "ff"); canarySample02.put("Description", "This is description for sample 6."); canarySample02.put("Flag", "Flag Value 6"); @@ -834,11 +840,12 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException SampleTypeHelper sampleHelper = new SampleTypeHelper(this); sampleHelper.createSampleType(new SampleTypeDefinition(SAMPLE_TYPE_NAME) - .setFields(List.of(new FieldDefinition("Field01", ColumnType.String))), + .setFields(List.of(field01.getFieldDefinition())), sampleData); - List dbFieldsToCheck = Arrays.asList("Name", "Flag/Comment", "Field01", "Description"); - List> resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + List dbFieldsToCheck = Arrays.asList("Name", "Flag/Comment", field01.toString(), "Description"); + Map fieldKeyMap = Map.of("Flag/Comment", "Flag", field01.toString(), field01.getName()); + List> resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().fatal().verifyTrue("Newly inserted SampleType data not as expected. Fatal error.", areDataListEqual(resultsFromDB, sampleData)); @@ -860,7 +867,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException sampleData.remove(testDataIndex); log("Check that the Sample has been removed."); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after a delete.", areDataListEqual(resultsFromDB, sampleData)); log("Now update a sample's description."); @@ -870,7 +877,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after a update of Description.", areDataListEqual(resultsFromDB, sampleData)); log("Now delete the sample's description."); @@ -878,7 +885,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after deleting the Description.", areDataListEqual(resultsFromDB, sampleData)); log("Let's repeat it all again for a sample's flag/comment."); @@ -887,7 +894,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after a update of Flag/Comment.", areDataListEqual(resultsFromDB, sampleData)); log("Now delete the sample's Flag/Comment."); @@ -895,7 +902,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after deleting the Flag/Comment.", areDataListEqual(resultsFromDB, sampleData)); log("Finally update and delete both flag and description for a sample."); @@ -905,7 +912,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after a adding a Description and a Flag/Comment to an existing sample.", areDataListEqual(resultsFromDB, sampleData)); @@ -916,7 +923,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after a updating both a Description and a Flag/Comment.", areDataListEqual(resultsFromDB, sampleData)); @@ -926,7 +933,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException updateSampleType(sampleData.get(testDataIndex)); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("Sample Type data is not as expected after deleting the Description and Flag/Comment.", areDataListEqual(resultsFromDB, sampleData)); @@ -935,15 +942,15 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException for(Map sample : sampleData) { - String fieldValue = sample.get("Field01"); - sample.replace("Field01", fieldValue.toUpperCase()); + String fieldValue = sample.get(field01.getName()); + sample.replace(field01.getName(), fieldValue.toUpperCase()); } List fileData = new ArrayList<>(); - fileData.add(String.format("%s\t%s", "Name", "Field01")); + fileData.add(String.format("%s\t%s", "Name", field01.getLabel())); for(Map sample : sampleData) { - fileData.add(String.format("%s\t%s", sample.get("Name"), sample.get("Field01"))); + fileData.add(String.format("%s\t%s", sample.get("Name"), sample.get(field01.getName()))); } String fileName = "SampleTypeTest_UpdateSamples.tsv"; @@ -953,7 +960,7 @@ public void testUpdateAndDeleteWithCommentsAndFlags() throws IOException sampleHelper.mergeImport(importFile); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, dbFieldsToCheck, fieldKeyMap); checker().verifyTrue("SampleType data is not as expected after using a file to update samples..", areDataListEqual(resultsFromDB, sampleData)); @@ -1020,7 +1027,7 @@ protected int getSampleIndexFromTestInput(String sampleName, List> getSampleDataFromDB(String folderPath, String sampleTypeName, List fields) + protected List> getSampleDataFromDB(String folderPath, String sampleTypeName, List fields, Map fieldKeyMap) { List> results = new ArrayList<>(6); Map tempRow; @@ -1041,21 +1048,18 @@ protected List> getSampleDataFromDB(String folderPath, Strin for(String key : row.keySet()) { - if (fields.contains(key)) + if (fields.contains(key) || fieldKeyMap.containsValue(key)) { - String tmpFlag = key; - - if(key.equalsIgnoreCase("Flag/Comment")) - tmpFlag = "Flag"; + String mappedKey = fieldKeyMap.getOrDefault(key, key); if (null == row.get(key)) { - tempRow.put(tmpFlag, ""); + tempRow.put(mappedKey, ""); } else { - tempRow.put(tmpFlag, row.get(key).toString()); + tempRow.put(mappedKey, row.get(key).toString()); } } @@ -1179,7 +1183,7 @@ public void testMissingFieldIndicatorAndRequiredFields() cv.addColumn(INDICATOR_FIELD_NAME); cv.saveCustomView(); - List> resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME)); + List> resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME), Map.of()); // After doing a bulk upload it looks like the value field is stored as an empty field in the DB. // Need to update the sample data to reflect what is expected from the DB. @@ -1232,7 +1236,7 @@ public void testMissingFieldIndicatorAndRequiredFields() Locator.xpath("//td[contains(@class, 'labkey-mv-indicator')]").findElements(getDriver()).size(), expectedMissingCount); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME)); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME), Map.of()); checker().verifyTrue("After updating a value the data in the DB is not as expected.", areDataListEqual(resultsFromDB, sampleData)); @@ -1272,7 +1276,7 @@ public void testMissingFieldIndicatorAndRequiredFields() Locator.xpath("//td[contains(@class, 'labkey-mv-indicator')]").findElements(getDriver()).size(), expectedMissingCount); - resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME)); + resultsFromDB = getSampleDataFromDB(getCurrentContainerPath(), SAMPLE_TYPE_NAME, Arrays.asList("Name", REQUIRED_FIELD_NAME, MISSING_FIELD_NAME, INDICATOR_FIELD_NAME), Map.of()); checker().verifyTrue("After adding a sample with a missing value through the UI the data in the DB is not as expected.", areDataListEqual(resultsFromDB, sampleData)); From d8c9e43c9495438518eead3ea1f01cd0e49b1aa9 Mon Sep 17 00:00:00 2001 From: cnathe Date: Wed, 16 Jul 2025 15:04:05 -0500 Subject: [PATCH 07/19] PropertyController.ValidateDomainFieldsAction and usage in TestDataGenerator.randomFieldName --- .../labkey/test/util/TestDataGenerator.java | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 04cd15b1e1..7f4075643d 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -20,11 +20,14 @@ import org.apache.commons.lang3.time.DateUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; import org.junit.Assert; import org.labkey.api.collections.CaseInsensitiveLinkedHashMap; import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.CommandResponse; import org.labkey.remoteapi.Connection; +import org.labkey.remoteapi.SimplePostCommand; import org.labkey.remoteapi.collections.CaseInsensitiveHashMap; import org.labkey.remoteapi.domain.CreateDomainCommand; import org.labkey.remoteapi.domain.DomainResponse; @@ -480,15 +483,11 @@ public static String randomString(int size, @Nullable String exclusion, @Nullabl { randIndex = (int)(charSetFrom.length() * Math.random()); c = charSetFrom.charAt(randIndex); - int repeatCount = randomInt(2, 15); // repeat between 2 and 15 times + int repeatCount = randomInt(2, 50); // repeat between 2 and 50 times val.append(StringUtils.repeat(c, repeatCount)); - break; // we don't want to hit another REPEAT_PLACEHOLDER or ALL_CHARS_PLACEHOLDER in this for loop } else if (c == ALL_CHARS_PLACEHOLDER) - { val.append(charSetFrom); - break; // we've added them all, no need to continue, and we don't want to hit another REPEAT_PLACEHOLDER or ALL_CHARS_PLACEHOLDER in this for loop - } else if (c == WIDE_PLACEHOLDER) val.append(WIDE_CHAR); else @@ -580,7 +579,12 @@ public static String randomFieldName(String part) public static String randomFieldName(String part, @Nullable String exclusion) { - return randomFieldName(part, randomInt(0, 5), randomInt(0, 5), exclusion); + return randomFieldName(part, exclusion, null); + } + + public static String randomFieldName(String part, @Nullable String exclusion, String domainKind) + { + return randomFieldName(part, randomInt(0, 5), randomInt(0, 5), exclusion, domainKind); } public static String randomFieldName(String part, int numStartChars, int numEndChars) @@ -590,6 +594,13 @@ public static String randomFieldName(String part, int numStartChars, int numEndC public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion) { + return randomFieldName(part, numStartChars, numEndChars, exclusion, null); + } + + public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion, String domainKind) + { + String _domainKind = StringUtils.isEmpty(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. String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING @@ -597,20 +608,36 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); - // Avoid generating fields names with reserved substitution format patterns. e.g. ":Date" or ":First" - if (numStartChars > 0 && part.length() >= 4 && randomFieldName.charAt(numStartChars - 1) == ':' && - StringUtils.isAlpha(part.substring(0, 4))) // The shortest pattern is four characters (see org.labkey.api.util.SubstitutionFormat.getFormatNames) - { - String regenExclusion = Objects.requireNonNullElse(exclusion, "") + ":"; - randomFieldName = randomFieldName.substring(0, numStartChars - 1) + - randomString(1, regenExclusion, chars) + - randomFieldName.substring(numStartChars + 1); - } + while (!validateDomainFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, randomFieldName)) + randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); - TestLogger.log("Generated random field name: " + randomFieldName); + TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); return randomFieldName; } + private static boolean validateDomainFieldName(Connection connection, String domainKind, String fieldName) + { + SimplePostCommand command = new SimplePostCommand("property", "validateDomainFields"); + JSONObject domainDesign = new JSONObject(); + JSONArray fields = new JSONArray(); + fields.put(new JSONObject(Map.of("name", fieldName))); + domainDesign.put("fields", fields); + JSONObject json = new JSONObject(); + json.put("kind", domainKind); + json.put("domainDesign", domainDesign); + command.setJsonObject(json); + + try + { + CommandResponse response = command.execute(connection, "/"); + return !response.getParsedData().containsKey("errors"); + } + catch (CommandException | IOException e) + { + throw new RuntimeException("Failed to validate domain field name: %s.".formatted(fieldName), e); + } + } + public static String randomChoice(List choices) { return choices.get(randomInt(0, choices.size() - 1)); From c5865f2be9e79ac0a18c9e9639c3270f45296704 Mon Sep 17 00:00:00 2001 From: cnathe Date: Wed, 16 Jul 2025 15:24:10 -0500 Subject: [PATCH 08/19] TestDataGenerator.randomFieldName to take DomainKind (defaults to SampleSet) --- src/org/labkey/test/params/FieldInfo.java | 13 +++++++++++-- .../test/tests/DomainFieldTypeChangeTest.java | 13 +++++++------ src/org/labkey/test/tests/SampleTypeTest.java | 6 +++--- .../test/tests/list/CrossFolderListTest.java | 15 +++++++-------- .../labkey/test/tests/list/ListLookupTest.java | 9 +++++---- src/org/labkey/test/tests/list/ListTest.java | 3 ++- .../labkey/test/tests/query/QueryLookupTest.java | 5 +++-- src/org/labkey/test/util/DomainUtils.java | 6 ++++++ src/org/labkey/test/util/TestDataGenerator.java | 8 ++++---- .../labkey/test/util/exp/DataClassAPIHelper.java | 12 ++++++------ 10 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/org/labkey/test/params/FieldInfo.java b/src/org/labkey/test/params/FieldInfo.java index f962aa8241..704ce69ebb 100644 --- a/src/org/labkey/test/params/FieldInfo.java +++ b/src/org/labkey/test/params/FieldInfo.java @@ -3,6 +3,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.labkey.test.params.FieldDefinition.ColumnType; +import org.labkey.test.util.DomainUtils; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.TestDataGenerator; @@ -53,13 +54,21 @@ public FieldInfo(String name) /** * Creates a FieldInfo with a semi-random name */ - public static FieldInfo random(String namePart, ColumnType columnType) + public static FieldInfo random(String namePart, ColumnType columnType, DomainUtils.DomainKind domainKind) { - FieldInfo field = new FieldInfo(TestDataGenerator.randomFieldName(namePart), columnType); + FieldInfo field = new FieldInfo(TestDataGenerator.randomFieldName(namePart, null, domainKind), columnType); field.setNamePart(namePart); return field; } + /** + * Creates a FieldInfo with a semi-random name + */ + public static FieldInfo random(String namePart, ColumnType columnType) + { + return random(namePart, columnType, null); + } + /** * Creates a String field with a semi-random name */ diff --git a/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java b/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java index c2ca5dee2e..d754c8ea91 100644 --- a/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java +++ b/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java @@ -20,6 +20,7 @@ import org.labkey.test.params.FieldInfo; import org.labkey.test.util.APIAssayHelper; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.DomainUtils; import org.labkey.test.util.PortalHelper; import org.labkey.test.util.TestDataGenerator; @@ -63,11 +64,11 @@ public void testProvisionedDomainFieldChanges() throws IOException, CommandExcep { log("Creating list with variety of data fields"); String listName = TestDataGenerator.randomDomainName("SampleListWithAllDataTypes"); - FieldInfo stringField = FieldInfo.random("name", FieldDefinition.ColumnType.String); - FieldInfo integerField = FieldInfo.random("Test/Integer", FieldDefinition.ColumnType.Integer); - FieldInfo decimalField = FieldInfo.random("Test/Decimal", FieldDefinition.ColumnType.Decimal); - FieldInfo dateField = FieldInfo.random("Test/Date", FieldDefinition.ColumnType.DateAndTime); - FieldInfo booleanField = FieldInfo.random("Test'/\"Boolean", FieldDefinition.ColumnType.Boolean); // GH Issue #755 + FieldInfo stringField = FieldInfo.random("name", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.IntList); + FieldInfo integerField = FieldInfo.random("Test/Integer", FieldDefinition.ColumnType.Integer, DomainUtils.DomainKind.IntList); + FieldInfo decimalField = FieldInfo.random("Test/Decimal", FieldDefinition.ColumnType.Decimal, DomainUtils.DomainKind.IntList); + FieldInfo dateField = FieldInfo.random("Test/Date", FieldDefinition.ColumnType.DateAndTime, DomainUtils.DomainKind.IntList); + FieldInfo booleanField = FieldInfo.random("Test'/\"Boolean", FieldDefinition.ColumnType.Boolean, DomainUtils.DomainKind.IntList); // GH Issue #755 TestDataGenerator dgen = new TestDataGenerator("lists", listName, getProjectName()) .withColumns(List.of( stringField.getFieldDefinition(), @@ -75,7 +76,7 @@ public void testProvisionedDomainFieldChanges() throws IOException, CommandExcep decimalField.getFieldDefinition(), dateField.getFieldDefinition(), booleanField.getFieldDefinition())); - dgen.createDomain(createDefaultConnection(), "IntList", Map.of("keyName", "id")); + dgen.createDomain(createDefaultConnection(), DomainUtils.DomainKind.IntList.name(), Map.of("keyName", "id")); log("Inserting sample rows in the list"); dgen.addCustomRow(Map.of( diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index b48341e1af..8dfbb831c2 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -160,11 +160,11 @@ public void testDateAndTimeValueUpdates() throws Exception final FieldDefinition txtField = new FieldDefinition( TestDataGenerator.randomFieldName("text"), ColumnType.String).setRequired(true); final FieldDefinition dateField = new FieldDefinition( - TestDataGenerator.randomFieldName("date", ":"), ColumnType.Date); + TestDataGenerator.randomFieldName("date"), ColumnType.Date); final FieldDefinition timeField = new FieldDefinition( - TestDataGenerator.randomFieldName("time", ":"), ColumnType.Time); + TestDataGenerator.randomFieldName("time"), ColumnType.Time); final FieldDefinition dateTimeField = new FieldDefinition( - TestDataGenerator.randomFieldName("dateTime", ":"), ColumnType.DateAndTime); + TestDataGenerator.randomFieldName("dateTime"), ColumnType.DateAndTime); final List fields = List.of(txtField, dateField, timeField, dateTimeField); SampleTypeDefinition sampleTypeDefinition = new SampleTypeDefinition(sampleTypeName).setFields(fields); diff --git a/src/org/labkey/test/tests/list/CrossFolderListTest.java b/src/org/labkey/test/tests/list/CrossFolderListTest.java index 4c05310a00..3849353a8d 100644 --- a/src/org/labkey/test/tests/list/CrossFolderListTest.java +++ b/src/org/labkey/test/tests/list/CrossFolderListTest.java @@ -27,7 +27,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -321,8 +320,8 @@ public void testLookupValidatorFolderScope() throws Exception { var firstListName = TestDataGenerator.randomDomainName("First"); var secondListName = TestDataGenerator.randomDomainName("Second"); - var textColumnName = TestDataGenerator.randomFieldName("Text"); - var lookupFieldName = TestDataGenerator.randomFieldName("LookAtFirst"); + var textColumnName = TestDataGenerator.randomFieldName("Text", null, DomainUtils.DomainKind.IntList); + var lookupFieldName = TestDataGenerator.randomFieldName("LookAtFirst", null, DomainUtils.DomainKind.IntList); var encodedLookupFieldName = EscapeUtil.fieldKeyEncodePart(lookupFieldName); // Create and configure list definitions @@ -388,11 +387,11 @@ private ListDefinition createListDef(String listName, List list private List testFields() { return Arrays.asList( - FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer).getFieldDefinition(), - FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal).getFieldDefinition(), - FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String).getFieldDefinition(), - FieldInfo.random("dateColumn", FieldDefinition.ColumnType.DateAndTime).getFieldDefinition(), - FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean).getFieldDefinition() + FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer, DomainUtils.DomainKind.IntList).getFieldDefinition(), + FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal, DomainUtils.DomainKind.IntList).getFieldDefinition(), + FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.IntList).getFieldDefinition(), + FieldInfo.random("dateColumn", FieldDefinition.ColumnType.DateAndTime, DomainUtils.DomainKind.IntList).getFieldDefinition(), + FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean, DomainUtils.DomainKind.IntList).getFieldDefinition() ); } diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java index 056a1a0115..b9dd74c7b9 100644 --- a/src/org/labkey/test/tests/list/ListLookupTest.java +++ b/src/org/labkey/test/tests/list/ListLookupTest.java @@ -14,6 +14,7 @@ import org.labkey.test.pages.list.EditListDefinitionPage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.DomainUtils; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.TestDataGenerator; import org.labkey.test.util.data.TestDataUtils; @@ -32,14 +33,14 @@ public class ListLookupTest extends BaseWebDriverTest { private static final String lookToListName = TestDataGenerator.randomDomainName("lookToList"); - private static final String lookToKeyFieldName = TestDataGenerator.randomFieldName("lookToKeyField"); - private static final String lookToFieldName = TestDataGenerator.randomFieldName("lookToField"); + private static final String lookToKeyFieldName = TestDataGenerator.randomFieldName("lookToKeyField", null, DomainUtils.DomainKind.IntList); + private static final String lookToFieldName = TestDataGenerator.randomFieldName("lookToField", null, DomainUtils.DomainKind.IntList); private static List> lookToListValues; private static String lookupKeyAsNameNumber; private static String lookupKeyAsNameFieldValue; private static final String lookFromListName = TestDataGenerator.randomDomainName("lookFromList"); - private static final String lookFromKeyFieldName = TestDataGenerator.randomFieldName("Look From Key Field"); - private static final String lookFromLookupFieldName = TestDataGenerator.randomFieldName("Look From Lookup Field"); + private static final String lookFromKeyFieldName = TestDataGenerator.randomFieldName("Look From Key Field", null, DomainUtils.DomainKind.IntList); + private static final String lookFromLookupFieldName = TestDataGenerator.randomFieldName("Look From Lookup Field", null, DomainUtils.DomainKind.IntList); private static final String lookFromLookupFieldKey = EscapeUtil.fieldKeyEncodePart(lookFromLookupFieldName); @BeforeClass diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 16feaf1b84..336dca7999 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -59,6 +59,7 @@ import org.labkey.test.util.AuditLogHelper; import org.labkey.test.util.DataRegionExportHelper; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.DomainUtils; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.LogMethod; import org.labkey.test.util.Maps; @@ -513,7 +514,7 @@ public void testNameTrimming() public void testLongName() { String listName = "A_+-:''.¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ“‘«æ…¬˚∆˙©√ƒ∂ßΩ≈ç√∫µ≤≥÷‹›fifl‡°·‚—±⁄€‹›‡‰Æ«»¢∫√∑∏∂"; - String fieldWithDefault = TestDataGenerator.randomFieldName("With Default"); + String fieldWithDefault = TestDataGenerator.randomFieldName("With Default", null, DomainUtils.DomainKind.IntList); EditListDefinitionPage listEditPage = _listHelper.beginCreateList(getProjectName(), listName); listEditPage.manuallyDefineFieldsWithAutoIncrementingKey("Key"); listEditPage.addField(new FieldDefinition(fieldWithDefault, ColumnType.String)); diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index 33e7852e37..dec685a921 100644 --- a/src/org/labkey/test/tests/query/QueryLookupTest.java +++ b/src/org/labkey/test/tests/query/QueryLookupTest.java @@ -13,6 +13,7 @@ import org.labkey.test.params.list.IntListDefinition; import org.labkey.test.params.list.VarListDefinition; import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.DomainUtils; import org.labkey.test.util.EscapeUtil; import org.labkey.test.util.data.TestDataUtils; @@ -28,8 +29,8 @@ 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); - private static final FieldInfo TSHIRT_COLUMN = FieldInfo.random("TShirt", FieldDefinition.ColumnType.String); + private static final FieldInfo NAME_COLUMN = FieldInfo.random("Name", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); + private static final FieldInfo TSHIRT_COLUMN = FieldInfo.random("TShirt", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); @Override protected void doCleanup(boolean afterTest) diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 55f324dc95..7bda02026e 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -94,4 +94,10 @@ public static void ensureDeleted(String containerPath, String schema, String tab } } + public enum DomainKind { + DataClass, + SampleSet, + IntList, + VarList + } } diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 7f4075643d..6f5b764f8d 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -582,7 +582,7 @@ public static String randomFieldName(String part, @Nullable String exclusion) return randomFieldName(part, exclusion, null); } - public static String randomFieldName(String part, @Nullable String exclusion, String domainKind) + public static String randomFieldName(String part, @Nullable String exclusion, DomainUtils.DomainKind domainKind) { return randomFieldName(part, randomInt(0, 5), randomInt(0, 5), exclusion, domainKind); } @@ -597,9 +597,9 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in return randomFieldName(part, numStartChars, numEndChars, exclusion, null); } - public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion, String domainKind) + public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion, DomainUtils.DomainKind domainKind) { - String _domainKind = StringUtils.isEmpty(domainKind) ? "SampleSet" : domainKind; + DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.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. @@ -615,7 +615,7 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in return randomFieldName; } - private static boolean validateDomainFieldName(Connection connection, String domainKind, String fieldName) + private static boolean validateDomainFieldName(Connection connection, DomainUtils.DomainKind domainKind, String fieldName) { SimplePostCommand command = new SimplePostCommand("property", "validateDomainFields"); JSONObject domainDesign = new JSONObject(); diff --git a/src/org/labkey/test/util/exp/DataClassAPIHelper.java b/src/org/labkey/test/util/exp/DataClassAPIHelper.java index 8ac77d5059..8125d240d0 100644 --- a/src/org/labkey/test/util/exp/DataClassAPIHelper.java +++ b/src/org/labkey/test/util/exp/DataClassAPIHelper.java @@ -55,12 +55,12 @@ static public TestDataGenerator createEmptyDataClass(String containerPath, DataC public static List dataClassTestFields() { return Arrays.asList( - FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer).getFieldDefinition(), - FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal).getFieldDefinition(), - FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String).getFieldDefinition(), - FieldInfo.random("sampleDate", FieldDefinition.ColumnType.DateAndTime).getFieldDefinition(), - FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean).getFieldDefinition(), - FieldInfo.random("attachmentColumn", FieldDefinition.ColumnType.Attachment).getFieldDefinition() + FieldInfo.random("intColumn", FieldDefinition.ColumnType.Integer, DomainUtils.DomainKind.DataClass).getFieldDefinition(), + FieldInfo.random("decimalColumn", FieldDefinition.ColumnType.Decimal, DomainUtils.DomainKind.DataClass).getFieldDefinition(), + FieldInfo.random("stringColumn", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.DataClass).getFieldDefinition(), + FieldInfo.random("sampleDate", FieldDefinition.ColumnType.DateAndTime, DomainUtils.DomainKind.DataClass).getFieldDefinition(), + FieldInfo.random("boolColumn", FieldDefinition.ColumnType.Boolean, DomainUtils.DomainKind.DataClass).getFieldDefinition(), + FieldInfo.random("attachmentColumn", FieldDefinition.ColumnType.Attachment, DomainUtils.DomainKind.DataClass).getFieldDefinition() ); } From d6bda8521e1b1cdf792c2181ac0b633219b5c041 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 10:19:42 -0500 Subject: [PATCH 09/19] Update PropertyController.ValidateDomainAndFieldNamesAction to also validate domain names based on the domain kind --- src/org/labkey/test/util/DomainUtils.java | 4 +- .../labkey/test/util/TestDataGenerator.java | 50 ++++++++++++------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 7bda02026e..9cc20bce6b 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -98,6 +98,8 @@ public enum DomainKind { DataClass, SampleSet, IntList, - VarList + VarList, + StudyDatasetDate, + StudyDatasetVisit } } diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index ddd699ba4d..87c355181d 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -531,7 +531,7 @@ public static String randomDomainName() public static String randomDomainName(@Nullable String part) { - return randomDomainName(part, randomInt(0, 10)); + return randomDomainName(part, randomInt(0, 20)); // TODO increase this after fix for 53478 } public static String randomInvalidDomainName(@Nullable String namePart, int numStartChars, int numEndChars) @@ -546,6 +546,16 @@ public static String randomDomainName(int numEndChars) return randomDomainName(null, numEndChars); } + public static String randomDomainName(@Nullable String namePart, @Nullable DomainUtils.DomainKind domainKind) + { + return randomDomainName(namePart, randomInt(0, 100), domainKind); + } + + public static String randomDomainName(@Nullable String namePart, int numEndChars) + { + return randomDomainName(namePart, numEndChars, null); + } + /** * Generate a random domain name of the specified size. * @@ -553,22 +563,19 @@ public static String randomDomainName(int numEndChars) * @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, int numEndChars) + public static String randomDomainName(@Nullable String namePart, int numEndChars, @Nullable DomainUtils.DomainKind domainKind) { - String domainName; - do - { - String firstChar = namePart != null ? namePart.charAt(0) + "" : randomString(1, null, ALPHANUMERIC_STRING); // domain needs to start with alphanumeric char; - String _namePart = namePart != null ? namePart.substring(1) : ""; - final String charset = namePart != null ? DOMAIN_SPECIAL_STRING : ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; - domainName = firstChar + randomName(_namePart, 0, numEndChars, charset, null); - } - while (Pattern.matches("(.*\\s--[^ ].*)|(.*\\s-[^- ].*)", domainName)); // domain name must not contain space followed by dash. (command like: Issue 49161) + String _namePart = namePart == null ? "" : namePart; + DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; + String charSet = namePart != null ? DOMAIN_SPECIAL_STRING : ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; + String domainName = randomName(_namePart, 0, numEndChars, charSet, null); + while (!validateDomainAndFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, domainName, null)) + domainName = randomName(_namePart, 0, numEndChars, charSet, null); // 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+", " "); - TestLogger.log("Generated random domain name: " + domainName); + TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); return domainName; } @@ -608,20 +615,27 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); - while (!validateDomainFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, randomFieldName)) + while (!validateDomainAndFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, null, randomFieldName)) randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); return randomFieldName; } - private static boolean validateDomainFieldName(Connection connection, DomainUtils.DomainKind domainKind, String fieldName) + private static boolean validateDomainAndFieldName(Connection connection, DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { - SimplePostCommand command = new SimplePostCommand("property", "validateDomainFields"); + SimplePostCommand command = new SimplePostCommand("property", "validateDomainAndFieldNames"); JSONObject domainDesign = new JSONObject(); - JSONArray fields = new JSONArray(); - fields.put(new JSONObject(Map.of("name", fieldName))); - domainDesign.put("fields", fields); + if (domainName != null) + { + domainDesign.put("name", domainName); + } + if (fieldName != null) + { + JSONArray fields = new JSONArray(); + fields.put(new JSONObject(Map.of("name", fieldName))); + domainDesign.put("fields", fields); + } JSONObject json = new JSONObject(); json.put("kind", domainKind); json.put("domainDesign", domainDesign); From 6fa8287114199e0d86984664cd5bc1b6d457314a Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 10:23:49 -0500 Subject: [PATCH 10/19] Update usages of randomDomainName to pass DomainKind --- src/org/labkey/test/tests/list/CrossFolderListTest.java | 4 ++-- src/org/labkey/test/tests/list/ListLookupTest.java | 4 ++-- src/org/labkey/test/tests/list/ListTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/test/tests/list/CrossFolderListTest.java b/src/org/labkey/test/tests/list/CrossFolderListTest.java index 3849353a8d..dbb9d15e50 100644 --- a/src/org/labkey/test/tests/list/CrossFolderListTest.java +++ b/src/org/labkey/test/tests/list/CrossFolderListTest.java @@ -318,8 +318,8 @@ public void testListsWithSameNameInTopAndSubfolder() throws Exception @Test // Issue 52501 public void testLookupValidatorFolderScope() throws Exception { - var firstListName = TestDataGenerator.randomDomainName("First"); - var secondListName = TestDataGenerator.randomDomainName("Second"); + var firstListName = TestDataGenerator.randomDomainName(null, DomainUtils.DomainKind.IntList); + var secondListName = TestDataGenerator.randomDomainName(null, DomainUtils.DomainKind.IntList); var textColumnName = TestDataGenerator.randomFieldName("Text", null, DomainUtils.DomainKind.IntList); var lookupFieldName = TestDataGenerator.randomFieldName("LookAtFirst", null, DomainUtils.DomainKind.IntList); var encodedLookupFieldName = EscapeUtil.fieldKeyEncodePart(lookupFieldName); diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java index b9dd74c7b9..c4922812ab 100644 --- a/src/org/labkey/test/tests/list/ListLookupTest.java +++ b/src/org/labkey/test/tests/list/ListLookupTest.java @@ -32,13 +32,13 @@ @Category({Daily.class, Data.class, Hosting.class}) public class ListLookupTest extends BaseWebDriverTest { - private static final String lookToListName = TestDataGenerator.randomDomainName("lookToList"); + private static final String lookToListName = TestDataGenerator.randomDomainName("lookToList", DomainUtils.DomainKind.IntList); private static final String lookToKeyFieldName = TestDataGenerator.randomFieldName("lookToKeyField", null, DomainUtils.DomainKind.IntList); private static final String lookToFieldName = TestDataGenerator.randomFieldName("lookToField", null, DomainUtils.DomainKind.IntList); private static List> lookToListValues; private static String lookupKeyAsNameNumber; private static String lookupKeyAsNameFieldValue; - private static final String lookFromListName = TestDataGenerator.randomDomainName("lookFromList"); + private static final String lookFromListName = TestDataGenerator.randomDomainName("lookFromList", DomainUtils.DomainKind.IntList); private static final String lookFromKeyFieldName = TestDataGenerator.randomFieldName("Look From Key Field", null, DomainUtils.DomainKind.IntList); private static final String lookFromLookupFieldName = TestDataGenerator.randomFieldName("Look From Lookup Field", null, DomainUtils.DomainKind.IntList); private static final String lookFromLookupFieldKey = EscapeUtil.fieldKeyEncodePart(lookFromLookupFieldName); diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 336dca7999..8e13727d05 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -542,7 +542,7 @@ public void testLongName() @Test public void testCreateListWithBOMFile() { - String listName = TestDataGenerator.randomDomainName("From BOM File", 4); + String listName = TestDataGenerator.randomDomainName("From BOM File", 4, DomainUtils.DomainKind.IntList); File bomFile = TestFileUtils.getSampleData("lists/TestUTF8_BOM.csv"); EditListDefinitionPage listEditPage = _listHelper.beginCreateList(getProjectName(), listName); @@ -991,7 +991,7 @@ public void testChangeListNameOverAPI() throws Exception public void testChangeListName() { - String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", 7); + String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", 7, DomainUtils.DomainKind.IntList); _listHelper.createList(PROJECT_VERIFY, listNameBefore, new FieldDefinition("name", ColumnType.String), @@ -2116,7 +2116,7 @@ public void customizeURLTest() @Test public void testPkNameParameterCollision() throws IOException, CommandException { - String listName = TestDataGenerator.randomDomainName("list_key_check"); + String listName = TestDataGenerator.randomDomainName("list_key_check", DomainUtils.DomainKind.IntList); String pkCol = "Name"; var dgen = new VarListDefinition(listName) From db34bee1a982371a9b11e0a69b26b849b6d80148 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 11:24:18 -0500 Subject: [PATCH 11/19] TestDataGenerator randomFieldName and randomDomainName to use getNumChars() for each generated name iteration --- .../labkey/test/util/TestDataGenerator.java | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 87c355181d..65fccacbed 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -531,7 +531,7 @@ public static String randomDomainName() public static String randomDomainName(@Nullable String part) { - return randomDomainName(part, randomInt(0, 20)); // TODO increase this after fix for 53478 + return randomDomainName(part, null); } public static String randomInvalidDomainName(@Nullable String namePart, int numStartChars, int numEndChars) @@ -541,36 +541,27 @@ public static String randomInvalidDomainName(@Nullable String namePart, int numS return domainName; } - public static String randomDomainName(int numEndChars) - { - return randomDomainName(null, numEndChars); - } - public static String randomDomainName(@Nullable String namePart, @Nullable DomainUtils.DomainKind domainKind) { - return randomDomainName(namePart, randomInt(0, 100), domainKind); - } - - public static String randomDomainName(@Nullable String namePart, int numEndChars) - { - return randomDomainName(namePart, numEndChars, null); + return randomDomainName(namePart, null, null, domainKind); } /** * Generate a random domain name of the specified size. * * @param namePart If a namePart is provided, the domain name will contain it. Pass null to generate a random alphanumeric single character for the prefix. + * @param numStartChars Number of random characters at end of name * @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, int numEndChars, @Nullable DomainUtils.DomainKind domainKind) + public static String randomDomainName(@Nullable String namePart, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable DomainUtils.DomainKind domainKind) { String _namePart = namePart == null ? "" : namePart; DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; - String charSet = namePart != null ? DOMAIN_SPECIAL_STRING : ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; - String domainName = randomName(_namePart, 0, numEndChars, charSet, null); - while (!validateDomainAndFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, domainName, null)) - domainName = randomName(_namePart, 0, numEndChars, charSet, null); + String charSet = ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; + String domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); + while (isDomainAndFieldNameInvalid(WebTestHelper.getRemoteApiConnection(false), _domainKind, domainName, null)) + domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); // 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+", " "); @@ -579,6 +570,11 @@ public static String randomDomainName(@Nullable String namePart, int numEndChars return domainName; } + private static int getNumChars(Integer val, int max) + { + return val != null ? val : randomInt(0, max); + } + public static String randomFieldName(String part) { return randomFieldName(part, null); @@ -591,20 +587,10 @@ public static String randomFieldName(String part, @Nullable String exclusion) public static String randomFieldName(String part, @Nullable String exclusion, DomainUtils.DomainKind domainKind) { - return randomFieldName(part, randomInt(0, 5), randomInt(0, 5), exclusion, domainKind); - } - - public static String randomFieldName(String part, int numStartChars, int numEndChars) - { - return randomFieldName(part, numStartChars, numEndChars, null); + return randomFieldName(part, null, null, exclusion, domainKind); } - public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion) - { - return randomFieldName(part, numStartChars, numEndChars, exclusion, null); - } - - public static String randomFieldName(@NotNull String part, int numStartChars, int numEndChars, @Nullable String exclusion, DomainUtils.DomainKind domainKind) + public static String randomFieldName(@NotNull String part, @Nullable Integer numStartChars, @Nullable Integer numEndChars, @Nullable String exclusion, @Nullable DomainUtils.DomainKind domainKind) { DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; @@ -613,16 +599,15 @@ public static String randomFieldName(@NotNull String part, int numStartChars, in String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER + ALL_CHARS_PLACEHOLDER; - String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); - - while (!validateDomainAndFieldName(WebTestHelper.getRemoteApiConnection(false), _domainKind, null, randomFieldName)) - randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); + String randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); + while (isDomainAndFieldNameInvalid(WebTestHelper.getRemoteApiConnection(false), _domainKind, null, randomFieldName)) + randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); return randomFieldName; } - private static boolean validateDomainAndFieldName(Connection connection, DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) + private static boolean isDomainAndFieldNameInvalid(Connection connection, DomainUtils.DomainKind domainKind, @Nullable String domainName, @Nullable String fieldName) { SimplePostCommand command = new SimplePostCommand("property", "validateDomainAndFieldNames"); JSONObject domainDesign = new JSONObject(); @@ -644,7 +629,7 @@ private static boolean validateDomainAndFieldName(Connection connection, DomainU try { CommandResponse response = command.execute(connection, "/"); - return !response.getParsedData().containsKey("errors"); + return response.getParsedData().containsKey("errors"); } catch (CommandException | IOException e) { From 330cf040793e0ccbb702323d3c59193f1cce91d4 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 11:27:04 -0500 Subject: [PATCH 12/19] Set DomainKind in usages of TestDataGenerator.randomDomainName() --- src/org/labkey/test/tests/list/ListTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 8e13727d05..5270d9b27f 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -542,7 +542,7 @@ public void testLongName() @Test public void testCreateListWithBOMFile() { - String listName = TestDataGenerator.randomDomainName("From BOM File", 4, DomainUtils.DomainKind.IntList); + String listName = TestDataGenerator.randomDomainName("From BOM File", DomainUtils.DomainKind.IntList); File bomFile = TestFileUtils.getSampleData("lists/TestUTF8_BOM.csv"); EditListDefinitionPage listEditPage = _listHelper.beginCreateList(getProjectName(), listName); @@ -991,7 +991,7 @@ public void testChangeListNameOverAPI() throws Exception public void testChangeListName() { - String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", 7, DomainUtils.DomainKind.IntList); + String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", DomainUtils.DomainKind.IntList); _listHelper.createList(PROJECT_VERIFY, listNameBefore, new FieldDefinition("name", ColumnType.String), From 4cbc2fbf22d897f46dbde910db08a6f110082019 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 15:10:09 -0500 Subject: [PATCH 13/19] CustomizeView to scroll into view the tree expand icon before clicking --- src/org/labkey/test/components/CustomizeView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/components/CustomizeView.java b/src/org/labkey/test/components/CustomizeView.java index 9d8fb0ff73..6871a97a8f 100644 --- a/src/org/labkey/test/components/CustomizeView.java +++ b/src/org/labkey/test/components/CustomizeView.java @@ -367,7 +367,9 @@ private WebElement expandPivots(CharSequence fieldKey) _driver.scrollIntoView(fieldRow, false); if (!StringUtils.trimToEmpty(fieldRow.getAttribute("class")).contains("expanded")) { - Locator.css(".x4-tree-expander").findElement(fieldRow).click(); + WebElement expander = Locator.css(".x4-tree-expander").findElement(fieldRow); + _driver.scrollIntoView(expander, true); + expander.click(); } Locator.tag("tr").withClass("x4-grid-tree-node-expanded").withAttribute("data-recordid", dataRecordId).waitForElement(getComponentElement(), 10000); WebDriverWrapper.waitFor(() -> Locator.css("tr[data-recordid] + tr:not(.x4-grid-row)").findElements(getComponentElement()).isEmpty(), 2000); // Spacer row appears during expansion animation From e395e6efe75f7e689e5b27ed1f6723eae10fc18e Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 16:06:14 -0500 Subject: [PATCH 14/19] text update for change to sample type duplicate name error message --- src/org/labkey/test/tests/SampleTypeTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index 8dfbb831c2..d294a853f2 100644 --- a/src/org/labkey/test/tests/SampleTypeTest.java +++ b/src/org/labkey/test/tests/SampleTypeTest.java @@ -93,7 +93,8 @@ public class SampleTypeTest extends BaseWebDriverTest private static final String FOLDER_NAME = "SampleTypeTestFolder"; private static final String LOOKUP_FOLDER = "LookupSampleTypeFolder"; private static final String CASE_INSENSITIVE_SAMPLE_TYPE = "CaseInsensitiveSampleType"; - private static final String LOWER_CASE_SAMPLE_TYPE = "caseinsensitivesampletype"; + private static final String LOWER_CASE_SAMPLE_TYPE = CASE_INSENSITIVE_SAMPLE_TYPE.toLowerCase(); + private static final String UPPER_CASE_SAMPLE_TYPE = CASE_INSENSITIVE_SAMPLE_TYPE.toUpperCase(); private static final TestUser USER_FOR_FILTERTEST = new TestUser("filter_user@sampletypetest.test"); @Override @@ -1382,7 +1383,7 @@ public void testSampleTypeNames() .goToCreateNewSampleType() .setName(LOWER_CASE_SAMPLE_TYPE) .clickSaveExpectingErrors(); - assertEquals("Sample Type creation error", Arrays.asList("A Sample Type with that name already exists."), errors); + assertEquals("Sample Type creation error", Arrays.asList("A Sample Type with name '" + LOWER_CASE_SAMPLE_TYPE + "' already exists."), errors); clickProject(PROJECT_NAME); assertElementPresent(Locator.linkWithText(CASE_INSENSITIVE_SAMPLE_TYPE)); assertElementNotPresent(Locator.linkWithText(LOWER_CASE_SAMPLE_TYPE)); @@ -1400,9 +1401,9 @@ public void testSampleTypeNames() log("Sample type cannot be renamed to an existing name"); goToProjectHome(); updatePage = sampleHelper.goToEditSampleType(updatedSampleType); - updatePage.setName(CASE_INSENSITIVE_SAMPLE_TYPE.toUpperCase()); + updatePage.setName(UPPER_CASE_SAMPLE_TYPE); assertTrue("Sample type rename conflict error", - updatePage.clickSaveExpectingErrors().contains("A Sample Type with name 'CASEINSENSITIVESAMPLETYPE' already exists.")); + updatePage.clickSaveExpectingErrors().contains("A Sample Type with name '" + UPPER_CASE_SAMPLE_TYPE + "' already exists.")); updatePage.clickCancel(); } From afb22ba511189942522bcd42663ca9dbbb2fb3c0 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 17 Jul 2025 16:06:29 -0500 Subject: [PATCH 15/19] SampleTypeHelper.convertMapToTsv to use TestDataUtils.tsvStringFromRowMaps --- src/org/labkey/test/util/SampleTypeHelper.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/org/labkey/test/util/SampleTypeHelper.java b/src/org/labkey/test/util/SampleTypeHelper.java index 84c657a397..1fe96d35c1 100644 --- a/src/org/labkey/test/util/SampleTypeHelper.java +++ b/src/org/labkey/test/util/SampleTypeHelper.java @@ -30,6 +30,7 @@ import org.labkey.test.pages.experiment.UpdateSampleTypePage; import org.labkey.test.params.FieldDefinition; import org.labkey.test.params.experiment.SampleTypeDefinition; +import org.labkey.test.util.data.TestDataUtils; import org.openqa.selenium.WebDriver; import java.io.File; @@ -81,18 +82,11 @@ public static SampleTypeHelper beginAtSampleTypesList(WebDriverWrapper dWrapper, @NotNull private static String convertMapToTsv(@NotNull List> data) { - // first the header - List rows = new ArrayList<>(); - rows.add(String.join("\t", data.get(0).keySet())); - data.forEach(dataMap -> { - StringBuilder row = new StringBuilder(); - data.get(0).keySet().forEach(key -> { - row.append(dataMap.get(key)); - row.append("\t"); - }); - rows.add(row.substring(0, row.lastIndexOf("\t"))); - }); - return String.join("\n", rows); + List headers = new ArrayList<>(data.get(0).keySet()); + List> rows = new ArrayList<>(); + for (Map row : data) + rows.add(new HashMap<>(row)); + return TestDataUtils.tsvStringFromRowMaps(rows, headers, true); } @Override From 72852c861eaa6caa6da3fc613051e3d1813d55dc Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 18 Jul 2025 11:00:23 -0500 Subject: [PATCH 16/19] BiologicsReportTest fix for which non-ASCII chars are replaced with # in PDF --- src/org/labkey/test/util/TestDataGenerator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 65fccacbed..6e81ddd597 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -74,10 +74,10 @@ public class TestDataGenerator { public static final String WIDE_CHAR = "\uD83D\uDC7E"; // 👾 - private static final char WIDE_PLACEHOLDER = '\u03A0'; // 'Π' - Wide character can't be picked from the string with 'charAt' - private static final char REPEAT_PLACEHOLDER = '\u22EF'; // '⋯' - Used to indicate that the char will be repeated - private static final char ALL_CHARS_PLACEHOLDER = '\u2211'; // '∑' - Used to indicate that all characters from the charset should be used - private static final String NON_LATIN_STRING = "\u0438\uC548\u306F"; // "и안は" + public static final char WIDE_PLACEHOLDER = '\u03A0'; // 'Π' - Wide character can't be picked from the string with 'charAt' + public static final char REPEAT_PLACEHOLDER = '\u22EF'; // '⋯' - Used to indicate that the char will be repeated + public static final char ALL_CHARS_PLACEHOLDER = '\u2211'; // '∑' - Used to indicate that all characters from the charset should be used + public static final String NON_LATIN_STRING = "\u0438\uC548\u306F"; // "и안は" // chose a Character random from this String public static final String CHARSET_STRING = "ABCDEFG01234abcdefvxyz~!@#$%^&*()-+=_{}[]|:;\"',.<>" + NON_LATIN_STRING + WIDE_PLACEHOLDER; public static final String ALPHANUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz"; From 9b0ad440b6147ee1be9a443fa97a7fe743f91881 Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 18 Jul 2025 11:00:52 -0500 Subject: [PATCH 17/19] FieldDefinition.labelFromName multiple spaces in the UI are collapsed into a single space --- src/org/labkey/test/params/FieldDefinition.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/params/FieldDefinition.java b/src/org/labkey/test/params/FieldDefinition.java index 568e903161..0c484493f1 100644 --- a/src/org/labkey/test/params/FieldDefinition.java +++ b/src/org/labkey/test/params/FieldDefinition.java @@ -123,7 +123,8 @@ else if (Character.isUpperCase(c) && Character.isLowerCase(chars[i - 1])) } } - return buf.toString(); + // Multiple spaces in the UI are collapsed into a single space + return buf.toString().replaceAll("\\s+", " "); } public String getEffectiveLabel() From 5186bcfc932415b784e9cb167f985a2e9bb342ac Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 18 Jul 2025 14:09:50 -0500 Subject: [PATCH 18/19] reset the min/max randomInt values for domain and field name generator (will increase with next PR) --- src/org/labkey/test/util/TestDataGenerator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index 6e81ddd597..c9c51d42ae 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -559,9 +559,10 @@ public static String randomDomainName(@Nullable String namePart, @Nullable Integ String _namePart = namePart == null ? "" : namePart; DomainUtils.DomainKind _domainKind = domainKind == null ? DomainUtils.DomainKind.SampleSet : domainKind; String charSet = ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; - String domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); + // TODO increase min to 5 and max to 50 + String domainName = randomName(_namePart, getNumChars(numStartChars, 0), getNumChars(numEndChars, 10), charSet, null); while (isDomainAndFieldNameInvalid(WebTestHelper.getRemoteApiConnection(false), _domainKind, domainName, null)) - domainName = randomName(_namePart, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), charSet, null); + domainName = randomName(_namePart, getNumChars(numStartChars, 0), getNumChars(numEndChars, 10), charSet, null); // 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+", " "); @@ -599,9 +600,10 @@ public static String randomFieldName(@NotNull String part, @Nullable Integer num String chars = ALL_ILLEGAL_QUERY_KEY_CHARACTERS + " %()=+-[]_|*`'\":;<>?!@#^" + NON_LATIN_STRING + WIDE_PLACEHOLDER + REPEAT_PLACEHOLDER + ALL_CHARS_PLACEHOLDER; - String randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); + // TODO increase max to 50 + String randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 5), chars, exclusion); while (isDomainAndFieldNameInvalid(WebTestHelper.getRemoteApiConnection(false), _domainKind, null, randomFieldName)) - randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 50), chars, exclusion); + randomFieldName = randomName(part, getNumChars(numStartChars, 5), getNumChars(numEndChars, 5), chars, exclusion); TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); return randomFieldName; From 7f24adc1d8dd97eae5d4c029a7704c8bf03e9f11 Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 18 Jul 2025 14:18:33 -0500 Subject: [PATCH 19/19] missed from last commit --- src/org/labkey/test/util/TestDataGenerator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index c9c51d42ae..263d9417fa 100644 --- a/src/org/labkey/test/util/TestDataGenerator.java +++ b/src/org/labkey/test/util/TestDataGenerator.java @@ -646,9 +646,11 @@ public static String randomChoice(List choices) public static int randomInt(int min, int max) { - if (min >= max) + if (min > max) throw new IllegalArgumentException("min must be less than max"); + if (min == max) + return min; return ThreadLocalRandom.current().nextInt((max - min) + 1) + min; }