summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbuildjsdocs.sh2
-rw-r--r--core/js/files/client.js673
-rw-r--r--core/js/files/fileinfo.js143
-rw-r--r--core/js/js.js1
-rw-r--r--core/js/tests/specHelper.js1
-rw-r--r--core/js/tests/specs/files/clientSpec.js712
-rw-r--r--tests/karma.config.js6
7 files changed, 1533 insertions, 5 deletions
diff --git a/buildjsdocs.sh b/buildjsdocs.sh
index 90562558f66..57eefb29fd0 100755
--- a/buildjsdocs.sh
+++ b/buildjsdocs.sh
@@ -11,7 +11,7 @@ NPM="$(which npm 2>/dev/null)"
PREFIX="build"
OUTPUT_DIR="build/jsdocs"
-JS_FILES="core/js/*.js apps/*/js/*.js"
+JS_FILES="core/js/*.js core/js/**/*.js apps/*/js/*.js"
if test -z "$NPM"
then
diff --git a/core/js/files/client.js b/core/js/files/client.js
new file mode 100644
index 00000000000..9bb7bb999fd
--- /dev/null
+++ b/core/js/files/client.js
@@ -0,0 +1,673 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global dav */
+
+(function(OC, FileInfo) {
+ /**
+ * @class OC.Files.Client
+ * @classdesc Client to access files on the server
+ *
+ * @param {Object} options
+ * @param {String} options.host host name
+ * @param {int} [options.port] port
+ * @param {boolean} [options.useHTTPS] whether to use https
+ * @param {String} [options.root] root path
+ * @param {String} [options.userName] user name
+ * @param {String} [options.password] password
+ *
+ * @since 8.2
+ */
+ var Client = function(options) {
+ this._root = options.root;
+ if (this._root.charAt(this._root.length - 1) === '/') {
+ this._root = this._root.substr(0, this._root.length - 1);
+ }
+
+ if (!options.port) {
+ // workaround in case port is null or empty
+ options.port = undefined;
+ }
+ var url = '';
+ var port = '';
+ if (options.useHTTPS) {
+ url += 'https://';
+ if (options.port && options.port !== 443) {
+ port = ':' + options.port;
+ }
+ } else {
+ url += 'http://';
+ if (options.port && options.port !== 80) {
+ port = ':' + options.port;
+ }
+ }
+ var credentials = '';
+ if (options.userName) {
+ credentials += encodeURIComponent(options.userName);
+ }
+ if (options.password) {
+ credentials += ':' + encodeURIComponent(options.password);
+ }
+ if (credentials.length > 0) {
+ url += credentials + '@';
+ }
+
+ url += options.host + port + this._root;
+ this._defaultHeaders = options.defaultHeaders || {'X-Requested-With': 'XMLHttpRequest'};
+ this._baseUrl = url;
+ this._client = new dav.Client({
+ baseUrl: this._baseUrl,
+ xmlNamespaces: {
+ 'DAV:': 'd',
+ 'http://owncloud.org/ns': 'oc'
+ }
+ });
+ this._client.xhrProvider = _.bind(this._xhrProvider, this);
+ };
+
+ Client.NS_OWNCLOUD = 'http://owncloud.org/ns';
+ Client.NS_DAV = 'DAV:';
+ Client._PROPFIND_PROPERTIES = [
+ /**
+ * Modified time
+ */
+ [Client.NS_DAV, 'getlastmodified'],
+ /**
+ * Etag
+ */
+ [Client.NS_DAV, 'getetag'],
+ /**
+ * Mime type
+ */
+ [Client.NS_DAV, 'getcontenttype'],
+ /**
+ * Resource type "collection" for folders, empty otherwise
+ */
+ [Client.NS_DAV, 'resourcetype'],
+ /**
+ * Compound file id, contains fileid + server instance id
+ */
+ [Client.NS_OWNCLOUD, 'id'],
+ /**
+ * Letter-coded permissions
+ */
+ [Client.NS_OWNCLOUD, 'permissions'],
+ //[Client.NS_OWNCLOUD, 'downloadURL'],
+ /**
+ * Folder sizes
+ */
+ [Client.NS_OWNCLOUD, 'size'],
+ /**
+ * File sizes
+ */
+ [Client.NS_DAV, 'getcontentlength']
+ ];
+
+ /**
+ * @memberof OC.Files
+ */
+ Client.prototype = {
+
+ /**
+ * Root path of the Webdav endpoint
+ *
+ * @type string
+ */
+ _root: null,
+
+ /**
+ * Client from the library
+ *
+ * @type nl.sara.webdav.Client
+ */
+ _client: null,
+
+ /**
+ * Returns the configured XHR provider for davclient
+ * @return {XMLHttpRequest}
+ */
+ _xhrProvider: function() {
+ var headers = this._defaultHeaders;
+ var xhr = new XMLHttpRequest();
+ var oldOpen = xhr.open;
+ // override open() method to add headers
+ xhr.open = function() {
+ var result = oldOpen.apply(this, arguments);
+ _.each(headers, function(value, key) {
+ xhr.setRequestHeader(key, value);
+ });
+ return result;
+ };
+ return xhr;
+ },
+
+ /**
+ * Prepends the base url to the given path sections
+ *
+ * @param {...String} path sections
+ *
+ * @return {String} base url + joined path, any leading or trailing slash
+ * will be kept
+ */
+ _buildUrl: function() {
+ var path = this._buildPath.apply(this, arguments);
+ if (path.charAt([path.length - 1]) === '/') {
+ path = path.substr(0, path.length - 1);
+ }
+ if (path.charAt(0) === '/') {
+ path = path.substr(1);
+ }
+ return this._baseUrl + '/' + path;
+ },
+
+ /**
+ * Append the path to the root and also encode path
+ * sections
+ *
+ * @param {...String} path sections
+ *
+ * @return {String} joined path, any leading or trailing slash
+ * will be kept
+ */
+ _buildPath: function() {
+ var path = OC.joinPaths.apply(this, arguments);
+ var sections = path.split('/');
+ var i;
+ for (i = 0; i < sections.length; i++) {
+ sections[i] = encodeURIComponent(sections[i]);
+ }
+ path = sections.join('/');
+ return path;
+ },
+
+ /**
+ * Parse headers string into a map
+ *
+ * @param {string} headersString headers list as string
+ *
+ * @return {Object.<String,Array>} map of header name to header contents
+ */
+ _parseHeaders: function(headersString) {
+ var headerRows = headersString.split('\n');
+ var headers = {};
+ for (var i = 0; i < headerRows.length; i++) {
+ var sepPos = headerRows[i].indexOf(':');
+ if (sepPos < 0) {
+ continue;
+ }
+
+ var headerName = headerRows[i].substr(0, sepPos);
+ var headerValue = headerRows[i].substr(sepPos + 2);
+
+ if (!headers[headerName]) {
+ // make it an array
+ headers[headerName] = [];
+ }
+
+ headers[headerName].push(headerValue);
+ }
+ return headers;
+ },
+
+ /**
+ * Parses the compound file id
+ *
+ * @param {string} compoundFileId compound file id as returned by the server
+ *
+ * @return {int} local file id, stripped of the instance id
+ */
+ _parseFileId: function(compoundFileId) {
+ if (!compoundFileId || compoundFileId.length < 8) {
+ return null;
+ }
+ return parseInt(compoundFileId.substr(0, 8), 10);
+ },
+
+ /**
+ * Parses the etag response which is in double quotes.
+ *
+ * @param {string} etag etag value in double quotes
+ *
+ * @return {string} etag without double quotes
+ */
+ _parseEtag: function(etag) {
+ if (etag.charAt(0) === '"') {
+ return etag.split('"')[1];
+ }
+ return etag;
+ },
+
+ /**
+ * Parse Webdav result
+ *
+ * @param {Object} response XML object
+ *
+ * @return {Array.<FileInfo>} array of file info
+ */
+ _parseFileInfo: function(response) {
+ var path = response.href;
+ if (path.substr(0, this._root.length) === this._root) {
+ path = path.substr(this._root.length);
+ }
+
+ if (path.charAt(path.length - 1) === '/') {
+ path = path.substr(0, path.length - 1);
+ }
+
+ path = '/' + decodeURIComponent(path);
+
+ if (response.propStat.length === 1 && response.propStat[0].status !== 200) {
+ return null;
+ }
+
+ var props = response.propStat[0].properties;
+
+ var data = {
+ id: this._parseFileId(props['{' + Client.NS_OWNCLOUD + '}id']),
+ path: OC.dirname(path) || '/',
+ name: OC.basename(path),
+ mtime: new Date(props['{' + Client.NS_DAV + '}getlastmodified']),
+ _props: props
+ };
+
+ var etagProp = props['{' + Client.NS_DAV + '}getetag'];
+ if (!_.isUndefined(etagProp)) {
+ data.etag = this._parseEtag(etagProp);
+ }
+
+ var sizeProp = props['{' + Client.NS_DAV + '}getcontentlength'];
+ if (!_.isUndefined(sizeProp)) {
+ data.size = parseInt(sizeProp, 10);
+ }
+
+ sizeProp = props['{' + Client.NS_OWNCLOUD + '}size'];
+ if (!_.isUndefined(sizeProp)) {
+ data.size = parseInt(sizeProp, 10);
+ }
+
+ var contentType = props['{' + Client.NS_DAV + '}getcontenttype'];
+ if (!_.isUndefined(contentType)) {
+ data.mimetype = contentType;
+ }
+
+ var resType = props['{' + Client.NS_DAV + '}resourcetype'];
+ var isFile = true;
+ if (!data.mimetype && resType) {
+ var xmlvalue = resType[0];
+ if (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {
+ data.mimetype = 'httpd/unix-directory';
+ isFile = false;
+ }
+ }
+
+ data.permissions = OC.PERMISSION_READ;
+ var permissionProp = props['{' + Client.NS_OWNCLOUD + '}permissions'];
+ if (!_.isUndefined(permissionProp)) {
+ var permString = permissionProp || '';
+ data.mountType = null;
+ for (var i = 0; i < permString.length; i++) {
+ var c = permString.charAt(i);
+ switch (c) {
+ // FIXME: twisted permissions
+ case 'C':
+ case 'K':
+ data.permissions |= OC.PERMISSION_CREATE;
+ if (!isFile) {
+ data.permissions |= OC.PERMISSION_UPDATE;
+ }
+ break;
+ case 'W':
+ if (isFile) {
+ // also add create permissions
+ data.permissions |= OC.PERMISSION_CREATE;
+ }
+ data.permissions |= OC.PERMISSION_UPDATE;
+ break;
+ case 'D':
+ data.permissions |= OC.PERMISSION_DELETE;
+ break;
+ case 'R':
+ data.permissions |= OC.PERMISSION_SHARE;
+ break;
+ case 'M':
+ if (!data.mountType) {
+ // TODO: how to identify external-root ?
+ data.mountType = 'external';
+ }
+ break;
+ case 'S':
+ // TODO: how to identify shared-root ?
+ data.mountType = 'shared';
+ break;
+ }
+ }
+ }
+
+ return new FileInfo(data);
+ },
+
+ /**
+ * Parse Webdav multistatus
+ *
+ * @param {Array} responses
+ */
+ _parseResult: function(responses) {
+ var self = this;
+ return _.map(responses, function(response) {
+ return self._parseFileInfo(response);
+ });
+ },
+
+ /**
+ * Returns whether the given status code means success
+ *
+ * @param {int} status status code
+ *
+ * @return true if status code is between 200 and 299 included
+ */
+ _isSuccessStatus: function(status) {
+ return status >= 200 && status <= 299;
+ },
+
+ /**
+ * Returns the default PROPFIND properties to use during a call.
+ *
+ * @return {Array.<Object>} array of properties
+ */
+ _getPropfindProperties: function() {
+ if (!this._propfindProperties) {
+ this._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {
+ return '{' + propDef[0] + '}' + propDef[1];
+ });
+ }
+ return this._propfindProperties;
+ },
+
+ /**
+ * Lists the contents of a directory
+ *
+ * @param {String} path path to retrieve
+ * @param {Object} [options] options
+ * @param {boolean} [options.includeParent=false] set to true to keep
+ * the parent folder in the result list
+ *
+ * @return {Promise} promise
+ */
+ getFolderContents: function(path, options) {
+ if (!path) {
+ path = '';
+ }
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+
+ // TODO: headers
+ this._client.propFind(
+ this._buildUrl(path),
+ this._getPropfindProperties(),
+ 1
+ ).then(function(result) {
+ if (self._isSuccessStatus(result.status)) {
+ var results = self._parseResult(result.body);
+ if (!options || !options.includeParent) {
+ // remove root dir, the first entry
+ results.shift();
+ }
+ deferred.resolve(result.status, results);
+ } else {
+ deferred.reject(result.status);
+ }
+ });
+ return promise;
+ },
+
+ /**
+ * Returns the file info of a given path.
+ *
+ * @param {String} path path
+ * @param {Array} [properties] list of webdav properties to
+ * retrieve
+ *
+ * @return {Promise} promise
+ */
+ getFileInfo: function(path) {
+ if (!path) {
+ path = '';
+ }
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+
+ // TODO: headers
+ this._client.propFind(
+ this._buildUrl(path),
+ this._getPropfindProperties(),
+ 0
+ ).then(
+ function(result) {
+ if (self._isSuccessStatus(result.status)) {
+ deferred.resolve(result.status, self._parseResult([result.body])[0]);
+ } else {
+ deferred.reject(result.status);
+ }
+ }
+ );
+ return promise;
+ },
+
+ /**
+ * Returns the contents of the given file.
+ *
+ * @param {String} path path to file
+ *
+ * @return {Promise}
+ */
+ getFileContents: function(path) {
+ if (!path) {
+ throw 'Missing argument "path"';
+ }
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+
+ this._client.request(
+ 'GET',
+ this._buildUrl(path),
+ this._defaultHeaders
+ ).then(
+ function(result) {
+ if (self._isSuccessStatus(result.status)) {
+ deferred.resolve(result.status, result.body);
+ } else {
+ deferred.reject(result.status);
+ }
+ }
+ );
+ return promise;
+ },
+
+ /**
+ * Puts the given data into the given file.
+ *
+ * @param {String} path path to file
+ * @param {String} body file body
+ * @param {Object} [options]
+ * @param {String} [options.contentType='text/plain'] content type
+ * @param {bool} [options.overwrite=true] whether to overwrite an existing file
+ *
+ * @return {Promise}
+ */
+ putFileContents: function(path, body, options) {
+ if (!path) {
+ throw 'Missing argument "path"';
+ }
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+ options = options || {};
+ var headers = _.extend({}, this._defaultHeaders);
+ var contentType = 'text/plain';
+ if (options.contentType) {
+ contentType = options.contentType;
+ }
+
+ headers['Content-Type'] = contentType;
+
+ if (_.isUndefined(options.overwrite) || options.overwrite) {
+ // will trigger 412 precondition failed if a file already exists
+ headers['If-None-Match'] = '*';
+ }
+
+ this._client.request(
+ 'PUT',
+ this._buildUrl(path),
+ headers,
+ body || ''
+ ).then(
+ function(result) {
+ if (self._isSuccessStatus(result.status)) {
+ deferred.resolve(result.status);
+ } else {
+ deferred.reject(result.status);
+ }
+ }
+ );
+ return promise;
+ },
+
+ _simpleCall: function(method, path) {
+ if (!path) {
+ throw 'Missing argument "path"';
+ }
+
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+
+ this._client.request(
+ method,
+ this._buildUrl(path),
+ this._defaultHeaders
+ ).then(
+ function(result) {
+ if (self._isSuccessStatus(result.status)) {
+ deferred.resolve(result.status);
+ } else {
+ deferred.reject(result.status);
+ }
+ }
+ );
+ return promise;
+ },
+
+ /**
+ * Creates a directory
+ *
+ * @param {String} path path to create
+ *
+ * @return {Promise}
+ */
+ createDirectory: function(path) {
+ return this._simpleCall('MKCOL', path);
+ },
+
+ /**
+ * Deletes a file or directory
+ *
+ * @param {String} path path to delete
+ *
+ * @return {Promise}
+ */
+ remove: function(path) {
+ return this._simpleCall('DELETE', path);
+ },
+
+ /**
+ * Moves path to another path
+ *
+ * @param {String} path path to move
+ * @param {String} destinationPath destination path
+ * @param {boolean} [allowOverwrite=false] true to allow overwriting,
+ * false otherwise
+ *
+ * @return {Promise} promise
+ */
+ move: function(path, destinationPath, allowOverwrite) {
+ if (!path) {
+ throw 'Missing argument "path"';
+ }
+ if (!destinationPath) {
+ throw 'Missing argument "destinationPath"';
+ }
+
+ var self = this;
+ var deferred = $.Deferred();
+ var promise = deferred.promise();
+ var headers =
+ _.extend({
+ 'Destination' : this._buildUrl(destinationPath)
+ }, this._defaultHeaders);
+
+ if (!allowOverwrite) {
+ headers['Overwrite'] = 'F';
+ }
+
+ this._client.request(
+ 'MOVE',
+ this._buildUrl(path),
+ headers
+ ).then(
+ function(response) {
+ if (self._isSuccessStatus(response.status)) {
+ deferred.resolve(response.status);
+ } else {
+ deferred.reject(response.status);
+ }
+ }
+ );
+ return promise;
+ }
+
+ };
+
+ if (!OC.Files) {
+ /**
+ * @namespace OC.Files
+ *
+ * @since 8.2
+ */
+ OC.Files = {};
+ }
+
+ /**
+ * Returns the default instance of the files client
+ *
+ * @return {OC.Files.Client} default client
+ *
+ * @since 8.2
+ */
+ OC.Files.getClient = function() {
+ if (OC.Files._defaultClient) {
+ return OC.Files._defaultClient;
+ }
+
+ var client = new OC.Files.Client({
+ host: OC.getHost(),
+ port: OC.getPort(),
+ root: OC.linkToRemoteBase('webdav'),
+ useHTTPS: OC.getProtocol() === 'https'
+ });
+ OC.Files._defaultClient = client;
+ return client;
+ };
+
+ OC.Files.Client = Client;
+})(OC, OC.Files.FileInfo);
+
diff --git a/core/js/files/fileinfo.js b/core/js/files/fileinfo.js
new file mode 100644
index 00000000000..95af07b7992
--- /dev/null
+++ b/core/js/files/fileinfo.js
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2015
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+(function(OC) {
+
+ /**
+ * @class OC.Files.FileInfo
+ * @classdesc File information
+ *
+ * @param {Object} data file data, see attributes for details
+ *
+ * @since 8.2
+ */
+ var FileInfo = function(data) {
+ if (!_.isUndefined(data.id)) {
+ this.id = parseInt(data.id, 10);
+ }
+
+ // TODO: normalize path
+ this.path = data.path || '';
+ this.name = data.name;
+
+ this.mtime = data.mtime;
+ this.etag = data.etag;
+ this.permissions = data.permissions;
+ this.size = data.size;
+ this.mimetype = data.mimetype || 'application/octet-stream';
+ this.mountType = data.mountType;
+ this.icon = data.icon;
+ this._props = data._props;
+
+ if (data.type) {
+ this.type = data.type;
+ } else if (this.mimetype === 'httpd/unix-directory') {
+ this.type = 'dir';
+ } else {
+ this.type = 'file';
+ }
+
+ if (!this.mimetype) {
+ if (this.type === 'dir') {
+ this.mimetype = 'httpd/unix-directory';
+ } else {
+ this.mimetype = 'application/octet-stream';
+ }
+ }
+ };
+
+ /**
+ * @memberof OC.Files
+ */
+ FileInfo.prototype = {
+ /**
+ * File id
+ *
+ * @type int
+ */
+ id: null,
+
+ /**
+ * File name
+ *
+ * @type String
+ */
+ name: null,
+
+ /**
+ * Path leading to the file, without the file name,
+ * and with a leading slash.
+ *
+ * @type String
+ */
+ path: null,
+
+ /**
+ * Mime type
+ *
+ * @type String
+ */
+ mimetype: null,
+
+ /**
+ * Icon URL.
+ *
+ * Can be used to override the mime type icon.
+ *
+ * @type String
+ */
+ icon: null,
+
+ /**
+ * File type. 'file' for files, 'dir' for directories.
+ *
+ * @type String
+ * @deprecated rely on mimetype instead
+ */
+ type: 'file',
+
+ /**
+ * Permissions.
+ *
+ * @see OC#PERMISSION_ALL for permissions
+ * @type int
+ */
+ permissions: null,
+
+ /**
+ * Modification time
+ *
+ * @type int
+ */
+ mtime: null,
+
+ /**
+ * Etag
+ *
+ * @type String
+ */
+ etag: null,
+
+ /**
+ * Mount type.
+ *
+ * One of null, "external-root", "shared" or "shared-root"
+ *
+ * @type string
+ */
+ mountType: null
+ };
+
+ if (!OC.Files) {
+ OC.Files = {};
+ }
+ OC.Files.FileInfo = FileInfo;
+})(OC);
+
diff --git a/core/js/js.js b/core/js/js.js
index 57c9871233b..ce552bb8ea2 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1428,7 +1428,6 @@ function initCore() {
$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
}
-
}
$(document).ready(initCore);
diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js
index cd387d76ce8..f09a7054c9f 100644
--- a/core/js/tests/specHelper.js
+++ b/core/js/tests/specHelper.js
@@ -86,6 +86,7 @@ window.firstDay = 0;
// setup dummy webroots
/* jshint camelcase: false */
window.oc_debug = true;
+// FIXME: oc_webroot is supposed to be only the path!!!
window.oc_webroot = location.href + '/';
window.oc_appswebroots = {
"files": window.oc_webroot + '/apps/files/'
diff --git a/core/js/tests/specs/files/clientSpec.js b/core/js/tests/specs/files/clientSpec.js
new file mode 100644
index 00000000000..67815d93f05
--- /dev/null
+++ b/core/js/tests/specs/files/clientSpec.js
@@ -0,0 +1,712 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OC.Files.Client tests', function() {
+ var Client = OC.Files.Client;
+ var baseUrl;
+ var client;
+
+ beforeEach(function() {
+ baseUrl = 'https://testhost:999/owncloud/remote.php/webdav/';
+
+ client = new Client({
+ host: 'testhost',
+ port: 999,
+ root: '/owncloud/remote.php/webdav',
+ useHTTPS: true
+ });
+ });
+ afterEach(function() {
+ client = null;
+ });
+
+ /**
+ * Send an status response and check that the given
+ * promise gets its success handler called with the error
+ * status code
+ *
+ * @param {Promise} promise promise
+ * @param {int} status status to test
+ */
+ function respondAndCheckStatus(promise, status) {
+ var successHandler = sinon.stub();
+ var failHandler = sinon.stub();
+ promise.done(successHandler);
+ promise.fail(failHandler);
+
+ fakeServer.requests[0].respond(
+ status,
+ {'Content-Type': 'application/xml'},
+ ''
+ );
+
+ promise.then(function() {
+ expect(successHandler.calledOnce).toEqual(true);
+ expect(successHandler.getCall(0).args[0]).toEqual(status);
+
+ expect(failHandler.notCalled).toEqual(true);
+ });
+
+ return promise;
+ }
+
+ /**
+ * Send an error response and check that the given
+ * promise gets its fail handler called with the error
+ * status code
+ *
+ * @param {Promise} promise promise object
+ * @param {int} status error status to test
+ */
+ function respondAndCheckError(promise, status) {
+ var successHandler = sinon.stub();
+ var failHandler = sinon.stub();
+ promise.done(successHandler);
+ promise.fail(failHandler);
+
+ fakeServer.requests[0].respond(
+ status,
+ {'Content-Type': 'application/xml'},
+ ''
+ );
+
+ promise.then(function() {
+ expect(failHandler.calledOnce).toEqual(true);
+ expect(failHandler.calledWith(status)).toEqual(true);
+
+ expect(successHandler.notCalled).toEqual(true);
+
+ fulfill();
+ });
+
+ return promise;
+ }
+
+ /**
+ * Returns a list of request properties parsed from the given request body.
+ *
+ * @param {string} requestBody request XML
+ *
+ * @return {Array.<String>} array of request properties in the format
+ * "{NS:}propname"
+ */
+ function getRequestedProperties(requestBody) {
+ var doc = (new window.DOMParser()).parseFromString(
+ requestBody,
+ 'application/xml'
+ );
+ var propRoots = doc.getElementsByTagNameNS('DAV:', 'prop');
+ var propsList = propRoots.item(0).childNodes;
+ return _.map(propsList, function(propNode) {
+ return '{' + propNode.namespaceURI + '}' + propNode.localName;
+ });
+ }
+
+ function makePropBlock(props) {
+ var s = '<d:prop>\n';
+
+ _.each(props, function(value, key) {
+ s += '<' + key + '>' + value + '</' + key + '>\n';
+ });
+
+ return s + '</d:prop>\n';
+ }
+
+ function makeResponseBlock(href, props, failedProps) {
+ var s = '<d:response>\n';
+ s += '<d:href>' + href + '</d:href>\n';
+ s += '<d:propstat>\n';
+ s += makePropBlock(props);
+ s += '<d:status>HTTP/1.1 200 OK</d:status>';
+ s += '</d:propstat>\n';
+ if (failedProps) {
+ s += '<d:propstat>\n';
+ _.each(failedProps, function(prop) {
+ s += '<' + prop + '/>\n';
+ });
+ s += '<d:status>HTTP/1.1 404 Not Found</d:status>\n';
+ s += '</d:propstat>\n';
+ }
+ return s + '</d:response>\n';
+ }
+
+ describe('file listing', function() {
+
+ var folderContentsXml =
+ '<?xml version="1.0" encoding="utf-8"?>' +
+ '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
+ {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+ 'd:getetag': '"56cfcabd79abb"',
+ 'd:resourcetype': '<d:collection/>',
+ 'oc:id': '00000011oc2d13a6a068',
+ 'oc:permissions': 'RDNVCK',
+ 'oc:size': 120
+ },
+ [
+ 'd:getcontenttype',
+ 'd:getcontentlength'
+ ]
+ ) +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt',
+ {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
+ 'd:getetag': '"559fcabd79a38"',
+ 'd:getcontenttype': 'text/plain',
+ 'd:getcontentlength': 250,
+ 'd:resourcetype': '',
+ 'oc:id': '00000051oc2d13a6a068',
+ 'oc:permissions': 'RDNVW'
+ },
+ [
+ 'oc:size',
+ ]
+ ) +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub',
+ {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT',
+ 'd:getetag': '"66cfcabd79abb"',
+ 'd:resourcetype': '<d:collection/>',
+ 'oc:id': '00000015oc2d13a6a068',
+ 'oc:permissions': 'RDNVCK',
+ 'oc:size': 100
+ },
+ [
+ 'd:getcontenttype',
+ 'd:getcontentlength'
+ ]
+ ) +
+ '</d:multistatus>';
+
+ it('sends PROPFIND with explicit properties to get file list', function() {
+ client.getFolderContents('path/to space/文件夹');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('PROPFIND');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(1);
+
+ var props = getRequestedProperties(fakeServer.requests[0].requestBody);
+ expect(props).toContain('{DAV:}getlastmodified');
+ expect(props).toContain('{DAV:}getcontentlength');
+ expect(props).toContain('{DAV:}getcontenttype');
+ expect(props).toContain('{DAV:}getetag');
+ expect(props).toContain('{DAV:}resourcetype');
+ expect(props).toContain('{http://owncloud.org/ns}id');
+ expect(props).toContain('{http://owncloud.org/ns}size');
+ expect(props).toContain('{http://owncloud.org/ns}permissions');
+ });
+ it('sends PROPFIND to base url when empty path given', function() {
+ client.getFolderContents('');
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].url).toEqual(baseUrl);
+ });
+ it('sends PROPFIND to base url when root path given', function() {
+ client.getFolderContents('/');
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].url).toEqual(baseUrl);
+ });
+ it('parses the result list into a FileInfo array', function() {
+ var promise = client.getFolderContents('path/to space/文件夹');
+
+ expect(fakeServer.requests.length).toEqual(1);
+
+ fakeServer.requests[0].respond(
+ 207,
+ {'Content-Type': 'application/xml'},
+ folderContentsXml
+ );
+
+ promise.then(function(status, response) {
+ expect(status).toEqual(207);
+ expect(_.isArray(response)).toEqual(true);
+
+ expect(response.length).toEqual(2);
+
+ // file entry
+ var info = response[0];
+ expect(info instanceof OC.Files.FileInfo).toEqual(true);
+ expect(info.id).toEqual(51);
+ expect(info.path).toEqual('/path/to space/文件夹');
+ expect(info.name).toEqual('One.txt');
+ expect(info.permissions).toEqual(31);
+ expect(info.size).toEqual(250);
+ expect(info.mtime.getTime()).toEqual(1436535485000);
+ expect(info.mimetype).toEqual('text/plain');
+ expect(info.etag).toEqual('559fcabd79a38');
+
+ // sub entry
+ info = response[1];
+ expect(info instanceof OC.Files.FileInfo).toEqual(true);
+ expect(info.id).toEqual(15);
+ expect(info.path).toEqual('/path/to space/文件夹');
+ expect(info.name).toEqual('sub');
+ expect(info.permissions).toEqual(31);
+ expect(info.size).toEqual(100);
+ expect(info.mtime.getTime()).toEqual(1436536800000);
+ expect(info.mimetype).toEqual('httpd/unix-directory');
+ expect(info.etag).toEqual('66cfcabd79abb');
+ });
+ return promise.promise();
+ });
+ it('returns parent node in result if specified', function() {
+ var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
+
+ expect(fakeServer.requests.length).toEqual(1);
+
+ fakeServer.requests[0].respond(
+ 207,
+ {'Content-Type': 'application/xml'},
+ folderContentsXml
+ );
+
+ promise.then(function(status, response) {
+ expect(status).toEqual(207);
+ expect(_.isArray(response)).toEqual(true);
+
+ expect(response.length).toEqual(3);
+
+ // root entry
+ var info = response[0];
+ expect(info instanceof OC.Files.FileInfo).toEqual(true);
+ expect(info.id).toEqual(11);
+ expect(info.path).toEqual('/path/to space');
+ expect(info.name).toEqual('文件夹');
+ expect(info.permissions).toEqual(31);
+ expect(info.size).toEqual(120);
+ expect(info.mtime.getTime()).toEqual(1436522405000);
+ expect(info.mimetype).toEqual('httpd/unix-directory');
+ expect(info.etag).toEqual('56cfcabd79abb');
+
+ // the two other entries follow
+ expect(response[1].id).toEqual(51);
+ expect(response[2].id).toEqual(15);
+ });
+
+ return promise;
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
+ return respondAndCheckError(promise, 404);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('file info', function() {
+ var responseXml =
+ '<?xml version="1.0" encoding="utf-8"?>' +
+ '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
+ {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+ 'd:getetag': '"56cfcabd79abb"',
+ 'd:resourcetype': '<d:collection/>',
+ 'oc:id': '00000011oc2d13a6a068',
+ 'oc:permissions': 'RDNVCK',
+ 'oc:size': 120
+ },
+ [
+ 'd:getcontenttype',
+ 'd:getcontentlength'
+ ]
+ ) +
+ '</d:multistatus>';
+
+ it('sends PROPFIND with zero depth to get single file info', function() {
+ client.getFileInfo('path/to space/文件夹');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('PROPFIND');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(0);
+
+ var props = getRequestedProperties(fakeServer.requests[0].requestBody);
+ expect(props).toContain('{DAV:}getlastmodified');
+ expect(props).toContain('{DAV:}getcontentlength');
+ expect(props).toContain('{DAV:}getcontenttype');
+ expect(props).toContain('{DAV:}getetag');
+ expect(props).toContain('{DAV:}resourcetype');
+ expect(props).toContain('{http://owncloud.org/ns}id');
+ expect(props).toContain('{http://owncloud.org/ns}size');
+ expect(props).toContain('{http://owncloud.org/ns}permissions');
+ });
+ it('parses the result into a FileInfo', function() {
+ var promise = client.getFileInfo('path/to space/文件夹');
+
+ expect(fakeServer.requests.length).toEqual(1);
+
+ fakeServer.requests[0].respond(
+ 207,
+ {'Content-Type': 'application/xml'},
+ responseXml
+ );
+
+ promise.then(function(status, response) {
+ expect(status).toEqual(207);
+ expect(_.isArray(response)).toEqual(false);
+
+ var info = response;
+ expect(info instanceof OC.Files.FileInfo).toEqual(true);
+ expect(info.id).toEqual(11);
+ expect(info.path).toEqual('/path/to space');
+ expect(info.name).toEqual('文件夹');
+ expect(info.permissions).toEqual(31);
+ expect(info.size).toEqual(120);
+ expect(info.mtime.getTime()).toEqual(1436522405000);
+ expect(info.mimetype).toEqual('httpd/unix-directory');
+ expect(info.etag).toEqual('56cfcabd79abb');
+ });
+
+ return promise;
+ });
+ it('properly parses entry inside root', function() {
+ var responseXml =
+ '<?xml version="1.0" encoding="utf-8"?>' +
+ '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/in%20root',
+ {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
+ 'd:getetag': '"56cfcabd79abb"',
+ 'd:resourcetype': '<d:collection/>',
+ 'oc:id': '00000011oc2d13a6a068',
+ 'oc:permissions': 'RDNVCK',
+ 'oc:size': 120
+ },
+ [
+ 'd:getcontenttype',
+ 'd:getcontentlength'
+ ]
+ ) +
+ '</d:multistatus>';
+
+ var promise = client.getFileInfo('in root');
+
+ expect(fakeServer.requests.length).toEqual(1);
+
+ fakeServer.requests[0].respond(
+ 207,
+ {'Content-Type': 'application/xml'},
+ responseXml
+ );
+
+ promise.then(function(status, response) {
+ expect(status).toEqual(207);
+ expect(_.isArray(response)).toEqual(false);
+
+ var info = response;
+ expect(info instanceof OC.Files.FileInfo).toEqual(true);
+ expect(info.id).toEqual(11);
+ expect(info.path).toEqual('/');
+ expect(info.name).toEqual('in root');
+ expect(info.permissions).toEqual(31);
+ expect(info.size).toEqual(120);
+ expect(info.mtime.getTime()).toEqual(1436522405000);
+ expect(info.mimetype).toEqual('httpd/unix-directory');
+ expect(info.etag).toEqual('56cfcabd79abb');
+ });
+
+ return promise;
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.getFileInfo('path/to space/文件夹');
+ return respondAndCheckError(promise, 404);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('permissions', function() {
+
+ function getFileInfoWithPermission(webdavPerm, isFile) {
+ var props = {
+ 'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
+ 'd:getetag': '"559fcabd79a38"',
+ 'd:getcontentlength': 250,
+ 'oc:id': '00000051oc2d13a6a068',
+ 'oc:permissions': webdavPerm,
+ };
+
+ if (isFile) {
+ props['d:getcontenttype'] = 'text/plain';
+ } else {
+ props['d:resourcetype'] = '<d:collection/>';
+ }
+
+ var responseXml =
+ '<?xml version="1.0" encoding="utf-8"?>' +
+ '<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
+ makeResponseBlock(
+ '/owncloud/remote.php/webdav/file.txt',
+ props
+ ) +
+ '</d:multistatus>';
+ var promise = client.getFileInfo('file.txt');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 207,
+ {'Content-Type': 'application/xml'},
+ responseXml
+ );
+
+ fakeServer.restore();
+ fakeServer = sinon.fakeServer.create();
+
+ return promise;
+ }
+
+ function testPermission(permission, isFile, expectedPermissions) {
+ var promise = getFileInfoWithPermission(permission, isFile);
+ promise.then(function(result) {
+ expect(result.permissions).toEqual(expectedPermissions);
+ });
+ return promise;
+ }
+
+ function testMountType(permission, isFile, expectedMountType) {
+ var promise = getFileInfoWithPermission(permission, isFile);
+ promise.then(function(result) {
+ expect(result.mountType).toEqual(expectedMountType);
+ });
+ return promise;
+ }
+
+ it('properly parses file permissions', function() {
+ // permission, isFile, expectedPermissions
+ var testCases = [
+ ['', true, OC.PERMISSION_READ],
+ ['C', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
+ ['K', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
+ ['W', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+ ['D', true, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
+ ['R', true, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
+ ['CKWDR', true, OC.PERMISSION_ALL]
+ ];
+ return Promise.all(
+ _.map(testCases, function(testCase) {
+ return testPermission.apply(testCase);
+ })
+ );
+ });
+ it('properly parses folder permissions', function() {
+ var testCases = [
+ ['', false, OC.PERMISSION_READ],
+ ['C', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+ ['K', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
+ ['W', false, OC.PERMISSION_READ | OC.PERMISSION_UPDATE],
+ ['D', false, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
+ ['R', false, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
+ ['CKWDR', false, OC.PERMISSION_ALL]
+ ];
+
+ return Promise.all(
+ _.map(testCases, function(testCase) {
+ return testPermission.apply(testCase);
+ })
+ );
+ });
+ it('properly parses mount types', function() {
+ var testCases = [
+ ['CKWDR', false, null],
+ ['M', false, 'external'],
+ ['S', false, 'shared'],
+ ['SM', false, 'shared']
+ ];
+
+ return Promise.all(
+ _.map(testCases, function(testCase) {
+ return testMountType.apply(testCase);
+ })
+ );
+ });
+ });
+
+ describe('get file contents', function() {
+ it('returns file contents', function() {
+ var promise = client.getFileContents('path/to space/文件夹/One.txt');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('GET');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+
+ fakeServer.requests[0].respond(
+ 200,
+ {'Content-Type': 'text/plain'},
+ 'some contents'
+ );
+
+ promise.then(function(status, response) {
+ expect(status).toEqual(200);
+ expect(response).toEqual('some contents');
+ });
+
+ return promise;
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.getFileContents('path/to space/文件夹/One.txt');
+ return respondAndCheckError(promise, 409);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('put file contents', function() {
+ it('sends PUT with file contents', function() {
+ var promise = client.putFileContents(
+ 'path/to space/文件夹/One.txt',
+ 'some contents'
+ );
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('PUT');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+ expect(fakeServer.requests[0].requestBody).toEqual('some contents');
+ expect(fakeServer.requests[0].requestHeaders['If-None-Match']).toEqual('*');
+ expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/plain;charset=utf-8');
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('sends PUT with file contents with headers matching options', function() {
+ var promise = client.putFileContents(
+ 'path/to space/文件夹/One.txt',
+ 'some contents',
+ {
+ overwrite: false,
+ contentType: 'text/markdown'
+ }
+ );
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('PUT');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
+ expect(fakeServer.requests[0].requestBody).toEqual('some contents');
+ expect(fakeServer.requests[0].requestHeaders['If-None-Match']).not.toBeDefined();
+ expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/markdown;charset=utf-8');
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.putFileContents(
+ 'path/to space/文件夹/One.txt',
+ 'some contents'
+ );
+ return respondAndCheckError(promise, 409);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('create directory', function() {
+ it('sends MKCOL with specified path', function() {
+ var promise = client.createDirectory('path/to space/文件夹/new dir');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('MKCOL');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/new%20dir');
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.createDirectory('path/to space/文件夹/new dir');
+ return respondAndCheckError(promise, 404);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('deletion', function() {
+ it('sends DELETE with specified path', function() {
+ var promise = client.remove('path/to space/文件夹');
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('DELETE');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.remove('path/to space/文件夹');
+ return respondAndCheckError(promise, 404);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+
+ describe('move', function() {
+ it('sends MOVE with specified paths with fail on overwrite by default', function() {
+ var promise = client.move(
+ 'path/to space/文件夹',
+ 'path/to space/anotherdir/文件夹'
+ );
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('MOVE');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Destination)
+ .toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Overwrite)
+ .toEqual('F');
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('sends MOVE with silent overwrite mode when specified', function() {
+ var promise = client.move(
+ 'path/to space/文件夹',
+ 'path/to space/anotherdir/文件夹',
+ {allowOverwrite: true}
+ );
+
+ expect(fakeServer.requests.length).toEqual(1);
+ expect(fakeServer.requests[0].method).toEqual('MOVE');
+ expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Destination)
+ .toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
+ expect(fakeServer.requests[0].requestHeaders.Overwrite)
+ .not.toBeDefined();
+
+ return respondAndCheckStatus(promise, 201);
+ });
+ it('rejects promise when an error occurred', function() {
+ var promise = client.move(
+ 'path/to space/文件夹',
+ 'path/to space/anotherdir/文件夹',
+ {allowOverwrite: true}
+ );
+ return respondAndCheckError(promise, 404);
+ });
+ it('throws exception if arguments are missing', function() {
+ // TODO
+ });
+ });
+});
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 64a94ef230b..dc621ae0f74 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -164,15 +164,15 @@ module.exports = function(config) {
// need to test the core app as well ?
if (testCore) {
// core tests
- files.push(corePath + 'tests/specs/*.js');
+ files.push(corePath + 'tests/specs/**/*.js');
}
function addApp(app) {
// if only a string was specified, expand to structure
if (typeof(app) === 'string') {
app = {
- srcFiles: 'apps/' + app + '/js/*.js',
- testFiles: 'apps/' + app + '/tests/js/*.js'
+ srcFiles: 'apps/' + app + '/js/**/*.js',
+ testFiles: 'apps/' + app + '/tests/js/**/*.js'
};
}