From 74b5ce8fd4311f0d6f6a59e0636d343807b79d74 Mon Sep 17 00:00:00 2001
From: Robin Appelman <robin@icewind.nl>
Date: Wed, 27 Sep 2017 17:46:24 +0200
Subject: [PATCH] Some tests for the remote cloud api

Signed-off-by: Robin Appelman <robin@icewind.nl>
---
 .drone.yml                                    |  10 ++
 build/integration/config/behat.yml            |  13 +-
 .../features/bootstrap/BasicStructure.php     |   5 +
 .../features/bootstrap/RemoteContext.php      | 147 ++++++++++++++++++
 .../remoteapi_features/remote.feature         |  37 +++++
 lib/private/Remote/Api/OCS.php                |  21 ++-
 lib/private/Remote/Instance.php               |   3 +
 7 files changed, 232 insertions(+), 4 deletions(-)
 create mode 100644 build/integration/features/bootstrap/RemoteContext.php
 create mode 100644 build/integration/remoteapi_features/remote.feature

diff --git a/.drone.yml b/.drone.yml
index 186969ccd06..986c03e20d3 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -599,6 +599,15 @@ pipeline:
     when:
       matrix:
         TESTS: integration-trashbin
+  integration-remote-api:
+    image: nextcloudci/integration-php7.0:integration-php7.0-6
+    commands:
+      - ./occ maintenance:install --admin-pass=admin --data-dir=/dev/shm/nc_int
+      - cd build/integration
+      - ./run.sh remoteapi_features/remote.feature
+    when:
+      matrix:
+        TESTS: integration-remote-api
   acceptance-access-levels:
       image: nextcloudci/integration-php7.0:integration-php7.0-6
       commands:
@@ -801,6 +810,7 @@ matrix:
     - TESTS: integration-transfer-ownership-features
     - TESTS: integration-ldap-features
     - TESTS: integration-trashbin
+    - TESTS: integration-remote-api
     - TESTS: acceptance
       TESTS-ACCEPTANCE: access-levels
     - TESTS: acceptance
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 3573f9d6a6b..428d4d45b78 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -85,7 +85,18 @@ default:
               - admin
               - admin
             regular_user_password: what_for
-
+    remoteapi:
+      paths:
+        - %paths.base%/../remoteapi_features
+      contexts:
+        - FeatureContext:
+            baseUrl:  http://localhost:8080/ocs/
+            admin:
+              - admin
+              - admin
+            regular_user_password: 123456
+        - RemoteContext:
+            remote:  http://localhost:8080
   extensions:
       jarnaiz\JUnitFormatter\JUnitFormatterExtension:
           filename: report.xml
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index 03392ff6619..f2449242bee 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -62,6 +62,11 @@ trait BasicStructure {
 	/** @var string */
 	private $requestToken;
 
+	protected $adminUser;
+	protected $regularUser;
+	protected $localBaseUrl;
+	protected $remoteBaseUrl;
+
 	public function __construct($baseUrl, $admin, $regular_user_password) {
 
 		// Initialize your context here
diff --git a/build/integration/features/bootstrap/RemoteContext.php b/build/integration/features/bootstrap/RemoteContext.php
new file mode 100644
index 00000000000..7e413021381
--- /dev/null
+++ b/build/integration/features/bootstrap/RemoteContext.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+use Behat\Behat\Context\Context;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+/**
+ * Remote context.
+ */
+class RemoteContext implements Context {
+	/** @var \OC\Remote\Instance */
+	protected $remoteInstance;
+
+	/** @var \OC\Remote\Credentials */
+	protected $credentails;
+
+	/** @var \OC\Remote\User */
+	protected $userResult;
+
+	protected $remoteUrl;
+
+	protected $lastException;
+
+	public function __construct($remote) {
+		require_once __DIR__ . '/../../../../lib/base.php';
+		$this->remoteUrl = $remote;
+	}
+
+	protected function getApiClient() {
+		return new \OC\Remote\Api\OCS($this->remoteInstance, $this->credentails, \OC::$server->getHTTPClientService());
+	}
+
+	/**
+	 * @Given /^using remote server "(REMOTE|NON_EXISTING)"$/
+	 *
+	 * @param string $remoteServer "NON_EXISTING" or "REMOTE"
+	 */
+	public function selectRemoteInstance($remoteServer) {
+		if ($remoteServer == "REMOTE") {
+			$baseUri = $this->remoteUrl;
+		} else {
+			$baseUri = 'nonexistingnextcloudserver.local';
+		}
+		$this->lastException = null;
+		try {
+			$this->remoteInstance = new \OC\Remote\Instance($baseUri, \OC::$server->getMemCacheFactory()->createLocal(), \OC::$server->getHTTPClientService());
+			// trigger the status request
+			$this->remoteInstance->getProtocol();
+		} catch (\Exception $e) {
+			$this->lastException = $e;
+		}
+	}
+
+	/**
+	 * @Then /^the remote version should be "([^"]*)"$/
+	 * @param string $version
+	 */
+	public function theRemoteVersionShouldBe($version) {
+		if ($version === '__current_version__') {
+			$version = \OC::$server->getConfig()->getSystemValue('version', '0.0.0.0');
+		}
+
+		PHPUnit_Framework_Assert::assertEquals($version, $this->remoteInstance->getVersion());
+	}
+
+	/**
+	 * @Then /^the remote protocol should be "([^"]*)"$/
+	 * @param string $protocol
+	 */
+	public function theRemoteProtocolShouldBe($protocol) {
+		PHPUnit_Framework_Assert::assertEquals($protocol, $this->remoteInstance->getProtocol());
+	}
+
+	/**
+	 * @Given /^using credentials "([^"]*)", "([^"]*)"/
+	 * @param string $user
+	 * @param string $password
+	 */
+	public function usingCredentials($user, $password) {
+		$this->credentails = new \OC\Remote\Credentials($user, $password);
+	}
+
+	/**
+	 * @When /^getting the remote user info for "([^"]*)"$/
+	 * @param string $user
+	 */
+	public function remoteUserInfo($user) {
+		$this->lastException = null;
+		try {
+			$this->userResult = $this->getApiClient()->getUser($user);
+		} catch (\Exception $e) {
+			$this->lastException = $e;
+		}
+	}
+
+	/**
+	 * @Then /^the remote user should have userid "([^"]*)"$/
+	 * @param string $user
+	 */
+	public function remoteUserId($user) {
+		PHPUnit_Framework_Assert::assertEquals($user, $this->userResult->getUserId());
+	}
+
+	/**
+	 * @Then /^the request should throw a "([^"]*)"$/
+	 * @param string $class
+	 */
+	public function lastError($class) {
+		PHPUnit_Framework_Assert::assertEquals($class, get_class($this->lastException));
+	}
+
+	/**
+	 * @Then /^the capability "([^"]*)" is "([^"]*)"$/
+	 * @param string $key
+	 * @param string $value
+	 */
+	public function hasCapability($key, $value) {
+		$capabilities = $this->getApiClient()->getCapabilities();
+		$current = $capabilities;
+		$parts = explode('.', $key);
+		foreach ($parts as $part) {
+			if ($current !== null) {
+				$current = isset($current[$part]) ? $current[$part] : null;
+			}
+		}
+		PHPUnit_Framework_Assert::assertEquals($value, $current);
+	}
+}
diff --git a/build/integration/remoteapi_features/remote.feature b/build/integration/remoteapi_features/remote.feature
new file mode 100644
index 00000000000..72daf8226cd
--- /dev/null
+++ b/build/integration/remoteapi_features/remote.feature
@@ -0,0 +1,37 @@
+Feature: remote
+
+  Scenario: Get status of remote server
+    Given using remote server "REMOTE"
+    Then the remote version should be "__current_version__"
+    And the remote protocol should be "http"
+
+  Scenario: Get status of a non existing server
+    Given using remote server "NON_EXISTING"
+    Then the request should throw a "OC\Remote\Api\NotFoundException"
+
+  Scenario: Get user info for a remote user
+    Given using remote server "REMOTE"
+    And user "user0" exists
+    And using credentials "user0", "123456"
+    When getting the remote user info for "user0"
+    Then the remote user should have userid "user0"
+
+  Scenario: Get user info for a non existing remote user
+    Given using remote server "REMOTE"
+    And user "user0" exists
+    And using credentials "user0", "123456"
+    When getting the remote user info for "user_non_existing"
+    Then the request should throw a "OC\Remote\Api\NotFoundException"
+
+  Scenario: Get user info with invalid credentials
+    Given using remote server "REMOTE"
+    And user "user0" exists
+    And using credentials "user0", "invalid"
+    When getting the remote user info for "user0"
+    Then the request should throw a "OC\ForbiddenException"
+
+  Scenario: Get capability of remote server
+    Given using remote server "REMOTE"
+    And user "user0" exists
+    And using credentials "user0", "invalid"
+    Then the capability "theming.name" is "Nextcloud"
diff --git a/lib/private/Remote/Api/OCS.php b/lib/private/Remote/Api/OCS.php
index d7027ad3f4b..f02538ad03b 100644
--- a/lib/private/Remote/Api/OCS.php
+++ b/lib/private/Remote/Api/OCS.php
@@ -22,6 +22,7 @@
 namespace OC\Remote\Api;
 
 
+use GuzzleHttp\Exception\ClientException;
 use OC\ForbiddenException;
 use OC\Remote\User;
 use OCP\API;
@@ -39,8 +40,18 @@ class OCS extends ApiBase {
 	 * @throws \Exception
 	 */
 	protected function request($method, $url, array $body = [], array $query = [], array $headers = []) {
-		$response = json_decode(parent::request($method, '/ocs/v2.php/' . $url, $body, $query, $headers), true);
-		if (!isset($result['ocs']) || !isset($result['ocs']['meta'])) {
+		try {
+			$response = json_decode(parent::request($method, '/ocs/v2.php/' . $url, $body, $query, $headers), true);
+		} catch (ClientException $e) {
+			if ($e->getResponse()->getStatusCode() === 404) {
+				throw new NotFoundException();
+			} else if ($e->getResponse()->getStatusCode() === 403 || $e->getResponse()->getStatusCode() === 401) {
+				throw new ForbiddenException();
+			} else {
+				throw $e;
+			}
+		}
+		if (!isset($response['ocs']) || !isset($response['ocs']['meta'])) {
 			throw new \Exception('Invalid ocs response');
 		}
 		if ($response['ocs']['meta']['statuscode'] === API::RESPOND_UNAUTHORISED) {
@@ -60,7 +71,11 @@ class OCS extends ApiBase {
 		return new User($this->request('get', 'cloud/users/' . $userId));
 	}
 
+	/**
+	 * @return array The capabilities in the form of [$appId => [$capability => $value]]
+	 */
 	public function getCapabilities() {
-		return $this->request('get', 'cloud/capabilities');
+		$result = $this->request('get', 'cloud/capabilities');
+		return $result['capabilities'];
 	}
 }
diff --git a/lib/private/Remote/Instance.php b/lib/private/Remote/Instance.php
index 3e8f22f4df4..f61fbe202ab 100644
--- a/lib/private/Remote/Instance.php
+++ b/lib/private/Remote/Instance.php
@@ -21,6 +21,7 @@
 
 namespace OC\Remote;
 
+use OC\Remote\Api\NotFoundException;
 use OCP\Http\Client\IClientService;
 use OCP\ICache;
 
@@ -111,6 +112,8 @@ class Instance {
 			if ($status) {
 				$this->cache->set($key, $status, 5 * 60);
 				$this->status = $status;
+			} else {
+				throw new NotFoundException('Remote server not found at address ' . $this->url);
 			}
 		}
 		return $status;
-- 
GitLab