diff --git a/config/config.sample.php b/config/config.sample.php
index 60932ab7d9b77df499c73dcbb86161e63a62c073..f17473b7d8014b88a42abfb8c7e08759d9664b58 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -576,6 +576,15 @@ $CONFIG = array(
  */
 'appstoreurl' => 'https://api.owncloud.com/v1',
 
+/**
+ * Whether to experimental apps in the appstore interface
+ *
+ * Experimental apps are not checked for security issues and are new or known
+ * to be unstable and under heavy development. Installing these can cause data
+ * loss or security breaches.
+ */
+'appstore.experimental.enabled' => false,
+
 /**
  * Use the ``apps_paths`` parameter to set the location of the Apps directory,
  * which should be scanned for available apps, and where user-specific apps
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index 880a276c7253fe1e9ba59dcc1d211133f6162942..87a6a9216d2492ab0f415c83d808f34988f71d39 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -123,7 +123,7 @@
 					if(OC_User::isAdminUser(OC_User::getUser())):
 				?>
 					<li id="apps-management">
-						<a href="<?php print_unescaped(OC_Helper::linkToRoute('settings_apps')); ?>" tabindex="4"
+						<a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4"
 							<?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>>
 							<img class="app-icon svg" alt="" src="<?php print_unescaped(OC_Helper::imagePath('settings', 'apps.svg')); ?>">
 							<div class="icon-loading-dark" style="display:none;"></div>
diff --git a/lib/private/app.php b/lib/private/app.php
index 84bc23608fb0a2c56644938ffef014cf936b2887..ee92f8f5adc9e7baa8a6794b952e00a2dcad4e3d 100644
--- a/lib/private/app.php
+++ b/lib/private/app.php
@@ -61,6 +61,7 @@ class OC_App {
 	static private $loadedApps = array();
 	static private $altLogin = array();
 	private static $shippedApps = null;
+	const officialApp = 200;
 
 	/**
 	 * clean the appId
@@ -306,8 +307,13 @@ class OC_App {
 	 * @return int
 	 */
 	public static function downloadApp($app) {
-		$appData= OCSClient::getApplication($app);
-		$download= OCSClient::getApplicationDownload($app, 1);
+		$ocsClient = new OCSClient(
+			\OC::$server->getHTTPClientService(),
+			\OC::$server->getConfig(),
+			\OC::$server->getLogger()
+		);
+		$appData = $ocsClient->getApplication($app);
+		$download= $ocsClient->getApplicationDownload($app);
 		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
 			// Replace spaces in download link without encoding entire URL
 			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
@@ -780,8 +786,9 @@ class OC_App {
 	}
 
 	/**
-	 * Lists all apps, this is used in apps.php
+	 * List all apps, this is used in apps.php
 	 *
+	 * @param bool $onlyLocal
 	 * @return array
 	 */
 	public static function listAllApps($onlyLocal = false) {
@@ -819,8 +826,7 @@ class OC_App {
 
 				if (isset($info['shipped']) and ($info['shipped'] == 'true')) {
 					$info['internal'] = true;
-					$info['internallabel'] = (string)$l->t('Recommended');
-					$info['internalclass'] = 'recommendedapp';
+					$info['level'] = self::officialApp;
 					$info['removable'] = false;
 				} else {
 					$info['internal'] = false;
@@ -845,7 +851,7 @@ class OC_App {
 			}
 		}
 		if ($onlyLocal) {
-			$remoteApps = array();
+			$remoteApps = [];
 		} else {
 			$remoteApps = OC_App::getAppstoreApps();
 		}
@@ -865,34 +871,6 @@ class OC_App {
 		} else {
 			$combinedApps = $appList;
 		}
-		// bring the apps into the right order with a custom sort function
-		usort($combinedApps, function ($a, $b) {
-
-			// priority 1: active
-			if ($a['active'] != $b['active']) {
-				return $b['active'] - $a['active'];
-			}
-
-			// priority 2: shipped
-			$aShipped = (array_key_exists('shipped', $a) && $a['shipped'] === 'true') ? 1 : 0;
-			$bShipped = (array_key_exists('shipped', $b) && $b['shipped'] === 'true') ? 1 : 0;
-			if ($aShipped !== $bShipped) {
-				return ($bShipped - $aShipped);
-			}
-
-			// priority 3: recommended
-			$internalClassA = isset($a['internalclass']) ? $a['internalclass'] : '';
-			$internalClassB = isset($b['internalclass']) ? $b['internalclass'] : '';
-			if ($internalClassA != $internalClassB) {
-				$aTemp = ($internalClassA == 'recommendedapp' ? 1 : 0);
-				$bTemp = ($internalClassB == 'recommendedapp' ? 1 : 0);
-				return ($bTemp - $aTemp);
-			}
-
-			// priority 4: alphabetical
-			return strcasecmp($a['name'], $b['name']);
-
-		});
 
 		return $combinedApps;
 	}
@@ -913,15 +891,24 @@ class OC_App {
 	}
 
 	/**
-	 * get a list of all apps on apps.owncloud.com
-	 *
-	 * @return array|false multi-dimensional array of apps.
-	 *     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
+	 * Get a list of all apps on the appstore
+	 * @param string $filter
+	 * @param string $category
+	 * @return array|bool  multi-dimensional array of apps.
+	 *                     Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
 	 */
 	public static function getAppstoreApps($filter = 'approved', $category = null) {
-		$categories = array($category);
+		$categories = [$category];
+
+		$ocsClient = new OCSClient(
+			\OC::$server->getHTTPClientService(),
+			\OC::$server->getConfig(),
+			\OC::$server->getLogger()
+		);
+
+
 		if (is_null($category)) {
-			$categoryNames = OCSClient::getCategories();
+			$categoryNames = $ocsClient->getCategories();
 			if (is_array($categoryNames)) {
 				// Check that categories of apps were retrieved correctly
 				if (!$categories = array_keys($categoryNames)) {
@@ -933,34 +920,36 @@ class OC_App {
 		}
 
 		$page = 0;
-		$remoteApps = OCSClient::getApplications($categories, $page, $filter);
-		$app1 = array();
+		$remoteApps = $ocsClient->getApplications($categories, $page, $filter);
+		$apps = [];
 		$i = 0;
 		$l = \OC::$server->getL10N('core');
 		foreach ($remoteApps as $app) {
 			$potentialCleanId = self::getInternalAppIdByOcs($app['id']);
 			// enhance app info (for example the description)
-			$app1[$i] = OC_App::parseAppInfo($app);
-			$app1[$i]['author'] = $app['personid'];
-			$app1[$i]['ocs_id'] = $app['id'];
-			$app1[$i]['internal'] = 0;
-			$app1[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
-			$app1[$i]['update'] = false;
-			$app1[$i]['groups'] = false;
-			$app1[$i]['score'] = $app['score'];
-			$app1[$i]['removable'] = false;
+			$apps[$i] = OC_App::parseAppInfo($app);
+			$apps[$i]['author'] = $app['personid'];
+			$apps[$i]['ocs_id'] = $app['id'];
+			$apps[$i]['internal'] = 0;
+			$apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
+			$apps[$i]['update'] = false;
+			$apps[$i]['groups'] = false;
+			$apps[$i]['score'] = $app['score'];
+			$apps[$i]['removable'] = false;
 			if ($app['label'] == 'recommended') {
-				$app1[$i]['internallabel'] = (string)$l->t('Recommended');
-				$app1[$i]['internalclass'] = 'recommendedapp';
+				$apps[$i]['internallabel'] = (string)$l->t('Recommended');
+				$apps[$i]['internalclass'] = 'recommendedapp';
 			}
 
 			$i++;
 		}
 
-		if (empty($app1)) {
+
+
+		if (empty($apps)) {
 			return false;
 		} else {
-			return $app1;
+			return $apps;
 		}
 	}
 
@@ -1084,7 +1073,12 @@ class OC_App {
 	public static function installApp($app) {
 		$l = \OC::$server->getL10N('core');
 		$config = \OC::$server->getConfig();
-		$appData=OCSClient::getApplication($app);
+		$ocsClient = new OCSClient(
+			\OC::$server->getHTTPClientService(),
+			$config,
+			\OC::$server->getLogger()
+		);
+		$appData = $ocsClient->getApplication($app);
 
 		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
 		if (!is_numeric($app)) {
diff --git a/lib/private/app/appmanager.php b/lib/private/app/appmanager.php
index 2a147d4de6f4b99bf067eeae6743ce1998064188..c9d4a777c4aaf72b56dcb4b15496fa696cd718d2 100644
--- a/lib/private/app/appmanager.php
+++ b/lib/private/app/appmanager.php
@@ -203,7 +203,7 @@ class AppManager implements IAppManager {
 	/**
 	 * Clear the cached list of apps when enabling/disabling an app
 	 */
-	protected function clearAppsCache() {
+	public function clearAppsCache() {
 		$settingsMemCache = $this->memCacheFactory->create('settings');
 		$settingsMemCache->clear('listApps');
 	}
diff --git a/lib/private/installer.php b/lib/private/installer.php
index e30344b1b10f5cec832607c408d29439c2864490..41f13f0f5f9acc8ce1aab15eddefbb7033144c16 100644
--- a/lib/private/installer.php
+++ b/lib/private/installer.php
@@ -222,8 +222,13 @@ class OC_Installer{
 	 * @throws Exception
 	 */
 	public static function updateAppByOCSId($ocsId) {
-		$appData = OCSClient::getApplication($ocsId);
-		$download = OCSClient::getApplicationDownload($ocsId, 1);
+		$ocsClient = new OCSClient(
+			\OC::$server->getHTTPClientService(),
+			\OC::$server->getConfig(),
+			\OC::$server->getLogger()
+		);
+		$appData = $ocsClient->getApplication($ocsId);
+		$download = $ocsClient->getApplicationDownload($ocsId);
 
 		if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
 			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
@@ -385,8 +390,12 @@ class OC_Installer{
 		$ocsid=OC_Appconfig::getValue( $app, 'ocsid', '');
 
 		if($ocsid<>'') {
-
-			$ocsdata=OCSClient::getApplication($ocsid);
+			$ocsClient = new OCSClient(
+				\OC::$server->getHTTPClientService(),
+				\OC::$server->getConfig(),
+				\OC::$server->getLogger()
+			);
+			$ocsdata = $ocsClient->getApplication($ocsid);
 			$ocsversion= (string) $ocsdata['version'];
 			$currentversion=OC_App::getAppVersion($app);
 			if (version_compare($ocsversion, $currentversion, '>')) {
diff --git a/lib/private/ocsclient.php b/lib/private/ocsclient.php
index f69426ddafeb8dff624c296d5f056a50480650a6..30747c0d5fb237fa2e231ec51d87c9be9e7b353e 100644
--- a/lib/private/ocsclient.php
+++ b/lib/private/ocsclient.php
@@ -32,36 +32,52 @@
 
 namespace OC;
 
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\ILogger;
+
 /**
- * This class provides an easy way for apps to store config values in the
- * database.
+ * Class OCSClient is a class for communication with the ownCloud appstore
+ *
+ * @package OC
  */
-
 class OCSClient {
+	/** @var IClientService */
+	private $httpClientService;
+	/** @var IConfig */
+	private $config;
+	/** @var ILogger */
+	private $logger;
+
+	/**
+	 * @param IClientService $httpClientService
+	 * @param IConfig $config
+	 * @param ILogger $logger
+	 */
+	public function __construct(IClientService $httpClientService,
+								IConfig $config,
+								ILogger $logger) {
+		$this->httpClientService = $httpClientService;
+		$this->config = $config;
+		$this->logger = $logger;
+	}
 
 	/**
 	 * Returns whether the AppStore is enabled (i.e. because the AppStore is disabled for EE)
 	 *
 	 * @return bool
 	 */
-	public static function isAppStoreEnabled() {
-		if (\OC::$server->getConfig()->getSystemValue('appstoreenabled', true) === false ) {
-			return false;
-		}
-
-		return true;
+	public function isAppStoreEnabled() {
+		return $this->config->getSystemValue('appstoreenabled', true) === true;
 	}
 
 	/**
 	 * Get the url of the OCS AppStore server.
 	 *
 	 * @return string of the AppStore server
-	 *
-	 * This function returns the url of the OCS AppStore server. It´s possible
-	 * to set it in the config file or it will fallback to the default
 	 */
-	private static function getAppStoreURL() {
-		return \OC::$server->getConfig()->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1');
+	private function getAppStoreUrl() {
+		return $this->config->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1');
 	}
 
 	/**
@@ -71,36 +87,50 @@ class OCSClient {
 	 * @note returns NULL if config value appstoreenabled is set to false
 	 * This function returns a list of all the application categories on the OCS server
 	 */
-	public static function getCategories() {
-		if (!self::isAppStoreEnabled()) {
+	public function getCategories() {
+		if (!$this->isAppStoreEnabled()) {
 			return null;
 		}
-		$url = self::getAppStoreURL() . '/content/categories';
 
-		$client = \OC::$server->getHTTPClientService()->newClient();
+		$client = $this->httpClientService->newClient();
 		try {
-			$response = $client->get($url, ['timeout' => 5]);
+			$response = $client->get(
+				$this->getAppStoreUrl() . '/content/categories',
+				[
+					'timeout' => 5,
+				]
+			);
 		} catch(\Exception $e) {
-			return null;
-		}
-
-		if($response->getStatusCode() !== 200) {
+			$this->logger->error(
+				sprintf('Could not get categories: %s', $e->getMessage()),
+				[
+					'app' => 'core',
+				]
+			);
 			return null;
 		}
 
 		$loadEntities = libxml_disable_entity_loader(true);
-		$data = simplexml_load_string($response->getBody());
+		$data = @simplexml_load_string($response->getBody());
 		libxml_disable_entity_loader($loadEntities);
 
+		if($data === false) {
+			$this->logger->error(
+				'Could not get categories, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+			return null;
+		}
+
 		$tmp = $data->data;
 		$cats = [];
 
 		foreach ($tmp->category as $value) {
-
 			$id = (int)$value->id;
 			$name = (string)$value->name;
 			$cats[$id] = $name;
-
 		}
 
 		return $cats;
@@ -108,50 +138,63 @@ class OCSClient {
 
 	/**
 	 * Get all the applications from the OCS server
-	 *
-	 * @return array|null an array of application data or null
-	 *
-	 * This function returns a list of all the applications on the OCS server
-	 * @param array|string $categories
+	 * @param array $categories
 	 * @param int $page
 	 * @param string $filter
+	 * @return array An array of application data
 	 */
-	public static function getApplications($categories, $page, $filter) {
-		if (!self::isAppStoreEnabled()) {
-			return (array());
+	public function getApplications(array $categories, $page, $filter) {
+		if (!$this->isAppStoreEnabled()) {
+			return [];
 		}
 
-		if (is_array($categories)) {
-			$categoriesString = implode('x', $categories);
-		} else {
-			$categoriesString = $categories;
-		}
-
-		$version = '&version=' . implode('x', \OC_Util::getVersion());
-		$filterUrl = '&filter=' . urlencode($filter);
-		$url = self::getAppStoreURL() . '/content/data?categories=' . urlencode($categoriesString)
-			. '&sortmode=new&page=' . urlencode($page) . '&pagesize=100' . $filterUrl . $version;
-		$apps = [];
-
-		$client = \OC::$server->getHTTPClientService()->newClient();
+		$client = $this->httpClientService->newClient();
 		try {
-			$response = $client->get($url, ['timeout' => 5]);
+			$response = $client->get(
+				$this->getAppStoreUrl() . '/content/data',
+				[
+					'timeout' => 5,
+					'query' => [
+						'version' => implode('x', \OC_Util::getVersion()),
+						'filter' => $filter,
+						'categories' => implode('x', $categories),
+						'sortmode' => 'new',
+						'page' => $page,
+						'pagesize' => 100,
+						'approved' => $filter
+					],
+				]
+			);
 		} catch(\Exception $e) {
-			return null;
-		}
-
-		if($response->getStatusCode() !== 200) {
-			return null;
+			$this->logger->error(
+				sprintf('Could not get applications: %s', $e->getMessage()),
+				[
+					'app' => 'core',
+				]
+			);
+			return [];
 		}
 
 		$loadEntities = libxml_disable_entity_loader(true);
-		$data = simplexml_load_string($response->getBody());
+		$data = @simplexml_load_string($response->getBody());
 		libxml_disable_entity_loader($loadEntities);
 
+		if($data === false) {
+			$this->logger->error(
+				'Could not get applications, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+			return [];
+		}
+
 		$tmp = $data->data->content;
 		$tmpCount = count($tmp);
+
+		$apps = [];
 		for ($i = 0; $i < $tmpCount; $i++) {
-			$app = array();
+			$app = [];
 			$app['id'] = (string)$tmp[$i]->id;
 			$app['name'] = (string)$tmp[$i]->name;
 			$app['label'] = (string)$tmp[$i]->label;
@@ -167,9 +210,11 @@ class OCSClient {
 			$app['description'] = (string)$tmp[$i]->description;
 			$app['score'] = (string)$tmp[$i]->score;
 			$app['downloads'] = (int)$tmp[$i]->downloads;
+			$app['level'] = (int)$tmp[$i]->approved;
 
 			$apps[] = $app;
 		}
+
 		return $apps;
 	}
 
@@ -182,84 +227,111 @@ class OCSClient {
 	 *
 	 * This function returns an applications from the OCS server
 	 */
-	public static function getApplication($id) {
-		if (!self::isAppStoreEnabled()) {
+	public function getApplication($id) {
+		if (!$this->isAppStoreEnabled()) {
 			return null;
 		}
-		$url = self::getAppStoreURL() . '/content/data/' . urlencode($id);
-		$client = \OC::$server->getHTTPClientService()->newClient();
+
+		$client = $this->httpClientService->newClient();
 		try {
-			$response = $client->get($url, ['timeout' => 5]);
+			$response = $client->get(
+				$this->getAppStoreUrl() . '/content/data/' . urlencode($id),
+				[
+					'timeout' => 5,
+				]
+			);
 		} catch(\Exception $e) {
-			return null;
-		}
-
-		if($response->getStatusCode() !== 200) {
+			$this->logger->error(
+				sprintf('Could not get application: %s', $e->getMessage()),
+				[
+					'app' => 'core',
+				]
+			);
 			return null;
 		}
 
 		$loadEntities = libxml_disable_entity_loader(true);
-		$data = simplexml_load_string($response->getBody());
+		$data = @simplexml_load_string($response->getBody());
 		libxml_disable_entity_loader($loadEntities);
 
-		$tmp = $data->data->content;
-		if (is_null($tmp)) {
-			\OC_Log::write('core', 'Invalid OCS content returned for app ' . $id, \OC_Log::FATAL);
+		if($data === false) {
+			$this->logger->error(
+				'Could not get application, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
 			return null;
 		}
+
+		$tmp = $data->data->content;
+
 		$app = [];
-		$app['id'] = $tmp->id;
-		$app['name'] = $tmp->name;
-		$app['version'] = $tmp->version;
-		$app['type'] = $tmp->typeid;
-		$app['label'] = $tmp->label;
-		$app['typename'] = $tmp->typename;
-		$app['personid'] = $tmp->personid;
-		$app['detailpage'] = $tmp->detailpage;
-		$app['preview1'] = $tmp->smallpreviewpic1;
-		$app['preview2'] = $tmp->smallpreviewpic2;
-		$app['preview3'] = $tmp->smallpreviewpic3;
+		$app['id'] = (int)$tmp->id;
+		$app['name'] = (string)$tmp->name;
+		$app['version'] = (string)$tmp->version;
+		$app['type'] = (string)$tmp->typeid;
+		$app['label'] = (string)$tmp->label;
+		$app['typename'] = (string)$tmp->typename;
+		$app['personid'] = (string)$tmp->personid;
+		$app['detailpage'] = (string)$tmp->detailpage;
+		$app['preview1'] = (string)$tmp->smallpreviewpic1;
+		$app['preview2'] = (string)$tmp->smallpreviewpic2;
+		$app['preview3'] = (string)$tmp->smallpreviewpic3;
 		$app['changed'] = strtotime($tmp->changed);
-		$app['description'] = $tmp->description;
-		$app['detailpage'] = $tmp->detailpage;
-		$app['score'] = $tmp->score;
+		$app['description'] = (string)$tmp->description;
+		$app['detailpage'] = (string)$tmp->detailpage;
+		$app['score'] = (int)$tmp->score;
 
 		return $app;
 	}
 
 	/**
 	 * Get the download url for an application from the OCS server
-	 *
+	 * @param $id
 	 * @return array|null an array of application data or null
-	 *
-	 * This function returns an download url for an applications from the OCS server
-	 * @param string $id
-	 * @param integer $item
 	 */
-	public static function getApplicationDownload($id, $item) {
-		if (!self::isAppStoreEnabled()) {
+	public function getApplicationDownload($id) {
+		if (!$this->isAppStoreEnabled()) {
 			return null;
 		}
-		$url = self::getAppStoreURL() . '/content/download/' . urlencode($id) . '/' . urlencode($item);
-		$client = \OC::$server->getHTTPClientService()->newClient();
+		$url = $this->getAppStoreUrl() . '/content/download/' . urlencode($id) . '/1';
+		$client = $this->httpClientService->newClient();
 		try {
-			$response = $client->get($url, ['timeout' => 5]);
+			$response = $client->get(
+				$url,
+				[
+					'timeout' => 5,
+				]
+			);
 		} catch(\Exception $e) {
-			return null;
-		}
-
-		if($response->getStatusCode() !== 200) {
+			$this->logger->error(
+				sprintf('Could not get application download URL: %s', $e->getMessage()),
+				[
+					'app' => 'core',
+				]
+			);
 			return null;
 		}
 
 		$loadEntities = libxml_disable_entity_loader(true);
-		$data = simplexml_load_string($response->getBody());
+		$data = @simplexml_load_string($response->getBody());
 		libxml_disable_entity_loader($loadEntities);
 
+		if($data === false) {
+			$this->logger->error(
+				'Could not get application download URL, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+			return null;
+		}
+
 		$tmp = $data->data->content;
-		$app = array();
+		$app = [];
 		if (isset($tmp->downloadlink)) {
-			$app['downloadlink'] = $tmp->downloadlink;
+			$app['downloadlink'] = (string)$tmp->downloadlink;
 		} else {
 			$app['downloadlink'] = '';
 		}
diff --git a/lib/private/server.php b/lib/private/server.php
index 8c5169f229e39b0fa3992891f3823cfe885a72aa..cfdbd800a77c6c21eb977f733136f99dd42edfad 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -391,6 +391,13 @@ class Server extends SimpleContainer implements IServerContainer {
 				new \OC_Defaults()
 			);
 		});
+		$this->registerService('OcsClient', function(Server $c) {
+			return new OCSClient(
+				$this->getHTTPClientService(),
+				$this->getConfig(),
+				$this->getLogger()
+			);
+		});
 	}
 
 	/**
@@ -836,6 +843,13 @@ class Server extends SimpleContainer implements IServerContainer {
 		return $this->webRoot;
 	}
 
+	/**
+	 * @return \OC\OCSClient
+	 */
+	public function getOcsClient() {
+		return $this->query('OcsClient');
+	}
+
 	/**
 	 * @return \OCP\IDateTimeZone
 	 */
diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php
index ee1412fba7437b736e4c9ec32c2b0931195e854a..448276ca7feb6ea4dce97e761a3182fd76855098 100644
--- a/lib/private/templatelayout.php
+++ b/lib/private/templatelayout.php
@@ -107,7 +107,7 @@ class OC_TemplateLayout extends OC_Template {
 			$userDisplayName = OC_User::getDisplayName();
 			$this->assign('user_displayname', $userDisplayName);
 			$this->assign('user_uid', OC_User::getUser());
-			$this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), OC_Helper::linkToRoute('settings_apps')) === 0 );
+			$this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), \OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')) === 0 );
 			$this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true));
 			$this->assign('userAvatarSet', \OC_Helper::userAvatarSet(OC_User::getUser()));
 		} else if ($renderAs == 'error') {
diff --git a/lib/public/app/iappmanager.php b/lib/public/app/iappmanager.php
index f50a7f64174ea65cb0a3a701ede81cf2c4fef9f9..69b8c335d6728ae97fe79a781bd219f5a28b2cdc 100644
--- a/lib/public/app/iappmanager.php
+++ b/lib/public/app/iappmanager.php
@@ -78,4 +78,9 @@ interface IAppManager {
 	 * @return string[]
 	 */
 	public function getInstalledApps();
+
+	/**
+	 * Clear the cached list of apps when enabling/disabling an app
+	 */
+	public function clearAppsCache();
 }
diff --git a/settings/application.php b/settings/application.php
index b4596037964be76b14fa036e2ee94d3d5d5e80a5..07a458d249f5d52e6423ae4b929e87ba1f0d04cc 100644
--- a/settings/application.php
+++ b/settings/application.php
@@ -71,7 +71,10 @@ class Application extends App {
 				$c->query('Request'),
 				$c->query('L10N'),
 				$c->query('Config'),
-				$c->query('ICacheFactory')
+				$c->query('ICacheFactory'),
+				$c->query('INavigationManager'),
+				$c->query('IAppManager'),
+				$c->query('OcsClient')
 			);
 		});
 		$container->registerService('SecuritySettingsController', function(IContainer $c) {
@@ -191,6 +194,15 @@ class Application extends App {
 		$container->registerService('ClientService', function(IContainer $c) {
 			return $c->query('ServerContainer')->getHTTPClientService();
 		});
+		$container->registerService('INavigationManager', function(IContainer $c) {
+			return $c->query('ServerContainer')->getNavigationManager();
+		});
+		$container->registerService('IAppManager', function(IContainer $c) {
+			return $c->query('ServerContainer')->getAppManager();
+		});
+		$container->registerService('OcsClient', function(IContainer $c) {
+			return $c->query('ServerContainer')->getOcsClient();
+		});
 		$container->registerService('Util', function(IContainer $c) {
 			return new \OC_Util();
 		});
diff --git a/settings/apps.php b/settings/apps.php
deleted file mode 100644
index 7245b6610e05cab90aee33705d79cd03268cd7e2..0000000000000000000000000000000000000000
--- a/settings/apps.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Frank Karlitschek <frank@owncloud.org>
- * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
- * @author Lukas Reschke <lukas@owncloud.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @copyright Copyright (c) 2015, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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, version 3,
- * along with this program.  If not, see <http://www.gnu.org/licenses/>
- *
- */
-
-OC_Util::checkAdminUser();
-\OC::$server->getSession()->close();
-
-// Load the files we need
-\OC_Util::addVendorScript('handlebars/handlebars');
-\OCP\Util::addScript("settings", "settings");
-\OCP\Util::addStyle("settings", "settings");
-\OC_Util::addVendorScript('select2/select2');
-\OC_Util::addVendorStyle('select2/select2');
-\OCP\Util::addScript("settings", "apps");
-\OC_App::setActiveNavigationEntry( "core_apps" );
-
-$tmpl = new OC_Template( "settings", "apps", "user" );
-$tmpl->printPage();
-
diff --git a/settings/controller/appsettingscontroller.php b/settings/controller/appsettingscontroller.php
index 9a85f6d3b97eec0eb473138aae8d1a71561066f5..f1b62bb1d3839e01c3a3e9af4283e105adaf71f8 100644
--- a/settings/controller/appsettingscontroller.php
+++ b/settings/controller/appsettingscontroller.php
@@ -27,8 +27,13 @@ namespace OC\Settings\Controller;
 use OC\App\DependencyAnalyzer;
 use OC\App\Platform;
 use OC\OCSClient;
+use OCP\App\IAppManager;
 use \OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\TemplateResponse;
 use OCP\ICacheFactory;
+use OCP\INavigationManager;
 use OCP\IRequest;
 use OCP\IL10N;
 use OCP\IConfig;
@@ -44,6 +49,12 @@ class AppSettingsController extends Controller {
 	private $config;
 	/** @var \OCP\ICache */
 	private $cache;
+	/** @var INavigationManager */
+	private $navigationManager;
+	/** @var IAppManager */
+	private $appManager;
+	/** @var OCSClient */
+	private $ocsClient;
 
 	/**
 	 * @param string $appName
@@ -51,16 +62,53 @@ class AppSettingsController extends Controller {
 	 * @param IL10N $l10n
 	 * @param IConfig $config
 	 * @param ICacheFactory $cache
+	 * @param INavigationManager $navigationManager
+	 * @param IAppManager $appManager
+	 * @param OCSClient $ocsClient
 	 */
 	public function __construct($appName,
 								IRequest $request,
 								IL10N $l10n,
 								IConfig $config,
-								ICacheFactory $cache) {
+								ICacheFactory $cache,
+								INavigationManager $navigationManager,
+								IAppManager $appManager,
+								OCSClient $ocsClient) {
 		parent::__construct($appName, $request);
 		$this->l10n = $l10n;
 		$this->config = $config;
 		$this->cache = $cache->create($appName);
+		$this->navigationManager = $navigationManager;
+		$this->appManager = $appManager;
+		$this->ocsClient = $ocsClient;
+	}
+
+	/**
+	 * Enables or disables the display of experimental apps
+	 * @param bool $state
+	 * @return DataResponse
+	 */
+	public function changeExperimentalConfigState($state) {
+		$this->config->setSystemValue('appstore.experimental.enabled', $state);
+		$this->appManager->clearAppsCache();
+		return new DataResponse();
+	}
+
+	/**
+	 * @NoCSRFRequired
+	 * @return TemplateResponse
+	 */
+	public function viewApps() {
+		$params = [];
+		$params['experimentalEnabled'] = $this->config->getSystemValue('appstore.experimental.enabled', false);
+		$this->navigationManager->setActiveEntry('core_apps');
+
+		$templateResponse = new TemplateResponse($this->appName, 'apps', $params, 'user');
+		$policy = new ContentSecurityPolicy();
+		$policy->addAllowedImageDomain('https://apps.owncloud.com');
+		$templateResponse->setContentSecurityPolicy($policy);
+
+		return $templateResponse;
 	}
 
 	/**
@@ -77,16 +125,15 @@ class AppSettingsController extends Controller {
 			['id' => 1, 'displayName' => (string)$this->l10n->t('Not enabled')],
 		];
 
-		if(OCSClient::isAppStoreEnabled()) {
-			$categories[] = ['id' => 2, 'displayName' => (string)$this->l10n->t('Recommended')];
+		if($this->ocsClient->isAppStoreEnabled()) {
 			// apps from external repo via OCS
-			$ocs = OCSClient::getCategories();
+			$ocs = $this->ocsClient->getCategories();
 			if ($ocs) {
 				foreach($ocs as $k => $v) {
-					$categories[] = array(
+					$categories[] = [
 						'id' => $k,
 						'displayName' => str_replace('ownCloud ', '', $v)
-					);
+					];
 				}
 			}
 		}
@@ -97,7 +144,8 @@ class AppSettingsController extends Controller {
 	}
 
 	/**
-	 * Get all available categories
+	 * Get all available apps in a category
+	 *
 	 * @param int $category
 	 * @return array
 	 */
@@ -134,16 +182,9 @@ class AppSettingsController extends Controller {
 					});
 					break;
 				default:
-					if ($category === 2) {
-						$apps = \OC_App::getAppstoreApps('approved');
-						if ($apps) {
-							$apps = array_filter($apps, function ($app) {
-								return isset($app['internalclass']) && $app['internalclass'] === 'recommendedapp';
-							});
-						}
-					} else {
-						$apps = \OC_App::getAppstoreApps('approved', $category);
-					}
+					$filter = $this->config->getSystemValue('appstore.experimental.enabled', false) ? 'all' : 'approved';
+
+					$apps = \OC_App::getAppstoreApps($filter, $category);
 					if (!$apps) {
 						$apps = array();
 					} else {
diff --git a/settings/css/settings.css b/settings/css/settings.css
index c619bd7b9b30a04f2aa97a3c92aaedbb741be514..eb6b0f54053a269ba0e4cb6f0c5c998f833d7332 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -210,6 +210,24 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
 	opacity: .5;
 }
 
+.app-level {
+	color: white;
+}
+
+.app-level .official, .app-level .approved {
+	background-color: #E8C805;
+	border-radius: 2px;
+	margin-left: 5px;
+	padding: 3px;
+}
+
+.app-level .experimental {
+	background-color: #F02405;
+	border-radius: 2px;
+	margin-left: 5px;
+	padding: 3px;
+}
+
 #apps-list {
 	position: relative;
 	height: 100%;
@@ -236,6 +254,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
 .app-name,
 .app-version,
 .app-score,
+.app-level,
 .recommendedapp {
 	display: inline-block;
 }
@@ -261,7 +280,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
 	white-space: pre-line;
 }
 
-#app-category-2 {
+#app-category-1 {
 	border-bottom: 1px solid #e8e8e8;
 }
 
diff --git a/settings/js/apps.js b/settings/js/apps.js
index 3db84e8acd57a07dcd63a2fc7fec92cc1557e1de..f54611369b3b6b6eb1414592ac5cd8c1c73cefdc 100644
--- a/settings/js/apps.js
+++ b/settings/js/apps.js
@@ -9,6 +9,17 @@ Handlebars.registerHelper('score', function() {
 	}
 	return new Handlebars.SafeString('');
 });
+Handlebars.registerHelper('level', function() {
+	if(typeof this.level !== 'undefined') {
+		if(this.level === 200) {
+			return new Handlebars.SafeString('<span class="official">Official</span>');
+		} else if(this.level === 100) {
+			return new Handlebars.SafeString('<span class="approved">Approved</span>');
+		} else {
+			return new Handlebars.SafeString('<span class="experimental">Experimental</span>');
+		}
+	}
+});
 
 OC.Settings = OC.Settings || {};
 OC.Settings.Apps = OC.Settings.Apps || {
@@ -73,7 +84,6 @@ OC.Settings.Apps = OC.Settings.Apps || {
 		this._loadCategoryCall = $.ajax(OC.generateUrl('settings/apps/list?category={categoryId}', {
 			categoryId: categoryId
 		}), {
-			data:{},
 			type:'GET',
 			success: function (apps) {
 				OC.Settings.Apps.State.apps = _.indexBy(apps.apps, 'id');
@@ -81,13 +91,27 @@ OC.Settings.Apps = OC.Settings.Apps || {
 				var template = Handlebars.compile(source);
 
 				if (apps.apps.length) {
+					apps.apps.sort(function(a,b) {
+						return b.level - a.level;
+					});
+
+					var firstExperimental = false;
 					_.each(apps.apps, function(app) {
-						OC.Settings.Apps.renderApp(app, template, null);
+						if(app.level === 0 && firstExperimental === false) {
+							firstExperimental = true;
+							OC.Settings.Apps.renderApp(app, template, null, true);
+						} else {
+							OC.Settings.Apps.renderApp(app, template, null, false);
+						}
 					});
 				} else {
 					$('#apps-list').addClass('hidden');
 					$('#apps-list-empty').removeClass('hidden');
 				}
+
+				$('.app-level .official').tipsy({fallback: t('core', 'Official apps are developed by and within the ownCloud community and its GitHub repository and offer functionality central to ownCloud. They are ready for serious use.')});
+				$('.app-level .approved').tipsy({fallback: t('core', 'Approved apps are developed by trusted developers and have passed a cursory security check. They are actively maintained in an open code repository and their maintainers deem them to be stable for casual to normal use.')});
+				$('.app-level .experimental').tipsy({fallback: t('core', 'This app is not checked for security issues and is new or known to be unstable. Install on your own risk.')});
 			},
 			complete: function() {
 				$('#apps-list').removeClass('icon-loading');
@@ -95,7 +119,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
 		});
 	},
 
-	renderApp: function(app, template, selector) {
+	renderApp: function(app, template, selector, firstExperimental) {
 		if (!template) {
 			var source   = $("#app-template").html();
 			template = Handlebars.compile(source);
@@ -103,6 +127,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
 		if (typeof app === 'string') {
 			app = OC.Settings.Apps.State.apps[app];
 		}
+		app.firstExperimental = firstExperimental;
 
 		var html = template(app);
 		if (selector) {
@@ -438,6 +463,16 @@ OC.Settings.Apps = OC.Settings.Apps || {
 			$select.change();
 		});
 
+		$(document).on('click', '#enable-experimental-apps', function () {
+			var state = $('#enable-experimental-apps').prop('checked');
+			$.ajax(OC.generateUrl('settings/apps/experimental'), {
+				data: {state: state},
+				type: 'POST',
+				success:function () {
+					location.reload();
+				}
+			});
+		});
 	}
 };
 
diff --git a/settings/routes.php b/settings/routes.php
index 5a069e5a1c69f1f25b7845de76b56669e6b8ac83..86b7fa2375c52eae740b67244a800cc97e1274b3 100644
--- a/settings/routes.php
+++ b/settings/routes.php
@@ -33,25 +33,27 @@
 namespace OC\Settings;
 
 $application = new Application();
-$application->registerRoutes($this, array(
-	'resources' => array(
-		'groups' => array('url' => '/settings/users/groups'),
-		'users' => array('url' => '/settings/users/users')
-	),
-	'routes' => array(
-		array('name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'),
-		array('name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'),
-		array('name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'),
-		array('name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'),
-		array('name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'),
-		array('name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'),
-		array('name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'),
-		array('name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'),
-		array('name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'),
-		array('name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'),
+$application->registerRoutes($this, [
+	'resources' => [
+		'groups' => ['url' => '/settings/users/groups'],
+		'users' => ['url' => '/settings/users/users']
+	],
+	'routes' => [
+		['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'],
+		['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'],
+		['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'],
+		['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
+		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
+		['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
+		['name' => 'AppSettings#changeExperimentalConfigState', 'url' => '/settings/apps/experimental', 'verb' => 'POST'],
+		['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'],
+		['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
+		['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
+		['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
+		['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'],
 		['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'],
-	)
-));
+	]
+]);
 
 /** @var $this \OCP\Route\IRouter */
 
@@ -62,8 +64,6 @@ $this->create('settings_personal', '/settings/personal')
 	->actionInclude('settings/personal.php');
 $this->create('settings_users', '/settings/users')
 	->actionInclude('settings/users.php');
-$this->create('settings_apps', '/settings/apps')
-	->actionInclude('settings/apps.php');
 $this->create('settings_admin', '/settings/admin')
 	->actionInclude('settings/admin.php');
 // Settings ajax actions
diff --git a/settings/templates/apps.php b/settings/templates/apps.php
index a2fe5d9b63a8b4cc39566e8bea8fe49484ed46cd..f930ce6d444f046035243baee3a3f6df23f8e706 100644
--- a/settings/templates/apps.php
+++ b/settings/templates/apps.php
@@ -1,3 +1,27 @@
+<?php
+style('settings', 'settings');
+vendor_style(
+	'core',
+	[
+		'select2/select2',
+	]
+);
+vendor_script(
+	'core',
+	[
+		'handlebars/handlebars',
+		'select2/select2'
+	]
+);
+script(
+	'settings',
+	[
+		'settings',
+		'apps',
+	]
+);
+/** @var array $_ */
+?>
 <script id="categories-template" type="text/x-handlebars-template">
 {{#each this}}
 	<li id="app-category-{{id}}" data-category-id="{{id}}" tabindex="0">
@@ -16,6 +40,18 @@
 </script>
 
 <script id="app-template" type="text/x-handlebars">
+	{{#if firstExperimental}}
+		<div style="background-color: lightyellow; border-top:1px solid black; border-bottom:  1px solid black;">
+			<h2><?php p($l->t('Experimental applications ahead')) ?></h2>
+			<p>
+				<?php p($l->t('Experimental apps are not checked for security ' .
+			'issues and are new or known to be unstable and under heavy ' .
+			'development. Installing these can cause data loss or security ' .
+			'breaches.')) ?>
+			</p>
+		</div>
+	{{/if}}
+
 	<div class="section" id="app-{{id}}">
 	{{#if preview}}
 	<div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden">
@@ -23,6 +59,9 @@
 	{{/if}}
 	<h2 class="app-name"><a href="{{detailpage}}" target="_blank">{{name}}</a></h2>
 	<div class="app-version"> {{version}}</div>
+	<div class="app-level">
+		{{{level}}}
+	</div>
 	<div class="app-author"><?php p($l->t('by')); ?> {{author}}
 		{{#if licence}}
 		({{licence}}-<?php p($l->t('licensed')); ?>)
@@ -95,6 +134,24 @@
 	<ul id="apps-categories">
 
 	</ul>
+	<div id="app-settings">
+		<div id="app-settings-header">
+			<button class="settings-button" data-apps-slide-toggle="#app-settings-content"></button>
+		</div>
+
+		<div id="app-settings-content" style="color: #c33">
+			<input type="checkbox" id="enable-experimental-apps" <?php if($_['experimentalEnabled']) { print_unescaped('checked="checked"'); }?>>
+			<label for="enable-experimental-apps"><?php p($l->t('Enable experimental apps')) ?></label>
+			<p>
+				<small>
+					<?php p($l->t('Experimental apps are not checked for security ' .
+						'issues and are new or known to be unstable and under heavy ' .
+						'development. Installing these can cause data loss or security ' .
+						'breaches.')) ?>
+				</small>
+			</p>
+		</div>
+	</div>
 </div>
 <div id="app-content">
 	<div id="apps-list" class="icon-loading"></div>
diff --git a/tests/lib/OCSClientTest.php b/tests/lib/OCSClientTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c83103b574a109f27208cd3f3f0422743ab1f60
--- /dev/null
+++ b/tests/lib/OCSClientTest.php
@@ -0,0 +1,931 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+use OC\OCSClient;
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\ILogger;
+
+/**
+ * Class OCSClientTest
+ */
+class OCSClientTest extends \Test\TestCase {
+	/** @var OCSClient */
+	private $ocsClient;
+	/** @var IConfig */
+	private $config;
+	/** @var IClientService */
+	private $clientService;
+	/** @var ILogger */
+	private $logger;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->config = $this->getMockBuilder('\OCP\IConfig')
+			->disableOriginalConstructor()->getMock();
+		$this->clientService = $this->getMock('\OCP\Http\Client\IClientService');
+		$this->logger = $this->getMock('\OCP\ILogger');
+
+		$this->ocsClient = new OCSClient(
+			$this->clientService,
+			$this->config,
+			$this->logger
+		);
+	}
+
+	public function testIsAppStoreEnabledSuccess() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->assertTrue($this->ocsClient->isAppStoreEnabled());
+	}
+
+	public function testIsAppStoreEnabledFail() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(false));
+		$this->assertFalse($this->ocsClient->isAppStoreEnabled());
+	}
+
+	public function testGetAppStoreUrl() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+		$this->assertSame('https://api.owncloud.com/v1', Test_Helper::invokePrivate($this->ocsClient, 'getAppStoreUrl'));
+	}
+
+	public function testGetCategoriesDisabledAppStore() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(false));
+		$this->assertNull($this->ocsClient->getCategories());
+	}
+
+	public function testGetCategoriesExceptionClient() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/categories',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->throwException(new \Exception('TheErrorMessage')));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get categories: TheErrorMessage',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getCategories());
+	}
+
+	public function testGetCategoriesParseError() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('MyInvalidXml'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/categories',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get categories, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getCategories());
+	}
+
+	public function testGetCategoriesSuccessful() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('<?xml version="1.0"?>
+				<ocs>
+				 <meta>
+				  <status>ok</status>
+				  <statuscode>100</statuscode>
+				  <message></message>
+				  <totalitems>6</totalitems>
+				 </meta>
+				 <data>
+				  <category>
+				   <id>920</id>
+				   <name>ownCloud Multimedia</name>
+				  </category>
+				  <category>
+				   <id>921</id>
+				   <name>ownCloud PIM</name>
+				  </category>
+				  <category>
+				   <id>922</id>
+				   <name>ownCloud Productivity</name>
+				  </category>
+				  <category>
+				   <id>923</id>
+				   <name>ownCloud Game</name>
+				  </category>
+				  <category>
+				   <id>924</id>
+				   <name>ownCloud Tool</name>
+				  </category>
+				  <category>
+				   <id>925</id>
+				   <name>ownCloud other</name>
+				  </category>
+				 </data>
+				</ocs>
+				'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/categories',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$expected = [
+			920 => 'ownCloud Multimedia',
+			921 => 'ownCloud PIM',
+			922 => 'ownCloud Productivity',
+			923 => 'ownCloud Game',
+			924 => 'ownCloud Tool',
+			925 => 'ownCloud other',
+		];
+		$this->assertSame($expected, $this->ocsClient->getCategories());
+	}
+
+	public function testGetApplicationsDisabledAppStore() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(false));
+		$this->assertSame([], $this->ocsClient->getApplications([], 1, 'approved'));
+	}
+
+	public function testGetApplicationsExceptionClient() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data',
+				[
+					'timeout' => 5,
+					'query' => [
+						'version' => implode('x', \OC_Util::getVersion()),
+						'filter' => 'approved',
+						'categories' => '815x1337',
+						'sortmode' => 'new',
+						'page' => 1,
+						'pagesize' => 100,
+						'approved' => 'approved',
+					],
+				]
+			)
+			->will($this->throwException(new \Exception('TheErrorMessage')));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get applications: TheErrorMessage',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved'));
+	}
+
+	public function testGetApplicationsParseError() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('MyInvalidXml'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data',
+				[
+					'timeout' => 5,
+					'query' => [
+						'version' => implode('x', \OC_Util::getVersion()),
+						'filter' => 'approved',
+						'categories' => '815x1337',
+						'sortmode' => 'new',
+						'page' => 1,
+						'pagesize' => 100,
+						'approved' => 'approved',
+					],
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get applications, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved'));
+	}
+
+	public function testGetApplicationsSuccessful() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('<?xml version="1.0"?>
+				<ocs>
+				 <meta>
+				  <status>ok</status>
+				  <statuscode>100</statuscode>
+				  <message></message>
+				  <totalitems>2</totalitems>
+				  <itemsperpage>100</itemsperpage>
+				 </meta>
+				 <data>
+				  <content details="summary">
+				   <id>168707</id>
+				   <name>Calendar 8.0</name>
+				   <version>0.6.4</version>
+				   <label>recommended</label>
+				   <changed>2015-02-09T15:23:56+01:00</changed>
+				   <created>2015-01-26T04:35:19+01:00</created>
+				   <typeid>921</typeid>
+				   <typename>ownCloud PIM</typename>
+				   <language></language>
+				   <personid>owncloud</personid>
+				   <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage>
+				   <downloads>5393</downloads>
+				   <score>60</score>
+				   <description>Calendar App for ownCloud</description>
+				   <comments>7</comments>
+				   <fans>10</fans>
+				   <licensetype>16</licensetype>
+				   <approved>0</approved>
+				   <category>1</category>
+				   <license>AGPL</license>
+				   <preview1></preview1>
+				   <detailpage>https://apps.owncloud.com/content/show.php?content=168707</detailpage>
+				   <downloadtype1></downloadtype1>
+				   <downloadway1>0</downloadway1>
+				   <downloadprice1>0</downloadprice1>
+				   <downloadlink1>http://apps.owncloud.com/content/download.php?content=168707&amp;id=1</downloadlink1>
+				   <downloadgpgsignature1></downloadgpgsignature1>
+				   <downloadgpgfingerprint1></downloadgpgfingerprint1>
+				   <downloadpackagename1></downloadpackagename1>
+				   <downloadrepository1></downloadrepository1>
+				   <downloadname1></downloadname1>
+				   <downloadsize1>885</downloadsize1>
+				  </content>
+				  <content details="summary">
+				   <id>168708</id>
+				   <name>Contacts 8.0</name>
+				   <version>0.3.0.18</version>
+				   <label>recommended</label>
+				   <changed>2015-02-09T15:18:58+01:00</changed>
+				   <created>2015-01-26T04:45:17+01:00</created>
+				   <typeid>921</typeid>
+				   <typename>ownCloud PIM</typename>
+				   <language></language>
+				   <personid>owncloud</personid>
+				   <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage>
+				   <downloads>4237</downloads>
+				   <score>58</score>
+				   <description></description>
+				   <comments>3</comments>
+				   <fans>6</fans>
+				   <licensetype>16</licensetype>
+				   <approved>200</approved>
+				   <category>1</category>
+				   <license>AGPL</license>
+				   <preview1></preview1>
+				   <detailpage>https://apps.owncloud.com/content/show.php?content=168708</detailpage>
+				   <downloadtype1></downloadtype1>
+				   <downloadway1>0</downloadway1>
+				   <downloadprice1>0</downloadprice1>
+				   <downloadlink1>http://apps.owncloud.com/content/download.php?content=168708&amp;id=1</downloadlink1>
+				   <downloadgpgsignature1></downloadgpgsignature1>
+				   <downloadgpgfingerprint1></downloadgpgfingerprint1>
+				   <downloadpackagename1></downloadpackagename1>
+				   <downloadrepository1></downloadrepository1>
+				   <downloadname1></downloadname1>
+				   <downloadsize1>1409</downloadsize1>
+				  </content>
+				 </data>
+				</ocs> '));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data',
+				[
+					'timeout' => 5,
+					'query' => [
+						'version' => implode('x', \OC_Util::getVersion()),
+						'filter' => 'approved',
+						'categories' => '815x1337',
+						'sortmode' => 'new',
+						'page' => 1,
+						'pagesize' => 100,
+						'approved' => 'approved',
+					],
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$expected = [
+			[
+				'id' => '168707',
+				'name' => 'Calendar 8.0',
+				'label' => 'recommended',
+				'version' => '0.6.4',
+				'type' => '921',
+				'typename' => 'ownCloud PIM',
+				'personid' => 'owncloud',
+				'license' => 'AGPL',
+				'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168707',
+				'preview' => '',
+				'preview-full' => '',
+				'changed' => 1423491836,
+				'description' => 'Calendar App for ownCloud',
+				'score' => '60',
+				'downloads' => 5393,
+				'level' => 0,
+			],
+			[
+				'id' => '168708',
+				'name' => 'Contacts 8.0',
+				'label' => 'recommended',
+				'version' => '0.3.0.18',
+				'type' => '921',
+				'typename' => 'ownCloud PIM',
+				'personid' => 'owncloud',
+				'license' => 'AGPL',
+				'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168708',
+				'preview' => '',
+				'preview-full' => '',
+				'changed' => 1423491538,
+				'description' => '',
+				'score' => '58',
+				'downloads' => 4237,
+				'level' => 200,
+			],
+		];
+		$this->assertEquals($expected, $this->ocsClient->getApplications([815, 1337], 1, 'approved'));
+	}
+
+	public function tesGetApplicationDisabledAppStore() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(false));
+		$this->assertNull($this->ocsClient->getApplication('MyId'));
+	}
+
+	public function testGetApplicationExceptionClient() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data/MyId',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->throwException(new \Exception('TheErrorMessage')));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get application: TheErrorMessage',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getApplication('MyId'));
+	}
+
+	public function testGetApplicationParseError() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('MyInvalidXml'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data/MyId',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get application, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getApplication('MyId'));
+	}
+
+	public function testGetApplicationSuccessful() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('<?xml version="1.0"?>
+				<ocs>
+				 <meta>
+				  <status>ok</status>
+				  <statuscode>100</statuscode>
+				  <message></message>
+				 </meta>
+				 <data>
+				  <content details="full">
+				   <id>166053</id>
+				   <name>Versioning</name>
+				   <version>0.0.1</version>
+				   <label>recommended</label>
+				   <typeid>925</typeid>
+				   <typename>ownCloud other</typename>
+			   <language></language>
+			   <personid>owncloud</personid>
+			   <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage>
+			   <created>2014-07-07T16:34:40+02:00</created>
+			   <changed>2014-07-07T16:34:40+02:00</changed>
+			   <downloads>140</downloads>
+			   <score>50</score>
+			   <description>Placeholder for future updates</description>
+			   <summary></summary>
+			   <feedbackurl></feedbackurl>
+			   <changelog></changelog>
+			   <homepage></homepage>
+			   <homepagetype></homepagetype>
+			   <homepage2></homepage2>
+			   <homepagetype2></homepagetype2>
+			   <homepage3></homepage3>
+			   <homepagetype3></homepagetype3>
+			   <homepage4></homepage4>
+			   <homepagetype4></homepagetype4>
+			   <homepage5></homepage5>
+			   <homepagetype5></homepagetype5>
+			   <homepage6></homepage6>
+			   <homepagetype6></homepagetype6>
+			   <homepage7></homepage7>
+			   <homepagetype7></homepagetype7>
+			   <homepage8></homepage8>
+			   <homepagetype8></homepagetype8>
+			   <homepage9></homepage9>
+			   <homepagetype9></homepagetype9>
+			   <homepage10></homepage10>
+			   <homepagetype10></homepagetype10>
+			   <licensetype>16</licensetype>
+			   <license>AGPL</license>
+			   <donationpage></donationpage>
+			   <comments>0</comments>
+			   <commentspage>http://apps.owncloud.com/content/show.php?content=166053</commentspage>
+			   <fans>0</fans>
+			   <fanspage>http://apps.owncloud.com/content/show.php?action=fan&amp;content=166053</fanspage>
+			   <knowledgebaseentries>0</knowledgebaseentries>
+			   <knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&amp;content=166053</knowledgebasepage>
+			   <depend>ownCloud 7</depend>
+			   <preview1></preview1>
+			   <preview2></preview2>
+			   <preview3></preview3>
+			   <previewpic1></previewpic1>
+			   <previewpic2></previewpic2>
+			   <previewpic3></previewpic3>
+			   <picsmall1></picsmall1>
+			   <picsmall2></picsmall2>
+			   <picsmall3></picsmall3>
+			   <detailpage>https://apps.owncloud.com/content/show.php?content=166053</detailpage>
+			   <downloadtype1></downloadtype1>
+			   <downloadprice1>0</downloadprice1>
+			   <downloadlink1>http://apps.owncloud.com/content/download.php?content=166053&amp;id=1</downloadlink1>
+			   <downloadname1></downloadname1>
+			   <downloadgpgfingerprint1></downloadgpgfingerprint1>
+			   <downloadgpgsignature1></downloadgpgsignature1>
+			   <downloadpackagename1></downloadpackagename1>
+			   <downloadrepository1></downloadrepository1>
+			   <downloadsize1>1</downloadsize1>
+			  </content>
+			 </data>
+			</ocs>
+			'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/data/MyId',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$expected = [
+			'id' => 166053,
+			'name' => 'Versioning',
+			'version' => '0.0.1',
+			'type' => '925',
+			'label' => 'recommended',
+			'typename' => 'ownCloud other',
+			'personid' => 'owncloud',
+			'detailpage' => 'https://apps.owncloud.com/content/show.php?content=166053',
+			'preview1' => '',
+			'preview2' => '',
+			'preview3' => '',
+			'changed' => 1404743680,
+			'description' => 'Placeholder for future updates',
+			'score' => 50,
+		];
+		$this->assertSame($expected, $this->ocsClient->getApplication('MyId'));
+	}
+
+	public function testGetApplicationDownloadDisabledAppStore() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(false));
+		$this->assertNull($this->ocsClient->getApplicationDownload('MyId'));
+	}
+
+	public function testGetApplicationDownloadExceptionClient() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/download/MyId/1',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->throwException(new \Exception('TheErrorMessage')));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get application download URL: TheErrorMessage',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getApplicationDownload('MyId'));
+	}
+
+	public function testGetApplicationDownloadParseError() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('MyInvalidXml'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/download/MyId/1',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$this->logger
+			->expects($this->once())
+			->method('error')
+			->with(
+				'Could not get application download URL, content was no valid XML',
+				[
+					'app' => 'core',
+				]
+			);
+
+		$this->assertNull($this->ocsClient->getApplicationDownload('MyId'));
+	}
+
+	public function testGetApplicationDownloadUrlSuccessful() {
+		$this->config
+			->expects($this->at(0))
+			->method('getSystemValue')
+			->with('appstoreenabled', true)
+			->will($this->returnValue(true));
+		$this->config
+			->expects($this->at(1))
+			->method('getSystemValue')
+			->with('appstoreurl', 'https://api.owncloud.com/v1')
+			->will($this->returnValue('https://api.owncloud.com/v1'));
+
+		$response = $this->getMock('\OCP\Http\Client\IResponse');
+		$response
+			->expects($this->once())
+			->method('getBody')
+			->will($this->returnValue('<?xml version="1.0"?>
+				<ocs>
+				 <meta>
+				  <status>ok</status>
+				  <statuscode>100</statuscode>
+				  <message></message>
+				 </meta>
+				 <data>
+				  <content details="download">
+				   <downloadlink>https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip</downloadlink>
+				   <mimetype>application/zip</mimetype>
+				   <gpgfingerprint></gpgfingerprint>
+				   <gpgsignature></gpgsignature>
+				   <packagename></packagename>
+				   <repository></repository>
+				  </content>
+				 </data>
+				</ocs>
+				'));
+
+		$client = $this->getMock('\OCP\Http\Client\IClient');
+		$client
+			->expects($this->once())
+			->method('get')
+			->with(
+				'https://api.owncloud.com/v1/content/download/MyId/1',
+				[
+					'timeout' => 5,
+				]
+			)
+			->will($this->returnValue($response));
+
+		$this->clientService
+			->expects($this->once())
+			->method('newClient')
+			->will($this->returnValue($client));
+
+		$expected = [
+			'downloadlink' => 'https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip',
+		];
+		$this->assertSame($expected, $this->ocsClient->getApplicationDownload('MyId'));
+	}
+}
diff --git a/tests/settings/controller/AppSettingsControllerTest.php b/tests/settings/controller/AppSettingsControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d6379deb9c81abf196c25d138d7ecd1bf3bd6dd2
--- /dev/null
+++ b/tests/settings/controller/AppSettingsControllerTest.php
@@ -0,0 +1,231 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Settings\Controller;
+
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use Test\TestCase;
+use OCP\IRequest;
+use OCP\IL10N;
+use OCP\IConfig;
+use OCP\ICache;
+use OCP\INavigationManager;
+use OCP\App\IAppManager;
+use OC\OCSClient;
+
+/**
+ * Class AppSettingsControllerTest
+ *
+ * @package OC\Settings\Controller
+ */
+class AppSettingsControllerTest extends TestCase {
+	/** @var AppSettingsController */
+	private $appSettingsController;
+	/** @var IRequest */
+	private $request;
+	/** @var IL10N */
+	private $l10n;
+	/** @var IConfig */
+	private $config;
+	/** @var ICache */
+	private $cache;
+	/** @var INavigationManager */
+	private $navigationManager;
+	/** @var IAppManager */
+	private $appManager;
+	/** @var OCSClient */
+	private $ocsClient;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->request = $this->getMockBuilder('\OCP\IRequest')
+			->disableOriginalConstructor()->getMock();
+		$this->l10n = $this->getMockBuilder('\OCP\IL10N')
+			->disableOriginalConstructor()->getMock();
+		$this->l10n->expects($this->any())
+			->method('t')
+			->will($this->returnArgument(0));
+		$this->config = $this->getMockBuilder('\OCP\IConfig')
+			->disableOriginalConstructor()->getMock();
+		$cacheFactory = $this->getMockBuilder('\OCP\ICacheFactory')
+			->disableOriginalConstructor()->getMock();
+		$this->cache = $this->getMockBuilder('\OCP\ICache')
+			->disableOriginalConstructor()->getMock();
+		$cacheFactory
+			->expects($this->once())
+			->method('create')
+			->with('settings')
+			->will($this->returnValue($this->cache));
+
+		$this->navigationManager = $this->getMockBuilder('\OCP\INavigationManager')
+			->disableOriginalConstructor()->getMock();
+		$this->appManager = $this->getMockBuilder('\OCP\App\IAppManager')
+			->disableOriginalConstructor()->getMock();
+		$this->ocsClient = $this->getMockBuilder('\OC\OCSClient')
+			->disableOriginalConstructor()->getMock();
+
+		$this->appSettingsController = new AppSettingsController(
+			'settings',
+			$this->request,
+			$this->l10n,
+			$this->config,
+			$cacheFactory,
+			$this->navigationManager,
+			$this->appManager,
+			$this->ocsClient
+		);
+	}
+
+	public function testChangeExperimentalConfigStateTrue() {
+		$this->config
+			->expects($this->once())
+			->method('setSystemValue')
+			->with('appstore.experimental.enabled', true);
+		$this->appManager
+			->expects($this->once())
+			->method('clearAppsCache');
+		$this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(true));
+	}
+
+	public function testChangeExperimentalConfigStateFalse() {
+		$this->config
+			->expects($this->once())
+			->method('setSystemValue')
+			->with('appstore.experimental.enabled', false);
+		$this->appManager
+			->expects($this->once())
+			->method('clearAppsCache');
+		$this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(false));
+	}
+
+	public function testListCategoriesCached() {
+		$this->cache
+			->expects($this->exactly(2))
+			->method('get')
+			->with('listCategories')
+			->will($this->returnValue(['CachedArray']));
+		$this->assertSame(['CachedArray'], $this->appSettingsController->listCategories());
+	}
+
+	public function testListCategoriesNotCachedWithoutAppStore() {
+		$expected = [
+			[
+				'id' => 0,
+				'displayName' => 'Enabled',
+			],
+			[
+				'id' => 1,
+				'displayName' => 'Not enabled',
+			],
+		];
+		$this->cache
+			->expects($this->once())
+			->method('get')
+			->with('listCategories')
+			->will($this->returnValue(null));
+		$this->cache
+			->expects($this->once())
+			->method('set')
+			->with('listCategories', $expected, 3600);
+
+
+		$this->assertSame($expected, $this->appSettingsController->listCategories());
+	}
+
+	public function testListCategoriesNotCachedWithAppStore() {
+		$expected = [
+			[
+				'id' => 0,
+				'displayName' => 'Enabled',
+			],
+			[
+				'id' => 1,
+				'displayName' => 'Not enabled',
+			],
+			[
+				'id' => 0,
+				'displayName' => 'Tools',
+			],
+			[
+				'id' => 1,
+				'displayName' => 'Awesome Games',
+			],
+			[
+				'id' => 2,
+				'displayName' => 'PIM',
+			],
+			[
+				'id' => 3,
+				'displayName' => 'Papershop',
+			],
+		];
+
+		$this->cache
+			->expects($this->once())
+			->method('get')
+			->with('listCategories')
+			->will($this->returnValue(null));
+		$this->cache
+			->expects($this->once())
+			->method('set')
+			->with('listCategories', $expected, 3600);
+
+		$this->ocsClient
+			->expects($this->once())
+			->method('isAppStoreEnabled')
+			->will($this->returnValue(true));
+		$this->ocsClient
+			->expects($this->once())
+			->method('getCategories')
+			->will($this->returnValue(
+				[
+					'ownCloud Tools',
+					'Awesome Games',
+					'ownCloud PIM',
+					'Papershop',
+				]
+			));
+
+		$this->assertSame($expected, $this->appSettingsController->listCategories());
+	}
+
+	public function testViewApps() {
+		$this->config
+			->expects($this->once())
+			->method('getSystemValue')
+			->with('appstore.experimental.enabled', false);
+		$this->navigationManager
+			->expects($this->once())
+			->method('setActiveEntry')
+			->with('core_apps');
+
+		$policy = new ContentSecurityPolicy();
+		$policy->addAllowedImageDomain('https://apps.owncloud.com');
+
+		$expected = new TemplateResponse('settings', 'apps', ['experimentalEnabled' => false], 'user');
+		$expected->setContentSecurityPolicy($policy);
+
+		$this->assertEquals($expected, $this->appSettingsController->viewApps());
+	}
+}