Bug 567736: [RJ-Server] Add unit tests for RService.uploadFile and
RService.downloadFile

Change-Id: If83e85f35220ba8575c5ffdb21ffa104a15c7853
diff --git a/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/AbstractLocalNodeTest.java b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/AbstractLocalNodeTest.java
new file mode 100644
index 0000000..7def5a7
--- /dev/null
+++ b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/AbstractLocalNodeTest.java
@@ -0,0 +1,105 @@
+/*=============================================================================#
+ # Copyright (c) 2020 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.rj.servi.pool;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.security.auth.login.LoginException;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.rmi.RMIRegistry;
+import org.eclipse.statet.jcommons.rmi.RMIRegistryManager;
+import org.eclipse.statet.jcommons.status.NullProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+
+import org.eclipse.statet.rj.server.util.RJContext;
+import org.eclipse.statet.rj.servi.RServi;
+import org.eclipse.statet.rj.servi.RServiUtils;
+import org.eclipse.statet.rj.servi.node.RServiImpl;
+import org.eclipse.statet.rj.servi.node.RServiNodeConfig;
+import org.eclipse.statet.rj.servi.node.RServiNodeFactory;
+import org.eclipse.statet.rj.servi.node.RServiNodeManager;
+
+
+@NonNullByDefault
+public abstract class AbstractLocalNodeTest extends AbstractServiTest {
+	
+	
+	private final String name;
+	
+	protected RServiNodeManager localR= nonNullLateInit();
+	
+	
+	public AbstractLocalNodeTest() throws Exception {
+		super();
+		this.name= getClass().getSimpleName();
+	}
+	
+	
+	@BeforeEach
+	public void initNode() throws Exception {
+		final RJContext context= ServiTests.getRJContext();
+		final RMIRegistry rmiRegistry= RMIRegistryManager.INSTANCE.getEmbeddedPrivateRegistry(
+				new NullProgressMonitor() );
+		
+		final RServiNodeConfig nodeConfig= new RServiNodeConfig();
+		nodeConfig.load(getEnvConfiguration("default"));
+		
+		final RServiNodeFactory nodeFactory= RServiImpl.createLocalNodeFactory(this.name, context);
+		nodeFactory.setRegistry(rmiRegistry);
+		nodeFactory.setConfig(nodeConfig);
+		
+		this.localR= nonNullAssert(
+				RServiImpl.createNodeManager(this.name, rmiRegistry, nodeFactory) );
+	}
+	
+	@AfterEach
+	public void dispose() throws Exception {
+		final List<Throwable> exceptions= new ArrayList<>();
+		disposeServis(exceptions);
+		
+		final var localR= this.localR;
+		if (localR != null) {
+			try {
+				localR.stop();
+			}
+			catch (final Exception e) {
+				exceptions.add(e);
+			}
+			finally {
+				this.localR= nonNullLateInit();
+			}
+		}
+		
+		reportErrors(exceptions);
+	}
+	
+	
+	protected RServi getServi(final String id)
+			throws NoSuchElementException, LoginException, StatusException {
+		final RServi servi= RServiUtils.getRServi(this.localR, id);
+		onServiGet(servi);
+		return servi;
+	}
+	
+}
diff --git a/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/LocalNodeTest.java b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/LocalNodeTest.java
index 60f8265..f4b3820 100644
--- a/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/LocalNodeTest.java
+++ b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/pool/LocalNodeTest.java
@@ -18,26 +18,17 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
-import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
-
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.NoSuchElementException;
 
 import javax.management.OperationsException;
 import javax.security.auth.login.LoginException;
 
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
-import org.eclipse.statet.jcommons.rmi.RMIRegistry;
-import org.eclipse.statet.jcommons.rmi.RMIRegistryManager;
 import org.eclipse.statet.jcommons.status.NullProgressMonitor;
 import org.eclipse.statet.jcommons.status.ProgressMonitor;
 import org.eclipse.statet.jcommons.status.Status;
@@ -46,73 +37,18 @@
 import org.eclipse.statet.rj.RjException;
 import org.eclipse.statet.rj.data.RDataUtils;
 import org.eclipse.statet.rj.data.UnexpectedRDataException;
-import org.eclipse.statet.rj.server.util.RJContext;
 import org.eclipse.statet.rj.servi.RServi;
-import org.eclipse.statet.rj.servi.RServiUtils;
-import org.eclipse.statet.rj.servi.node.RServiImpl;
-import org.eclipse.statet.rj.servi.node.RServiNodeConfig;
-import org.eclipse.statet.rj.servi.node.RServiNodeFactory;
-import org.eclipse.statet.rj.servi.node.RServiNodeManager;
 
 
 @EnabledIfEnvironmentVariable(named= "STATET_TEST_FILES", matches= ".+")
 @NonNullByDefault
-public class LocalNodeTest extends AbstractServiTest {
-	
-	
-	private RServiNodeManager localR= nonNullLateInit();
+public class LocalNodeTest extends AbstractLocalNodeTest {
 	
 	
 	public LocalNodeTest() throws Exception {
 	}
 	
 	
-	@BeforeEach
-	public void initNode() throws Exception {
-		final RJContext context= ServiTests.getRJContext();
-		final RMIRegistry rmiRegistry= RMIRegistryManager.INSTANCE.getEmbeddedPrivateRegistry(
-				new NullProgressMonitor() );
-		
-		final RServiNodeConfig nodeConfig= new RServiNodeConfig();
-		nodeConfig.load(getEnvConfiguration("default"));
-		
-		final RServiNodeFactory nodeFactory= RServiImpl.createLocalNodeFactory(
-				"LocalNodeTest", context );
-		nodeFactory.setRegistry(rmiRegistry);
-		nodeFactory.setConfig(nodeConfig);
-		
-		this.localR= nonNullAssert(
-				RServiImpl.createNodeManager("LocalNodeTest", rmiRegistry, nodeFactory) );
-	}
-	
-	@AfterEach
-	public void dispose() throws Exception {
-		final List<Throwable> exceptions= new ArrayList<>();
-		disposeServis(exceptions);
-		
-		final var localR= this.localR;
-		if (localR != null) {
-			try {
-				localR.stop();
-			}
-			catch (final Exception e) {
-				exceptions.add(e);
-			}
-			finally {
-				this.localR= nonNullLateInit();
-			}
-		}
-		
-		reportErrors(exceptions);
-	}
-	
-	private RServi getServi(final String id) throws NoSuchElementException, LoginException, StatusException {
-		final RServi servi= RServiUtils.getRServi(this.localR, id);
-		onServiGet(servi);
-		return servi;
-	}
-	
-	
 	@Test
 	public void test1() throws RjException, OperationsException,
 			NoSuchElementException, LoginException, StatusException, UnexpectedRDataException {
diff --git a/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/service/tests/RServiceTest.java b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/service/tests/RServiceTest.java
new file mode 100644
index 0000000..473f6ef
--- /dev/null
+++ b/servi/org.eclipse.statet.rj.servi-tests/src/org/eclipse/statet/rj/servi/service/tests/RServiceTest.java
@@ -0,0 +1,229 @@
+/*=============================================================================#
+ # Copyright (c) 2020 Stephan Wahlbrink and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.rj.servi.service.tests;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.status.NullProgressMonitor;
+import org.eclipse.statet.jcommons.status.ProgressMonitor;
+import org.eclipse.statet.jcommons.status.StatusException;
+
+import org.eclipse.statet.rj.data.RDataUtils;
+import org.eclipse.statet.rj.data.RObject;
+import org.eclipse.statet.rj.data.impl.DefaultRObjectFactory;
+import org.eclipse.statet.rj.servi.RServi;
+import org.eclipse.statet.rj.servi.pool.AbstractLocalNodeTest;
+
+
+@EnabledIfEnvironmentVariable(named= "STATET_TEST_FILES", matches= ".+")
+@NonNullByDefault
+public class RServiceTest extends AbstractLocalNodeTest {
+	
+	
+	private static Path tempDir= nonNullLateInit();
+	
+	
+	public RServiceTest() throws Exception {
+	}
+	
+	
+	@BeforeAll
+	public static void initTemp() throws IOException {
+		tempDir= Files.createTempDirectory("RServiceTest");
+	}
+	
+	
+	@BeforeEach
+	@Override
+	public void initNode() throws Exception {
+		super.initNode();
+		this.localR.start();
+	}
+	
+	
+	@Test
+	public void uploadFile_Stream() throws Exception {
+		final ProgressMonitor m= new NullProgressMonitor();
+		final RServi servi= getServi("uploadFile_Stream");
+		
+		final byte[] content= "Hello World!".getBytes(StandardCharsets.UTF_8);
+		RObject rContentObj;
+		
+		servi.uploadFile(new ByteArrayInputStream(content), content.length, "relative.txt", 0, m);
+		rContentObj= servi.createFunctionCall("readBin")
+				.addChar("con", "relative.txt")
+				.addInt("n", 100)
+				.addChar("what", "raw")
+				.evalData(m);
+		assertArrayEquals(content, RDataUtils.checkRRawVector(rContentObj).getData().toRawArray());
+		
+		final var rwd= Path.of(RDataUtils.checkSingleCharValue(
+				servi.createFunctionCall("getwd").evalData(m) ));
+		servi.uploadFile(new ByteArrayInputStream(content), content.length,
+				rwd.resolve("absolute.txt").toString(), 0, m );
+		rContentObj= servi.createFunctionCall("readBin")
+				.addChar("con", "absolute.txt")
+				.addInt("n", 100)
+				.addChar("what", "raw")
+				.evalData(m);
+		assertArrayEquals(content, RDataUtils.checkRRawVector(rContentObj).getData().toRawArray());
+		
+		assertThrows(StatusException.class, () -> {
+			servi.uploadFile(new ByteArrayInputStream(content), content.length,
+					"invalid://text.txt", 0, m );
+		});
+		
+		assertThrows(StatusException.class, () -> {
+			servi.uploadFile(new ByteArrayInputStream(content), content.length + 1,
+					"toosmall.txt", 0, m );
+		});
+		
+		closeServi(servi);
+	}
+	
+	@Test
+	public void uploadFile_Example1() throws Exception {
+		final ProgressMonitor m= new NullProgressMonitor();
+		final RServi servi= getServi("uploadFile_Example1");
+		
+		final byte[] content= "Hello World! - example1".getBytes(StandardCharsets.UTF_8);
+		final Path testFile= tempDir.resolve("uploadFile_Example1.txt");
+		
+		Files.write(testFile, content);
+		try (final var in= Files.newInputStream(testFile)) {
+			servi.uploadFile(in, Files.size(testFile), "test.txt", 0, m);
+		}
+		final var rContent= servi.createFunctionCall("readBin")
+				.addChar("con", "test.txt")
+				.addInt("n", 100)
+				.addChar("what", "raw")
+				.evalData(m);
+		closeServi(servi);
+		
+		assertArrayEquals(content, RDataUtils.checkRRawVector(rContent).getData().toRawArray());
+	}
+	
+	@Test
+	public void downloadFile_Stream() throws Exception {
+		final ProgressMonitor m= new NullProgressMonitor();
+		final RServi servi= getServi("downloadFile_Stream");
+		
+		final byte[] content= "Hello World! - example1".getBytes(StandardCharsets.UTF_8);
+		final ByteArrayOutputStream out= new ByteArrayOutputStream();
+		
+		servi.createFunctionCall("writeBin")
+				.add(DefaultRObjectFactory.INSTANCE.createRawVector(content))
+				.addChar("con", "relative.txt").addLogi("useBytes", true)
+				.evalVoid(m);
+		out.reset();
+		servi.downloadFile(out, "relative.txt", 0, m);
+		assertArrayEquals(content, out.toByteArray());
+		
+		final var rwd= Path.of(RDataUtils.checkSingleCharValue(
+				servi.createFunctionCall("getwd").evalData(m) ));
+		servi.createFunctionCall("writeBin")
+				.add(DefaultRObjectFactory.INSTANCE.createRawVector(content))
+				.addChar("con", "absolute.txt").addLogi("useBytes", true)
+				.evalVoid(m);
+		out.reset();
+		servi.downloadFile(out, rwd.resolve("absolute.txt").toString(), 0, m);
+		assertArrayEquals(content, out.toByteArray());
+		
+		assertThrows(StatusException.class, () -> {
+			out.reset();
+			servi.downloadFile(out, "invalid://text.txt", 0, m);
+		});
+		
+		assertThrows(StatusException.class, () -> {
+			out.reset();
+			servi.downloadFile(out, "missing.txt", 0, m);
+		});
+		
+		closeServi(servi);
+	}
+	
+	@Test
+	public void downloadFile_Array() throws Exception {
+		final ProgressMonitor m= new NullProgressMonitor();
+		final RServi servi= getServi("downloadFile_Stream");
+		
+		final byte[] content= "Hello World! - example1".getBytes(StandardCharsets.UTF_8);
+		byte[] rContent;
+		
+		servi.createFunctionCall("writeBin")
+				.add(DefaultRObjectFactory.INSTANCE.createRawVector(content))
+				.addChar("con", "relative.txt").addLogi("useBytes", true)
+				.evalVoid(m);
+		rContent= servi.downloadFile("relative.txt", 0, m);
+		assertArrayEquals(content, rContent);
+		
+		final var rwd= Path.of(RDataUtils.checkSingleCharValue(
+				servi.createFunctionCall("getwd").evalData(m) ));
+		servi.createFunctionCall("writeBin")
+				.add(DefaultRObjectFactory.INSTANCE.createRawVector(content))
+				.addChar("con", "absolute.txt").addLogi("useBytes", true)
+				.evalVoid(m);
+		rContent= servi.downloadFile(rwd.resolve("absolute.txt").toString(), 0, m);
+		assertArrayEquals(content, rContent);
+		
+		assertThrows(StatusException.class, () -> {
+			servi.downloadFile("invalid://text.txt", 0, m);
+		});
+		
+		assertThrows(StatusException.class, () -> {
+			servi.downloadFile("missing.txt", 0, m);
+		});
+		
+		closeServi(servi);
+	}
+	
+	@Test
+	public void downloadFile_Example1() throws Exception {
+		final ProgressMonitor m= new NullProgressMonitor();
+		final RServi servi= getServi("downloadFile_Example1");
+		
+		final byte[] content= "Hello World! - example1".getBytes(StandardCharsets.UTF_8);
+		final Path testFile= tempDir.resolve("downloadFile_Example1.txt");
+		
+		servi.createFunctionCall("writeBin")
+				.add(DefaultRObjectFactory.INSTANCE.createRawVector(content))
+				.addChar("con", "test.txt").addLogi("useBytes", true)
+				.evalVoid(m);
+		try (final var out= Files.newOutputStream(testFile)) {
+			servi.downloadFile(out, "test.txt", 0, m);
+		}
+		closeServi(servi);
+		final var fileText= Files.readAllBytes(testFile);
+		
+		assertArrayEquals(content, fileText);
+	}
+	
+}