From 10f4ccb9584bd939ac7797270e65fafb9c48680a Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Thu, 8 May 2025 16:36:36 -0700 Subject: [PATCH 1/6] If we've coerced our data, let's first try primary key but still allow for alternate key (for trigger scripts?) --- src/org/labkey/test/tests/list/ListTest.java | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index c00b99fba4..0072883191 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -1812,6 +1812,66 @@ private void testTricky(String listName, boolean autoKey) throws IOException } + @Test // Issue 52098, Issue 49422 + public void testNumberLikeLookup() + { + String lookupListName = TestDataGenerator.randomDomainName("lookupList"); + String lookupKeyFieldName = TestDataGenerator.randomFieldName("lookupKeyField"); + String lookupFieldName = TestDataGenerator.randomFieldName("lookupField"); + log("Create a list to use as a lookup table with some number-like names."); + _listHelper.createList(PROJECT_VERIFY, lookupListName, lookupKeyFieldName, + new FieldDefinition(lookupFieldName, ColumnType.String)); + String bulkData = lookupFieldName + "\n" + + "1E2\n" + + "102\n" + + "Lookup\n" + + ".123"; + _listHelper.bulkImportData(bulkData); + DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); + CustomizeView customizer = dataRegionTable.openCustomizeGrid(); + customizer.showHiddenItems(); + customizer.addColumn(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)); + customizer.clickViewGrid(); + List> actualValues = dataRegionTable.getTableData(); + String keyNumber = actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)); + _listHelper.insertNewRow(Map.of(lookupFieldName, keyNumber)); + + log("Create a second list that looks up to the first list"); + String baseListName = TestDataGenerator.randomDomainName("base"); + String baseKeyFieldName = TestDataGenerator.randomFieldName("base Key Field"); + String baseLookupFieldName = TestDataGenerator.randomFieldName("base Lookup Field"); + _listHelper.createList(PROJECT_VERIFY, baseListName, baseKeyFieldName); + EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(baseListName); + listDefinitionPage.getFieldsPanel() + .addField(baseLookupFieldName) + .setType(ColumnType.Lookup) + .setLookup(new FieldDefinition.IntLookup("lists", lookupListName)); + listDefinitionPage.clickSave(); + + log("Import data into the second list using number-like lookup values."); + bulkData = baseLookupFieldName + "\n" + + "1E2\n" + + keyNumber +"\n" + + ".123\n" + + "Lookup\n" + + keyNumber + "\n" + + "102"; + _listHelper.clickImportData() + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submit(); + log("Verify the import succeeds and resolves the lookups appropriately."); + List> expectedData = List.of( + Map.of(baseLookupFieldName, "1E2"), + Map.of(baseLookupFieldName, keyNumber), + Map.of(baseLookupFieldName, ".123"), + Map.of(baseLookupFieldName, "Lookup"), + Map.of(baseLookupFieldName, keyNumber), + Map.of(baseLookupFieldName, "102") + ); + validateDataRegionTableForTricky(expectedData); + } + private void validateDataRegionTableForTricky(List> expectedValue) { DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); From 325b40e7b5e089be980578d006d6cb987196edf9 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Mon, 12 May 2025 06:57:36 -0700 Subject: [PATCH 2/6] Change type to TextChoice --- src/org/labkey/test/util/TestDataUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/labkey/test/util/TestDataUtils.java b/src/org/labkey/test/util/TestDataUtils.java index c6e9886e3e..64f0b6d232 100644 --- a/src/org/labkey/test/util/TestDataUtils.java +++ b/src/org/labkey/test/util/TestDataUtils.java @@ -81,7 +81,7 @@ public class TestDataUtils () -> new FieldDefinition("PSS Tracking No."), () -> new FieldDefinition("Product/bottle size", FieldDefinition.ColumnType.Decimal), () -> new FieldDefinition("Time point / Pull Date", FieldDefinition.ColumnType.DateAndTime), - () -> new FieldDefinition("Cell Type (Epz, Spz, PS)"), + () -> new FieldDefinition("Cell Type (Epz, Spz, PS)", FieldDefinition.ColumnType.TextChoice).setTextChoiceValues(List.of("Epz", "Spz", "PS")), () -> new FieldDefinition("Concentration (ng/uL)", FieldDefinition.ColumnType.Decimal), () -> new FieldDefinition("Lot no. (Replacement tube) 1"), () -> new FieldDefinition("Date of Collection (DD/MMM/YYY)", FieldDefinition.ColumnType.Date), From 144d766cae1a6d48922870c933c860a397f16f96 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Fri, 16 May 2025 10:37:46 -0700 Subject: [PATCH 3/6] Use fieldKey for expected data map --- src/org/labkey/test/tests/list/ListTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 0072883191..2b49f3ea31 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -1840,6 +1840,7 @@ public void testNumberLikeLookup() String baseListName = TestDataGenerator.randomDomainName("base"); String baseKeyFieldName = TestDataGenerator.randomFieldName("base Key Field"); String baseLookupFieldName = TestDataGenerator.randomFieldName("base Lookup Field"); + String baseLookupFieldKey = EscapeUtil.fieldKeyEncodePart(baseLookupFieldName); _listHelper.createList(PROJECT_VERIFY, baseListName, baseKeyFieldName); EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(baseListName); listDefinitionPage.getFieldsPanel() @@ -1862,12 +1863,12 @@ public void testNumberLikeLookup() .submit(); log("Verify the import succeeds and resolves the lookups appropriately."); List> expectedData = List.of( - Map.of(baseLookupFieldName, "1E2"), - Map.of(baseLookupFieldName, keyNumber), - Map.of(baseLookupFieldName, ".123"), - Map.of(baseLookupFieldName, "Lookup"), - Map.of(baseLookupFieldName, keyNumber), - Map.of(baseLookupFieldName, "102") + Map.of(baseLookupFieldKey, "1E2"), + Map.of(baseLookupFieldKey, keyNumber), + Map.of(baseLookupFieldKey, ".123"), + Map.of(baseLookupFieldKey, "Lookup"), + Map.of(baseLookupFieldKey, keyNumber), + Map.of(baseLookupFieldKey, "102") ); validateDataRegionTableForTricky(expectedData); } From f36494341193eebbb2388d5c1c321de19ad32b6c Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 20 May 2025 15:14:55 -0700 Subject: [PATCH 4/6] Add more scenarios for list data import --- src/org/labkey/test/tests/list/ListTest.java | 163 +++++++++++++++++-- 1 file changed, 153 insertions(+), 10 deletions(-) diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 2b49f3ea31..0cfa9e0145 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -1836,7 +1836,7 @@ public void testNumberLikeLookup() String keyNumber = actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)); _listHelper.insertNewRow(Map.of(lookupFieldName, keyNumber)); - log("Create a second list that looks up to the first list"); + log("Create a second list that looks up to the first list without the `Ensure value` checkbox selected."); String baseListName = TestDataGenerator.randomDomainName("base"); String baseKeyFieldName = TestDataGenerator.randomFieldName("base Key Field"); String baseLookupFieldName = TestDataGenerator.randomFieldName("base Lookup Field"); @@ -1849,28 +1849,171 @@ public void testNumberLikeLookup() .setLookup(new FieldDefinition.IntLookup("lists", lookupListName)); listDefinitionPage.clickSave(); - log("Import data into the second list using number-like lookup values."); + // without lookup validator + // without alternate keys + log("Import data into the second list without alternate keys."); + bulkData = baseLookupFieldName + "\n" + keyNumber; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); + List> expectedData = List.of( + Map.of(baseLookupFieldKey, actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) + ); + validateDataRegionTableForTricky(expectedData); + + log("Clean out list before next import."); + resetList(); + log("Import data into second list without alternate keys supplying invalid primary key"); + bulkData = baseLookupFieldName + "\n1000"; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds but invalid primary key is left unresolved."); + expectedData = List.of( + Map.of(baseLookupFieldKey, "<1000>") + ); + validateDataRegionTableForTricky(expectedData); + + log("Check for error if not using alternate key and type does not match"); + bulkData = baseLookupFieldName + "\nnoneSuch"; + ImportDataPage importDataPage = _listHelper.clickImportData(); + String error = importDataPage + .setText(bulkData) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key not as expected", "Could not convert value 'noneSuch' (String) for Integer field '" + baseLookupFieldName + "'", error); + + // with alternate keys + log("Clean out list before next import."); + importDataPage.cancel(); + resetList(); + log("Import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); bulkData = baseLookupFieldName + "\n" + - "1E2\n" + - keyNumber +"\n" + - ".123\n" + - "Lookup\n" + - keyNumber + "\n" + - "102"; + "1E2\n" + // valid alternate key looking like a number + keyNumber +"\n" + // valid alternate key same value as a primary key + ".123\n" + // valid alternate key looking like a float + "Lookup\n" + // valid alternate key that is a string + keyNumber + "\n" + // another copy + "102\n" + // valid number-like alternate key + actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)) + "\n" + // primary key value not matching an alternate key + "1000" // primary key-type value that doesn't match + ; _listHelper.clickImportData() .setText(bulkData) .setImportLookupByAlternateKey(true) .submit(); log("Verify the import succeeds and resolves the lookups appropriately."); - List> expectedData = List.of( + expectedData = List.of( + Map.of(baseLookupFieldKey, "1E2"), + Map.of(baseLookupFieldKey, keyNumber), + Map.of(baseLookupFieldKey, ".123"), + Map.of(baseLookupFieldKey, "Lookup"), + Map.of(baseLookupFieldKey, keyNumber), + Map.of(baseLookupFieldKey, "102"), + Map.of(baseLookupFieldKey, actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))), + Map.of(baseLookupFieldKey, "<1000>") + ); + validateDataRegionTableForTricky(expectedData); + + log("Check for error if providing non-matching string value that is not a number"); + bulkData = baseLookupFieldName + "\nNotAValue"; + importDataPage = _listHelper.clickImportData(); + error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message after supplying invalid alternate key not as expected", "Value 'NotAValue' not found for field " + baseLookupFieldName + " in the current context.", error); + + log("Clean out list before next import."); + importDataPage.cancel(); + resetList(); + + // without lookup validator + log("Add Lookup Validator to lookup field"); + listDefinitionPage = _listHelper.goToEditDesign(baseListName); + listDefinitionPage.getFieldsPanel() + .getField(baseLookupFieldName) + .expand() + .setLookupValidatorEnabled(true); + listDefinitionPage.clickSave(); + + // without alternate keys + log("With lookup validation on, import data into the second list without alternate keys."); + bulkData = baseLookupFieldName + "\n" + keyNumber; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); + expectedData = List.of( + Map.of(baseLookupFieldKey, actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) + ); + validateDataRegionTableForTricky(expectedData); + + log("With lookup validation on, import data and provide an invalid primary key."); + importDataPage = _listHelper.clickImportData(); + error = importDataPage + .setText(baseLookupFieldName + "\n1000") + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key value not as expected", "Value '1000' was not present in lookup target 'lists." + lookupListName + "' for field '" + baseLookupFieldName + "'", error); + + log("With lookup validation on, import data and provide an invalid primary key of type string."); + error = importDataPage + .setText(baseLookupFieldName + "\nLook") + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key type not as expected", "Could not convert value 'Look' (String) for Integer field '" + baseLookupFieldName + "'", error); + + // with alternate keys + log("Clean out list before next import."); + importDataPage.cancel(); + resetList(); + log("With lookup validation on, import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); + bulkData = baseLookupFieldName + "\n" + + "1E2\n" + // valid alternate key looking like a number + keyNumber +"\n" + // valid alternate key same value as a primary key + ".123\n" + // valid alternate key looking like a float + "Lookup\n" + // valid alternate key that is a string + keyNumber + "\n" + // another copy + "102\n" + // valid number-like alternate key + actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)) + "\n" // primary key value not matching an alternate key + ; + _listHelper.clickImportData() + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submit(); + log("Verify the import succeeds and resolves the lookups appropriately."); + expectedData = List.of( Map.of(baseLookupFieldKey, "1E2"), Map.of(baseLookupFieldKey, keyNumber), Map.of(baseLookupFieldKey, ".123"), Map.of(baseLookupFieldKey, "Lookup"), Map.of(baseLookupFieldKey, keyNumber), - Map.of(baseLookupFieldKey, "102") + Map.of(baseLookupFieldKey, "102"), + Map.of(baseLookupFieldKey, actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) ); validateDataRegionTableForTricky(expectedData); + + bulkData = baseLookupFieldName + "\nInvalid"; + importDataPage = _listHelper.clickImportData(); + error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid string alternate key not as expected", "Value 'Invalid' not found for field " + baseLookupFieldName + " in the current context.", error); + + bulkData = baseLookupFieldName + "\n1234"; + error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid number-like alternate key not as expected", "Value '1234' was not present in lookup target 'lists." + lookupListName + "' for field '" + baseLookupFieldName + "'", error); + + } + + private void resetList() + { + DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); + dataRegionTable.checkAllOnPage(); + dataRegionTable.deleteSelectedRows(); } private void validateDataRegionTableForTricky(List> expectedValue) From 140426e62a08d5ad898136fc29c6bc5ee5e6784a Mon Sep 17 00:00:00 2001 From: Matthew Bellew Date: Wed, 21 May 2025 14:39:52 -0700 Subject: [PATCH 5/6] Add AK to vehicle.Manufacturers don't show required validation error after conversion error for the same field --- modules/simpletest/module.properties | 2 +- .../schemas/dbscripts/postgresql/vehicle-25.000-25.001.sql | 1 + .../schemas/dbscripts/sqlserver/vehicle-25.000-25.001.sql | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 modules/simpletest/resources/schemas/dbscripts/postgresql/vehicle-25.000-25.001.sql create mode 100644 modules/simpletest/resources/schemas/dbscripts/sqlserver/vehicle-25.000-25.001.sql diff --git a/modules/simpletest/module.properties b/modules/simpletest/module.properties index f565005b39..1cbcc2392f 100644 --- a/modules/simpletest/module.properties +++ b/modules/simpletest/module.properties @@ -1,4 +1,4 @@ Name: simpletest -SchemaVersion: 25.000 +SchemaVersion: 25.001 SupportedDatabases: mssql, pgsql ManageVersion: true diff --git a/modules/simpletest/resources/schemas/dbscripts/postgresql/vehicle-25.000-25.001.sql b/modules/simpletest/resources/schemas/dbscripts/postgresql/vehicle-25.000-25.001.sql new file mode 100644 index 0000000000..44593bbb3d --- /dev/null +++ b/modules/simpletest/resources/schemas/dbscripts/postgresql/vehicle-25.000-25.001.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX AK_Name ON vehicle.Manufacturers (Name); \ No newline at end of file diff --git a/modules/simpletest/resources/schemas/dbscripts/sqlserver/vehicle-25.000-25.001.sql b/modules/simpletest/resources/schemas/dbscripts/sqlserver/vehicle-25.000-25.001.sql new file mode 100644 index 0000000000..44593bbb3d --- /dev/null +++ b/modules/simpletest/resources/schemas/dbscripts/sqlserver/vehicle-25.000-25.001.sql @@ -0,0 +1 @@ +CREATE UNIQUE INDEX AK_Name ON vehicle.Manufacturers (Name); \ No newline at end of file From 08d76c08efa3c9772d59139d4bffd51c5f43ccc1 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Thu, 22 May 2025 08:38:11 -0700 Subject: [PATCH 6/6] Extract test into its own class --- .../test/tests/list/ListLookupTest.java | 288 ++++++++++++++++++ src/org/labkey/test/tests/list/ListTest.java | 204 ------------- 2 files changed, 288 insertions(+), 204 deletions(-) create mode 100644 src/org/labkey/test/tests/list/ListLookupTest.java diff --git a/src/org/labkey/test/tests/list/ListLookupTest.java b/src/org/labkey/test/tests/list/ListLookupTest.java new file mode 100644 index 0000000000..f2b982b948 --- /dev/null +++ b/src/org/labkey/test/tests/list/ListLookupTest.java @@ -0,0 +1,288 @@ +package org.labkey.test.tests.list; + +import org.jetbrains.annotations.Nullable; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.CommandException; +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.categories.Daily; +import org.labkey.test.categories.Data; +import org.labkey.test.categories.Hosting; +import org.labkey.test.components.CustomizeView; +import org.labkey.test.pages.ImportDataPage; +import org.labkey.test.pages.list.EditListDefinitionPage; +import org.labkey.test.params.FieldDefinition; +import org.labkey.test.util.DataRegionTable; +import org.labkey.test.util.EscapeUtil; +import org.labkey.test.util.TestDataGenerator; +import org.labkey.test.util.query.QueryApiHelper; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +// Issue 52098, Issue 49422 +@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 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 lookFromLookupFieldKey = EscapeUtil.fieldKeyEncodePart(lookFromLookupFieldName); + + @BeforeClass + public static void setupProject() + { + ListLookupTest init = getCurrentTest(); + init.doSetup(); + } + + private void doSetup() + { + log("Setup project and list module"); + _containerHelper.createProject(getProjectName(), null); + + log("Create a list to use as a lookup table with some number-like names."); + _listHelper.createList(getProjectName(), lookToListName, lookToKeyFieldName, + new FieldDefinition(lookToFieldName, FieldDefinition.ColumnType.String)); + String bulkData = lookToFieldName + "\n" + + "1E2\n" + + "102\n" + + "Lookup\n" + + ".123"; + _listHelper.bulkImportData(bulkData); + + DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); + CustomizeView customizer = dataRegionTable.openCustomizeGrid(); + customizer.showHiddenItems(); + customizer.addColumn(EscapeUtil.fieldKeyEncodePart(lookToKeyFieldName)); + customizer.clickViewGrid(); + lookToListValues = dataRegionTable.getTableData(); + lookupKeyAsNameNumber = lookToListValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookToKeyFieldName)); + lookupKeyAsNameFieldValue = lookToListValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookToFieldName)); + _listHelper.insertNewRow(Map.of(lookToFieldName, lookupKeyAsNameNumber)); + + log("Create a second list that looks up to the first list."); + _listHelper.createList(getProjectName(), lookFromListName, lookFromKeyFieldName); + EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(lookFromListName); + listDefinitionPage.getFieldsPanel() + .addField(lookFromLookupFieldName) + .setLookup(new FieldDefinition.IntLookup("lists", lookToListName)); + listDefinitionPage.clickSave(); + } + + + @Test + public void testWithoutValidatorOrAlternateKeys() throws IOException, CommandException + { + setLookupValidatorEnabled(false); + resetList(); + + log("Import data into the second list without alternate keys."); + String bulkData = lookFromLookupFieldName + "\n" + lookupKeyAsNameNumber; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); + List> expectedData = List.of( + Map.of(lookFromLookupFieldKey, lookupKeyAsNameFieldValue) + ); + validateListValues(expectedData); + + log("Clean out list before next import."); + resetList(); + log("Import data into second list without alternate keys supplying invalid primary key"); + bulkData = lookFromLookupFieldName + "\n1000"; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds but invalid primary key is left unresolved."); + expectedData = List.of( + Map.of(lookFromLookupFieldKey, "<1000>") + ); + validateListValues(expectedData); + + log("Check for error if not using alternate key and type does not match"); + bulkData = lookFromLookupFieldName + "\nnoneSuch"; + ImportDataPage importDataPage = _listHelper.clickImportData(); + String error = importDataPage + .setText(bulkData) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key not as expected", "Could not convert value 'noneSuch' (String) for Integer field '" + lookFromLookupFieldName + "'", error); + } + + @Test + public void testWithoutValidatorWithAlternateKeys() throws IOException, CommandException + { + setLookupValidatorEnabled(false); + log("Clean out list before next import."); + resetList(); + log("Import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); + String bulkData = lookFromLookupFieldName + "\n" + + "1E2\n" + // valid alternate key looking like a number + lookupKeyAsNameNumber + "\n" + // valid alternate key same value as a primary key + ".123\n" + // valid alternate key looking like a float + "Lookup\n" + // valid alternate key that is a string + lookupKeyAsNameNumber + "\n" + // another copy + "102\n" + // valid number-like alternate key + lookToListValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookToKeyFieldName)) + "\n" + // primary key value not matching an alternate key + "1000" // primary key-type value that doesn't match + ; + _listHelper.clickImportData() + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submit(); + log("Verify the import succeeds and resolves the lookups appropriately."); + List> expectedData = List.of( + Map.of(lookFromLookupFieldKey, "1E2"), + Map.of(lookFromLookupFieldKey, lookupKeyAsNameNumber), + Map.of(lookFromLookupFieldKey, ".123"), + Map.of(lookFromLookupFieldKey, "Lookup"), + Map.of(lookFromLookupFieldKey, lookupKeyAsNameNumber), + Map.of(lookFromLookupFieldKey, "102"), + Map.of(lookFromLookupFieldKey, lookToListValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookToFieldName))), + Map.of(lookFromLookupFieldKey, "<1000>") + ); + validateListValues(expectedData); + + log("Check for error if providing non-matching string value that is not a number"); + bulkData = lookFromLookupFieldName + "\nNotAValue"; + ImportDataPage importDataPage = _listHelper.clickImportData(); + String error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message after supplying invalid alternate key not as expected", "Value 'NotAValue' not found for field " + lookFromLookupFieldName + " in the current context.", error); + } + + @Test + public void testWithLookupValidatorWithoutAlternateKeys() throws IOException, CommandException + { + setLookupValidatorEnabled(true); + log("Clean out list before next import."); + resetList(); + + // without alternate keys + log("With lookup validation on, import data into the second list without alternate keys."); + String bulkData = lookFromLookupFieldName + "\n" + lookupKeyAsNameNumber; + _listHelper.clickImportData() + .setText(bulkData) + .submit(); + log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); + List> expectedData = List.of( + Map.of(lookFromLookupFieldKey, lookupKeyAsNameFieldValue) + ); + validateListValues(expectedData); + + log("With lookup validation on, import data and provide an invalid primary key."); + ImportDataPage importDataPage = _listHelper.clickImportData(); + String error = importDataPage + .setText(lookFromLookupFieldName + "\n1000") + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key value not as expected", "Value '1000' was not present in lookup target 'lists." + lookToListName + "' for field '" + lookFromLookupFieldName + "'", error); + + log("With lookup validation on, import data and provide an invalid primary key of type string."); + error = importDataPage + .setText(lookFromLookupFieldName + "\nLook") + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid primary key type not as expected", "Could not convert value 'Look' (String) for Integer field '" + lookFromLookupFieldName + "'", error); + } + + @Test + public void testWithLookupValidatorAndAlternateKeys() throws IOException, CommandException + { + setLookupValidatorEnabled(true); + log("Clean out list before next import."); + resetList(); + + log("With lookup validation on, import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); + String bulkData = lookFromLookupFieldName + "\n" + + "1E2\n" + // valid alternate key looking like a number + lookupKeyAsNameNumber + "\n" + // valid alternate key same value as a primary key + ".123\n" + // valid alternate key looking like a float + "Lookup\n" + // valid alternate key that is a string + lookupKeyAsNameNumber + "\n" + // another copy + "102\n" + // valid number-like alternate key + lookToListValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookToKeyFieldName)) + "\n" // primary key value not matching an alternate key + ; + _listHelper.clickImportData() + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submit(); + log("Verify the import succeeds and resolves the lookups appropriately."); + List> expectedData = List.of( + Map.of(lookFromLookupFieldKey, "1E2"), + Map.of(lookFromLookupFieldKey, lookupKeyAsNameNumber), + Map.of(lookFromLookupFieldKey, ".123"), + Map.of(lookFromLookupFieldKey, "Lookup"), + Map.of(lookFromLookupFieldKey, lookupKeyAsNameNumber), + Map.of(lookFromLookupFieldKey, "102"), + Map.of(lookFromLookupFieldKey, lookToListValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookToFieldName))) + ); + validateListValues(expectedData); + + bulkData = lookFromLookupFieldName + "\nInvalid"; + ImportDataPage importDataPage = _listHelper.clickImportData(); + String error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid string alternate key not as expected", "Value 'Invalid' not found for field " + lookFromLookupFieldName + " in the current context.", error); + + bulkData = lookFromLookupFieldName + "\n1234"; + error = importDataPage + .setText(bulkData) + .setImportLookupByAlternateKey(true) + .submitExpectingError(); + checker().withScreenshot().verifyEquals("Error message for invalid number-like alternate key not as expected", "Value '1234' was not present in lookup target 'lists." + lookToListName + "' for field '" + lookFromLookupFieldName + "'", error); + + } + + private void setLookupValidatorEnabled(boolean enabled) + { + log("Setting lookup validator to " + enabled + " on list " + lookFromListName); + EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(lookFromListName); + listDefinitionPage.getFieldsPanel() + .getField(lookFromLookupFieldName) + .expand() + .setLookupValidatorEnabled(enabled); + listDefinitionPage.clickSave(); + } + + private void resetList() throws IOException, CommandException + { + new QueryApiHelper(createDefaultConnection(), getProjectName(), "lists", lookFromListName).truncateTable(); + } + + private void validateListValues(List> expectedValue) + { + DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); + List> actualValue = dataRegionTable.getTableData(); + + assertEquals("List data not as expected after action.", + expectedValue, actualValue); + } + + @Override + protected @Nullable String getProjectName() + { + return "List Lookup Test"; + } + + @Override + public List getAssociatedModules() + { + return Arrays.asList("list"); + } + +} diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java index 0cfa9e0145..c00b99fba4 100644 --- a/src/org/labkey/test/tests/list/ListTest.java +++ b/src/org/labkey/test/tests/list/ListTest.java @@ -1812,210 +1812,6 @@ private void testTricky(String listName, boolean autoKey) throws IOException } - @Test // Issue 52098, Issue 49422 - public void testNumberLikeLookup() - { - String lookupListName = TestDataGenerator.randomDomainName("lookupList"); - String lookupKeyFieldName = TestDataGenerator.randomFieldName("lookupKeyField"); - String lookupFieldName = TestDataGenerator.randomFieldName("lookupField"); - log("Create a list to use as a lookup table with some number-like names."); - _listHelper.createList(PROJECT_VERIFY, lookupListName, lookupKeyFieldName, - new FieldDefinition(lookupFieldName, ColumnType.String)); - String bulkData = lookupFieldName + "\n" + - "1E2\n" + - "102\n" + - "Lookup\n" + - ".123"; - _listHelper.bulkImportData(bulkData); - DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); - CustomizeView customizer = dataRegionTable.openCustomizeGrid(); - customizer.showHiddenItems(); - customizer.addColumn(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)); - customizer.clickViewGrid(); - List> actualValues = dataRegionTable.getTableData(); - String keyNumber = actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)); - _listHelper.insertNewRow(Map.of(lookupFieldName, keyNumber)); - - log("Create a second list that looks up to the first list without the `Ensure value` checkbox selected."); - String baseListName = TestDataGenerator.randomDomainName("base"); - String baseKeyFieldName = TestDataGenerator.randomFieldName("base Key Field"); - String baseLookupFieldName = TestDataGenerator.randomFieldName("base Lookup Field"); - String baseLookupFieldKey = EscapeUtil.fieldKeyEncodePart(baseLookupFieldName); - _listHelper.createList(PROJECT_VERIFY, baseListName, baseKeyFieldName); - EditListDefinitionPage listDefinitionPage = _listHelper.goToEditDesign(baseListName); - listDefinitionPage.getFieldsPanel() - .addField(baseLookupFieldName) - .setType(ColumnType.Lookup) - .setLookup(new FieldDefinition.IntLookup("lists", lookupListName)); - listDefinitionPage.clickSave(); - - // without lookup validator - // without alternate keys - log("Import data into the second list without alternate keys."); - bulkData = baseLookupFieldName + "\n" + keyNumber; - _listHelper.clickImportData() - .setText(bulkData) - .submit(); - log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); - List> expectedData = List.of( - Map.of(baseLookupFieldKey, actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) - ); - validateDataRegionTableForTricky(expectedData); - - log("Clean out list before next import."); - resetList(); - log("Import data into second list without alternate keys supplying invalid primary key"); - bulkData = baseLookupFieldName + "\n1000"; - _listHelper.clickImportData() - .setText(bulkData) - .submit(); - log("Verify the import succeeds but invalid primary key is left unresolved."); - expectedData = List.of( - Map.of(baseLookupFieldKey, "<1000>") - ); - validateDataRegionTableForTricky(expectedData); - - log("Check for error if not using alternate key and type does not match"); - bulkData = baseLookupFieldName + "\nnoneSuch"; - ImportDataPage importDataPage = _listHelper.clickImportData(); - String error = importDataPage - .setText(bulkData) - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key not as expected", "Could not convert value 'noneSuch' (String) for Integer field '" + baseLookupFieldName + "'", error); - - // with alternate keys - log("Clean out list before next import."); - importDataPage.cancel(); - resetList(); - log("Import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); - bulkData = baseLookupFieldName + "\n" + - "1E2\n" + // valid alternate key looking like a number - keyNumber +"\n" + // valid alternate key same value as a primary key - ".123\n" + // valid alternate key looking like a float - "Lookup\n" + // valid alternate key that is a string - keyNumber + "\n" + // another copy - "102\n" + // valid number-like alternate key - actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)) + "\n" + // primary key value not matching an alternate key - "1000" // primary key-type value that doesn't match - ; - _listHelper.clickImportData() - .setText(bulkData) - .setImportLookupByAlternateKey(true) - .submit(); - log("Verify the import succeeds and resolves the lookups appropriately."); - expectedData = List.of( - Map.of(baseLookupFieldKey, "1E2"), - Map.of(baseLookupFieldKey, keyNumber), - Map.of(baseLookupFieldKey, ".123"), - Map.of(baseLookupFieldKey, "Lookup"), - Map.of(baseLookupFieldKey, keyNumber), - Map.of(baseLookupFieldKey, "102"), - Map.of(baseLookupFieldKey, actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))), - Map.of(baseLookupFieldKey, "<1000>") - ); - validateDataRegionTableForTricky(expectedData); - - log("Check for error if providing non-matching string value that is not a number"); - bulkData = baseLookupFieldName + "\nNotAValue"; - importDataPage = _listHelper.clickImportData(); - error = importDataPage - .setText(bulkData) - .setImportLookupByAlternateKey(true) - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message after supplying invalid alternate key not as expected", "Value 'NotAValue' not found for field " + baseLookupFieldName + " in the current context.", error); - - log("Clean out list before next import."); - importDataPage.cancel(); - resetList(); - - // without lookup validator - log("Add Lookup Validator to lookup field"); - listDefinitionPage = _listHelper.goToEditDesign(baseListName); - listDefinitionPage.getFieldsPanel() - .getField(baseLookupFieldName) - .expand() - .setLookupValidatorEnabled(true); - listDefinitionPage.clickSave(); - - // without alternate keys - log("With lookup validation on, import data into the second list without alternate keys."); - bulkData = baseLookupFieldName + "\n" + keyNumber; - _listHelper.clickImportData() - .setText(bulkData) - .submit(); - log("Verify the import succeeds and resolves by primary key when not expecting alternate keys."); - expectedData = List.of( - Map.of(baseLookupFieldKey, actualValues.get(0).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) - ); - validateDataRegionTableForTricky(expectedData); - - log("With lookup validation on, import data and provide an invalid primary key."); - importDataPage = _listHelper.clickImportData(); - error = importDataPage - .setText(baseLookupFieldName + "\n1000") - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key value not as expected", "Value '1000' was not present in lookup target 'lists." + lookupListName + "' for field '" + baseLookupFieldName + "'", error); - - log("With lookup validation on, import data and provide an invalid primary key of type string."); - error = importDataPage - .setText(baseLookupFieldName + "\nLook") - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid primary key type not as expected", "Could not convert value 'Look' (String) for Integer field '" + baseLookupFieldName + "'", error); - - // with alternate keys - log("Clean out list before next import."); - importDataPage.cancel(); - resetList(); - log("With lookup validation on, import data into the second list using number-like lookup values expecting alternate keys but also accepting primary keys."); - bulkData = baseLookupFieldName + "\n" + - "1E2\n" + // valid alternate key looking like a number - keyNumber +"\n" + // valid alternate key same value as a primary key - ".123\n" + // valid alternate key looking like a float - "Lookup\n" + // valid alternate key that is a string - keyNumber + "\n" + // another copy - "102\n" + // valid number-like alternate key - actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupKeyFieldName)) + "\n" // primary key value not matching an alternate key - ; - _listHelper.clickImportData() - .setText(bulkData) - .setImportLookupByAlternateKey(true) - .submit(); - log("Verify the import succeeds and resolves the lookups appropriately."); - expectedData = List.of( - Map.of(baseLookupFieldKey, "1E2"), - Map.of(baseLookupFieldKey, keyNumber), - Map.of(baseLookupFieldKey, ".123"), - Map.of(baseLookupFieldKey, "Lookup"), - Map.of(baseLookupFieldKey, keyNumber), - Map.of(baseLookupFieldKey, "102"), - Map.of(baseLookupFieldKey, actualValues.get(1).get(EscapeUtil.fieldKeyEncodePart(lookupFieldName))) - ); - validateDataRegionTableForTricky(expectedData); - - bulkData = baseLookupFieldName + "\nInvalid"; - importDataPage = _listHelper.clickImportData(); - error = importDataPage - .setText(bulkData) - .setImportLookupByAlternateKey(true) - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid string alternate key not as expected", "Value 'Invalid' not found for field " + baseLookupFieldName + " in the current context.", error); - - bulkData = baseLookupFieldName + "\n1234"; - error = importDataPage - .setText(bulkData) - .setImportLookupByAlternateKey(true) - .submitExpectingError(); - checker().withScreenshot().verifyEquals("Error message for invalid number-like alternate key not as expected", "Value '1234' was not present in lookup target 'lists." + lookupListName + "' for field '" + baseLookupFieldName + "'", error); - - } - - private void resetList() - { - DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver()); - dataRegionTable.checkAllOnPage(); - dataRegionTable.deleteSelectedRows(); - } - private void validateDataRegionTableForTricky(List> expectedValue) { DataRegionTable dataRegionTable = new DataRegionTable("query", getDriver());