Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
252df22
Add domain name fuzzing to many tests
labkey-tchad Aug 12, 2025
7aa58af
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 13, 2025
17b993e
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 13, 2025
27fd866
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 14, 2025
40d5d44
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 15, 2025
8e8b93b
Fuzzing fixes
labkey-tchad Aug 15, 2025
5dbd168
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 16, 2025
740149e
More fuzzing fixes
labkey-tchad Aug 16, 2025
1ff3afd
Merge origin/develop into fb_moreDomainNameFuzzing
labkey-tchad Aug 18, 2025
b85fffc
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 18, 2025
b4867b0
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 18, 2025
6286411
Fix URL encoding of pipeline job notifications
labkey-tchad Aug 19, 2025
57991b1
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 19, 2025
b36375e
More test fixes
labkey-tchad Aug 19, 2025
28b5c62
Fix build
labkey-tchad Aug 20, 2025
3b6dd5a
Minor fix
labkey-tchad Aug 20, 2025
debaf2a
Pager fix
labkey-tchad Aug 20, 2025
7b87a42
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 20, 2025
a355ea7
Better fix for encoding
labkey-tchad Aug 20, 2025
7e4e8c6
Remove selenium.By usages
labkey-tchad Aug 20, 2025
805f62e
Adjust wait
labkey-tchad Aug 20, 2025
430b04c
Comment
labkey-tchad Aug 20, 2025
f037db3
Fix EOF
labkey-tchad Aug 20, 2025
df7b873
Fix comments
labkey-tchad Aug 20, 2025
967bcc4
Merge branch 'develop' into fb_moreDomainNameFuzzing
labkey-danield Aug 25, 2025
59fe975
Merge remote-tracking branch 'origin/develop' into fb_moreDomainNameF…
labkey-tchad Aug 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/org/labkey/test/AppLocators.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.labkey.test;

public abstract class AppLocators
{
private AppLocators() {}

public static final Locator.XPathLocator detailHeaderName = Locator.tagWithClass("h2", "detail__header--name");
}
2 changes: 2 additions & 0 deletions src/org/labkey/test/Locators.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

public abstract class Locators
{
private Locators() { }

public static final Locator documentRoot = Locator.css(":root");
public static final Locator.IdLocator folderMenu = Locator.id("folderBar");
public static final Locator.XPathLocator labkeyError = Locator.byClass("labkey-error");
Expand Down
6 changes: 6 additions & 0 deletions src/org/labkey/test/WebDriverWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,12 @@ public String call()
}
}

@Contract(pure = true)
public WebDriverWait quickWait()
{
return new WebDriverWait(getDriver(), Duration.ofSeconds(1));
}

@Contract(pure = true)
public WebDriverWait shortWait()
{
Expand Down
3 changes: 1 addition & 2 deletions src/org/labkey/test/components/ui/Pager.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ private int pageSize(String pageSize) // only works on GridPanel
}
else
{
// Tooltip sometimes blocks button. Click active option to dismiss menu.
activeLi.click();
elementCache().jumpToDropdown.collapse();
}

return initialSize;
Expand Down
27 changes: 27 additions & 0 deletions src/org/labkey/test/components/ui/grids/EditableGrid.java
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,33 @@ public List<String> getFilteredDropdownListForCell(int row, CharSequence columnI
return lookupSelect.getOptions();
}

/**
* Values will be quoted appropriately for pasting into editable grid lookups.
*/
public static String getPastableColumn(List<?> values)
{
List<String> valueList = new ArrayList<>();
for (Object value : values)
{
String strVal = CSVFormat.DEFAULT.format(value); // Just quote commas
valueList.add(strVal);
}
return String.join("\n", valueList);
}

/**
* Pastes text to a single column of the grid.
* @param columnIdentifier fieldKey, name, or label of column
* @param pasteValues list of values to paste
* @return A Reference to this editableGrid object.
*/
public EditableGrid pasteColumn(CharSequence columnIdentifier, List<?> pasteValues)
{
if (pasteValues.isEmpty())
throw new IllegalArgumentException("No paste values provided");
return pasteFromCell(0, columnIdentifier, getPastableColumn(pasteValues), false);
}

/**
* Pastes delimited text to the grid, via a single target. The component is clever enough to target
* text into cells based on text delimiters; thus we can paste a square of data into the grid.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.labkey.test.components.ui.grids;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.labkey.test.params.FieldKey;
Expand Down Expand Up @@ -132,7 +133,11 @@ else if (_fieldLabels.size() < _fieldReferences.size())
}
}

return null;
String capitalized = StringUtils.capitalize(label);
if (capitalized.equals(label))
return null;
else
return findColumnHeaderByLabel(capitalized); // Handle domain names that aren't capitalized
}

public static class FieldReference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.labkey.test.util.TestLogger;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;

/**
* <p>
Expand Down Expand Up @@ -43,6 +44,12 @@ public WebElement getComponentElement()
return componentElement;
}

@Override
protected void waitForReady()
{
getWrapper().quickWait().until(ExpectedConditions.visibilityOf(getComponentElement()));
}

/**
* Get the status as indicated by the icon.
*
Expand Down Expand Up @@ -73,7 +80,7 @@ else if (status.contains("is-complete"))
*/
public String getMessage()
{
if(elementCache().message.isDisplayed())
if (elementCache().message.isDisplayed())
{
return elementCache().message.getText();
}
Expand Down
10 changes: 9 additions & 1 deletion src/org/labkey/test/selenium/ReclickingWebElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.labkey.test.selenium;

import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -49,7 +50,7 @@
public class ReclickingWebElement extends WebElementDecorator
{
// Extract the element info from ElementClickInterceptedException message.
private static final Pattern interceptingElPattern = Pattern.compile("Element .* is not clickable .*<(?<tag>[a-zA-Z]+) (?<attributes>.+)> obscures it");
private static final Pattern interceptingElPattern = Pattern.compile("Element .* is not clickable .*<(?<tag>[a-zA-Z0-9]+) (?<attributes>.+)> obscures it");
private static final Pattern elAttributePattern = Pattern.compile("(?<name>[a-zA-Z-]+)=\"(?<value>[^\"]+)\"");

public ReclickingWebElement(@NotNull WebElement decoratedElement)
Expand Down Expand Up @@ -216,6 +217,13 @@ private void revealElement(WebElement el, String shortMessage)
{
List<WebElement> interceptingElements = interceptingElLoc.findElements(getDriver());
TestLogger.debug("Found %s element(s) matching extracted locator: %s".formatted(interceptingElements.size(), shortMessage));

if (Strings.CI.containsAny(interceptingElLoc.toString(), "popover", "ws-pre-wrap", "tip"))
{
// Move mouse to corner to dismiss tooltips
new Actions(getDriver()).moveToLocation(0,0).perform();
}

if (interceptingElements.size() == 1)
{
//noinspection ResultOfMethodCallIgnored
Expand Down
34 changes: 25 additions & 9 deletions src/org/labkey/test/tests/TextChoiceSampleTypeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,29 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm
updatePage.clickSave();

log("Validate that the expected rows after the update are in the log.");
String fieldOldValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" +
"&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" +
"&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20%C3%85%5C%7C%C3%85%7CBB%7CCC%7CDD%7CE%20E%20E%7C%C2%83%C2%83%7CGG%7CH%2C%20Text%20Choice%20Validator";
String fieldUpdateValues = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false&Dimension=false" +
"&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false&Scannable=false" +
"&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator";
String fieldSharedValues = AuditLogHelper.encodeValues(
"Name", textChoiceFieldName,
"Type", "String",
"Scale", "4000",
"PHI", "Not PHI",
"DefaultScale", "Linear",
"Required", "false",
"Hidden", "false",
"MvEnabled", "false",
"Measure", "false",
"Dimension", "false",
"ShownInInsert", "true",
"ShownInDetails", "true",
"ShownInUpdate", "true",
"ShownInLookupView", "false",
"RecommendedVariable", "false",
"ExcludedFromShifting", "false",
"Scannable", "false",
"DefaultValueType", "Editable default");
String fieldOldValues = fieldSharedValues + "&" + AuditLogHelper.encodeValues(
"Validator", "Text Choice Validator, \u00C5\\|\u00C5|BB|CC|DD|E E E|\u0083\u0083|GG|H, Text Choice Validator");
String fieldUpdateValues = fieldSharedValues + "&" + AuditLogHelper.encodeValues(
"Validator", "Text Choice Validator, BB|CC and here is an update|E E E|GG|H|\u0083\u0083 updated|\u00C5\\|\u00C5, Text Choice Validator");
AuditLogHelper.DetailedAuditEventRow fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified",
"The following property was updated: Validator", "", fieldOldValues, fieldUpdateValues, null);
AuditLogHelper.DetailedAuditEventRow expectedDomainEvent = new AuditLogHelper.DetailedAuditEventRow(null, sampleTypeName, null,
Expand Down Expand Up @@ -548,9 +565,8 @@ public void testUpdatingAndDeletingValuesInSampleType() throws IOException, Comm
updatePage.clickSave();

log("Validate that the expected rows after the update are in the log.");
String fieldUpdateValues2 = "Name=TextChoice_Field_1&Type=String&Scale=4000&PHI=Not%20PHI&DefaultScale=Linear&Required=false&Hidden=false&MvEnabled=false&Measure=false" +
"&Dimension=false&ShownInInsert=true&ShownInDetails=true&ShownInUpdate=true&ShownInLookupView=false&RecommendedVariable=false&ExcludedFromShifting=false" +
"&Scannable=false&DefaultValueType=Editable%20default&Validator=Text%20Choice%20Validator%2C%20BB%7CCC%20and%20here%20is%20an%20update%7CE%20E%20E%7CGG%7CH%20no%20change%7C%C2%83%C2%83%20updated%7C%C3%85%5C%7C%C3%85%2C%20Text%20Choice%20Validator";
String fieldUpdateValues2 = fieldSharedValues + "&" + AuditLogHelper.encodeValues(
"Validator", "Text Choice Validator, BB|CC and here is an update|E E E|GG|H no change|\u0083\u0083 updated|\u00C5\\|\u00C5, Text Choice Validator");
fieldEvent = new AuditLogHelper.DetailedAuditEventRow(null, textChoiceFieldName, "Modified",
"The following property was updated: Validator", "", fieldUpdateValues, fieldUpdateValues2, null);
pass = _auditLogHelper.validateLastDomainAuditEvents(sampleTypeName, getProjectName(), expectedDomainEvent, Map.of(textChoiceFieldName, fieldEvent));
Expand Down
40 changes: 40 additions & 0 deletions src/org/labkey/test/util/AuditLogHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,46 @@ public String getLogString()
}
}

/**
* URL-encode fields and values for {@link DetailedAuditEventRow#newValues} or {@link DetailedAuditEventRow#oldValues}
* @param pairs alternating field names and their associated values
* @return URL-encoded String for use in DetailedAuditEventRow
*/
public static String encodeValues(String... pairs)
{
if (pairs.length % 2 != 0)
{
throw new IllegalArgumentException("pairs length must be even");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pairs.length; i = i + 2)
{
if (!sb.isEmpty())
sb.append("&");
sb.append(encodeValue(pairs[i])).append("=").append(encodeValue(pairs[i + 1]));
}
return sb.toString();
}

/**
* Perform selective URL encoding for use {@link DetailedAuditEventRow#newValues} or
* {@link DetailedAuditEventRow#oldValues}
* @param value raw key or value
* @return Partially URL-encoded value
*/
private static String encodeValue(String value)
{
return EscapeUtil.encode(value)
// Parentheses aren't encoded
.replace("%28", "(")
.replace("%29", ")");
}

public static String formatDataChange(String name, String oldValue, String newValue)
{
return name + ": " + oldValue + " > " + newValue;
}

public @NotNull Map<String, DetailedAuditEventRow> getDomainPropertyEvents(String domainName, Integer domainEventId)
{
if (domainEventId == null)
Expand Down
13 changes: 10 additions & 3 deletions src/org/labkey/test/util/DomainUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,18 @@ public static void ensureDeleted(String containerPath, String schema, String tab
}

public enum DomainKind {
DataClass,
SampleSet,
Assay,
DataClass, // aka "Sources"
SampleSet, // aka "Sample Type"
IntList,
VarList,
StudyDatasetDate,
StudyDatasetVisit
StudyDatasetVisit,
;

public String randomName(String namePart)
{
return TestDataGenerator.randomDomainName(namePart, this);
}
}
}
31 changes: 29 additions & 2 deletions src/org/labkey/test/util/ExcelHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.labkey.test.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.format.CellGeneralFormatter;
import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.usermodel.Cell;
Expand All @@ -28,13 +29,15 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.WorkbookUtil;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -266,7 +269,7 @@ public static Map<String, List<Map<String, String>>> loadData(File file)
{
try (Workbook workbook = ExcelHelper.create(file))
{
Map<String, List<Map<String, String>>> allData = new LinkedHashMap<>();
Map<String, List<Map<String, String>>> allData = new WorsheetMap();

for (int s = 0; s < workbook.getNumberOfSheets(); s++)
{
Expand All @@ -291,11 +294,35 @@ public static Map<String, List<Map<String, String>>> loadData(File file)
allData.put(sheet.getSheetName(), rowMaps);
}

return allData;
return Collections.unmodifiableMap(allData);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}

/**
* Truncate and make safe a proposed Excel sheet name
* @see org.labkey.api.data.ExcelWriter#cleanSheetName(String)
*/
public static String sheetName(String sheetName)
{
return WorkbookUtil.createSafeSheetName(StringUtils.truncate(sheetName, 31), '_');
}
}

class WorsheetMap extends LinkedHashMap<String, List<Map<String, String>>>
{
@Override
public List<Map<String, String>> get(Object key)
{
return super.get(ExcelHelper.sheetName((String) key));
}

@Override
public List<Map<String, String>> getOrDefault(Object key, List<Map<String, String>> defaultValue)
{
return super.getOrDefault(ExcelHelper.sheetName((String) key), defaultValue);
}
}
10 changes: 9 additions & 1 deletion src/org/labkey/test/util/URLBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,19 @@ public URLBuilder setQuery(Map<String, ?> query)
public URLBuilder setAppResourcePath(Object... pathParts)
{
List<String> encodedParts = Arrays.stream(pathParts).map(Objects::requireNonNull).map(String::valueOf)
.map(s -> EscapeUtil.encode(s).replace("+", " ")).collect(Collectors.toList());
.map(this::encodeAppResourcePathPart).collect(Collectors.toList());
setFragment("/" + String.join("/", encodedParts));
return this;
}

private String encodeAppResourcePathPart(String pathPart)
{
return EscapeUtil.encode(pathPart)
// We generally don't encode parentheses in app resource paths
.replace("%28", "(")
.replace("%29", ")");
}

/**
* Append a fragment to the URL.<br>
* e.g. <code>setResourcePath("marker")</code> will append "#marker" to the built URL
Expand Down