diff --git a/lib/storage/acl.js b/lib/storage/acl.js
index 2267bbbca96..7d5b580c12f 100644
--- a/lib/storage/acl.js
+++ b/lib/storage/acl.js
@@ -20,6 +20,7 @@
'use strict';
+var arrify = require('arrify');
var is = require('is');
var nodeutil = require('util');
@@ -66,8 +67,8 @@ var nodeutil = require('util');
function Acl(options) {
AclRoleAccessorMethods.call(this);
- this.makeReq = options.makeReq;
this.pathPrefix = options.pathPrefix;
+ this.request_ = options.request;
}
/**
@@ -232,28 +233,29 @@ nodeutil.inherits(Acl, AclRoleAccessorMethods);
* }, function(err, aclObject, apiResponse) {});
*/
Acl.prototype.add = function(options, callback) {
- var that = this;
+ var self = this;
- var body = {
- entity: options.entity,
- role: options.role.toUpperCase()
- };
-
- var query = null;
+ var query = {};
if (options.generation) {
- query = {
- generation: options.generation
- };
+ query.generation = options.generation;
}
- this.makeReq_('POST', '', query, body, function(err, resp) {
+ this.request({
+ method: 'POST',
+ uri: '',
+ qs: query,
+ json: {
+ entity: options.entity,
+ role: options.role.toUpperCase()
+ }
+ }, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
}
- callback(null, that.makeAclObject_(resp), resp);
+ callback(null, self.makeAclObject_(resp), resp);
});
};
@@ -287,16 +289,19 @@ Acl.prototype.add = function(options, callback) {
* }, function(err, apiResponse) {});
*/
Acl.prototype.delete = function(options, callback) {
- var path = '/' + encodeURIComponent(options.entity);
- var query = null;
+ var query = {};
if (options.generation) {
- query = {
- generation: options.generation
- };
+ query.generation = options.generation;
}
- this.makeReq_('DELETE', path, query, null, callback);
+ this.request({
+ method: 'DELETE',
+ uri: '/' + encodeURIComponent(options.entity),
+ qs: query
+ }, function(err, resp) {
+ callback(err, resp);
+ });
};
/**
@@ -346,9 +351,9 @@ Acl.prototype.delete = function(options, callback) {
* }, function(err, aclObject, apiResponse) {});
*/
Acl.prototype.get = function(options, callback) {
- var that = this;
+ var self = this;
var path = '';
- var query = null;
+ var query = {};
if (is.fn(options)) {
callback = options;
@@ -357,24 +362,25 @@ Acl.prototype.get = function(options, callback) {
path = '/' + encodeURIComponent(options.entity);
if (options.generation) {
- query = {
- generation: options.generation
- };
+ query.generation = options.generation;
}
}
- this.makeReq_('GET', path, query, null, function(err, resp) {
+ this.request({
+ uri: path,
+ qs: query,
+ }, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
}
- var results = resp;
+ var results;
if (resp.items) {
- results = (resp.items || []).map(that.makeAclObject_);
+ results = arrify(resp.items).map(self.makeAclObject_);
} else {
- results = that.makeAclObject_(results);
+ results = self.makeAclObject_(resp);
}
callback(null, results, resp);
@@ -420,27 +426,28 @@ Acl.prototype.get = function(options, callback) {
* }, function(err, aclObject, apiResponse) {});
*/
Acl.prototype.update = function(options, callback) {
- var that = this;
- var path = '/' + encodeURIComponent(options.entity);
- var query = null;
+ var self = this;
+
+ var query = {};
if (options.generation) {
- query = {
- generation: options.generation
- };
+ query.generation = options.generation;
}
- var body = {
- role: options.role.toUpperCase()
- };
-
- this.makeReq_('PUT', path, query, body, function(err, resp) {
+ this.request({
+ method: 'PUT',
+ uri: '/' + encodeURIComponent(options.entity),
+ qs: query,
+ json: {
+ role: options.role.toUpperCase()
+ }
+ }, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
}
- callback(null, that.makeAclObject_(resp), resp);
+ callback(null, self.makeAclObject_(resp), resp);
});
};
@@ -473,8 +480,9 @@ Acl.prototype.makeAclObject_ = function(accessControlObject) {
* @param {*} body - Request body contents.
* @param {function} callback - The callback function.
*/
-Acl.prototype.makeReq_ = function(method, path, query, body, callback) {
- this.makeReq(method, this.pathPrefix + path, query, body, callback);
+Acl.prototype.request = function(reqOpts, callback) {
+ reqOpts.uri = this.pathPrefix + reqOpts.uri;
+ this.request_(reqOpts, callback);
};
module.exports = Acl;
@@ -522,7 +530,7 @@ AclRoleAccessorMethods.roles = [
];
AclRoleAccessorMethods.prototype._assignAccessMethods = function(role) {
- var that = this;
+ var self = this;
var accessMethods = AclRoleAccessorMethods.accessMethods;
var entities = AclRoleAccessorMethods.entities;
@@ -553,7 +561,7 @@ AclRoleAccessorMethods.prototype._assignAccessMethods = function(role) {
callback = entityId;
}
- that[accessMethod]({
+ self[accessMethod]({
entity: apiEntity,
role: role
}, callback);
diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js
index c10ed8b9d54..ea7c1e32fca 100644
--- a/lib/storage/bucket.js
+++ b/lib/storage/bucket.js
@@ -20,12 +20,13 @@
'use strict';
+var arrify = require('arrify');
var async = require('async');
var extend = require('extend');
-var format = require('string-format-obj');
var fs = require('fs');
var is = require('is');
var mime = require('mime-types');
+var nodeutil = require('util');
var path = require('path');
/**
@@ -41,22 +42,22 @@ var Acl = require('./acl.js');
var File = require('./file.js');
/**
- * @type {module:common/streamrouter}
+ * @type {module:common/serviceObject}
* @private
*/
-var streamRouter = require('../common/stream-router.js');
+var ServiceObject = require('../common/service-object.js');
/**
- * @type {module:common/util}
+ * @type {module:common/streamrouter}
* @private
*/
-var util = require('../common/util.js');
+var streamRouter = require('../common/stream-router.js');
/**
- * @const {string}
+ * @type {module:common/util}
* @private
*/
-var STORAGE_BASE_URL = 'https://www.googleapis.com/storage/v1/b';
+var util = require('../common/util.js');
/**
* The size of a file (in bytes) must be greater than this number to
@@ -87,20 +88,139 @@ var RESUMABLE_THRESHOLD = 5000000;
* var gcloud = require('gcloud');
*
* var gcs = gcloud.storage({
+ * keyFilename: '/path/to/keyfile.json',
* projectId: 'grape-spaceship-123'
* });
*
- * var albums = gcs.bucket('albums');
+ * var bucket = gcs.bucket('albums');
*/
+
function Bucket(storage, name) {
- this.metadata = {};
+ var methods = {
+ /**
+ * Create a bucket.
+ *
+ * @param {object=} config - See {module:storage#createBucket}.
+ *
+ * @example
+ * bucket.create(function(err, zone, apiResponse) {
+ * if (!err) {
+ * // The zone was created successfully.
+ * }
+ * });
+ */
+ create: true,
+
+ /**
+ * Delete the bucket.
+ *
+ * @resource [Buckets: delete API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/delete}
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this
+ * request.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * bucket.delete(function(err, apiResponse) {});
+ */
+ delete: true,
+
+ /**
+ * Check if the bucket exists.
+ *
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this
+ * request.
+ * @param {boolean} callback.exists - Whether the bucket exists or not.
+ *
+ * @example
+ * bucket.exists(function(err, exists) {});
+ */
+ exists: true,
+
+ /**
+ * Get a bucket if it exists.
+ *
+ * You may optionally use this to "get or create" an object by providing an
+ * object with `autoCreate` set to `true`. Any extra configuration that is
+ * normally required for the `create` method must be contained within this
+ * object as well.
+ *
+ * @param {options=} options - Configuration object.
+ * @param {boolean} options.autoCreate - Automatically create the object if
+ * it does not exist. Default: `false`
+ *
+ * @example
+ * bucket.get(function(err, bucket, apiResponse) {
+ * // `bucket.metadata` has been populated.
+ * });
+ */
+ get: true,
+
+ /**
+ * Get the bucket's metadata.
+ *
+ * To set metadata, see {module:storage/bucket#setMetadata}.
+ *
+ * @resource [Buckets: get API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/get}
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this
+ * request.
+ * @param {object} callback.metadata - Tbe bucket's metadata.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * bucket.getMetadata(function(err, metadata, apiResponse) {});
+ */
+ getMetadata: true,
+
+ /**
+ * Set the bucket's metadata.
+ *
+ * @resource [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
+ *
+ * @param {object} metadata - The metadata you wish to set.
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this
+ * request.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * //-
+ * // Set website metadata field on the bucket.
+ * //-
+ * bucket.setMetadata({
+ * website: {
+ * mainPageSuffix: 'http://example.com',
+ * notFoundPage: 'http://example.com/404.html'
+ * }
+ * }, function(err, apiResponse) {});
+ *
+ * //-
+ * // Enable versioning for your bucket.
+ * //-
+ * bucket.setMetadata({
+ * versioning: {
+ * enabled: true
+ * }
+ * }, function(err, apiResponse) {});
+ */
+ setMetadata: true
+ };
+
+ ServiceObject.call(this, {
+ parent: storage,
+ baseUrl: '/b',
+ id: name,
+ createMethod: storage.createBucket.bind(storage),
+ methods: methods
+ });
+
this.name = name;
this.storage = storage;
- if (!this.name) {
- throw new Error('A bucket name is needed to use Google Cloud Storage.');
- }
-
/**
* Google Cloud Storage uses access control lists (ACLs) to manage object and
* bucket access. ACLs are the mechanism you use to share objects with other
@@ -138,12 +258,12 @@ function Bucket(storage, name) {
* }, function(err, aclObject) {});
*/
this.acl = new Acl({
- makeReq: this.makeReq_.bind(this),
+ request: this.request.bind(this),
pathPrefix: '/acl'
});
this.acl.default = new Acl({
- makeReq: this.makeReq_.bind(this),
+ request: this.request.bind(this),
pathPrefix: '/defaultObjectAcl'
});
@@ -209,6 +329,8 @@ function Bucket(storage, name) {
/* jshint ignore:end */
}
+nodeutil.inherits(Bucket, ServiceObject);
+
/**
* Combine mutliple files into one new file.
*
@@ -269,13 +391,10 @@ Bucket.prototype.combine = function(sources, destination, callback) {
}
}
- this.storage.makeAuthenticatedRequest_({
+ // Make the request from the destination File object.
+ destination.request({
method: 'POST',
- uri: format('{base}/{destBucket}/o/{destFile}/compose', {
- base: STORAGE_BASE_URL,
- destBucket: destination.bucket.name,
- destFile: encodeURIComponent(destination.name)
- }),
+ uri: '/compose',
json: {
destination: {
contentType: destination.metadata.contentType
@@ -310,24 +429,6 @@ Bucket.prototype.combine = function(sources, destination, callback) {
}
};
-/**
- * Delete the bucket.
- *
- * @resource [Buckets: delete API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/delete}
- *
- * @param {function=} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request
- * @param {object} callback.apiResponse - The full API response.
- *
- * @example
- * var bucket = gcs.bucket('delete-me');
- * bucket.delete(function(err, apiResponse) {});
- */
-Bucket.prototype.delete = function(callback) {
- callback = callback || util.noop;
- this.makeReq_('DELETE', '', null, true, callback);
-};
-
/**
* Iterate over the bucket's files, calling `file.delete()` on each.
*
@@ -444,6 +545,10 @@ Bucket.prototype.deleteFiles = function(query, callback) {
* var file = bucket.file('my-existing-file.png');
*/
Bucket.prototype.file = function(name, options) {
+ if (!name) {
+ throw Error('A file name must be specified.');
+ }
+
return new File(this, name, options);
};
@@ -544,62 +649,39 @@ Bucket.prototype.getFiles = function(query, callback) {
query = {};
}
- this.makeReq_('GET', '/o', query, true, function(err, resp) {
+ this.request({
+ uri: '/o',
+ qs: query
+ }, function(err, resp) {
if (err) {
callback(err, null, null, resp);
return;
}
- var files = (resp.items || []).map(function(item) {
+ var files = arrify(resp.items).map(function(file) {
var options = {};
if (query.versions) {
- options.generation = item.generation;
+ options.generation = file.generation;
}
- var file = self.file(item.name, options);
- file.metadata = item;
+ var fileInstance = self.file(file.name, options);
+ fileInstance.metadata = file;
- return file;
+ return fileInstance;
});
var nextQuery = null;
-
if (resp.nextPageToken) {
- nextQuery = extend({}, query, { pageToken: resp.nextPageToken });
+ nextQuery = extend({}, query, {
+ pageToken: resp.nextPageToken
+ });
}
callback(null, files, nextQuery, resp);
});
};
-/**
- * Get the bucket's metadata.
- *
- * To set metadata, see {module:storage/bucket#setMetadata}.
- *
- * @resource [Buckets: get API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/get}
- *
- * @param {function=} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request
- * @param {object} callback.metadata - Tbe bucket's metadata.
- * @param {object} callback.apiResponse - The full API response.
- *
- * @example
- * bucket.getMetadata(function(err, metadata, apiResponse) {});
- */
-Bucket.prototype.getMetadata = function(callback) {
- callback = callback || util.noop;
- this.makeReq_('GET', '', null, true, function(err, resp) {
- if (err) {
- callback(err, null, resp);
- return;
- }
- this.metadata = resp;
- callback(null, this.metadata, resp);
- }.bind(this));
-};
-
/**
* Make the bucket listing private.
*
@@ -685,9 +767,16 @@ Bucket.prototype.makePrivate = function(options, callback) {
// You aren't allowed to set both predefinedAcl & acl properties on a bucket
// so acl must explicitly be nullified.
- var metadata = { acl: null };
+ var metadata = {
+ acl: null
+ };
- self.makeReq_('PATCH', '', query, metadata, function(err, resp) {
+ self.request({
+ method: 'PATCH',
+ uri: '',
+ qs: query,
+ json: metadata
+ }, function(err, resp) {
if (err) {
done(err);
return;
@@ -816,52 +905,6 @@ Bucket.prototype.makePublic = function(options, callback) {
}
};
-/**
- * Set the bucket's metadata.
- *
- * @resource [Buckets: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch}
- *
- * @param {object} metadata - The metadata you wish to set.
- * @param {function=} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request
- * @param {object} callback.apiResponse - The full API response.
- *
- * @example
- * //-
- * // Set website metadata field on the bucket.
- * //-
- * bucket.setMetadata({
- * website: {
- * mainPageSuffix: 'http://example.com',
- * notFoundPage: 'http://example.com/404.html'
- * }
- * }, function(err, apiResponse) {});
- *
- * //-
- * // Enable versioning for your bucket.
- * //-
- * bucket.setMetadata({
- * versioning: {
- * enabled: true
- * }
- * }, function(err, apiResponse) {});
- */
-Bucket.prototype.setMetadata = function(metadata, callback) {
- var that = this;
- callback = callback || util.noop;
-
- this.makeReq_('PATCH', '', null, metadata, function(err, resp) {
- if (err) {
- callback(err, resp);
- return;
- }
-
- that.metadata = resp;
-
- callback(null, resp);
- });
-};
-
/**
* Upload a file to the bucket. This is a convenience method that wraps
* {module:storage/file#createWriteStream}.
@@ -1081,32 +1124,6 @@ Bucket.prototype.makeAllFilesPublicPrivate_ = function(options, callback) {
});
};
-/**
- * Make a new request object from the provided arguments and wrap the callback
- * to intercept non-successful responses.
- *
- * @private
- *
- * @param {string} method - Action.
- * @param {string} path - Request path.
- * @param {*} query - Request query object.
- * @param {*} body - Request body contents.
- * @param {function} callback - The callback function.
- */
-Bucket.prototype.makeReq_ = function(method, path, query, body, callback) {
- var reqOpts = {
- method: method,
- qs: query,
- uri: STORAGE_BASE_URL + '/' + this.name + path
- };
-
- if (body) {
- reqOpts.json = body;
- }
-
- this.storage.makeAuthenticatedRequest_(reqOpts, callback);
-};
-
/*! Developer Documentation
*
* This method can be used with either a callback or as a readable object
diff --git a/lib/storage/file.js b/lib/storage/file.js
index 1870da0d62d..d58c94ec2cb 100644
--- a/lib/storage/file.js
+++ b/lib/storage/file.js
@@ -28,11 +28,12 @@ var format = require('string-format-obj');
var fs = require('fs');
var hashStreamValidation = require('hash-stream-validation');
var is = require('is');
+var nodeutil = require('util');
var once = require('once');
var pumpify = require('pumpify');
+var resumableUpload = require('gcs-resumable-upload');
var streamEvents = require('stream-events');
var through = require('through2');
-var resumableUpload = require('gcs-resumable-upload');
var zlib = require('zlib');
/**
@@ -41,6 +42,12 @@ var zlib = require('zlib');
*/
var Acl = require('./acl.js');
+/**
+ * @type {module:common/serviceObject}
+ * @private
+ */
+var ServiceObject = require('../common/service-object.js');
+
/**
* @type {module:common/util}
* @private
@@ -57,6 +64,12 @@ var SigningError = createErrorClass('SigningError', function(message) {
this.message = message;
});
+/**
+ * @const {string}
+ * @private
+ */
+var STORAGE_DOWNLOAD_BASE_URL = 'https://storage.googleapis.com';
+
/**
* @const {string}
* @private
@@ -77,18 +90,57 @@ var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';
*
* @alias module:storage/file
* @constructor
+ *
+ * @example
+ * var gcloud = require('gcloud');
+ *
+ * var gcs = gcloud.storage({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'grape-spaceship-123'
+ * });
+ *
+ * var myBucket = gcs.bucket('my-bucket');
+ *
+ * var file = myBucket.file('my-file');
*/
function File(bucket, name, options) {
- if (!name) {
- throw Error('A file name must be specified.');
- }
+ var methods = {
+ /**
+ * Check if the file exists.
+ *
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this
+ * request.
+ * @param {boolean} callback.exists - Whether the file exists or not.
+ *
+ * @example
+ * file.exists(function(err, exists) {});
+ */
+ exists: true,
+
+ /**
+ * Get a file object and its metadata if it exists.
+ *
+ * @example
+ * file.get(function(err, file, apiResponse) {
+ * // file.metadata` has been populated.
+ * });
+ */
+ get: true
+ };
+
+ ServiceObject.call(this, {
+ parent: bucket,
+ baseUrl: '/o',
+ id: encodeURIComponent(name),
+ methods: methods
+ });
options = options || {};
this.bucket = bucket;
this.generation = parseInt(options.generation, 10);
- this.makeReq_ = bucket.makeReq_.bind(bucket);
- this.metadata = {};
+ this.storage = bucket.parent;
Object.defineProperty(this, 'name', {
enumerable: true,
@@ -117,23 +169,19 @@ function File(bucket, name, options) {
* //-
* // Make a file publicly readable.
* //-
- * var gcs = gcloud.storage({
- * projectId: 'grape-spaceship-123'
- * });
- *
- * var myFile = gcs.bucket('my-bucket').file('my-file');
- *
- * myFile.acl.add({
+ * file.acl.add({
* entity: 'allUsers',
* role: gcs.acl.READER_ROLE
* }, function(err, aclObject) {});
*/
this.acl = new Acl({
- makeReq: this.makeReq_,
- pathPrefix: '/o/' + encodeURIComponent(this.name) + '/acl'
+ request: this.request.bind(this),
+ pathPrefix: '/acl'
});
}
+nodeutil.inherits(File, ServiceObject);
+
/**
* Copy this file to another file. By default, this will copy the file to the
* same bucket, but you can choose to copy it to another Bucket by providing
@@ -233,124 +281,27 @@ File.prototype.copy = function(destination, callback) {
throw noDestinationError;
}
- var path = format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', {
- srcName: encodeURIComponent(this.name),
- destBucket: destBucket.name,
- destName: encodeURIComponent(destName)
- });
-
var query = {};
-
if (this.generation) {
query.sourceGeneration = this.generation;
}
- this.makeReq_('POST', path, query, null, function(err, resp) {
- if (err) {
- callback(err, null, resp);
- return;
- }
-
- callback(null, newFile || destBucket.file(destName), resp);
- });
-};
-
-/**
- * Move this file to another location. By default, this will move the file to
- * the same bucket, but you can choose to move it to another Bucket by providing
- * either a Bucket or File object.
- *
- * **Warning**:
- * There is currently no atomic `move` method in the Google Cloud Storage API,
- * so this method is a composition of {module:storage/file#copy} (to the new
- * location) and {module:storage/file#delete} (from the old location). While
- * unlikely, it is possible that an error returned to your callback could be
- * triggered from either one of these API calls failing, which could leave a
- * duplicate file lingering.
- *
- * @resource [Objects: copy API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy}
- *
- * @throws {Error} If the destination file is not provided.
- *
- * @param {string|module:storage/bucket|module:storage/file} destination -
- * Destination file.
- * @param {function=} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request
- * @param {module:storage/file} callback.destinationFile - The destination File.
- * @param {object} callback.apiResponse - The full API response.
- *
- * @example
- * //-
- * // You can pass in a variety of types for the destination.
- * //
- * // For all of the below examples, assume we are working with the following
- * // Bucket and File objects.
- * //-
- * var bucket = gcs.bucket('my-bucket');
- * var file = bucket.file('my-image.png');
- *
- * //-
- * // If you pass in a string for the destination, the file is moved to its
- * // current bucket, under the new name provided.
- * //-
- * file.move('my-image-new.png', function(err, destinationFile, apiResponse) {
- * // `my-bucket` no longer contains:
- * // - "my-image.png"
- * // but contains instead:
- * // - "my-image-new.png"
- *
- * // `destinationFile` is an instance of a File object that refers to your
- * // new file.
- * });
- *
- * //-
- * // If you pass in a Bucket object, the file will be moved to that bucket
- * // using the same name.
- * //-
- * var anotherBucket = gcs.bucket('another-bucket');
- *
- * file.move(anotherBucket, function(err, destinationFile, apiResponse) {
- * // `my-bucket` no longer contains:
- * // - "my-image.png"
- * //
- * // `another-bucket` now contains:
- * // - "my-image.png"
- *
- * // `destinationFile` is an instance of a File object that refers to your
- * // new file.
- * });
- *
- * //-
- * // If you pass in a File object, you have complete control over the new
- * // bucket and filename.
- * //-
- * var anotherFile = anotherBucket.file('my-awesome-image.png');
- *
- * file.move(anotherFile, function(err, destinationFile, apiResponse) {
- * // `my-bucket` no longer contains:
- * // - "my-image.png"
- * //
- * // `another-bucket` now contains:
- * // - "my-awesome-image.png"
- *
- * // Note:
- * // The `destinationFile` parameter is equal to `anotherFile`.
- * });
- */
-File.prototype.move = function(destination, callback) {
- var self = this;
-
- callback = callback || util.noop;
+ newFile = newFile || destBucket.file(destName);
- this.copy(destination, function(err, destinationFile, apiResponse) {
+ this.request({
+ method: 'POST',
+ uri: format('/copyTo/b/{bucketName}/o/{fileName}', {
+ bucketName: destBucket.name,
+ fileName: encodeURIComponent(destName)
+ }),
+ qs: query
+ }, function(err, resp) {
if (err) {
- callback(err, null, apiResponse);
+ callback(err, null, resp);
return;
}
- self.delete(function(err, apiResponse) {
- callback(err, destinationFile, apiResponse);
- });
+ callback(null, newFile, resp);
});
};
@@ -393,8 +344,7 @@ File.prototype.move = function(destination, callback) {
* // backup of your remote data.
* //-
* var fs = require('fs');
- * var myBucket = gcs.bucket('my-bucket');
- * var remoteFile = myBucket.file('image.png');
+ * var remoteFile = bucket.file('image.png');
* var localFilename = '/Users/stephen/Photos/image.png';
*
* remoteFile.createReadStream()
@@ -459,9 +409,10 @@ File.prototype.createReadStream = function(options) {
// returned to the user.
function makeRequest() {
var reqOpts = {
- uri: format('https://storage.googleapis.com/{b}/{o}', {
- b: self.bucket.name,
- o: encodeURIComponent(self.name)
+ uri: format('{downloadBaseUrl}/{bucketName}/{fileName}', {
+ downloadBaseUrl: STORAGE_DOWNLOAD_BASE_URL,
+ bucketName: self.bucket.name,
+ fileName: encodeURIComponent(self.name)
}),
gzip: true
};
@@ -481,7 +432,7 @@ File.prototype.createReadStream = function(options) {
};
}
- var requestStream = self.bucket.storage.makeAuthenticatedRequest_(reqOpts);
+ var requestStream = self.storage.makeAuthenticatedRequest(reqOpts);
var validateStream;
// We listen to the response event from the request stream so that we can...
@@ -603,9 +554,6 @@ File.prototype.createReadStream = function(options) {
* @param {string} callback.uri - The resumable upload's unique session URI.
*
* @example
- * var bucket = gcs.bucket('my-bucket');
- * var file = bucket.file('large-file.zip');
- *
* file.createResumableUpload(function(err, uri) {
* if (!err) {
* // `uri` can be used to PUT data to.
@@ -619,7 +567,7 @@ File.prototype.createResumableUpload = function(metadata, callback) {
}
resumableUpload.createURI({
- authClient: this.bucket.storage.makeAuthenticatedRequest_.authClient,
+ authClient: this.bucket.storage.authClient,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
@@ -657,6 +605,8 @@ File.prototype.createResumableUpload = function(metadata, callback) {
* completely, however this is **not recommended**.
*
* @example
+ * var fs = require('fs');
+ *
* //-
* //
Uploading a File
* //
@@ -664,11 +614,8 @@ File.prototype.createResumableUpload = function(metadata, callback) {
* // have the option of using {module:storage/bucket#upload}, but that is just
* // a convenience method which will do the following.
* //-
- * var fs = require('fs');
- * var image = myBucket.file('image.png');
- *
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
- * .pipe(image.createWriteStream())
+ * .pipe(file.createWriteStream())
* .on('error', function(err) {})
* .on('finish', function() {
* // The file upload is complete.
@@ -677,11 +624,8 @@ File.prototype.createResumableUpload = function(metadata, callback) {
* //-
* // Uploading a File with gzip compression
* //-
- * var fs = require('fs');
- * var htmlFile = myBucket.file('index.html');
- *
* fs.createReadStream('/Users/stephen/site/index.html')
- * .pipe(htmlFile.createWriteStream({ gzip: true }))
+ * .pipe(file.createWriteStream({ gzip: true }))
* .on('error', function(err) {})
* .on('finish', function() {
* // The file upload is complete.
@@ -700,11 +644,8 @@ File.prototype.createResumableUpload = function(metadata, callback) {
* // {module:storage/bucket#upload} to do this, which is just a wrapper around
* // the following.
* //-
- * var fs = require('fs');
- * var image = myBucket.file('image.png');
- *
* fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
- * .pipe(image.createWriteStream({
+ * .pipe(file.createWriteStream({
* metadata: {
* contentType: 'image/jpeg',
* metadata: {
@@ -846,15 +787,17 @@ File.prototype.createWriteStream = function(options) {
File.prototype.delete = function(callback) {
callback = callback || util.noop;
- var path = '/o/' + encodeURIComponent(this.name);
-
var query = {};
if (this.generation) {
query.generation = this.generation;
}
- this.makeReq_('DELETE', path, query, null, function(err, resp) {
+ this.request({
+ method: 'DELETE',
+ uri: '',
+ qs: query
+ }, function(err, resp) {
if (err) {
callback(err, resp);
return;
@@ -930,17 +873,18 @@ File.prototype.download = function(options, callback) {
*/
File.prototype.getMetadata = function(callback) {
var self = this;
- callback = callback || util.noop;
- var path = '/o/' + encodeURIComponent(this.name);
+ callback = callback || util.noop;
var query = {};
-
if (this.generation) {
query.generation = this.generation;
}
- this.makeReq_('GET', path, query, null, function(err, resp) {
+ this.request({
+ uri: '',
+ qs: query
+ }, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
@@ -1079,9 +1023,7 @@ File.prototype.getSignedPolicy = function(options, callback) {
conditions: conditions
};
- var makeAuthenticatedRequest_ = this.bucket.storage.makeAuthenticatedRequest_;
-
- makeAuthenticatedRequest_.getCredentials(function(err, credentials) {
+ this.storage.getCredentials(function(err, credentials) {
if (err) {
callback(new SigningError(err.message));
return;
@@ -1208,9 +1150,7 @@ File.prototype.getSignedUrl = function(options, callback) {
options.resource = '/' + this.bucket.name + '/' + name;
- var makeAuthenticatedRequest_ = this.bucket.storage.makeAuthenticatedRequest_;
-
- makeAuthenticatedRequest_.getCredentials(function(err, credentials) {
+ this.storage.getCredentials(function(err, credentials) {
if (err) {
callback(new SigningError(err.message));
return;
@@ -1266,67 +1206,6 @@ File.prototype.getSignedUrl = function(options, callback) {
});
};
-/**
- * Merge the given metadata with the current remote file's metadata. This will
- * set metadata if it was previously unset or update previously set metadata. To
- * unset previously set metadata, set its value to null.
- *
- * You can set custom key/value pairs in the metadata key of the given object,
- * however the other properties outside of this object must adhere to the
- * [official API documentation](https://goo.gl/BOnnCK).
- *
- * See the examples below for more information.
- *
- * @resource [Objects: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch}
- *
- * @param {object} metadata - The metadata you wish to update.
- * @param {function=} callback - The callback function.
- * @param {?error} callback.err - An error returned while making this request
- * @param {object} callback.apiResponse - The full API response.
- *
- * @example
- * file.setMetadata({
- * contentType: 'application/x-font-ttf',
- * metadata: {
- * my: 'custom',
- * properties: 'go here'
- * }
- * }, function(err, apiResponse) {});
- *
- * // Assuming current metadata = { hello: 'world', unsetMe: 'will do' }
- * file.setMetadata({
- * metadata: {
- * abc: '123', // will be set.
- * unsetMe: null, // will be unset (deleted).
- * hello: 'goodbye' // will be updated from 'hello' to 'goodbye'.
- * }
- * }, function(err, apiResponse) {
- * // metadata should now be { abc: '123', hello: 'goodbye' }
- * });
- */
-File.prototype.setMetadata = function(metadata, callback) {
- callback = callback || util.noop;
-
- var that = this;
- var path = '/o/' + encodeURIComponent(this.name);
- var query = {};
-
- if (this.generation) {
- query.generation = this.generation;
- }
-
- this.makeReq_('PATCH', path, query, metadata, function(err, resp) {
- if (err) {
- callback(err, resp);
- return;
- }
-
- that.metadata = resp;
-
- callback(null, resp);
- });
-};
-
/**
* Make a file private to the project and remove all other permissions.
* Set `options.strict` to true to make the file private to only the owner.
@@ -1352,27 +1231,37 @@ File.prototype.setMetadata = function(metadata, callback) {
* file.makePrivate({ strict: true }, function(err) {});
*/
File.prototype.makePrivate = function(options, callback) {
- var that = this;
+ var self = this;
+
if (is.fn(options)) {
callback = options;
options = {};
}
- var path = '/o/' + encodeURIComponent(this.name);
- var query = { predefinedAcl: options.strict ? 'private' : 'projectPrivate' };
+
+ var query = {
+ predefinedAcl: options.strict ? 'private' : 'projectPrivate'
+ };
// You aren't allowed to set both predefinedAcl & acl properties on a file, so
// acl must explicitly be nullified, destroying all previous acls on the file.
- var metadata = { acl: null };
+ var metadata = {
+ acl: null
+ };
callback = callback || util.noop;
- this.makeReq_('PATCH', path, query, metadata, function(err, resp) {
+ this.request({
+ method: 'PATCH',
+ uri: '',
+ qs: query,
+ json: metadata
+ }, function(err, resp) {
if (err) {
callback(err, resp);
return;
}
- that.metadata = resp;
+ self.metadata = resp;
callback(null, resp);
});
@@ -1401,6 +1290,170 @@ File.prototype.makePublic = function(callback) {
});
};
+/**
+ * Move this file to another location. By default, this will move the file to
+ * the same bucket, but you can choose to move it to another Bucket by providing
+ * either a Bucket or File object.
+ *
+ * **Warning**:
+ * There is currently no atomic `move` method in the Google Cloud Storage API,
+ * so this method is a composition of {module:storage/file#copy} (to the new
+ * location) and {module:storage/file#delete} (from the old location). While
+ * unlikely, it is possible that an error returned to your callback could be
+ * triggered from either one of these API calls failing, which could leave a
+ * duplicate file lingering.
+ *
+ * @resource [Objects: copy API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy}
+ *
+ * @throws {Error} If the destination file is not provided.
+ *
+ * @param {string|module:storage/bucket|module:storage/file} destination -
+ * Destination file.
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request
+ * @param {module:storage/file} callback.destinationFile - The destination File.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * //-
+ * // You can pass in a variety of types for the destination.
+ * //
+ * // For all of the below examples, assume we are working with the following
+ * // Bucket and File objects.
+ * //-
+ * var bucket = gcs.bucket('my-bucket');
+ * var file = bucket.file('my-image.png');
+ *
+ * //-
+ * // If you pass in a string for the destination, the file is moved to its
+ * // current bucket, under the new name provided.
+ * //-
+ * file.move('my-image-new.png', function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * // but contains instead:
+ * // - "my-image-new.png"
+ *
+ * // `destinationFile` is an instance of a File object that refers to your
+ * // new file.
+ * });
+ *
+ * //-
+ * // If you pass in a Bucket object, the file will be moved to that bucket
+ * // using the same name.
+ * //-
+ * var anotherBucket = gcs.bucket('another-bucket');
+ *
+ * file.move(anotherBucket, function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-image.png"
+ *
+ * // `destinationFile` is an instance of a File object that refers to your
+ * // new file.
+ * });
+ *
+ * //-
+ * // If you pass in a File object, you have complete control over the new
+ * // bucket and filename.
+ * //-
+ * var anotherFile = anotherBucket.file('my-awesome-image.png');
+ *
+ * file.move(anotherFile, function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-awesome-image.png"
+ *
+ * // Note:
+ * // The `destinationFile` parameter is equal to `anotherFile`.
+ * });
+ */
+File.prototype.move = function(destination, callback) {
+ var self = this;
+
+ callback = callback || util.noop;
+
+ this.copy(destination, function(err, destinationFile, apiResponse) {
+ if (err) {
+ callback(err, null, apiResponse);
+ return;
+ }
+
+ self.delete(function(err, apiResponse) {
+ callback(err, destinationFile, apiResponse);
+ });
+ });
+};
+
+/**
+ * Merge the given metadata with the current remote file's metadata. This will
+ * set metadata if it was previously unset or update previously set metadata. To
+ * unset previously set metadata, set its value to null.
+ *
+ * You can set custom key/value pairs in the metadata key of the given object,
+ * however the other properties outside of this object must adhere to the
+ * [official API documentation](https://goo.gl/BOnnCK).
+ *
+ * See the examples below for more information.
+ *
+ * @resource [Objects: patch API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch}
+ *
+ * @param {object} metadata - The metadata you wish to update.
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * file.setMetadata({
+ * contentType: 'application/x-font-ttf',
+ * metadata: {
+ * my: 'custom',
+ * properties: 'go here'
+ * }
+ * }, function(err, apiResponse) {});
+ *
+ * // Assuming current metadata = { hello: 'world', unsetMe: 'will do' }
+ * file.setMetadata({
+ * metadata: {
+ * abc: '123', // will be set.
+ * unsetMe: null, // will be unset (deleted).
+ * hello: 'goodbye' // will be updated from 'hello' to 'goodbye'.
+ * }
+ * }, function(err, apiResponse) {
+ * // metadata should now be { abc: '123', hello: 'goodbye' }
+ * });
+ */
+File.prototype.setMetadata = function(metadata, callback) {
+ var self = this;
+
+ callback = callback || util.noop;
+
+ var query = {};
+ if (this.generation) {
+ query.generation = this.generation;
+ }
+
+ this.request({
+ method: 'PATCH',
+ uri: '',
+ qs: query,
+ json: metadata
+ }, function(err, resp) {
+ if (err) {
+ callback(err, resp);
+ return;
+ }
+
+ self.metadata = resp;
+
+ callback(null, resp);
+ });
+};
+
/**
* This creates a gcs-resumable-upload upload stream.
*
@@ -1415,7 +1468,7 @@ File.prototype.startResumableUpload_ = function(dup, metadata) {
var self = this;
var uploadStream = resumableUpload({
- authClient: this.bucket.storage.makeAuthenticatedRequest_.authClient,
+ authClient: this.storage.authClient,
bucket: this.bucket.name,
file: this.name,
generation: this.generation,
@@ -1452,8 +1505,8 @@ File.prototype.startSimpleUpload_ = function(dup, metadata) {
qs: {
name: self.name
},
- uri: format('{base}/{bucket}/o', {
- base: STORAGE_UPLOAD_BASE_URL,
+ uri: format('{uploadBaseUrl}/{bucket}/o', {
+ uploadBaseUrl: STORAGE_UPLOAD_BASE_URL,
bucket: self.bucket.name
})
};
@@ -1463,7 +1516,7 @@ File.prototype.startSimpleUpload_ = function(dup, metadata) {
}
util.makeWritableStream(dup, {
- makeAuthenticatedRequest: self.bucket.storage.makeAuthenticatedRequest_,
+ makeAuthenticatedRequest: this.storage.makeAuthenticatedRequest,
metadata: metadata,
request: reqOpts
}, function(data) {
diff --git a/lib/storage/index.js b/lib/storage/index.js
index 0cae08b5b93..62247da20b3 100644
--- a/lib/storage/index.js
+++ b/lib/storage/index.js
@@ -20,7 +20,9 @@
'use strict';
+var arrify = require('arrify');
var extend = require('extend');
+var nodeutil = require('util');
/**
* @type {module:storage/bucket}
@@ -29,29 +31,22 @@ var extend = require('extend');
var Bucket = require('./bucket.js');
/**
- * @type {module:common/streamrouter}
+ * @type {module:common/service}
* @private
*/
-var streamRouter = require('../common/stream-router.js');
+var Service = require('../common/service.js');
/**
- * @type {module:common/util}
- * @private
- */
-var util = require('../common/util.js');
-
-/**
- * Required scopes for Google Cloud Storage API.
- * @const {array}
+ * @type {module:common/streamrouter}
* @private
*/
-var SCOPES = ['https://www.googleapis.com/auth/devstorage.full_control'];
+var streamRouter = require('../common/stream-router.js');
/**
- * @const {string}
+ * @type {module:common/util}
* @private
*/
-var STORAGE_BASE_URL = 'https://www.googleapis.com/storage/v1/b';
+var util = require('../common/util.js');
/*! Developer Documentation
*
@@ -88,16 +83,19 @@ function Storage(options) {
return new Storage(options);
}
- this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({
- credentials: options.credentials,
- keyFile: options.keyFilename,
- scopes: SCOPES,
- email: options.email
- });
+ var config = {
+ baseUrl: 'https://www.googleapis.com/storage/v1',
+ projectIdRequired: false,
+ scopes: [
+ 'https://www.googleapis.com/auth/devstorage.full_control'
+ ]
+ };
- this.projectId = options.projectId;
+ Service.call(this, config, options);
}
+nodeutil.inherits(Storage, Service);
+
/**
* Google Cloud Storage uses access control lists (ACLs) to manage object and
* bucket access. ACLs are the mechanism you use to share objects with other
@@ -157,7 +155,7 @@ Storage.prototype.acl = Storage.acl;
/**
* Get a reference to a Google Cloud Storage bucket.
*
- * @param {object|string} name - Name of the existing bucket.
+ * @param {object|string} name - Name of the bucket.
* @return {module:storage/bucket}
*
* @example
@@ -172,6 +170,10 @@ Storage.prototype.acl = Storage.acl;
* var photos = gcs.bucket('photos');
*/
Storage.prototype.bucket = function(name) {
+ if (!name) {
+ throw new Error('A bucket name is needed to use Google Cloud Storage.');
+ }
+
return new Bucket(this, name);
};
@@ -233,19 +235,20 @@ Storage.prototype.bucket = function(name) {
*/
Storage.prototype.createBucket = function(name, metadata, callback) {
var self = this;
+
if (!name) {
throw new Error('A name is required to create a bucket.');
}
+
if (!callback) {
callback = metadata;
metadata = {};
}
- var query = {
- project: this.projectId
- };
+
var body = extend(metadata, {
name: name
});
+
var storageClasses = {
dra: 'DURABLE_REDUCED_AVAILABILITY',
nearline: 'NEARLINE'
@@ -258,13 +261,22 @@ Storage.prototype.createBucket = function(name, metadata, callback) {
}
});
- this.makeReq_('POST', '', query, body, function(err, resp) {
+ this.request({
+ method: 'POST',
+ uri: '/b',
+ qs: {
+ project: this.projectId
+ },
+ json: body
+ }, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
}
+
var bucket = self.bucket(name);
bucket.metadata = resp;
+
callback(null, bucket, resp);
});
};
@@ -340,56 +352,39 @@ Storage.prototype.createBucket = function(name, metadata, callback) {
* });
*/
Storage.prototype.getBuckets = function(query, callback) {
- var that = this;
+ var self = this;
+
if (!callback) {
callback = query;
query = {};
}
+
query.project = query.project || this.projectId;
- this.makeReq_('GET', '', query, null, function(err, resp) {
+
+ this.request({
+ uri: '/b',
+ qs: query
+ }, function(err, resp) {
if (err) {
callback(err, null, null, resp);
return;
}
- var buckets = (resp.items || []).map(function(item) {
- var bucket = that.bucket(item.id);
- bucket.metadata = item;
- return bucket;
+
+ var buckets = arrify(resp.items).map(function(bucket) {
+ var bucketInstance = self.bucket(bucket.id);
+ bucketInstance.metadata = bucket;
+ return bucketInstance;
});
+
var nextQuery = null;
if (resp.nextPageToken) {
nextQuery = extend({}, query, { pageToken: resp.nextPageToken });
}
+
callback(null, buckets, nextQuery, resp);
});
};
-/**
- * Make a new request object from the provided arguments and wrap the callback
- * to intercept non-successful responses.
- *
- * @private
- *
- * @param {string} method - Action.
- * @param {string} path - Request path.
- * @param {*} query - Request query object.
- * @param {*} body - Request body contents.
- * @param {function} callback - The callback function.
- */
-Storage.prototype.makeReq_ = function(method, path, query, body, callback) {
- var reqOpts = {
- method: method,
- qs: query,
- uri: STORAGE_BASE_URL + path
- };
-
- if (body) {
- reqOpts.json = body;
- }
-
- this.makeAuthenticatedRequest_(reqOpts, callback);
-};
-
/*! Developer Documentation
*
* This method can be used with either a callback or as a readable object
diff --git a/system-test/storage.js b/system-test/storage.js
index e7960f99ffd..5c46a55f6ae 100644
--- a/system-test/storage.js
+++ b/system-test/storage.js
@@ -75,14 +75,10 @@ function setHash(obj, file, done) {
}
describe('storage', function() {
- var bucket;
+ var bucket = storage.bucket(BUCKET_NAME);
before(function(done) {
- storage.createBucket(BUCKET_NAME, function(err, newBucket) {
- assert.ifError(err);
- bucket = newBucket;
- done();
- });
+ bucket.create(done);
});
after(function(done) {
@@ -384,7 +380,8 @@ describe('storage', function() {
describe('getting buckets', function() {
var bucketsToCreate = [
- generateBucketName(), generateBucketName()
+ generateBucketName(),
+ generateBucketName()
];
before(function(done) {
@@ -685,10 +682,11 @@ describe('storage', function() {
});
it('should copy an existing file', function(done) {
- bucket.upload(files.logo.path, 'CloudLogo', function(err, file) {
+ var opts = { destination: 'CloudLogo' };
+ bucket.upload(files.logo.path, opts, function(err, file) {
assert.ifError(err);
+
file.copy('CloudLogoCopy', function(err, copiedFile) {
- assert.ifError(err);
async.parallel([
file.delete.bind(file),
copiedFile.delete.bind(copiedFile)
@@ -801,20 +799,20 @@ describe('storage', function() {
describe('file generations', function() {
var VERSIONED_BUCKET_NAME = generateBucketName();
- var versionedBucket;
+ var versionedBucket = storage.bucket(VERSIONED_BUCKET_NAME);
before(function(done) {
- var opts = { versioning: { enabled: true } };
-
- storage.createBucket(VERSIONED_BUCKET_NAME, opts, function(err, bucket) {
- assert.ifError(err);
- versionedBucket = bucket;
- done();
- });
+ versionedBucket.create({
+ versioning: {
+ enabled: true
+ }
+ }, done);
});
after(function(done) {
- versionedBucket.deleteFiles({ versions: true }, function(err) {
+ versionedBucket.deleteFiles({
+ versions: true
+ }, function(err) {
if (err) {
done(err);
return;
diff --git a/test/storage/acl.js b/test/storage/acl.js
index 43a62c1cfe2..a5ea659152c 100644
--- a/test/storage/acl.js
+++ b/test/storage/acl.js
@@ -31,23 +31,22 @@ describe('storage/acl', function() {
var ENTITY = 'user-user@example.com';
beforeEach(function() {
- acl = new Acl({ makeReq: MAKE_REQ, pathPrefix: PATH_PREFIX });
+ acl = new Acl({ request: MAKE_REQ, pathPrefix: PATH_PREFIX });
});
describe('initialization', function() {
it('should assign makeReq and pathPrefix', function() {
- assert.equal(acl.makeReq, MAKE_REQ);
- assert.equal(acl.pathPrefix, PATH_PREFIX);
+ assert.strictEqual(acl.pathPrefix, PATH_PREFIX);
+ assert.strictEqual(acl.request_, MAKE_REQ);
});
});
describe('add', function() {
it('makes the correct api request', function(done) {
- acl.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'POST');
- assert.equal(path, '');
- assert.strictEqual(query, null);
- assert.deepEqual(body, { entity: ENTITY, role: ROLE });
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'POST');
+ assert.strictEqual(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.json, { entity: ENTITY, role: ROLE });
done();
};
@@ -63,7 +62,7 @@ describe('storage/acl', function() {
return expectedAclObject;
};
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(null, apiResponse);
};
@@ -75,7 +74,7 @@ describe('storage/acl', function() {
});
it('executes the callback with an error', function(done) {
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(ERROR);
};
@@ -87,7 +86,8 @@ describe('storage/acl', function() {
it('executes the callback with apiResponse', function(done) {
var resp = { success: true };
- acl.makeReq_ = function(method, path, query, body, callback) {
+
+ acl.request = function(reqOpts, callback) {
callback(null, resp);
};
@@ -100,11 +100,9 @@ describe('storage/acl', function() {
describe('delete', function() {
it('makes the correct api request', function(done) {
- acl.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'DELETE');
- assert.equal(path, '/' + encodeURIComponent(ENTITY));
- assert.strictEqual(query, null);
- assert.strictEqual(body, null);
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'DELETE');
+ assert.strictEqual(reqOpts.uri, '/' + encodeURIComponent(ENTITY));
done();
};
@@ -113,7 +111,7 @@ describe('storage/acl', function() {
});
it('should execute the callback with an error', function(done) {
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(ERROR);
};
@@ -125,7 +123,8 @@ describe('storage/acl', function() {
it('should execute the callback with apiResponse', function(done) {
var resp = { success: true };
- acl.makeReq_ = function(method, path, query, body, callback) {
+
+ acl.request = function(reqOpts, callback) {
callback(null, resp);
};
@@ -139,11 +138,8 @@ describe('storage/acl', function() {
describe('get', function() {
describe('all ACL objects', function() {
it('should make the correct API request', function(done) {
- acl.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '');
- assert.strictEqual(query, null);
- assert.strictEqual(body, null);
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '');
done();
};
@@ -154,8 +150,8 @@ describe('storage/acl', function() {
it('should accept a configuration object', function(done) {
var generation = 1;
- acl.makeReq_ = function(method, path, query) {
- assert.equal(query.generation, generation);
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.generation, generation);
done();
};
@@ -182,7 +178,7 @@ describe('storage/acl', function() {
return expectedAclObjects[index];
};
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(null, apiResponse);
};
@@ -196,11 +192,8 @@ describe('storage/acl', function() {
describe('ACL object for an entity', function() {
it('should get a specific ACL object', function(done) {
- acl.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '/' + encodeURIComponent(ENTITY));
- assert.strictEqual(query, null);
- assert.strictEqual(body, null);
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '/' + encodeURIComponent(ENTITY));
done();
};
@@ -211,8 +204,8 @@ describe('storage/acl', function() {
it('should accept a configuration object', function(done) {
var generation = 1;
- acl.makeReq_ = function(method, path, query) {
- assert.equal(query.generation, generation);
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.generation, generation);
done();
};
@@ -228,7 +221,7 @@ describe('storage/acl', function() {
return expectedAclObject;
};
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(null, apiResponse);
};
@@ -241,7 +234,7 @@ describe('storage/acl', function() {
});
it('should execute the callback with an error', function(done) {
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(ERROR);
};
@@ -253,7 +246,8 @@ describe('storage/acl', function() {
it('should execute the callback with apiResponse', function(done) {
var resp = { success: true };
- acl.makeReq_ = function(method, path, query, body, callback) {
+
+ acl.request = function(reqOpts, callback) {
callback(null, resp);
};
@@ -266,11 +260,10 @@ describe('storage/acl', function() {
describe('update', function() {
it('should make the correct API request', function(done) {
- acl.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'PUT');
- assert.equal(path, '/' + encodeURIComponent(ENTITY));
- assert.strictEqual(query, null);
- assert.deepEqual(body, { role: ROLE });
+ acl.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'PUT');
+ assert.strictEqual(reqOpts.uri, '/' + encodeURIComponent(ENTITY));
+ assert.deepEqual(reqOpts.json, { role: ROLE });
done();
};
@@ -286,7 +279,7 @@ describe('storage/acl', function() {
return expectedAclObject;
};
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(null, apiResponse);
};
@@ -298,7 +291,7 @@ describe('storage/acl', function() {
});
it('should execute the callback with an error', function(done) {
- acl.makeReq_ = function(method, path, query, body, callback) {
+ acl.request = function(reqOpts, callback) {
callback(ERROR);
};
@@ -310,7 +303,8 @@ describe('storage/acl', function() {
it('should execute the callback with apiResponse', function(done) {
var resp = { success: true };
- acl.makeReq_ = function(method, path, query, body, callback) {
+
+ acl.request = function(reqOpts, callback) {
callback(null, resp);
};
@@ -344,29 +338,6 @@ describe('storage/acl', function() {
});
});
});
-
- describe('makeReq_', function() {
- it('patches requests through to the makeReq function', function(done) {
- var method = 'POST';
- var path = '/path';
- var query = { a: 'b', c: 'd' };
- var body = { a: 'b', c: 'd' };
- var callback = util.noop;
-
- // This is overriding the method we passed on instantiation.
- acl.makeReq = function(m, p, q, b, c) {
- assert.equal(m, method);
- assert.equal(p, PATH_PREFIX + path);
- assert.deepEqual(q, query);
- assert.deepEqual(b, body);
- assert.equal(c, callback);
-
- done();
- };
-
- acl.makeReq_(method, path, query, body, callback);
- });
- });
});
describe('storage/AclRoleAccessorMethods', function() {
diff --git a/test/storage/bucket.js b/test/storage/bucket.js
index bf72eb23b74..0f3132b2161 100644
--- a/test/storage/bucket.js
+++ b/test/storage/bucket.js
@@ -20,12 +20,14 @@ var arrify = require('arrify');
var assert = require('assert');
var async = require('async');
var extend = require('extend');
-var format = require('string-format-obj');
var mime = require('mime-types');
var mockery = require('mockery');
+var nodeutil = require('util');
var propAssign = require('prop-assign');
var request = require('request');
var stream = require('stream');
+
+var ServiceObject = require('../../lib/common/service-object.js');
var util = require('../../lib/common/util.js');
function FakeFile(bucket, name) {
@@ -80,25 +82,39 @@ var fakeStreamRouter = {
}
};
+function FakeAcl() {
+ this.calledWith_ = [].slice.call(arguments);
+}
+
+function FakeServiceObject() {
+ this.calledWith_ = arguments;
+ ServiceObject.apply(this, arguments);
+}
+
+nodeutil.inherits(FakeServiceObject, ServiceObject);
+
describe('Bucket', function() {
var Bucket;
- var BUCKET_NAME = 'test-bucket';
var bucket;
- var options = {
- makeAuthenticatedRequest_: function(req, callback) {
- callback(null, req);
- }
+
+ var STORAGE = {
+ createBucket: util.noop
};
+ var BUCKET_NAME = 'test-bucket';
before(function() {
- mockery.registerMock('./file.js', FakeFile);
- mockery.registerMock('../common/stream-router.js', fakeStreamRouter);
mockery.registerMock('async', fakeAsync);
mockery.registerMock('request', fakeRequest);
+ mockery.registerMock('../common/service-object.js', FakeServiceObject);
+ mockery.registerMock('../common/stream-router.js', fakeStreamRouter);
+ mockery.registerMock('./acl.js', FakeAcl);
+ mockery.registerMock('./file.js', FakeFile);
+
mockery.enable({
useCleanCache: true,
warnOnUnregistered: false
});
+
Bucket = require('../../lib/storage/bucket.js');
});
@@ -110,7 +126,7 @@ describe('Bucket', function() {
beforeEach(function() {
requestOverride = null;
eachLimitOverride = null;
- bucket = new Bucket(options, BUCKET_NAME);
+ bucket = new Bucket(STORAGE, BUCKET_NAME);
});
describe('instantiation', function() {
@@ -118,18 +134,68 @@ describe('Bucket', function() {
assert(extended); // See `fakeStreamRouter.extend`
});
- it('should re-use provided connection', function() {
- assert.deepEqual(bucket.authenticateReq_, options.authenticateReq_);
+ it('should localize the name', function() {
+ assert.strictEqual(bucket.name, BUCKET_NAME);
});
- it('should default metadata to an empty object', function() {
- assert.deepEqual(bucket.metadata, {});
+ it('should localize the storage instance', function() {
+ assert.strictEqual(bucket.storage, STORAGE);
});
- it('should throw if no name was provided', function() {
- assert.throws(function() {
- new Bucket();
- }, /A bucket name is needed/);
+ it('should create an ACL object', function() {
+ FakeServiceObject.prototype.request = {
+ bind: function(context) {
+ return context;
+ }
+ };
+
+ var bucket = new Bucket(STORAGE, BUCKET_NAME);
+ assert.deepEqual(bucket.acl.calledWith_[0], {
+ request: bucket,
+ pathPrefix: '/acl'
+ });
+ });
+
+ it('should create a default ACL object', function() {
+ FakeServiceObject.prototype.request = {
+ bind: function(context) {
+ return context;
+ }
+ };
+
+ var bucket = new Bucket(STORAGE, BUCKET_NAME);
+ assert.deepEqual(bucket.acl.default.calledWith_[0], {
+ request: bucket,
+ pathPrefix: '/defaultObjectAcl'
+ });
+ });
+
+ it('should inherit from ServiceObject', function(done) {
+ var storageInstance = extend({}, STORAGE, {
+ createBucket: {
+ bind: function(context) {
+ assert.strictEqual(context, storageInstance);
+ done();
+ }
+ }
+ });
+
+ var bucket = new Bucket(storageInstance, BUCKET_NAME);
+ assert(bucket instanceof ServiceObject);
+
+ var calledWith = bucket.calledWith_[0];
+
+ assert.strictEqual(calledWith.parent, storageInstance);
+ assert.strictEqual(calledWith.baseUrl, '/b');
+ assert.strictEqual(calledWith.id, BUCKET_NAME);
+ assert.deepEqual(calledWith.methods, {
+ create: true,
+ delete: true,
+ exists: true,
+ get: true,
+ getMetadata: true,
+ setMetadata: true
+ });
});
});
@@ -157,40 +223,42 @@ describe('Bucket', function() {
it('should accept string or file input for sources', function(done) {
var file1 = bucket.file('1.txt');
var file2 = '2.txt';
+ var destinationFileName = 'destination.txt';
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert.equal(reqOpts.json.sourceObjects[0].name, file1.name);
- assert.equal(reqOpts.json.sourceObjects[1].name, file2);
- done();
- };
+ var originalFileMethod = bucket.file;
+ bucket.file = function(name) {
+ var file = originalFileMethod(name);
- bucket.combine([file1, file2], 'destination.txt');
- });
+ if (name === '2.txt') {
+ return file;
+ }
+
+ assert.strictEqual(name, destinationFileName);
- it('should accept string or file input for destination', function(done) {
- var destinations = [
- 'destination.txt',
- bucket.file('destination.txt')
- ];
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'POST');
+ assert.strictEqual(reqOpts.uri, '/compose');
+ assert.strictEqual(reqOpts.json.sourceObjects[0].name, file1.name);
+ assert.strictEqual(reqOpts.json.sourceObjects[1].name, file2);
- async.each(destinations, function(destination, next) {
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert(reqOpts.uri.indexOf(bucket.name + '/o/destination.txt') > -1);
- next();
+ done();
};
- bucket.combine(['1', '2'], destination);
- }, done);
+ return file;
+ };
+
+ bucket.combine([file1, file2], destinationFileName);
});
it('should use content type from the destination metadata', function(done) {
- var destination = 'destination.txt';
+ var destination = bucket.file('destination.txt');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert.equal(
+ destination.request = function(reqOpts) {
+ assert.strictEqual(
reqOpts.json.destination.contentType,
- mime.contentType(destination)
+ mime.contentType(destination.name)
);
+
done();
};
@@ -201,11 +269,12 @@ describe('Bucket', function() {
var destination = bucket.file('destination.txt');
destination.metadata = { contentType: 'content-type' };
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert.equal(
+ destination.request = function(reqOpts) {
+ assert.strictEqual(
reqOpts.json.destination.contentType,
destination.metadata.contentType
);
+
done();
};
@@ -213,13 +282,14 @@ describe('Bucket', function() {
});
it('should detect dest content type if not in metadata', function(done) {
- var destination = 'destination.txt';
+ var destination = bucket.file('destination.txt');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert.equal(
+ destination.request = function(reqOpts) {
+ assert.strictEqual(
reqOpts.json.destination.contentType,
- mime.contentType(destination)
+ mime.contentType(destination.name)
);
+
done();
};
@@ -227,30 +297,22 @@ describe('Bucket', function() {
});
it('should throw if content type cannot be determined', function() {
- var error =
- 'A content type could not be detected for the destination file.';
-
assert.throws(function() {
bucket.combine(['1', '2'], 'destination');
- }, new RegExp(error));
+ }, /A content type could not be detected/);
});
it('should make correct API request', function(done) {
var sources = [bucket.file('1.txt'), bucket.file('2.txt')];
var destination = bucket.file('destination.txt');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- var expectedUri = format('{base}/{bucket}/o/{file}/compose', {
- base: 'https://www.googleapis.com/storage/v1/b',
- bucket: destination.bucket.name,
- file: encodeURIComponent(destination.name)
- });
-
- assert.equal(reqOpts.uri, expectedUri);
+ destination.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '/compose');
assert.deepEqual(reqOpts.json, {
destination: { contentType: mime.contentType(destination.name) },
sourceObjects: [{ name: sources[0].name }, { name: sources[1].name }]
});
+
done();
};
@@ -259,10 +321,10 @@ describe('Bucket', function() {
it('should encode the destination file name', function(done) {
var sources = [bucket.file('1.txt'), bucket.file('2.txt')];
- var destination = 'needs encoding.jpg';
+ var destination = bucket.file('needs encoding.jpg');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
- assert.equal(reqOpts.uri.indexOf(destination), -1);
+ destination.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri.indexOf(destination), -1);
done();
};
@@ -276,7 +338,7 @@ describe('Bucket', function() {
var destination = bucket.file('destination.txt');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) {
+ destination.request = function(reqOpts) {
assert.deepEqual(reqOpts.json.sourceObjects, [
{ name: sources[0].name, generation: sources[0].metadata.generation },
{ name: sources[1].name, generation: sources[1].metadata.generation }
@@ -290,9 +352,9 @@ describe('Bucket', function() {
it('should execute the callback', function(done) {
var sources = [bucket.file('1.txt'), bucket.file('2.txt')];
- var destination = 'destination.txt';
+ var destination = bucket.file('destination.txt');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts, callback) {
+ destination.request = function(reqOpts, callback) {
callback();
};
@@ -301,62 +363,31 @@ describe('Bucket', function() {
it('should execute the callback with an error', function(done) {
var sources = [bucket.file('1.txt'), bucket.file('2.txt')];
- var destination = 'destination.txt';
+ var destination = bucket.file('destination.txt');
var error = new Error('Error.');
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts, callback) {
+ destination.request = function(reqOpts, callback) {
callback(error);
};
bucket.combine(sources, destination, function(err) {
- assert.equal(err, error);
+ assert.strictEqual(err, error);
done();
});
});
it('should execute the callback with apiResponse', function(done) {
var sources = [bucket.file('1.txt'), bucket.file('2.txt')];
- var destination = 'destination.txt';
+ var destination = bucket.file('destination.txt');
var resp = { success: true };
- bucket.storage.makeAuthenticatedRequest_ = function(reqOpts, callback) {
+ destination.request = function(reqOpts, callback) {
callback(null, resp);
};
bucket.combine(sources, destination, function(err, obj, apiResponse) {
- assert.equal(resp, apiResponse);
- done();
- });
- });
- });
-
- describe('delete', function() {
- it('should delete the bucket', function(done) {
- bucket.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'DELETE');
- assert.equal(path, '');
- assert.strictEqual(query, null);
- assert.strictEqual(body, true);
- done();
- };
- bucket.delete();
- });
-
- it('should execute callback', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback();
- };
- bucket.delete(done);
- });
-
- it('should execute callback with apiResponse', function(done) {
- var resp = { success: true };
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, resp);
- };
- bucket.delete(function(err, apiResponse) {
- assert.deepEqual(resp, apiResponse);
+ assert.strictEqual(resp, apiResponse);
done();
});
});
@@ -473,6 +504,12 @@ describe('Bucket', function() {
file = bucket.file(FILE_NAME, options);
});
+ it('should throw if no name is provided', function() {
+ assert.throws(function() {
+ bucket.file();
+ }, /A file name must be specified/);
+ });
+
it('should return a File object', function() {
assert(file instanceof FakeFile);
});
@@ -492,20 +529,19 @@ describe('Bucket', function() {
describe('getFiles', function() {
it('should get files without a query', function(done) {
- bucket.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '/o');
- assert.deepEqual(query, {});
- assert.strictEqual(body, true);
+ bucket.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '/o');
+ assert.deepEqual(reqOpts.qs, {});
done();
};
+
bucket.getFiles(util.noop);
});
it('should get files with a query', function(done) {
var token = 'next-page-token';
- bucket.makeReq_ = function(method, path, query) {
- assert.deepEqual(query, { maxResults: 5, pageToken: token });
+ bucket.request = function(reqOpts) {
+ assert.deepEqual(reqOpts.qs, { maxResults: 5, pageToken: token });
done();
};
bucket.getFiles({ maxResults: 5, pageToken: token }, util.noop);
@@ -513,7 +549,7 @@ describe('Bucket', function() {
it('should return nextQuery if more results exist', function() {
var token = 'next-page-token';
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, { nextPageToken: token, items: [] });
};
bucket.getFiles({ maxResults: 5 }, function(err, results, nextQuery) {
@@ -523,7 +559,7 @@ describe('Bucket', function() {
});
it('should return null nextQuery if there are no more results', function() {
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, { items: [] });
};
bucket.getFiles({ maxResults: 5 }, function(err, results, nextQuery) {
@@ -532,7 +568,7 @@ describe('Bucket', function() {
});
it('should return File objects', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, {
items: [{ name: 'fake-file-name', generation: 1 }]
});
@@ -546,7 +582,7 @@ describe('Bucket', function() {
});
it('should return versioned Files if queried for versions', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, {
items: [{ name: 'fake-file-name', generation: 1 }]
});
@@ -562,7 +598,7 @@ describe('Bucket', function() {
it('should return apiResponse in callback', function(done) {
var resp = { items: [{ name: 'fake-file-name' }] };
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, resp);
};
bucket.getFiles(function(err, files, nextQuery, apiResponse) {
@@ -575,7 +611,7 @@ describe('Bucket', function() {
var error = new Error('Error.');
var apiResponse = {};
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(error, apiResponse);
};
@@ -597,7 +633,7 @@ describe('Bucket', function() {
my: 'custom metadata'
}
};
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback(null, { items: [fileMetadata] });
};
bucket.getFiles(function(err, files) {
@@ -608,72 +644,58 @@ describe('Bucket', function() {
});
});
- describe('getMetadata', function() {
- var metadata = { a: 'b', c: 'd' };
+ describe('makePrivate', function() {
+ it('should set predefinedAcl & privatize files', function(done) {
+ var didSetPredefinedAcl = false;
+ var didMakeFilesPrivate = false;
- it('should get the metadata of a bucket', function(done) {
- bucket.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '');
- assert.strictEqual(query, null);
- assert.strictEqual(body, true);
- done();
- };
- bucket.getMetadata();
- });
+ bucket.request = function(reqOpts, callback) {
+ // Correct request.
+ assert.equal(reqOpts.method, 'PATCH');
+ assert.equal(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.qs, { predefinedAcl: 'projectPrivate' });
+ assert.deepEqual(reqOpts.json, { acl: null });
- it('should execute callback', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ didSetPredefinedAcl = true;
callback();
};
- bucket.getMetadata(done);
- });
- it('should update metadata property on object', function() {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, metadata);
+ bucket.makeAllFilesPublicPrivate_ = function(opts, callback) {
+ assert.strictEqual(opts.private, true);
+ assert.strictEqual(opts.force, true);
+ didMakeFilesPrivate = true;
+ callback();
};
- assert.deepEqual(bucket.metadata, {});
- bucket.getMetadata(function(err, newMetadata) {
- assert.deepEqual(newMetadata, metadata);
- });
- assert.deepEqual(bucket.metadata, metadata);
- });
- it('should pass metadata to callback', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, metadata);
- };
- bucket.getMetadata(function(err, fileMetadata) {
- assert.deepEqual(fileMetadata, metadata);
+ bucket.makePrivate({ includeFiles: true, force: true }, function(err) {
+ assert.ifError(err);
+ assert(didSetPredefinedAcl);
+ assert(didMakeFilesPrivate);
done();
});
});
- it('should pass apiResponse to callback', function(done) {
- var resp = metadata;
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, resp);
+ it('should not make files private by default', function(done) {
+ bucket.request = function(reqOpts, callback) {
+ callback();
};
- bucket.getMetadata(function(err, fileMetadata, apiResponse) {
- assert.deepEqual(resp, apiResponse);
- done();
- });
+
+ bucket.makeAllFilesPublicPrivate_ = function() {
+ throw new Error('Please, no. I do not want to be called.');
+ };
+
+ bucket.makePrivate(done);
});
- it('should execute callback with error & API response', function(done) {
+ it('should execute callback with error', function(done) {
var error = new Error('Error.');
- var apiResponse = {};
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(error, apiResponse);
+ bucket.request = function(reqOpts, callback) {
+ callback(error);
};
- bucket.getMetadata(function(err, metadata, apiResponse_) {
- assert.strictEqual(err, error);
- assert.strictEqual(metadata, null);
- assert.strictEqual(apiResponse_, apiResponse);
-
+ bucket.makePrivate(function(err) {
+ assert.equal(err, error);
done();
});
});
@@ -681,7 +703,7 @@ describe('Bucket', function() {
describe('makePublic', function() {
beforeEach(function() {
- bucket.makeReq_ = function(method, path, query, body, callback) {
+ bucket.request = function(reqOpts, callback) {
callback();
};
});
@@ -754,120 +776,6 @@ describe('Bucket', function() {
});
});
- describe('makePrivate', function() {
- it('should set predefinedAcl & privatize files', function(done) {
- var didSetPredefinedAcl = false;
- var didMakeFilesPrivate = false;
-
- bucket.makeReq_ = function(method, path, query, body, callback) {
- // Correct request.
- assert.equal(method, 'PATCH');
- assert.equal(path, '');
- assert.deepEqual(query, { predefinedAcl: 'projectPrivate' });
- assert.deepEqual(body, { acl: null });
-
- didSetPredefinedAcl = true;
- callback();
- };
-
- bucket.makeAllFilesPublicPrivate_ = function(opts, callback) {
- assert.strictEqual(opts.private, true);
- assert.strictEqual(opts.force, true);
- didMakeFilesPrivate = true;
- callback();
- };
-
- bucket.makePrivate({ includeFiles: true, force: true }, function(err) {
- assert.ifError(err);
- assert(didSetPredefinedAcl);
- assert(didMakeFilesPrivate);
- done();
- });
- });
-
- it('should not make files private by default', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback();
- };
-
- bucket.makeAllFilesPublicPrivate_ = function() {
- throw new Error('Please, no. I do not want to be called.');
- };
-
- bucket.makePrivate(done);
- });
-
- it('should execute callback with error', function(done) {
- var error = new Error('Error.');
-
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(error);
- };
-
- bucket.makePrivate(function(err) {
- assert.equal(err, error);
- done();
- });
- });
- });
-
- describe('setMetadata', function() {
- var metadata = { fake: 'metadata' };
-
- it('should set metadata', function(done) {
- bucket.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'PATCH');
- assert.equal(path, '');
- assert.deepEqual(body, metadata);
- done();
- };
- bucket.setMetadata(metadata);
- });
-
- it('should execute callback', function(done) {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback();
- };
- bucket.setMetadata(metadata, done);
- });
-
- it('should execute callback with apiResponse', function(done) {
- var resp = { success: true };
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, resp);
- };
- bucket.setMetadata(metadata, function(err, apiResponse) {
- assert.deepEqual(resp, apiResponse);
- done();
- });
- });
-
- it('should execute callback with error & API response', function(done) {
- var error = new Error('Error.');
- var apiResponse = {};
-
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(error, apiResponse);
- };
-
- bucket.setMetadata(metadata, function(err, apiResponse_) {
- assert.strictEqual(err, error);
- assert.strictEqual(apiResponse_, apiResponse);
-
- done();
- });
- });
-
- it('should update internal metadata property', function() {
- bucket.makeReq_ = function(method, path, query, body, callback) {
- callback(null, metadata);
- };
- bucket.setMetadata(metadata, function() {
- assert.deepEqual(bucket.metadata, metadata);
- });
- });
- });
-
describe('upload', function() {
var basename = 'proto_query.json';
var filepath = 'test/testdata/' + basename;
@@ -1199,30 +1107,4 @@ describe('Bucket', function() {
});
});
});
-
- describe('makeReq_', function() {
- var method = 'POST';
- var path = '/path';
- var query = { a: 'b', c: { d: 'e' } };
- var body = { hi: 'there' };
-
- it('should make correct request', function(done) {
- bucket.storage.makeAuthenticatedRequest_ = function(request) {
- var basePath = 'https://www.googleapis.com/storage/v1/b';
- assert.equal(request.method, method);
- assert.equal(request.uri, basePath + '/' + bucket.name + path);
- assert.deepEqual(request.qs, query);
- assert.deepEqual(request.json, body);
- done();
- };
- bucket.makeReq_(method, path, query, body, util.noop);
- });
-
- it('should execute callback', function(done) {
- bucket.storage.makeAuthenticatedRequest_ = function(request, callback) {
- callback();
- };
- bucket.makeReq_(method, path, query, body, done);
- });
- });
});
diff --git a/test/storage/file.js b/test/storage/file.js
index 0f0d7b0e126..2765e5fde19 100644
--- a/test/storage/file.js
+++ b/test/storage/file.js
@@ -17,7 +17,6 @@
'use strict';
var assert = require('assert');
-var Bucket = require('../../lib/storage/bucket.js');
var duplexify = require('duplexify');
var extend = require('extend');
var format = require('string-format-obj');
@@ -29,7 +28,10 @@ var stream = require('stream');
var through = require('through2');
var tmp = require('tmp');
var url = require('url');
-var util = require('../../lib/common/util');
+
+var Bucket = require('../../lib/storage/bucket.js');
+var ServiceObject = require('../../lib/common/service-object.js');
+var util = require('../../lib/common/util.js');
var makeWritableStreamOverride;
var handleRespOverride;
@@ -72,21 +74,34 @@ fakeResumableUpload.createURI = function() {
return createURI.apply(null, arguments);
};
+function FakeServiceObject() {
+ this.calledWith_ = arguments;
+ ServiceObject.apply(this, arguments);
+}
+
+nodeutil.inherits(FakeServiceObject, ServiceObject);
+
describe('File', function() {
var File;
- var FILE_NAME = 'file-name.png';
var file;
+
+ var FILE_NAME = 'file-name.png';
var directoryFile;
- var bucket;
+
+ var STORAGE;
+ var BUCKET;
before(function() {
- mockery.registerMock('request', fakeRequest);
mockery.registerMock('gcs-resumable-upload', fakeResumableUpload);
+ mockery.registerMock('request', fakeRequest);
+ mockery.registerMock('../common/service-object.js', FakeServiceObject);
mockery.registerMock('../common/util.js', fakeUtil);
+
mockery.enable({
useCleanCache: true,
warnOnUnregistered: false
});
+
File = require('../../lib/storage/file.js');
});
@@ -96,8 +111,10 @@ describe('File', function() {
});
beforeEach(function() {
- var options = {
- makeAuthenticatedRequest_: function(req, callback) {
+ STORAGE = {
+ createBucket: util.noop,
+ request: util.noop,
+ makeAuthenticatedRequest: function(req, callback) {
if (callback) {
(callback.onAuthenticated || callback)(null, req);
} else {
@@ -105,13 +122,14 @@ describe('File', function() {
}
}
};
- bucket = new Bucket(options, 'bucket-name');
- file = new File(bucket, FILE_NAME);
- file.makeReq_ = util.noop;
+ BUCKET = new Bucket(STORAGE, 'bucket-name');
- directoryFile = new File(bucket, 'directory/file.jpg');
- directoryFile.makeReq_ = util.noop;
+ file = new File(BUCKET, FILE_NAME);
+ file.request = util.noop;
+
+ directoryFile = new File(BUCKET, 'directory/file.jpg');
+ directoryFile.request = util.noop;
handleRespOverride = null;
makeWritableStreamOverride = null;
@@ -120,20 +138,36 @@ describe('File', function() {
});
describe('initialization', function() {
- it('should throw if no name is provided', function() {
- assert.throws(function() {
- new File(bucket);
- }, /A file name must be specified/);
- });
-
it('should assign file name', function() {
assert.equal(file.name, FILE_NAME);
});
+ it('should assign the bucket instance', function() {
+ assert.strictEqual(file.bucket, BUCKET);
+ });
+
+ it('should assign the storage instance', function() {
+ assert.strictEqual(file.storage, BUCKET.storage);
+ });
+
it('should accept specifying a generation', function() {
- var file = new File(bucket, 'name', { generation: 2 });
+ var file = new File(BUCKET, 'name', { generation: 2 });
assert.equal(file.generation, 2);
});
+
+ it('should inherit from ServiceObject', function() {
+ assert(file instanceof ServiceObject);
+
+ var calledWith = file.calledWith_[0];
+
+ assert.strictEqual(calledWith.parent, BUCKET);
+ assert.strictEqual(calledWith.baseUrl, '/o');
+ assert.strictEqual(calledWith.id, encodeURIComponent(FILE_NAME));
+ assert.deepEqual(calledWith.methods, {
+ exists: true,
+ get: true
+ });
+ });
});
describe('copy', function() {
@@ -144,17 +178,15 @@ describe('File', function() {
});
it('should URI encode file names', function(done) {
- var newFile = new File(bucket, 'nested/file.jpg');
+ var newFile = new File(BUCKET, 'nested/file.jpg');
- var expectedPath =
- format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', {
- srcName: encodeURIComponent(directoryFile.name),
- destBucket: file.bucket.name,
- destName: encodeURIComponent(newFile.name)
- });
+ var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
+ destBucket: file.bucket.name,
+ destName: encodeURIComponent(newFile.name)
+ });
- directoryFile.makeReq_ = function(method, path) {
- assert.equal(path, expectedPath);
+ directoryFile.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, expectedPath);
done();
};
@@ -165,9 +197,9 @@ describe('File', function() {
var error = new Error('Error.');
var apiResponse = {};
- var newFile = new File(bucket, 'new-file');
+ var newFile = new File(BUCKET, 'new-file');
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(error, apiResponse);
};
@@ -181,11 +213,11 @@ describe('File', function() {
});
it('should send query.sourceGeneration if File has one', function(done) {
- var versionedFile = new File(bucket, 'name', { generation: 1 });
- var newFile = new File(bucket, 'new-file');
+ var versionedFile = new File(BUCKET, 'name', { generation: 1 });
+ var newFile = new File(BUCKET, 'new-file');
- versionedFile.makeReq_ = function(method, path, query) {
- assert.equal(query.sourceGeneration, 1);
+ versionedFile.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.sourceGeneration, 1);
done();
};
@@ -194,45 +226,37 @@ describe('File', function() {
describe('destination types', function() {
function assertPathEquals(file, expectedPath, callback) {
- file.makeReq_ = function(method, path) {
- assert.equal(path, expectedPath);
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, expectedPath);
callback();
};
}
it('should allow a string', function(done) {
var newFileName = 'new-file-name.png';
- var expectedPath =
- format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', {
- srcName: file.name,
- destBucket: file.bucket.name,
- destName: newFileName
- });
+ var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
+ destBucket: file.bucket.name,
+ destName: newFileName
+ });
assertPathEquals(file, expectedPath, done);
file.copy(newFileName);
});
it('should allow a Bucket', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- var expectedPath =
- format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', {
- srcName: file.name,
- destBucket: newBucket.name,
- destName: file.name
- });
+ var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
+ destBucket: BUCKET.name,
+ destName: file.name
+ });
assertPathEquals(file, expectedPath, done);
- file.copy(newBucket);
+ file.copy(BUCKET);
});
it('should allow a File', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- var newFile = new File(newBucket, 'new-file');
- var expectedPath =
- format('/o/{srcName}/copyTo/b/{destBucket}/o/{destName}', {
- srcName: file.name,
- destBucket: newBucket.name,
- destName: newFile.name
- });
+ var newFile = new File(BUCKET, 'new-file');
+ var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
+ destBucket: BUCKET.name,
+ destName: newFile.name
+ });
assertPathEquals(file, expectedPath, done);
file.copy(newFile);
});
@@ -247,14 +271,13 @@ describe('File', function() {
describe('returned File object', function() {
beforeEach(function() {
var resp = { success: true };
- file.makeReq_ = function(method, path, qs, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(null, resp);
};
});
it('should re-use file object if one is provided', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- var newFile = new File(newBucket, 'new-file');
+ var newFile = new File(BUCKET, 'new-file');
file.copy(newFile, function(err, copiedFile) {
assert.ifError(err);
assert.deepEqual(copiedFile, newFile);
@@ -266,25 +289,24 @@ describe('File', function() {
var newFilename = 'new-filename';
file.copy(newFilename, function(err, copiedFile) {
assert.ifError(err);
- assert.equal(copiedFile.bucket.name, bucket.name);
+ assert.equal(copiedFile.bucket.name, BUCKET.name);
assert.equal(copiedFile.name, newFilename);
done();
});
});
it('should create new file on the destination bucket', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- file.copy(newBucket, function(err, copiedFile) {
+ file.copy(BUCKET, function(err, copiedFile) {
assert.ifError(err);
- assert.equal(copiedFile.bucket.name, newBucket.name);
+ assert.equal(copiedFile.bucket.name, BUCKET.name);
assert.equal(copiedFile.name, file.name);
done();
});
});
it('should pass apiResponse into callback', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- file.copy(newBucket, function(err, copiedFile, apiResponse) {
+ file.copy(BUCKET, function(err, copiedFile, apiResponse) {
+ assert.ifError(err);
assert.deepEqual({ success: true }, apiResponse);
done();
});
@@ -292,94 +314,6 @@ describe('File', function() {
});
});
- describe('move', function() {
- it('should throw if no destination is provided', function() {
- assert.throws(function() {
- file.move();
- }, /should have a name/);
- });
-
- describe('copy to destination', function() {
- function assertCopyFile(file, expectedDestination, callback) {
- file.copy = function(destination) {
- assert.equal(destination, expectedDestination);
- callback();
- };
- }
-
- it('should call copy with string', function(done) {
- var newFileName = 'new-file-name.png';
- assertCopyFile(file, newFileName, done);
- file.move(newFileName);
- });
-
- it('should call copy with Bucket', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- assertCopyFile(file, newBucket, done);
- file.move(newBucket);
- });
-
- it('should call copy with File', function(done) {
- var newBucket = new Bucket({}, 'new-bucket');
- var newFile = new File(newBucket, 'new-file');
- assertCopyFile(file, newFile, done);
- file.move(newFile);
- });
-
- it('should fail if copy fails', function(done) {
- var error = new Error('Error.');
- file.copy = function(destination, callback) {
- callback(error);
- };
- file.move('new-filename', function(err) {
- assert.equal(err, error);
- done();
- });
- });
- });
-
- describe('delete original file', function() {
- it('should delete if copy is successful', function(done) {
- file.copy = function(destination, callback) {
- callback(null);
- };
- file.delete = function() {
- assert.equal(this, file);
- done();
- };
- file.move('new-filename');
- });
-
- it('should not delete if copy fails', function(done) {
- var deleteCalled = false;
- file.copy = function(destination, callback) {
- callback(new Error('Error.'));
- };
- file.delete = function() {
- deleteCalled = true;
- };
- file.move('new-filename', function() {
- assert.equal(deleteCalled, false);
- done();
- });
- });
-
- it('should fail if delete fails', function(done) {
- var error = new Error('Error.');
- file.copy = function(destination, callback) {
- callback();
- };
- file.delete = function(callback) {
- callback(error);
- };
- file.move('new-filename', function(err) {
- assert.equal(err, error);
- done();
- });
- });
- });
- });
-
describe('createReadStream', function() {
function getFakeRequest(data) {
var aborted = false;
@@ -499,13 +433,11 @@ describe('File', function() {
});
it('should send query.generation if File has one', function(done) {
- var versionedFile = new File(bucket, 'file.txt', { generation: 1 });
+ var versionedFile = new File(BUCKET, 'file.txt', { generation: 1 });
- versionedFile.bucket.storage.makeAuthenticatedRequest_ = function(rOpts) {
+ versionedFile.bucket.storage.makeAuthenticatedRequest = function(rOpts) {
assert.equal(rOpts.qs.generation, 1);
- setImmediate(function() {
- done();
- });
+ setImmediate(done);
return duplexify();
};
@@ -531,7 +463,7 @@ describe('File', function() {
it('should confirm the abort method exists', function(done) {
var reqStream = through();
- file.bucket.storage.makeAuthenticatedRequest_ = function() {
+ file.bucket.storage.makeAuthenticatedRequest = function() {
return reqStream;
};
@@ -554,7 +486,7 @@ describe('File', function() {
o: encodeURIComponent(file.name)
});
- file.bucket.storage.makeAuthenticatedRequest_ = function(opts) {
+ file.bucket.storage.makeAuthenticatedRequest = function(opts) {
assert.equal(opts.uri, expectedPath);
setImmediate(function() {
done();
@@ -566,7 +498,7 @@ describe('File', function() {
});
it('should accept gzip encoding', function(done) {
- file.bucket.storage.makeAuthenticatedRequest_ = function(opts) {
+ file.bucket.storage.makeAuthenticatedRequest = function(opts) {
assert.strictEqual(opts.gzip, true);
setImmediate(function() {
done();
@@ -581,7 +513,7 @@ describe('File', function() {
var ERROR = new Error('Error.');
beforeEach(function() {
- file.bucket.storage.makeAuthenticatedRequest_ = function(opts) {
+ file.bucket.storage.makeAuthenticatedRequest = function(opts) {
var stream = (requestOverride || request)(opts);
setImmediate(function() {
@@ -609,7 +541,7 @@ describe('File', function() {
requestOverride = getFakeRequest();
- file.bucket.storage.makeAuthenticatedRequest_ = function() {
+ file.bucket.storage.makeAuthenticatedRequest = function() {
setImmediate(function() {
assert.deepEqual(requestOverride.getRequestOptions(), fakeRequest);
done();
@@ -634,7 +566,7 @@ describe('File', function() {
it('should unpipe stream from an error on the response', function(done) {
var requestStream = through();
- file.bucket.storage.makeAuthenticatedRequest_ = function() {
+ file.bucket.storage.makeAuthenticatedRequest = function() {
setImmediate(function() {
// Must be a stream. Doesn't matter for the tests, though.
requestStream.emit('response', through());
@@ -670,7 +602,7 @@ describe('File', function() {
done();
};
- file.bucket.storage.makeAuthenticatedRequest_ = function() {
+ file.bucket.storage.makeAuthenticatedRequest = function() {
var stream = through();
setImmediate(function() {
stream.emit('complete', response);
@@ -714,7 +646,7 @@ describe('File', function() {
beforeEach(function() {
file.metadata.mediaLink = 'http://uri';
- file.bucket.storage.makeAuthenticatedRequest_ = function(opts, cb) {
+ file.bucket.storage.makeAuthenticatedRequest = function(opts, cb) {
if (cb) {
(cb.onAuthenticated || cb)(null, {});
} else {
@@ -914,7 +846,7 @@ describe('File', function() {
createURI: function(opts, callback) {
var bucket = file.bucket;
var storage = bucket.storage;
- var authClient = storage.makeAuthenticatedRequest_.authClient;
+ var authClient = storage.makeAuthenticatedRequest.authClient;
assert.strictEqual(opts.authClient, authClient);
assert.strictEqual(opts.bucket, bucket.name);
@@ -1218,13 +1150,14 @@ describe('File', function() {
describe('delete', function() {
it('should delete the file', function(done) {
- file.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'DELETE');
- assert.equal(path, '/o/' + FILE_NAME);
- assert.deepEqual(query, {});
- assert.strictEqual(body, null);
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'DELETE');
+ assert.equal(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.qs, {});
+
done();
};
+
file.delete();
});
@@ -1232,7 +1165,7 @@ describe('File', function() {
var error = new Error('Error.');
var apiResponse = {};
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(error, apiResponse);
};
@@ -1244,20 +1177,12 @@ describe('File', function() {
});
});
- it('should URI encode file names', function(done) {
- directoryFile.makeReq_ = function(method, path) {
- assert.equal(path, '/o/' + encodeURIComponent(directoryFile.name));
- done();
- };
-
- directoryFile.delete();
- });
-
it('should send query.generation if File has one', function(done) {
- var versionedFile = new File(bucket, 'new-file.txt', { generation: 1 });
+ var versionedFile = new File(BUCKET, 'new-file.txt', { generation: 1 });
+
+ versionedFile.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.generation, 1);
- versionedFile.makeReq_ = function(method, path, query) {
- assert.equal(query.generation, 1);
done();
};
@@ -1265,17 +1190,20 @@ describe('File', function() {
});
it('should execute callback', function(done) {
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback();
};
+
file.delete(done);
});
it('should execute callback with apiResponse', function(done) {
var resp = { success: true };
- file.makeReq_ = function(method, path, query, body, callback) {
+
+ file.request = function(reqOpts, callback) {
callback(null, resp);
};
+
file.delete(function(err, apiResponse) {
assert.deepEqual(resp, apiResponse);
done();
@@ -1419,30 +1347,21 @@ describe('File', function() {
var metadata = { a: 'b', c: 'd' };
it('should get the metadata of a file', function(done) {
- file.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '/o/' + FILE_NAME);
- assert.deepEqual(query, {});
- assert.strictEqual(body, null);
- done();
- };
- file.getMetadata();
- });
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.qs, {});
- it('should URI encode file names', function(done) {
- directoryFile.makeReq_ = function(method, path) {
- assert.equal(path, '/o/' + encodeURIComponent(directoryFile.name));
done();
};
- directoryFile.getMetadata();
+ file.getMetadata();
});
it('should send query.generation if File has one', function(done) {
- var versionedFile = new File(bucket, 'new-file.txt', { generation: 1 });
+ var versionedFile = new File(BUCKET, 'new-file.txt', { generation: 1 });
- versionedFile.makeReq_ = function(method, path, query) {
- assert.equal(query.generation, 1);
+ versionedFile.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.generation, 1);
done();
};
@@ -1450,15 +1369,16 @@ describe('File', function() {
});
it('should execute callback', function(done) {
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback();
};
+
file.getMetadata(done);
});
it('should execute callback with apiResponse', function(done) {
var resp = { success: true };
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(null, resp);
};
file.getMetadata(function(err, metadata, apiResponse) {
@@ -1468,7 +1388,7 @@ describe('File', function() {
});
it('should update metadata property on object', function() {
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(null, metadata);
};
assert.deepEqual(file.metadata, {});
@@ -1479,7 +1399,7 @@ describe('File', function() {
});
it('should pass metadata to callback', function(done) {
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(null, metadata);
};
file.getMetadata(function(err, fileMetadata) {
@@ -1493,8 +1413,8 @@ describe('File', function() {
var credentials = require('../testdata/privateKeyFile.json');
beforeEach(function() {
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(null, credentials);
};
});
@@ -1514,8 +1434,8 @@ describe('File', function() {
it('should return an error if getCredentials errors', function(done) {
var error = new Error('Error.');
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(error);
};
@@ -1529,8 +1449,8 @@ describe('File', function() {
});
it('should return an error if credentials are not present', function(done) {
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(null, {});
};
@@ -1783,8 +1703,8 @@ describe('File', function() {
var credentials = require('../testdata/privateKeyFile.json');
beforeEach(function() {
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(null, credentials);
};
});
@@ -1803,8 +1723,8 @@ describe('File', function() {
it('should return an error if getCredentials errors', function(done) {
var error = new Error('Error.');
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(error);
};
@@ -1819,8 +1739,8 @@ describe('File', function() {
});
it('should return an error if credentials are not present', function(done) {
- var storage = bucket.storage;
- storage.makeAuthenticatedRequest_.getCredentials = function(callback) {
+ var storage = BUCKET.storage;
+ storage.getCredentials = function(callback) {
callback(null, {});
};
@@ -1963,79 +1883,57 @@ describe('File', function() {
});
});
- describe('setMetadata', function() {
- var metadata = { fake: 'metadata' };
-
- it('should set metadata', function(done) {
- file.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'PATCH');
- assert.equal(path, '/o/' + file.name);
- assert.deepEqual(body, metadata);
- done();
- };
- file.setMetadata(metadata);
- });
+ describe('makePrivate', function() {
+ it('should execute callback with API response', function(done) {
+ var apiResponse = {};
- it('should URI encode file names', function(done) {
- directoryFile.makeReq_ = function(method, path) {
- assert.equal(path, '/o/' + encodeURIComponent(directoryFile.name));
- done();
+ file.request = function(reqOpts, callback) {
+ callback(null, apiResponse);
};
- directoryFile.setMetadata(metadata);
- });
-
- it('should send query.generation if File has one', function(done) {
- var versionedFile = new File(bucket, 'new-file.txt', { generation: 1 });
+ file.makePrivate(function(err, apiResponse_) {
+ assert.ifError(err);
+ assert.strictEqual(apiResponse_, apiResponse);
- versionedFile.makeReq_ = function(method, path, query) {
- assert.equal(query.generation, 1);
done();
- };
-
- versionedFile.setMetadata();
- });
-
- it('should execute callback', function(done) {
- file.makeReq_ = function(method, path, query, body, callback) {
- callback();
- };
- file.setMetadata(metadata, done);
+ });
});
it('should execute callback with error & API response', function(done) {
var error = new Error('Error.');
var apiResponse = {};
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(error, apiResponse);
};
- file.setMetadata(metadata, function(err, apiResponse_) {
+ file.makePrivate(function(err, apiResponse_) {
assert.strictEqual(err, error);
assert.strictEqual(apiResponse_, apiResponse);
+
done();
});
});
- it('should execute callback with apiResponse', function(done) {
- var resp = { success: true };
- file.makeReq_ = function(method, path, query, body, callback) {
- callback(null, resp);
- };
- file.setMetadata(metadata, function(err, apiResponse) {
- assert.deepEqual(resp, apiResponse);
+ it('should make the file private to project by default', function(done) {
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'PATCH');
+ assert.strictEqual(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.qs, { predefinedAcl: 'projectPrivate' });
+ assert.deepEqual(reqOpts.json, { acl: null });
done();
- });
+ };
+
+ file.makePrivate(util.noop);
});
- it('should update internal metadata property', function() {
- file.makeReq_ = function(method, path, query, body, callback) {
- callback(null, metadata);
+ it('should make the file private to user if strict = true', function(done) {
+ file.request = function(reqOpts) {
+ assert.deepEqual(reqOpts.qs, { predefinedAcl: 'private' });
+ done();
};
- file.setMetadata(metadata, function() {
- assert.deepEqual(file.metadata, metadata);
- });
+
+ file.makePrivate({ strict: true }, util.noop);
});
});
@@ -2058,60 +1956,156 @@ describe('File', function() {
});
});
- describe('makePrivate', function() {
- it('should execute callback with API response', function(done) {
- var apiResponse = {};
+ describe('move', function() {
+ it('should throw if no destination is provided', function() {
+ assert.throws(function() {
+ file.move();
+ }, /should have a name/);
+ });
- file.makeReq_ = function(method, path, query, body, callback) {
- callback(null, apiResponse);
+ describe('copy to destination', function() {
+ function assertCopyFile(file, expectedDestination, callback) {
+ file.copy = function(destination) {
+ assert.strictEqual(destination, expectedDestination);
+ callback();
+ };
+ }
+
+ it('should call copy with string', function(done) {
+ var newFileName = 'new-file-name.png';
+ assertCopyFile(file, newFileName, done);
+ file.move(newFileName);
+ });
+
+ it('should call copy with Bucket', function(done) {
+ assertCopyFile(file, BUCKET, done);
+ file.move(BUCKET);
+ });
+
+ it('should call copy with File', function(done) {
+ var newFile = new File(BUCKET, 'new-file');
+ assertCopyFile(file, newFile, done);
+ file.move(newFile);
+ });
+
+ it('should fail if copy fails', function(done) {
+ var error = new Error('Error.');
+ file.copy = function(destination, callback) {
+ callback(error);
+ };
+ file.move('new-filename', function(err) {
+ assert.equal(err, error);
+ done();
+ });
+ });
+ });
+
+ describe('delete original file', function() {
+ it('should delete if copy is successful', function(done) {
+ file.copy = function(destination, callback) {
+ callback(null);
+ };
+ file.delete = function() {
+ assert.equal(this, file);
+ done();
+ };
+ file.move('new-filename');
+ });
+
+ it('should not delete if copy fails', function(done) {
+ var deleteCalled = false;
+ file.copy = function(destination, callback) {
+ callback(new Error('Error.'));
+ };
+ file.delete = function() {
+ deleteCalled = true;
+ };
+ file.move('new-filename', function() {
+ assert.equal(deleteCalled, false);
+ done();
+ });
+ });
+
+ it('should fail if delete fails', function(done) {
+ var error = new Error('Error.');
+ file.copy = function(destination, callback) {
+ callback();
+ };
+ file.delete = function(callback) {
+ callback(error);
+ };
+ file.move('new-filename', function(err) {
+ assert.equal(err, error);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('setMetadata', function() {
+ var metadata = { fake: 'metadata' };
+
+ it('should set metadata', function(done) {
+ file.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.method, 'PATCH');
+ assert.strictEqual(reqOpts.uri, '');
+ assert.deepEqual(reqOpts.json, metadata);
+ done();
};
+ file.setMetadata(metadata);
+ });
- file.makePrivate(function(err, apiResponse_) {
- assert.ifError(err);
- assert.strictEqual(apiResponse_, apiResponse);
+ it('should send query.generation if File has one', function(done) {
+ var versionedFile = new File(BUCKET, 'new-file.txt', { generation: 1 });
+ versionedFile.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.qs.generation, 1);
done();
- });
+ };
+
+ versionedFile.setMetadata();
+ });
+
+ it('should execute callback', function(done) {
+ file.request = function(reqOpts, callback) {
+ callback();
+ };
+ file.setMetadata(metadata, done);
});
it('should execute callback with error & API response', function(done) {
var error = new Error('Error.');
var apiResponse = {};
- file.makeReq_ = function(method, path, query, body, callback) {
+ file.request = function(reqOpts, callback) {
callback(error, apiResponse);
};
- file.makePrivate(function(err, apiResponse_) {
+ file.setMetadata(metadata, function(err, apiResponse_) {
assert.strictEqual(err, error);
assert.strictEqual(apiResponse_, apiResponse);
-
done();
});
});
- it('should make the file private to project by default', function(done) {
- file.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'PATCH');
- assert.equal(path, '/o/' + encodeURIComponent(file.name));
- assert.deepEqual(query, { predefinedAcl: 'projectPrivate' });
- assert.deepEqual(body, { acl: null });
- done();
+ it('should execute callback with apiResponse', function(done) {
+ var resp = { success: true };
+ file.request = function(reqOpts, callback) {
+ callback(null, resp);
};
-
- file.makePrivate(util.noop);
+ file.setMetadata(metadata, function(err, apiResponse) {
+ assert.deepEqual(resp, apiResponse);
+ done();
+ });
});
- it('should make the file private to user if strict = true', function(done) {
- file.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'PATCH');
- assert.equal(path, '/o/' + encodeURIComponent(file.name));
- assert.deepEqual(query, { predefinedAcl: 'private' });
- assert.deepEqual(body, { acl: null });
- done();
+ it('should update internal metadata property', function() {
+ file.request = function(reqOpts, callback) {
+ callback(null, metadata);
};
-
- file.makePrivate({ strict: true }, util.noop);
+ file.setMetadata(metadata, function() {
+ assert.deepEqual(file.metadata, metadata);
+ });
});
});
@@ -2127,7 +2121,7 @@ describe('File', function() {
resumableUploadOverride = function(opts) {
var bucket = file.bucket;
var storage = bucket.storage;
- var authClient = storage.makeAuthenticatedRequest_.authClient;
+ var authClient = storage.makeAuthenticatedRequest.authClient;
assert.strictEqual(opts.authClient, authClient);
assert.strictEqual(opts.bucket, bucket.name);
@@ -2224,7 +2218,7 @@ describe('File', function() {
});
it('should send query.ifGenerationMatch if File has one', function(done) {
- var versionedFile = new File(bucket, 'new-file.txt', { generation: 1 });
+ var versionedFile = new File(BUCKET, 'new-file.txt', { generation: 1 });
makeWritableStreamOverride = function(stream, options) {
assert.equal(options.request.qs.ifGenerationMatch, 1);
diff --git a/test/storage/index.js b/test/storage/index.js
index 315472ad0e2..53aa5665649 100644
--- a/test/storage/index.js
+++ b/test/storage/index.js
@@ -20,7 +20,9 @@ var arrify = require('arrify');
var assert = require('assert');
var extend = require('extend');
var mockery = require('mockery');
+var nodeutil = require('util');
+var Service = require('../../lib/common/service.js');
var util = require('../../lib/common/util.js');
var fakeUtil = extend({}, util);
@@ -39,6 +41,13 @@ var fakeStreamRouter = {
}
};
+function FakeService() {
+ this.calledWith_ = arguments;
+ Service.apply(this, arguments);
+}
+
+nodeutil.inherits(FakeService, Service);
+
describe('Storage', function() {
var PROJECT_ID = 'project-id';
var Storage;
@@ -46,8 +55,10 @@ describe('Storage', function() {
var Bucket;
before(function() {
+ mockery.registerMock('../common/service.js', FakeService);
mockery.registerMock('../common/util.js', fakeUtil);
mockery.registerMock('../common/stream-router.js', fakeStreamRouter);
+
mockery.enable({
useCleanCache: true,
warnOnUnregistered: false
@@ -90,12 +101,27 @@ describe('Storage', function() {
fakeUtil.normalizeArguments = normalizeArguments;
});
- it('should set the project id', function() {
- assert.equal(storage.projectId, 'project-id');
+ it('should inherit from Service', function() {
+ assert(storage instanceof Service);
+
+ var calledWith = storage.calledWith_[0];
+
+ var baseUrl = 'https://www.googleapis.com/storage/v1';
+ assert.strictEqual(calledWith.baseUrl, baseUrl);
+ assert.strictEqual(calledWith.projectIdRequired, false);
+ assert.deepEqual(calledWith.scopes, [
+ 'https://www.googleapis.com/auth/devstorage.full_control'
+ ]);
});
});
describe('bucket', function() {
+ it('should throw if no name was provided', function() {
+ assert.throws(function() {
+ storage.bucket();
+ }, /A bucket name is needed/);
+ });
+
it('should accept a string for a name', function() {
var newBucketName = 'new-bucket-name';
var bucket = storage.bucket(newBucketName);
@@ -110,11 +136,12 @@ describe('Storage', function() {
var BUCKET = { name: BUCKET_NAME };
it('should make correct API request', function(done) {
- storage.makeReq_ = function(method, path, query, body, callback) {
- assert.equal(method, 'POST');
- assert.equal(path, '');
- assert.equal(query.project, storage.projectId);
- assert.equal(body.name, BUCKET_NAME);
+ storage.request = function(reqOpts, callback) {
+ assert.strictEqual(reqOpts.method, 'POST');
+ assert.strictEqual(reqOpts.uri, '/b');
+ assert.strictEqual(reqOpts.qs.project, storage.projectId);
+ assert.strictEqual(reqOpts.json.name, BUCKET_NAME);
+
callback();
};
@@ -122,8 +149,8 @@ describe('Storage', function() {
});
it('should accept a name, metadata, and callback', function(done) {
- storage.makeReq_ = function(method, path, query, body, callback) {
- assert.deepEqual(body, extend(METADATA, { name: BUCKET_NAME }));
+ storage.request = function(reqOpts, callback) {
+ assert.deepEqual(reqOpts.json, extend(METADATA, { name: BUCKET_NAME }));
callback(null, METADATA);
};
storage.bucket = function(name) {
@@ -137,7 +164,7 @@ describe('Storage', function() {
});
it('should accept a name and callback only', function(done) {
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback();
};
storage.createBucket(BUCKET_NAME, done);
@@ -153,7 +180,7 @@ describe('Storage', function() {
storage.bucket = function() {
return BUCKET;
};
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, METADATA);
};
storage.createBucket(BUCKET_NAME, function(err, bucket) {
@@ -166,7 +193,7 @@ describe('Storage', function() {
it('should execute callback on error', function(done) {
var error = new Error('Error.');
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(error);
};
storage.createBucket(BUCKET_NAME, function(err) {
@@ -177,7 +204,7 @@ describe('Storage', function() {
it('should execute callback with apiResponse', function(done) {
var resp = { success: true };
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, resp);
};
storage.createBucket(BUCKET_NAME, function(err, bucket, apiResponse) {
@@ -187,30 +214,28 @@ describe('Storage', function() {
});
it('should expand the Nearline option', function(done) {
- storage.makeReq_ = function(method, path, query, body) {
- assert.strictEqual(body.storageClass, 'NEARLINE');
+ storage.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.json.storageClass, 'NEARLINE');
done();
};
storage.createBucket(BUCKET_NAME, { nearline: true }, function() {});
});
it('should expand the Durable Reduced Availability option', function(done) {
- storage.makeReq_ = function(method, path, query, body) {
+ storage.request = function(reqOpts) {
+ var body = reqOpts.json;
assert.strictEqual(body.storageClass, 'DURABLE_REDUCED_AVAILABILITY');
done();
};
storage.createBucket(BUCKET_NAME, { dra: true }, function() {});
});
-
});
describe('getBuckets', function() {
it('should get buckets without a query', function(done) {
- storage.makeReq_ = function(method, path, query, body) {
- assert.equal(method, 'GET');
- assert.equal(path, '');
- assert.deepEqual(query, { project: storage.projectId });
- assert.strictEqual(body, null);
+ storage.request = function(reqOpts) {
+ assert.strictEqual(reqOpts.uri, '/b');
+ assert.deepEqual(reqOpts.qs, { project: storage.projectId });
done();
};
storage.getBuckets(util.noop);
@@ -218,8 +243,8 @@ describe('Storage', function() {
it('should get buckets with a query', function(done) {
var token = 'next-page-token';
- storage.makeReq_ = function(method, path, query) {
- assert.deepEqual(query, {
+ storage.request = function(reqOpts) {
+ assert.deepEqual(reqOpts.qs, {
project: storage.projectId,
maxResults: 5,
pageToken: token
@@ -231,7 +256,7 @@ describe('Storage', function() {
it('should return nextQuery if more results exist', function() {
var token = 'next-page-token';
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, { nextPageToken: token, items: [] });
};
storage.getBuckets({ maxResults: 5 }, function(err, results, nextQuery) {
@@ -241,7 +266,7 @@ describe('Storage', function() {
});
it('should return null nextQuery if there are no more results', function() {
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, { items: [] });
};
storage.getBuckets({ maxResults: 5 }, function(err, results, nextQuery) {
@@ -250,7 +275,7 @@ describe('Storage', function() {
});
it('should return Bucket objects', function(done) {
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, { items: [{ id: 'fake-bucket-name' }] });
};
storage.getBuckets(function(err, buckets) {
@@ -262,7 +287,7 @@ describe('Storage', function() {
it('should return apiResponse', function(done) {
var resp = { items: [{ id: 'fake-bucket-name' }] };
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, resp);
};
storage.getBuckets(function(err, buckets, nextQuery, apiResponse) {
@@ -279,7 +304,7 @@ describe('Storage', function() {
my: 'custom metadata'
}
};
- storage.makeReq_ = function(method, path, query, body, callback) {
+ storage.request = function(reqOpts, callback) {
callback(null, { items: [bucketMetadata] });
};
storage.getBuckets(function(err, buckets) {