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 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() 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/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/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/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/DomainFieldTypeChangeTest.java b/src/org/labkey/test/tests/DomainFieldTypeChangeTest.java index fa645f76a7..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 = 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, 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/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 ae784c8077..30600208a1 100644 --- a/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java +++ b/src/org/labkey/test/tests/SampleTypeFolderExportImportTest.java @@ -448,9 +448,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/tests/SampleTypeTest.java b/src/org/labkey/test/tests/SampleTypeTest.java index 0dbe2c380b..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 @@ -160,11 +161,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); @@ -215,9 +216,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); @@ -229,19 +230,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); } @@ -250,11 +254,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(), @@ -336,11 +340,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~") @@ -353,13 +359,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())) @@ -575,7 +581,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(); @@ -782,6 +788,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."); @@ -790,38 +797,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"); @@ -835,11 +842,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)); @@ -861,7 +869,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."); @@ -871,7 +879,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."); @@ -879,7 +887,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."); @@ -888,7 +896,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."); @@ -896,7 +904,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."); @@ -906,7 +914,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)); @@ -917,7 +925,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)); @@ -927,7 +935,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)); @@ -936,15 +944,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"; @@ -954,7 +962,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)); @@ -1021,7 +1029,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; @@ -1042,21 +1050,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()); } } @@ -1180,7 +1185,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. @@ -1233,7 +1238,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)); @@ -1273,7 +1278,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)); @@ -1378,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)); @@ -1396,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(); } diff --git a/src/org/labkey/test/tests/component/GridPanelTest.java b/src/org/labkey/test/tests/component/GridPanelTest.java index 4c4e64cab7..033b531f20 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/list/CrossFolderListTest.java b/src/org/labkey/test/tests/list/CrossFolderListTest.java index eb5191000b..dbb9d15e50 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; @@ -26,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; @@ -133,8 +133,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 +182,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 +206,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 +220,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,39 +287,41 @@ 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 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 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); // Create and configure list definitions @@ -364,11 +387,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, 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() + ); } @Override diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java index 056a1a0115..c4922812ab 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; @@ -31,15 +32,15 @@ @Category({Daily.class, Data.class, Hosting.class}) 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 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 lookFromKeyFieldName = TestDataGenerator.randomFieldName("Look From Key Field"); - private static final String lookFromLookupFieldName = TestDataGenerator.randomFieldName("Look From Lookup Field"); + 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); @BeforeClass diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 16feaf1b84..5270d9b27f 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)); @@ -541,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", DomainUtils.DomainKind.IntList); File bomFile = TestFileUtils.getSampleData("lists/TestUTF8_BOM.csv"); EditListDefinitionPage listEditPage = _listHelper.beginCreateList(getProjectName(), listName); @@ -990,7 +991,7 @@ public void testChangeListNameOverAPI() throws Exception public void testChangeListName() { - String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", 7); + String listNameBefore = TestDataGenerator.randomDomainName("Before Rename", DomainUtils.DomainKind.IntList); _listHelper.createList(PROJECT_VERIFY, listNameBefore, new FieldDefinition("name", ColumnType.String), @@ -2115,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) diff --git a/src/org/labkey/test/tests/query/QueryLookupTest.java b/src/org/labkey/test/tests/query/QueryLookupTest.java index 2bb9f73069..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,10 +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 = - 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, DomainUtils.DomainKind.VarList); + private static final FieldInfo TSHIRT_COLUMN = FieldInfo.random("TShirt", FieldDefinition.ColumnType.String, DomainUtils.DomainKind.VarList); @Override protected void doCleanup(boolean afterTest) @@ -64,16 +63,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 +85,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); diff --git a/src/org/labkey/test/util/DomainUtils.java b/src/org/labkey/test/util/DomainUtils.java index 55f324dc95..9cc20bce6b 100644 --- a/src/org/labkey/test/util/DomainUtils.java +++ b/src/org/labkey/test/util/DomainUtils.java @@ -94,4 +94,12 @@ public static void ensureDeleted(String containerPath, String schema, String tab } } + public enum DomainKind { + DataClass, + SampleSet, + IntList, + VarList, + StudyDatasetDate, + StudyDatasetVisit + } } 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 diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java index b4af9779a6..f9d16ae2af 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; @@ -71,8 +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 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"; @@ -474,7 +479,16 @@ 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, 50); // repeat between 2 and 50 times + val.append(StringUtils.repeat(c, repeatCount)); + } + else if (c == ALL_CHARS_PLACEHOLDER) + val.append(charSetFrom); + else if (c == WIDE_PLACEHOLDER) val.append(WIDE_CHAR); else val.append(c); @@ -496,7 +510,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 @@ -517,7 +531,7 @@ public static String randomDomainName() public static String randomDomainName(@Nullable String part) { - return randomDomainName(part, randomInt(0, 10)); + return randomDomainName(part, null); } public static String randomInvalidDomainName(@Nullable String namePart, int numStartChars, int numEndChars) @@ -527,37 +541,41 @@ public static String randomInvalidDomainName(@Nullable String namePart, int numS return domainName; } - public static String randomDomainName(int numEndChars) + public static String randomDomainName(@Nullable String namePart, @Nullable DomainUtils.DomainKind domainKind) { - return randomDomainName(null, numEndChars); + 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) + public static String randomDomainName(@Nullable String namePart, @Nullable Integer numStartChars, @Nullable Integer 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 = ALPHANUMERIC_STRING + DOMAIN_SPECIAL_STRING; + // 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, 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+", " "); - TestLogger.log("Generated random domain name: " + domainName); + TestLogger.log("Generated random domain name for domainKind " + _domainKind + ": " + domainName); 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); @@ -565,34 +583,60 @@ 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, int numStartChars, int numEndChars) + public static String randomFieldName(String part, @Nullable String exclusion, DomainUtils.DomainKind domainKind) { - 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) + 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; + // 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 + ALL_CHARS_PLACEHOLDER; + + // 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, 5), chars, exclusion); - String randomFieldName = randomName(part, numStartChars, numEndChars, chars, exclusion); + TestLogger.log("Generated random field name for domainKind " + _domainKind + ": " + randomFieldName); + return randomFieldName; + } - // 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) + 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(); + if (domainName != null) + { + domainDesign.put("name", domainName); + } + if (fieldName != null) { - String regenExclusion = Objects.requireNonNullElse(exclusion, "") + ":"; - randomFieldName = randomFieldName.substring(0, numStartChars - 1) + - randomString(1, regenExclusion, chars) + - randomFieldName.substring(numStartChars + 1); + 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); - TestLogger.log("Generated random field name: " + randomFieldName); - return randomFieldName; + 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) @@ -602,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; } diff --git a/src/org/labkey/test/util/exp/DataClassAPIHelper.java b/src/org/labkey/test/util/exp/DataClassAPIHelper.java index 53ab3aef05..8125d240d0 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, 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() + ); } /**