Skip to content

Commit ed7c14c

Browse files
authored
Selenium tests for file path import (#2582)
1 parent 6e1b898 commit ed7c14c

File tree

8 files changed

+533
-65
lines changed

8 files changed

+533
-65
lines changed

src/org/labkey/test/AssayAPITest.java

Lines changed: 128 additions & 23 deletions
Large diffs are not rendered by default.

src/org/labkey/test/tests/AttachmentFieldTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.labkey.test.components.DomainDesignerPage;
1414
import org.labkey.test.components.domain.DomainFieldRow;
1515
import org.labkey.test.components.domain.DomainFormPanel;
16+
import org.labkey.test.pages.admin.FileRootsManagementPage;
1617
import org.labkey.test.pages.experiment.UpdateSampleTypePage;
1718
import org.labkey.test.params.FieldDefinition;
1819
import org.labkey.test.params.experiment.SampleTypeDefinition;
@@ -81,6 +82,15 @@ public void testFileFieldInSampleType()
8182
setFormElement(Locator.name("quf_" + fieldName), SAMPLE_FILE);
8283
clickButton("Submit");
8384

85+
assertElementPresent(Locator.tagWithAttribute("a", "title", "Download attached file"));
86+
87+
clickAndWait(Locator.tagWithText("a", "S1"));
88+
clickAndWait(Locator.tagWithClass("a", "labkey-text-link").withText("edit"));
89+
waitForElement(Locator.tagContainingText("div", "sampletype/jpg_sample.jpg"));
90+
// Issue 53200: Update form incorrectly shows that a file is not available
91+
assertTextNotPresent("sampletype/jpg_sample.jpg (unavailable)");
92+
clickButton("Cancel");
93+
8494
log("Verifying view in browser works");
8595
clickAndWait(Locator.tagWithAttributeContaining("img", "title", SAMPLE_FILE.getName()));
8696
Assertions.assertThat(getDriver().getCurrentUrl()).as("File field view URL.").contains("core-downloadFileLink.view");
@@ -92,6 +102,49 @@ public void testFileFieldInSampleType()
92102

93103
File downloadedFile = doAndWaitForDownload(() -> Locator.tagWithAttributeContaining("img", "title", SAMPLE_FILE.getName()).findElement(getDriver()).click());
94104
Assert.assertTrue("Downloaded file is empty", downloadedFile.length() > 0);
105+
106+
// create a subfolder and set the Project file root to child folder file root, to simulate sample file path not under current file root
107+
String subFolder = "ChildFolder";
108+
_containerHelper.createSubfolder(getProjectName(), subFolder);
109+
clickFolder(subFolder);
110+
FileRootsManagementPage fileRootsManagementPage = goToFolderManagement().goToFilesTab();
111+
String childFileRoot = fileRootsManagementPage.getRootPath();
112+
goToProjectHome();
113+
fileRootsManagementPage = goToFolderManagement().goToFilesTab();
114+
fileRootsManagementPage.useCustomFileRoot(childFileRoot).clickSave();
115+
116+
// verify file path display for files that's present but outside of current file root
117+
verifyUnavailableFile();
118+
119+
// reset file root to default
120+
goToFolderManagement()
121+
.goToFilesTab()
122+
.selectFileRootType(FileRootsManagementPage.FileRootOption.siteDefault)
123+
.clickSave();
124+
goToProjectHome();
125+
clickAndWait(Locator.linkWithText(sampleTypeName));
126+
assertElementPresent(Locator.tagWithAttribute("a", "title", "Download attached file"));
127+
128+
// delete the file and verify file path that doesn't exist
129+
goToModule("FileContent");
130+
_fileBrowserHelper.deleteFile("sampletype");
131+
verifyUnavailableFile();
132+
}
133+
134+
private void verifyUnavailableFile()
135+
{
136+
String sampleTypeName = "Sample type with attachment";
137+
goToProjectHome();
138+
clickAndWait(Locator.linkWithText(sampleTypeName));
139+
waitForElement(Locator.tagContainingText("td", "jpg_sample.jpg (unavailable)"));
140+
assertElementNotPresent(Locator.tagWithAttribute("a", "title", "Download attached file"));
141+
142+
// "(unavailable)" suffix is present in update view
143+
clickAndWait(Locator.tagWithText("a", "S1"));
144+
clickAndWait(Locator.tagWithClass("a", "labkey-text-link").withText("edit"));
145+
waitForElement(Locator.tagContainingText("div", "jpg_sample.jpg (unavailable)"));
146+
assertElementNotPresent(Locator.tagWithAttributeContaining("img", "src", "/_icons/image.png"));
147+
95148
}
96149

97150
@Test

src/org/labkey/test/tests/SampleTypeTest.java

Lines changed: 168 additions & 35 deletions
Large diffs are not rendered by default.

src/org/labkey/test/util/APIAssayHelper.java

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.util.List;
5353
import java.util.Map;
5454

55+
import static org.junit.Assert.assertEquals;
5556
import static org.junit.Assert.fail;
5657

5758
public class APIAssayHelper extends AbstractAssayHelper
@@ -83,14 +84,44 @@ public ImportRunResponse importAssay(int assayID, String runFilePath, String pro
8384

8485
@LogMethod(quiet = true)
8586
public ImportRunResponse importAssay(int assayID, String runName, List<Map<String, Object>> dataRows, String projectPath,
86-
Map<String, Object> runProperties, Map<String, Object> batchProperties) throws CommandException, IOException
87+
Map<String, Object> runProperties, Map<String, Object> batchProperties, String errorMsg) throws CommandException, IOException
8788
{
8889
ImportRunCommand irc = new ImportRunCommand(assayID, dataRows);
8990
irc.setName(runName);
9091
irc.setProperties(runProperties);
9192
irc.setBatchProperties(batchProperties);
9293
irc.setTimeout(180000); // Wait 3 minutes for assay import
93-
return irc.execute(_test.createDefaultConnection(), projectPath);
94+
if (errorMsg != null)
95+
{
96+
try
97+
{
98+
irc.execute(_test.createDefaultConnection(), projectPath);
99+
throw new Exception("This should have failed");
100+
}
101+
catch (CommandException e)
102+
{
103+
Map<String, Object> responseJson = e.getProperties();
104+
if (!responseJson.containsKey("exception"))
105+
throw new CommandException("Response lacks exception");
106+
107+
String exception = responseJson.get("exception").toString();
108+
assertEquals("Invalid file path message not as expected", errorMsg, exception);
109+
return null;
110+
}
111+
catch (Exception e)
112+
{
113+
throw new CommandException(e.getMessage());
114+
}
115+
}
116+
else
117+
return irc.execute(_test.createDefaultConnection(), projectPath);
118+
}
119+
120+
@LogMethod(quiet = true)
121+
public ImportRunResponse importAssay(int assayID, String runName, List<Map<String, Object>> dataRows, String projectPath,
122+
Map<String, Object> runProperties, Map<String, Object> batchProperties) throws CommandException, IOException
123+
{
124+
return importAssay(assayID, runName, dataRows, projectPath, runProperties, batchProperties, null);
94125
}
95126

96127
@LogMethod(quiet = true)
@@ -280,7 +311,7 @@ public static Map<String, Integer> getProtocolIds(String containerPath, Connecti
280311
return resultData;
281312
}
282313

283-
public void saveBatch(String assayName, String runName, Map<String, Object> runProperties, List<Map<String, Object>> resultRows, String projectName) throws IOException, CommandException
314+
public void saveBatch(String assayName, String runName, Map<String, Object> runProperties, List<Map<String, Object>> resultRows, String projectName, @Nullable String errorMsg) throws Exception
284315
{
285316
int assayId = getIdFromAssayName(assayName, projectName);
286317

@@ -294,14 +325,35 @@ public void saveBatch(String assayName, String runName, Map<String, Object> runP
294325
runs.add(run);
295326
batch.setRuns(runs);
296327

297-
saveBatch(assayId, batch, projectName);
328+
saveBatch(assayId, batch, projectName, errorMsg);
298329
}
299330

300-
public void saveBatch(int assayId, Batch batch, String projectPath) throws IOException, CommandException
331+
public void saveBatch(int assayId, Batch batch, String projectPath, @Nullable String errorMsg) throws Exception
301332
{
302333
SaveAssayBatchCommand cmd = new SaveAssayBatchCommand(assayId, batch);
303334
cmd.setTimeout(180000); // Wait 3 minutes for assay import
304-
cmd.execute(_test.createDefaultConnection(), projectPath);
335+
if (errorMsg != null)
336+
{
337+
try
338+
{
339+
var result = cmd.execute(_test.createDefaultConnection(), projectPath);
340+
throw new Exception("This should have failed");
341+
}
342+
catch (CommandException e)
343+
{
344+
Map<String, Object> responseJson = e.getProperties();
345+
if (!responseJson.containsKey("exception"))
346+
throw new Exception("Response lacks exception");
347+
348+
String exception = responseJson.get("exception").toString();
349+
assertEquals("Invalid file path message not as expected", errorMsg, exception);
350+
}
351+
}
352+
else
353+
cmd.execute(_test.createDefaultConnection(), projectPath);
354+
355+
356+
305357
}
306358

307359
public Protocol createAssayDesignWithDefaults(String containerPath, String providerName, String assayName) throws IOException, CommandException

src/org/labkey/test/util/APITestHelper.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.io.IOException;
3939
import java.util.ArrayList;
4040
import java.util.List;
41+
import java.util.Map;
4142
import java.util.regex.Pattern;
4243

4344
import static org.junit.Assert.fail;
@@ -206,6 +207,35 @@ public static void injectCookies(@NotNull String username, HttpUriRequest method
206207
method.setHeader(session.getName(), session.getValue());
207208
}
208209

210+
/*
211+
reads a script result object, returns a string representation of an exception if it exists.
212+
calling code should check the result and assert accordingly.
213+
an exception means there was a server-side exception, rather than a script error.
214+
*/
215+
static public String parseScriptResult(Map<String, Object> scriptResult)
216+
{
217+
if (scriptResult.containsKey("exception"))
218+
{
219+
String exType = (String)scriptResult.get("exception");
220+
if (exType.contains("ERROR:"))
221+
return exType; // not an exception, but a friendly error message
222+
223+
ArrayList<String> frames = (ArrayList<String>)scriptResult.get("stackTrace");
224+
225+
StringBuilder builder = new StringBuilder();
226+
if (null != frames)
227+
{
228+
for (String frame : frames)
229+
{
230+
builder.append(frame + "\n");
231+
}
232+
return "An exception of type [" + exType + "] occurred while executing the script.\n[ " + builder + " ]";
233+
}
234+
}
235+
236+
return null;
237+
}
238+
209239
public static class ApiTestCase
210240
{
211241
private String _name;

src/org/labkey/test/util/EscapeUtil.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,31 @@
2828

2929
public class EscapeUtil
3030
{
31+
static public String toJSONStr(String str)
32+
{
33+
if (str == null) return null;
34+
StringBuilder escaped = new StringBuilder();
35+
for (char c : str.toCharArray()) {
36+
switch (c) {
37+
case '"': escaped.append("\\\""); break;
38+
case '\\': escaped.append("\\\\"); break;
39+
case '\b': escaped.append("\\b"); break;
40+
case '\f': escaped.append("\\f"); break;
41+
case '\n': escaped.append("\\n"); break;
42+
case '\r': escaped.append("\\r"); break;
43+
case '\t': escaped.append("\\t"); break;
44+
default:
45+
// Escape control characters (ASCII 0-31) and ensure Unicode compatibility
46+
if (c < 32) {
47+
escaped.append(String.format("\\u%04x", (int) c));
48+
} else {
49+
escaped.append(c);
50+
}
51+
}
52+
}
53+
return escaped.toString();
54+
}
55+
3156
static public String jsString(String s)
3257
{
3358
if (s == null)

src/org/labkey/test/util/FileBrowserHelper.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@
2020
import org.apache.commons.lang3.mutable.MutableObject;
2121
import org.jetbrains.annotations.Nullable;
2222
import org.junit.Assert;
23+
import org.labkey.remoteapi.CommandException;
24+
import org.labkey.remoteapi.Connection;
25+
import org.labkey.remoteapi.query.Filter;
26+
import org.labkey.remoteapi.query.SelectRowsCommand;
27+
import org.labkey.remoteapi.query.SelectRowsResponse;
2328
import org.labkey.test.BaseWebDriverTest;
2429
import org.labkey.test.Locator;
2530
import org.labkey.test.SortDirection;
2631
import org.labkey.test.TestProperties;
2732
import org.labkey.test.WebDriverWrapper;
33+
import org.labkey.test.WebTestHelper;
2834
import org.labkey.test.components.DomainDesignerPage;
2935
import org.labkey.test.components.ext4.Checkbox;
3036
import org.labkey.test.components.ext4.RadioButton;
@@ -40,6 +46,7 @@
4046
import org.openqa.selenium.support.ui.WebDriverWait;
4147

4248
import java.io.File;
49+
import java.io.IOException;
4350
import java.net.URLEncoder;
4451
import java.nio.charset.StandardCharsets;
4552
import java.time.Duration;
@@ -873,6 +880,58 @@ public void openFolderTree()
873880
}
874881
}
875882

883+
private String stringOrNull(Object value)
884+
{
885+
if (value == null)
886+
return null;
887+
return (String) value;
888+
}
889+
890+
public record FileDetailInfo(String fileName, String absoluteFilePath, String dataFileUrl, String webDavUrl, String webDavUrlRelative)
891+
{
892+
}
893+
894+
public FileDetailInfo getFileDetailInfo(String containerPath, String fileName)
895+
{
896+
List<String> filePathColumns = List.of("AbsoluteFilePath", "FileExists", "DataFileUrl", "WebDavUrl", "WebDavUrlRelative");
897+
try
898+
{
899+
Connection cn = WebTestHelper.getRemoteApiConnection();
900+
SelectRowsCommand cmd = new SelectRowsCommand("exp", "files");
901+
cmd.addFilter("Name", fileName, Filter.Operator.EQUAL);
902+
cmd.setColumns(filePathColumns);
903+
SelectRowsResponse response = cmd.execute(cn, "/" + containerPath);
904+
905+
for (Map<String, Object> row: response.getRows())
906+
{
907+
if (!(Boolean) row.get("FileExists"))
908+
continue;
909+
Object absoluteFilePath = row.get("AbsoluteFilePath");
910+
Object dataFileUrl = row.get("DataFileUrl");
911+
Object webDavUrl = row.get("WebDavUrl");
912+
Object webDavUrlRelative = row.get("WebDavUrlRelative");
913+
return new FileDetailInfo(fileName, stringOrNull(absoluteFilePath), stringOrNull(dataFileUrl), stringOrNull(webDavUrl), stringOrNull(webDavUrlRelative));
914+
}
915+
}
916+
catch (CommandException ce)
917+
{
918+
if (ce.getStatusCode() == 404)
919+
{
920+
return null;
921+
}
922+
else
923+
{
924+
throw new RuntimeException(ce);
925+
}
926+
}
927+
catch (IOException ioe)
928+
{
929+
throw new RuntimeException(ioe);
930+
}
931+
932+
return null;
933+
}
934+
876935
// See PageFlowUtil.encodeURIComponent()
877936
private static final Map<String, String> DECODE_UNRESERVED_MARKS = Map.of(
878937
"!", "%21",

src/org/labkey/test/util/data/TestDataUtils.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.labkey.serverapi.reader.TabLoader;
1515
import org.labkey.test.TestFileUtils;
1616
import org.labkey.test.params.FieldDefinition;
17-
import org.labkey.test.util.EscapeUtil;
1817
import org.labkey.test.util.TestDataGenerator;
1918
import org.labkey.test.util.TestLogger;
2019

@@ -296,6 +295,12 @@ public static String tsvStringFromRowMaps(List<Map<String, Object>> rowMaps, Lis
296295
return stringFromRowMaps(rowMaps, columns, includeHeaders, CSVFormat.TDF);
297296
}
298297

298+
public static String tsvStringFromRowMapsEscapeBackslash(List<Map<String, Object>> rowMaps, List<String> columns,
299+
boolean includeHeaders)
300+
{
301+
return stringFromRowMaps(rowMaps, columns, includeHeaders, CSVFormat.MYSQL);
302+
}
303+
299304
public static <T> List<Map<String, T>> mapsFromRows(List<List<T>> allRows)
300305
{
301306
List<Map<String, T>> rowMaps = new ArrayList<>();
@@ -439,6 +444,12 @@ public static <T> File writeRowsToTsv(String fileName, List<List<T>> rows) throw
439444
return writeRowsToFile(fileName, rows, CSVFormat.TDF);
440445
}
441446

447+
public static <T> File writeRowsToTsvEscapeBackslash(String fileName, List<List<T>> rows) throws IOException
448+
{
449+
return writeRowsToFile(fileName, rows, CSVFormat.MYSQL);
450+
}
451+
452+
442453
public static <T> File writeRowsToCsv(String fileName, List<List<T>> rows) throws IOException
443454
{
444455
return writeRowsToFile(fileName, rows, CSVFormat.DEFAULT);

0 commit comments

Comments
 (0)