Skip to content

Commit

Permalink
fix(#1100): empty file resource returns true on exist
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorsten Schlathoelter authored and christophd committed Jan 31, 2024
1 parent d385154 commit 9228b01
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
Expand All @@ -19,6 +19,9 @@

package org.citrusframework.spi;

import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.util.ReflectionHelper;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -33,8 +36,7 @@
import java.net.URLConnection;
import java.nio.file.Paths;

import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.util.ReflectionHelper;
import static java.lang.String.format;

/**
* Helps with resources of type classpath or file system.
Expand All @@ -47,6 +49,10 @@ public class Resources {
public static final String JAR_RESOURCE_PREFIX = "jar:";
public static final String HTTP_RESOURCE_PREFIX = "http:";

private Resources() {
// static access only
}

public static Resource create(String filePath) {
if (filePath.startsWith(CLASSPATH_RESOURCE_PREFIX)) {
return fromClasspath(filePath);
Expand Down Expand Up @@ -87,6 +93,7 @@ public static Resource create(URL url) {
public static Resource fromClasspath(String filePath) {
return new ClasspathResource(filePath);
}

public static Resource fromClasspath(String filePath, Class<?> contextClass) {
return fromClasspath(contextClass.getPackageName().replace(".", "/") + "/" + filePath);
}
Expand All @@ -96,7 +103,9 @@ public static Resource fromFileSystem(String filePath) {
}

/**
* Removes leading resource type information from given file path for classpath and file system typed resources.
* Removes leading resource type information from given file path for classpath and file system
* typed resources.
*
* @param filePath
* @return
*/
Expand Down Expand Up @@ -141,18 +150,22 @@ public boolean exists() {

@Override
public InputStream getInputStream() {
return ReflectionHelper.class.getClassLoader().getResourceAsStream(location.replace("\\","/"));
return ReflectionHelper.class.getClassLoader()
.getResourceAsStream(location.replace("\\", "/"));
}

@Override
public File getFile() {
if (!exists()) {
throw new CitrusRuntimeException(String.format("Failed to load classpath resource %s - does not exist", getLocation()));
throw new CitrusRuntimeException(
format("Failed to load classpath resource %s - does not exist", getLocation())
);
}

return Paths.get(getURI()).toFile();
}

@Override
public URI getURI() {
URL url = ReflectionHelper.class.getClassLoader().getResource(location);
try {
Expand Down Expand Up @@ -191,7 +204,8 @@ public InputStream getInputStream() {

@Override
public File getFile() {
throw new UnsupportedOperationException("ByteArrayResource does not provide access to a file");
throw new UnsupportedOperationException(
"ByteArrayResource does not provide access to a file");
}
}

Expand Down Expand Up @@ -263,11 +277,25 @@ public String getLocation() {

@Override
public boolean exists() {
if (url == null) {
return false;
}

if ("file".equals(url.getProtocol())) {
try {
// If we have a protocol, it means that the url is
// absolute and the file should be resolvable.
return new File(url.toURI()).exists();
} catch (URISyntaxException e) {
throw new CitrusRuntimeException("Unable to parse absolute file url: " + url);
}
}

URLConnection connection = null;
try {
connection = url.openConnection();
if (connection instanceof HttpURLConnection) {
return ((HttpURLConnection) connection).getResponseCode() == HttpURLConnection.HTTP_OK;
if (connection instanceof HttpURLConnection httpURLConnection) {
return httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK;
}

return connection.getContentLengthLong() > 0;
Expand All @@ -276,8 +304,8 @@ public boolean exists() {
} finally {
// close the http connection to avoid
// leaking gaps in case of an exception
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).disconnect();
if (connection instanceof HttpURLConnection httpURLConnection) {
httpURLConnection.disconnect();
}
}
}
Expand All @@ -294,16 +322,18 @@ public InputStream getInputStream() {
} finally {
// close the http connection to avoid
// leaking gaps in case of an exception
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).disconnect();
if (connection instanceof HttpURLConnection httpURLConnection) {
httpURLConnection.disconnect();
}
}
}

@Override
public File getFile() {
if (!"file".equals(url.getProtocol())) {
throw new CitrusRuntimeException("Failed to resolve to absolute file path because it does not reside in the file system: " + url);
throw new CitrusRuntimeException(
format("Failed to resolve to absolute file path because it does not reside in the file system: %s", url)
);
}
try {
return new File(url.toURI().getSchemeSpecificPart());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package org.citrusframework.spi;

import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.spi.Resources.ByteArrayResource;
import org.citrusframework.spi.Resources.ClasspathResource;
import org.citrusframework.spi.Resources.FileSystemResource;
import org.citrusframework.spi.Resources.UrlResource;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertThrows;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;

public class ResourcesTest {

private static URI baseUri;
private static URI baseFolderUri;
private static URI fileWithContentUri;
private static URI fileWithoutContentUri;
private static URI nonExistingFileUri;

@BeforeClass
public static void beforeClass() throws URISyntaxException {
Resource resource = Resources.create("/ResourcesTest");
baseUri = resource.getFile().getParentFile().toURI();
baseFolderUri = resource.getFile().toURI();

fileWithContentUri = new URI(baseFolderUri + "FileWithContent.json");
fileWithoutContentUri = new URI(baseFolderUri + "FileWithoutContent.txt");
nonExistingFileUri = new URI(baseFolderUri + "NonExistingFile.txt");
}

@Test
public void urlNullDoesNotExistTest() {
assertFalse(new UrlResource(null).exists());
}

@Test
public void classpathResourceTest() {
Resource withContentResource = Resources.create(
baseUri.relativize(fileWithContentUri).getPath());
assertTrue(withContentResource.exists());

Resource withoutContentResource = Resources.create(
baseUri.relativize(fileWithoutContentUri).getPath());
assertTrue(withoutContentResource.exists());

Resource nonExistingResource = Resources.create(
baseUri.relativize(nonExistingFileUri).getPath());
assertFalse(nonExistingResource.exists());
}

@Test
public void byteArrayResourceTest() {
Resource byteArrayResource = new ByteArrayResource(new byte[100]);
assertEquals(byteArrayResource.getLocation(), "");
assertTrue(byteArrayResource.exists());
assertTrue(byteArrayResource.getInputStream() instanceof ByteArrayInputStream);
assertThrows(UnsupportedOperationException.class, byteArrayResource::getFile);
}

@Test
public void defaultFileSystemResourceTest() {
Resource resource = Resources.create("/ResourcesTest");
File resourceFolder = resource.getFile();

assertTrue(resourceFolder.exists());
assertTrue(resourceFolder.isDirectory());

Resource withContentResource = Resources.create(fileWithContentUri.getPath());
assertTrue(withContentResource instanceof FileSystemResource);
assertTrue(withContentResource.exists());

Resource withoutContentResource = Resources.create(fileWithoutContentUri.getPath());
assertTrue(withoutContentResource instanceof FileSystemResource);
assertTrue(withoutContentResource.exists());

Resource nonExistingResource = Resources.create(nonExistingFileUri.getPath());
assertTrue(nonExistingResource instanceof ClasspathResource);
assertFalse(nonExistingResource.exists());
}

@Test
public void fileSystemResourceTest() throws IOException {
Resource resource = Resources.create("/ResourcesTest");
File file = resource.getFile();

assertTrue(file.exists());
assertTrue(file.isDirectory());

Resource withContentResource = Resources.create(fileWithContentUri.toString());
assertTrue(withContentResource instanceof FileSystemResource);
assertTrue(withContentResource.exists());
assertEquals(fileWithContentUri, withContentResource.getURI());
assertEquals(new File(fileWithContentUri).getPath(), withContentResource.getLocation());
try (InputStream inputStream = withContentResource.getInputStream()) {
assertNotNull(inputStream);
}

Resource withoutContentResource = Resources.create(fileWithoutContentUri.toString());
assertTrue(withoutContentResource instanceof FileSystemResource);
assertTrue(withoutContentResource.exists());
try (InputStream inputStream = withoutContentResource.getInputStream()) {
assertNotNull(inputStream);
}

Resource nonExistingResource = Resources.create(nonExistingFileUri.toString());
assertTrue(nonExistingResource instanceof FileSystemResource);
assertFalse(nonExistingResource.exists());
assertThrows(CitrusRuntimeException.class, nonExistingResource::getInputStream);

assertThrows(UnsupportedOperationException.class, () -> new FileSystemResource(new File(baseFolderUri)).getInputStream());
}

@Test
public void urlResourceTest() throws MalformedURLException {
Resource httpResource = Resources.create(Resources.HTTP_RESOURCE_PREFIX + "//host/context");
assertTrue(httpResource instanceof UrlResource);

URL withContentUrlMock = spy(fileWithContentUri.toURL());
UrlResource withContentResource = new UrlResource(withContentUrlMock);
assertTrue(withContentResource.exists());
assertEquals(new File(fileWithContentUri), withContentResource.getFile());
assertEquals(fileWithContentUri.toURL().toString(), withContentResource.getLocation());

URL withoutContentUrlMock = spy(fileWithoutContentUri.toURL());
UrlResource withoutContentResource = new UrlResource(withoutContentUrlMock);
assertTrue(withoutContentResource.exists());
assertEquals(new File(fileWithoutContentUri), withoutContentResource.getFile());

URL nonExistingUrlMock = spy(nonExistingFileUri.toURL());
UrlResource nonExistingResource = new UrlResource(nonExistingUrlMock);
assertFalse(nonExistingResource.exists());
assertEquals(new File(nonExistingFileUri), nonExistingResource.getFile());
}

@Test
public void urlResourceExistsOnConnectionTest() throws IOException {
URL withContentUrlMock = spy(new URL(Resources.HTTP_RESOURCE_PREFIX + "//host/context"));
HttpURLConnection urlConnectionMock = mock(HttpURLConnection.class);
doReturn(urlConnectionMock).when(withContentUrlMock).openConnection();
doReturn(HttpURLConnection.HTTP_OK).when(urlConnectionMock).getResponseCode();
assertTrue(new UrlResource(withContentUrlMock).exists());
}


@Test
public void urlResourceNotExistsOnConnectionFailureTest() throws IOException {
URL withContentUrlMock = spy(new URL(Resources.HTTP_RESOURCE_PREFIX + "//host/context"));
HttpURLConnection urlConnectionMock = mock(HttpURLConnection.class);
doReturn(urlConnectionMock).when(withContentUrlMock).openConnection();
doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(urlConnectionMock).getResponseCode();
assertFalse(new UrlResource(withContentUrlMock).exists());
}

@Test
public void uriSyntaxThrowsCitrusRuntimeException()
throws MalformedURLException, URISyntaxException {
URL withContentUrlMock = spy(fileWithContentUri.toURL());
doThrow(new URISyntaxException("xxxx", "Test Exception[uriSyntaxThrowsCitrusRuntimeException]"))
.when(withContentUrlMock).toURI();
UrlResource withContentResource = new UrlResource(withContentUrlMock);
assertThrows(CitrusRuntimeException.class, withContentResource::exists);
}

@Test
public void urlResourceNotExistsOnIOExceptionTest() throws IOException {
URL withContentUrlMock = spy(new URL(Resources.HTTP_RESOURCE_PREFIX + "//host/context"));
doThrow(new IOException("Test Exception[urlResourceNotExistsOnIoExceptionTest]"))
.when(withContentUrlMock).openConnection();
assertThrows(CitrusRuntimeException.class, () -> new UrlResource(withContentUrlMock).exists());
}

@Test
public void urlResourceInputStreamTest() throws IOException {
URL withContentUrlMock = spy(new URL(Resources.HTTP_RESOURCE_PREFIX + "//host/context"));
HttpURLConnection urlConnectionMock = mock(HttpURLConnection.class);
InputStream inputStreamMock = mock(InputStream.class);

doReturn(urlConnectionMock).when(withContentUrlMock).openConnection();
doReturn(inputStreamMock).when(urlConnectionMock).getInputStream();
assertEquals(inputStreamMock, new UrlResource(withContentUrlMock).getInputStream());

verify(urlConnectionMock).disconnect();
}

@Test
public void urlResourceInputStreamIOExceptionTest() throws IOException {
URL withContentUrlMock = spy(new URL(Resources.HTTP_RESOURCE_PREFIX + "//host/context"));
HttpURLConnection urlConnectionMock = mock(HttpURLConnection.class);

doReturn(urlConnectionMock).when(withContentUrlMock).openConnection();

doThrow(new IOException("Test Exception[urlResourceInputStreamIOExceptionTest]"))
.when(urlConnectionMock).getInputStream();

assertThrows(CitrusRuntimeException.class, () -> new UrlResource(withContentUrlMock).getInputStream());

verify(urlConnectionMock).disconnect();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name": "Max Mustermann"}
Empty file.

0 comments on commit 9228b01

Please sign in to comment.