Adds missing test cases of temporary files

This commit is contained in:
Lukasz Lenart
2025-07-19 11:01:54 +02:00
parent 824e7121b0
commit 37144a03f7
5 changed files with 568 additions and 19 deletions
@@ -209,7 +209,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
List<UploadedFile> values = uploadedFiles.computeIfAbsent(fieldName, k -> new ArrayList<>());
if (item.isInMemory()) {
LOG.debug("Creating temporary file representing in-memory uploaded item: {}", normalizeSpace(item.getFieldName()));
LOG.debug(() -> "Creating temporary file representing in-memory uploaded item: " + normalizeSpace(item.getFieldName()));
try {
File tempFile = createTemporaryFile(item.getName(), Path.of(saveDir));
@@ -229,8 +229,10 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
.build();
values.add(uploadedFile);
LOG.debug("Created temporary file for in-memory uploaded item: {} at {}",
normalizeSpace(item.getName()), tempFile.getAbsolutePath());
if (LOG.isDebugEnabled()) {
LOG.debug("Created temporary file for in-memory uploaded item: {} at {}",
normalizeSpace(item.getName()), tempFile.getAbsolutePath());
}
} catch (IOException e) {
LOG.warn("Failed to create temporary file for in-memory uploaded item: {}",
normalizeSpace(item.getName()), e);
@@ -277,10 +279,12 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
for (DiskFileItem item : diskFileItems) {
try {
if (item.isInMemory()) {
LOG.debug("Cleaning up in-memory item: {}", normalizeSpace(item.getFieldName()));
LOG.debug(() -> "Cleaning up in-memory item: " + normalizeSpace(item.getFieldName()));
} else {
Path itemPath = item.getPath();
LOG.debug("Cleaning up disk item: {} at {}", normalizeSpace(item.getFieldName()), itemPath);
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaning up disk item: {} at {}", normalizeSpace(item.getFieldName()), itemPath);
}
if (itemPath != null) {
File itemFile = itemPath.toFile();
if (itemFile.exists() && !itemFile.delete()) {
@@ -242,20 +242,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
}
}
/**
* Creates a temporary file based on the given filename and location.
*
* @param fileName file name
* @param location location
* @return a temporary file based on the given filename and location
*/
protected File createTemporaryFile(String fileName, Path location) {
String uid = UUID.randomUUID().toString().replace("-", "_");
File file = location.resolve("upload_" + uid + ".tmp").toFile();
LOG.debug("Creating temporary file: {} (originally: {})", file.getName(), fileName);
return file;
}
/**
* Streams the file upload stream to the specified file.
*
@@ -30,6 +30,9 @@ import org.springframework.mock.web.MockHttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -586,6 +589,160 @@ abstract class AbstractMultiPartRequestTest {
}
}
@Test
public void createTemporaryFileGeneratesSecureNames() {
// Create a test instance to access the protected method
AbstractMultiPartRequest testRequest = createMultipartRequest();
Path testLocation = Paths.get(tempDir);
// when - create multiple temporary files
File tempFile1 = testRequest.createTemporaryFile("test1.csv", testLocation);
File tempFile2 = testRequest.createTemporaryFile("test2.csv", testLocation);
File tempFile3 = testRequest.createTemporaryFile("../../../malicious.csv", testLocation);
// then - verify secure naming
assertThat(tempFile1.getName()).startsWith("upload_");
assertThat(tempFile1.getName()).endsWith(".tmp");
assertThat(tempFile2.getName()).startsWith("upload_");
assertThat(tempFile2.getName()).endsWith(".tmp");
assertThat(tempFile3.getName()).startsWith("upload_");
assertThat(tempFile3.getName()).endsWith(".tmp");
// Verify each file has a unique name
assertThat(tempFile1.getName()).isNotEqualTo(tempFile2.getName());
assertThat(tempFile2.getName()).isNotEqualTo(tempFile3.getName());
assertThat(tempFile1.getName()).isNotEqualTo(tempFile3.getName());
// Verify all files are in the correct location
assertThat(tempFile1.getParent()).isEqualTo(tempDir);
assertThat(tempFile2.getParent()).isEqualTo(tempDir);
assertThat(tempFile3.getParent()).isEqualTo(tempDir);
// Verify malicious filename doesn't affect the location
assertThat(tempFile3.getName()).doesNotContain("..");
assertThat(tempFile3.getName()).doesNotContain("/");
assertThat(tempFile3.getName()).doesNotContain("\\");
// Clean up test files
tempFile1.delete();
tempFile2.delete();
tempFile3.delete();
}
@Test
public void createTemporaryFileInSpecificDirectory() throws IOException {
// Create a subdirectory for testing
Path subDir = Paths.get(tempDir, "subdir");
Files.createDirectories(subDir);
AbstractMultiPartRequest testRequest = createMultipartRequest();
// when
File tempFile = testRequest.createTemporaryFile("test.csv", subDir);
// then - verify file is created in the specified subdirectory
assertThat(tempFile.getParent()).isEqualTo(subDir.toString());
assertThat(tempFile.getName()).startsWith("upload_");
assertThat(tempFile.getName()).endsWith(".tmp");
// Clean up
tempFile.delete();
Files.delete(subDir);
}
@Test
public void createTemporaryFileWithNullFileName() throws IOException {
AbstractMultiPartRequest testRequest = createMultipartRequest();
Path testLocation = Paths.get(tempDir);
// when - create temp file with null filename
File tempFile = testRequest.createTemporaryFile(null, testLocation);
// then - should still create a valid temporary file
assertThat(tempFile.getName()).startsWith("upload_");
assertThat(tempFile.getName()).endsWith(".tmp");
assertThat(tempFile.getParent()).isEqualTo(tempDir);
// Clean up
tempFile.delete();
}
@Test
public void createTemporaryFileWithEmptyFileName() throws IOException {
AbstractMultiPartRequest testRequest = createMultipartRequest();
Path testLocation = Paths.get(tempDir);
// when - create temp file with empty filename
File tempFile = testRequest.createTemporaryFile("", testLocation);
// then - should still create a valid temporary file
assertThat(tempFile.getName()).startsWith("upload_");
assertThat(tempFile.getName()).endsWith(".tmp");
assertThat(tempFile.getParent()).isEqualTo(tempDir);
// Clean up
tempFile.delete();
}
@Test
public void createTemporaryFileWithSpecialCharacters() {
AbstractMultiPartRequest testRequest = createMultipartRequest();
Path testLocation = Paths.get(tempDir);
// when - create temp files with various special characters
File tempFile1 = testRequest.createTemporaryFile("file with spaces.csv", testLocation);
File tempFile2 = testRequest.createTemporaryFile("file@#$%^&*().csv", testLocation);
File tempFile3 = testRequest.createTemporaryFile("файл.csv", testLocation); // Cyrillic
// then - all should create valid secure temporary files
File[] tempFiles = {tempFile1, tempFile2, tempFile3};
for (File tempFile : tempFiles) {
assertThat(tempFile.getName()).startsWith("upload_");
assertThat(tempFile.getName()).endsWith(".tmp");
assertThat(tempFile.getParent()).isEqualTo(tempDir);
// Verify no special characters leak into the actual filename
assertThat(tempFile.getName()).matches("upload_[a-zA-Z0-9_]+\\.tmp");
}
// All should have unique names
assertThat(tempFile1.getName()).isNotEqualTo(tempFile2.getName());
assertThat(tempFile2.getName()).isNotEqualTo(tempFile3.getName());
assertThat(tempFile1.getName()).isNotEqualTo(tempFile3.getName());
// Clean up
tempFile1.delete();
tempFile2.delete();
tempFile3.delete();
}
@Test
public void createTemporaryFileConsistentNaming() {
AbstractMultiPartRequest testRequest = createMultipartRequest();
Path testLocation = Paths.get(tempDir);
// when - create many temporary files to verify naming consistency
List<File> tempFiles = new ArrayList<>();
for (int i = 0; i < 100; i++) {
tempFiles.add(testRequest.createTemporaryFile("test" + i + ".csv", testLocation));
}
// then - all should follow the same naming pattern
for (File tempFile : tempFiles) {
assertThat(tempFile.getName()).startsWith("upload_");
assertThat(tempFile.getName()).endsWith(".tmp");
assertThat(tempFile.getParent()).isEqualTo(tempDir);
// Verify UUID pattern (without hyphens, replaced with underscores)
assertThat(tempFile.getName()).matches("upload_[a-zA-Z0-9_]+\\.tmp");
}
// Verify all names are unique
List<String> fileNames = tempFiles.stream().map(File::getName).toList();
assertThat(fileNames).doesNotHaveDuplicates();
// Clean up
tempFiles.forEach(File::delete);
}
protected String formFile(String fieldName, String filename, String content) {
return endline +
"--" + boundary + endline +
@@ -20,6 +20,7 @@ package org.apache.struts2.dispatcher.multipart;
import org.apache.commons.fileupload2.core.DiskFileItem;
import org.apache.struts2.dispatcher.LocalizedMessage;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.Test;
import java.io.File;
@@ -281,4 +282,175 @@ public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest {
}
}
@Test
public void processNormalFormFieldHandlesNullFieldName() throws IOException {
// Test null field name handling in processNormalFormField
String content =
endline + "--" + boundary + endline +
"Content-Disposition: form-data" + endline + // No name attribute
endline +
"field value without name" +
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"validfield\"" + endline +
endline +
"valid field value" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then - should only process the valid field
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.getParameter("validfield")).isEqualTo("valid field value");
assertThat(multiPart.getParameterNames().asIterator()).toIterable().hasSize(1);
}
@Test
public void processFileFieldHandlesNullFieldName() throws IOException {
// Test null field name handling in processFileField
String content =
endline + "--" + boundary + endline +
"Content-Disposition: form-data; filename=\"orphan.txt\"" + endline + // No name attribute
"Content-Type: text/plain" + endline +
endline +
"orphaned file content" +
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"validfile\"; filename=\"valid.txt\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"valid file content" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then - should only process the valid file
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.uploadedFiles).hasSize(1);
assertThat(multiPart.getFile("validfile")).hasSize(1);
assertThat(multiPart.getFile("validfile")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo("valid file content");
}
@Test
public void diskFileItemCleanupCoverage() throws IOException, NoSuchFieldException, IllegalAccessException {
// Test disk file item cleanup paths
String content = formFile("file1", "test1.csv", "1,2,3,4") +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when - force files to disk with small buffer
multiPart.setBufferSize("1");
multiPart.parse(mockRequest, tempDir);
// Access private field to verify disk file items are tracked
Field diskFileItemsField = JakartaMultiPartRequest.class.getDeclaredField("diskFileItems");
diskFileItemsField.setAccessible(true);
@SuppressWarnings("unchecked")
java.util.List<org.apache.commons.fileupload2.core.DiskFileItem> diskFileItems =
(java.util.List<org.apache.commons.fileupload2.core.DiskFileItem>) diskFileItemsField.get(multiPart);
// then - should have disk file items tracked
assertThat(diskFileItems).isNotEmpty();
// when - cleanup
multiPart.cleanUp();
// then - should clear tracking
assertThat(diskFileItems).isEmpty();
}
@Test
public void inMemoryVsDiskFileHandling() throws IOException {
// Test both in-memory and disk file handling paths
String smallContent = "small"; // Should be in-memory
String largeContent = "x".repeat(20000); // Should go to disk
String content = formFile("smallfile", "small.txt", smallContent) +
formFile("largefile", "large.txt", largeContent) +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when - use default buffer size
multiPart.parse(mockRequest, tempDir);
// then - both files should be processed
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.uploadedFiles).hasSize(2);
assertThat(multiPart.getFile("smallfile")).hasSize(1);
assertThat(multiPart.getFile("largefile")).hasSize(1);
// Verify content
assertThat(multiPart.getFile("smallfile")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo(smallContent);
assertThat(multiPart.getFile("largefile")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo(largeContent);
}
@Test
public void errorDuplicationPrevention() throws IOException {
// Test that duplicate errors are not added
JakartaMultiPartRequest multiPartRequest = new JakartaMultiPartRequest();
// Simulate adding the same error multiple times
IOException testException = new IOException("Test error");
LocalizedMessage errorMessage = multiPartRequest.buildErrorMessage(
testException.getClass(), testException.getMessage(), new Object[]{"test.csv"});
// when - try to add same error multiple times
multiPartRequest.errors.add(errorMessage);
if (!multiPartRequest.errors.contains(errorMessage)) {
multiPartRequest.errors.add(errorMessage); // Should not be added
}
if (!multiPartRequest.errors.contains(errorMessage)) {
multiPartRequest.errors.add(errorMessage); // Should not be added
}
// then - should only have one error
assertThat(multiPartRequest.getErrors()).hasSize(1);
}
@Test
public void processFileFieldHandlesEmptyFileName() throws IOException {
String content =
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"emptyfile\"; filename=\"\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"some content that should be ignored" +
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"validfile\"; filename=\"test.txt\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"valid file content" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then - should only process the file with valid filename
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.uploadedFiles).hasSize(1);
assertThat(multiPart.getFile("validfile")).hasSize(1);
assertThat(multiPart.getFile("emptyfile")).isEmpty();
assertThat(multiPart.getFile("validfile")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo("valid file content");
}
}
@@ -23,11 +23,15 @@ import org.apache.struts2.dispatcher.LocalizedMessage;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -161,4 +165,230 @@ public class JakartaStreamMultiPartRequestTest extends AbstractMultiPartRequestT
assertThat(result.length()).isGreaterThan(1024); // Verify it's larger than internal buffer
}
@Test
public void processFileItemAsFormFieldHandlesNullFieldName() throws IOException {
// Test the null field name path in processFileItemAsFormField
String content = formFile("", "test.csv", "data") + // Field name will be empty/null-like
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then - should complete without error, but no parameters should be added
assertThat(multiPart.getErrors()).isEmpty();
}
@Test
public void processFileItemAsFileFieldHandlesNullFieldName() throws IOException {
// This test covers the null field name path in processFileItemAsFileField
JakartaStreamMultiPartRequest streamMultiPart = new JakartaStreamMultiPartRequest();
// Create a mock file item with null field name
String content = "--" + boundary + endline +
"Content-Disposition: form-data; filename=\"test.csv\"" + endline +
"Content-Type: text/csv" + endline +
endline +
"test data" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
streamMultiPart.parse(mockRequest, tempDir);
// then - should complete without error but no files should be uploaded
assertThat(streamMultiPart.getErrors()).isEmpty();
assertThat(streamMultiPart.uploadedFiles).isEmpty();
}
@Test
public void exceedsMaxFilesPath() throws IOException {
// Test the exceedsMaxFiles method path
String content = formFile("file1", "test1.csv", "data1") +
formFile("file2", "test2.csv", "data2") +
formFile("file3", "test3.csv", "data3") +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when - set max files to 1
multiPart.setMaxFiles("1");
multiPart.parse(mockRequest, tempDir);
// then - should have only 1 file and errors for others
assertThat(multiPart.uploadedFiles).hasSize(1);
assertThat(multiPart.getErrors())
.isNotEmpty()
.allSatisfy(error ->
assertThat(error.getTextKey()).isEqualTo("struts.messages.upload.error.FileUploadFileCountLimitException")
);
}
@Test
public void actualSizeOfUploadedFilesCalculation() throws IOException {
// Test the actualSizeOfUploadedFiles method
String content = formFile("file1", "test1.csv", "data1234567890") + // 14 bytes + headers
formFile("file2", "test2.csv", "moredata") + // 8 bytes + headers
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then - should have uploaded files and calculate their total size
assertThat(multiPart.uploadedFiles).hasSize(2);
assertThat(multiPart.getFile("file1")).hasSize(1);
assertThat(multiPart.getFile("file2")).hasSize(1);
// Verify files have the expected content
assertThat(multiPart.getFile("file1")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo("data1234567890");
assertThat(multiPart.getFile("file2")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo("moredata");
}
@Test
public void createTemporaryFileMethod() throws Exception {
// Test the createTemporaryFile method directly
JakartaStreamMultiPartRequest streamMultiPart = new JakartaStreamMultiPartRequest();
Path testLocation = Paths.get(tempDir);
// when
File tempFile1 = streamMultiPart.createTemporaryFile("test.csv", testLocation);
File tempFile2 = streamMultiPart.createTemporaryFile("another.txt", testLocation);
// then
assertThat(tempFile1.getName()).startsWith("upload_");
assertThat(tempFile1.getName()).endsWith(".tmp");
assertThat(tempFile1.getParent()).isEqualTo(tempDir);
assertThat(tempFile2.getName()).startsWith("upload_");
assertThat(tempFile2.getName()).endsWith(".tmp");
assertThat(tempFile2.getParent()).isEqualTo(tempDir);
// Should be unique names
assertThat(tempFile1.getName()).isNotEqualTo(tempFile2.getName());
// Clean up
tempFile1.delete();
tempFile2.delete();
}
@Test
public void streamFileToDiskWithDifferentBufferSizes() throws IOException {
// Test streamFileToDisk with different buffer sizes
String largeContent = "x".repeat(5000); // Content larger than default buffer
String content = formFile("largefile", "large.csv", largeContent) +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when - use small buffer size to ensure multiple reads
multiPart.setBufferSize("100");
multiPart.parse(mockRequest, tempDir);
// then
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.getFile("largefile")).hasSize(1);
assertThat(multiPart.getFile("largefile")[0].getContent())
.asInstanceOf(InstanceOfAssertFactories.FILE)
.content()
.isEqualTo(largeContent);
}
@Test
public void exceedsMaxSizeOfFilesWithFileCleanup() throws IOException {
// Test the file deletion path when max size is exceeded
String content = formFile("file1", "test1.csv", "small") +
formFile("file2", "test2.csv", "this is a much larger file content that should exceed the limit") +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when - set very small max size
multiPart.setMaxSizeOfFiles("20");
multiPart.parse(mockRequest, tempDir);
// then - should have first file uploaded but error for second
assertThat(multiPart.uploadedFiles).hasSize(1);
assertThat(multiPart.getFile("file1")).hasSize(1);
assertThat(multiPart.getFile("file2")).isEmpty();
assertThat(multiPart.getErrors())
.isNotEmpty()
.anyMatch(error ->
error.getTextKey().equals("struts.messages.upload.error.FileUploadSizeException")
);
}
@Test
public void createUploadedFileWithVariousContentTypes() throws IOException {
// Test different content types and file names
String content =
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"textfile\"; filename=\"document.txt\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"Plain text content" +
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"jsonfile\"; filename=\"data.json\"" + endline +
"Content-Type: application/json" + endline +
endline +
"{\"key\": \"value\"}" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.uploadedFiles).hasSize(2);
// Verify text file
assertThat(multiPart.getFile("textfile")).hasSize(1);
assertThat(multiPart.getFile("textfile")[0].getContentType()).isEqualTo("text/plain");
assertThat(multiPart.getFile("textfile")[0].getOriginalName()).isEqualTo("document.txt");
// Verify JSON file
assertThat(multiPart.getFile("jsonfile")).hasSize(1);
assertThat(multiPart.getFile("jsonfile")[0].getContentType()).isEqualTo("application/json");
assertThat(multiPart.getFile("jsonfile")[0].getOriginalName()).isEqualTo("data.json");
}
@Test
public void emptyFileNameFieldsAreSkipped() throws IOException {
// Test files with empty names are skipped
String content =
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"emptyfile\"; filename=\"\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"This should be skipped" +
endline + "--" + boundary + endline +
"Content-Disposition: form-data; name=\"validfile\"; filename=\"valid.txt\"" + endline +
"Content-Type: text/plain" + endline +
endline +
"This should be processed" +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
// when
multiPart.parse(mockRequest, tempDir);
// then
assertThat(multiPart.getErrors()).isEmpty();
assertThat(multiPart.uploadedFiles).hasSize(1);
assertThat(multiPart.getFile("emptyfile")).isEmpty();
assertThat(multiPart.getFile("validfile")).hasSize(1);
}
}