diff --git a/docs/components/docs/docs.js b/docs/components/docs/docs.js index b5c48b3ad0b..186892bffd0 100644 --- a/docs/components/docs/docs.js +++ b/docs/components/docs/docs.js @@ -128,9 +128,19 @@ angular return obj.ctx && obj.isPrivate === false && obj.ignore === false; }) .map(function(obj) { + var alias = obj.tags.filter(function(tag) { + return tag.type === 'alias'; + })[0]; + + if (alias && alias.string.indexOf('module:') !== 0) { + alias = alias.string; + } else { + alias = false + } + return { data: obj, - name: obj.ctx.name, + name: (alias || obj.ctx.name).trim(), constructor: obj.tags.some(function(tag) { return tag.type === 'constructor'; }), @@ -144,7 +154,7 @@ angular }) .map(function(tag) { tag.description = $sce.trustAsHtml( - formatHtml(detectLinks(tag.description.replace(/^- /, '')))); + formatHtml(detectLinks(detectModules(tag.description.replace(/^- /, ''))))); tag.types = $sce.trustAsHtml(tag.types.reduceRight( reduceModules, []).join(', ')); tag.optional = tag.types.toString().indexOf('=') > -1; @@ -172,7 +182,7 @@ angular function getMixIns($sce, $q, $http, version, baseUrl) { return function(data) { var methodWithMixIns = data.filter(function(method) { - return method.mixes; + return method.mixes.length > 0; })[0]; if (!methodWithMixIns) { return data; diff --git a/lib/storage/acl.js b/lib/storage/acl.js new file mode 100644 index 00000000000..87e46bcb941 --- /dev/null +++ b/lib/storage/acl.js @@ -0,0 +1,296 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module storage/acl + */ + +'use strict'; + +/** + * @type module:common/util + * @private + */ +var util = require('../common/util.js'); + +/** + * 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 + * users and allow other users to access your buckets and objects. + * + * An ACL consists of one or more entries, where each entry grants permissions + * to a scope. Permissions define the actions that can be performed against an + * object or bucket (for example, `READ` or `WRITE`); the scope defines who the + * permission applies to (for example, a specific user or group of users). + * + * For more detailed information, see + * [About Access Control Lists](http://goo.gl/6qBBPO). + * + * @constructor + * @alias module:storage/acl + */ +function Acl(options) { + this.makeReq = options.makeReq; + this.pathPrefix = options.pathPrefix; +} + +/** + * Add access controls on a {module:storage/bucket} or {module:storage/file}. + * + * @param {object} options - Configuration object. + * @param {string} options.scope - Whose permissions will be added. + * @param {string} options.role - Permissions allowed for the defined scope. See + * {module:storage#acl}. + * @param {int=} options.generation - **File Objects Only** Select a specific + * revision of this file (as opposed to the latest version, the default). + * @param {function} callback - The callback function. + * + * @example + * myBucket.acl.add({ + * scope: 'user-useremail@example.com', + * role: storage.acl.OWNER_ROLE + * }, function(err, aclObject) {}); + * + * //- + * // For file ACL operations, you can also specify a `generation` property. + * //- + * myFile.acl.add({ + * scope: 'user-useremail@example.com', + * role: storage.acl.OWNER_ROLE, + * generation: 1 + * }, function(err, aclObject) {}); + */ +Acl.prototype.add = function(options, callback) { + var that = this; + + var body = { + entity: options.scope, + role: options.role.toUpperCase() + }; + + var query = null; + + if (options.generation) { + query = { + generation: options.generation + }; + } + + this.makeReq_('POST', '', query, body, function(err, resp) { + if (err) { + callback(err); + return; + } + + callback(null, that.makeAclObject_(resp)); + }); +}; + +/** + * Delete access controls on a {module:storage/bucket} or {module:storage/file}. + * + * @param {object=} options - Configuration object. + * @param {string} options.scope - Whose permissions will be revoked. + * @param {int=} options.generation - **File Objects Only** Select a specific + * revision of this file (as opposed to the latest version, the default). + * @param {function} callback - The callback function. + * + * @example + * myBucket.acl.delete({ + * scope: 'user-useremail@example.com' + * }, function(err) {}); + * + * //- + * // For file ACL operations, an options object is also accepted. + * //- + * myFile.acl.delete({ + * scope: 'user-useremail@example.com', + * generation: 1 + * }, function(err) {}); + */ +Acl.prototype.delete = function(options, callback) { + var path = '/' + encodeURIComponent(options.scope); + var query = null; + + if (options.generation) { + query = { + generation: options.generation + }; + } + + this.makeReq_('DELETE', path, query, null, callback); +}; + +/** + * Get access controls on a {module:storage/bucket} or {module:storage/file}. If + * a scope is omitted, you will receive an array of all applicable access + * controls. + * + * @param {object|function} options - Configuration object. If you want to + * receive a list of all access controls, pass the callback function as the + * only argument. + * @param {string=} options.scope - Whose permissions will be fetched. + * @param {int=} options.generation - **File Objects Only** Select a specific + * revision of this file (as opposed to the latest version, the default). + * + * @example + * myBucket.acl.get({ + * scope: 'user-useremail@example.com' + * }, function(err, aclObject) {}); + * + * //- + * // Get all access controls. + * //- + * myBucket.acl.get(function(err, aclObjects) { + * // aclObjects = [ + * // { + * // scope: 'user-useremail@example.com', + * // role: 'owner' + * // } + * // ] + * }); + * + * //- + * // For file ACL operations, an options object is also accepted. + * //- + * myFile.acl.get({ + * scope: 'user-useremail@example.com', + * generation: 1 + * } function(err, aclObject) {}); + */ +Acl.prototype.get = function(options, callback) { + var that = this; + var path = ''; + var query = null; + + if (util.is(options, 'function')) { + callback = options; + options = null; + } else { + path = '/' + encodeURIComponent(options.scope); + + if (options.generation) { + query = { + generation: options.generation + }; + } + } + + this.makeReq_('GET', path, query, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var results = resp; + + if (resp.items) { + results = (resp.items || []).map(that.makeAclObject_); + } else { + results = that.makeAclObject_(results); + } + + callback(null, results); + }); +}; + +/** + * Update access controls on a {module:storage/bucket} or {module:storage/file}. + * + * @param {object=} options - Configuration object. + * @param {string} options.scope - Whose permissions will be added. + * @param {string} options.role - Permissions allowed for the defined scope. See + * {module:storage#acl}. + * @param {int=} options.generation - **File Objects Only** Select a specific + * revision of this file (as opposed to the latest version, the default). + * @param {function} callback - The callback function. + * + * @example + * var storage = gcloud.storage(); + * + * myBucket.acl.update({ + * scope: 'user-useremail@example.com', + * role: storage.acl.WRITER_ROLE + * }, function(err) {}); + * + * //- + * // For file ACL operations, an options object is also accepted. + * //- + * myFile.acl.update({ + * scope: 'user-useremail@example.com', + * role: storage.acl.WRITER_ROLE, + * generation: 1 + * }, function(err) {}); + */ +Acl.prototype.update = function(options, callback) { + var that = this; + var path = '/' + encodeURIComponent(options.scope); + var query = null; + + if (options.generation) { + query = { + generation: options.generation + }; + } + + var body = { + role: options.role.toUpperCase() + }; + + this.makeReq_('PUT', path, query, body, function(err, resp) { + if (err) { + callback(err); + return; + } + + callback(null, that.makeAclObject_(resp)); + }); +}; + +/** + * Transform API responses to a consistent object format. + * + * @private + */ +Acl.prototype.makeAclObject_ = function(accessControlObject) { + var obj = { + scope: accessControlObject.scope, + role: accessControlObject.role + }; + + if (accessControlObject.projectTeam) { + obj.projectTeam = accessControlObject.projectTeam; + } + + return obj; +}; + +/** + * Patch requests up to the bucket's request object. + * + * @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. + */ +Acl.prototype.makeReq_ = function(method, path, query, body, callback) { + this.makeReq(method, this.pathPrefix + path, query, body, callback); +}; + +module.exports = Acl; diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js index 16b61daf126..35ca34d161b 100644 --- a/lib/storage/bucket.js +++ b/lib/storage/bucket.js @@ -25,6 +25,12 @@ var fs = require('fs'); var mime = require('mime'); var path = require('path'); +/** + * @type module:storage/acl + * @private + */ +var Acl = require('./acl.js'); + /** * @type module:storage/file * @private @@ -79,6 +85,84 @@ function Bucket(storage, name) { if (!this.name) { throw 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 + * users and allow other users to access your buckets and objects. + * + * An ACL consists of one or more entries, where each entry grants permissions + * to a scope. Permissions define the actions that can be performed against an + * object or bucket (for example, `READ` or `WRITE`); the scope defines who + * the permission applies to (for example, a specific user or group of users). + * + * For more detailed information, see + * [About Access Control Lists](http://goo.gl/6qBBPO). + * + * The `acl` object on a Bucket instance provides methods to get you a list of + * the ACLs defined on your bucket, as well as set, update, and delete them. + * + * Buckets also have + * [default ACLs](https://cloud.google.com/storage/docs/accesscontrol#default) + * for all created files. Default ACLs specify permissions that all new + * objects added to the bucket will inherit by default. You can add, delete, + * get, and update scopes and permissions for these as well with + * {module:storage/bucket#acl.default}. + * + * @mixes module:storage/acl + */ + this.acl = new Acl({ + makeReq: this.makeReq_.bind(this), + pathPrefix: '/acl' + }); + + this.acl.default = new Acl({ + makeReq: this.makeReq_.bind(this), + pathPrefix: '/defaultObjectAcl' + }); + + /* jshint ignore:start */ + /*! Developer Documentation + * + * Sadly, to generate the documentation properly, this comment block describes + * a useless variable named `ignored` and aliases it to `acl.default`. This is + * done so the doc building process picks this up, without adding cruft to the + * Bucket class itself. + */ + /** + * Google Cloud Storage Buckets have [default ACLs](http://goo.gl/YpGdyv) for + * all created files. You can add, delete, get, and update scopes and + * permissions for these as well. The method signatures and examples are all + * the same, after only prefixing the method call with `default`. + * + * @alias acl.default + */ + var aclDefault; + + /** + * Maps to {module:storage/bucket#acl.add}. + * @alias acl.default.add + */ + var aclDefaultAdd; + + /** + * Maps to {module:storage/bucket#acl.delete}. + * @alias acl.default.delete + */ + var aclDefaultDelete; + + /** + * Maps to {module:storage/bucket#acl.get}. + * @alias acl.default.get + */ + var aclDefaultGet; + + /** + * Maps to {module:storage/bucket#acl.update}. + * @alias acl.default.update + */ + var aclDefaultUpdate; + /* jshint ignore:end */ } /** @@ -211,8 +295,7 @@ Bucket.prototype.getMetadata = function(callback) { */ Bucket.prototype.setMetadata = function(metadata, callback) { callback = callback || util.noop; - this.makeReq_( - 'PATCH', '/b/' + this.name, null, metadata, function(err, resp) { + this.makeReq_('PATCH', '', null, metadata, function(err, resp) { if (err) { callback(err); return; diff --git a/lib/storage/file.js b/lib/storage/file.js index 8d727ce9a03..e6da583b7d8 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -25,6 +25,12 @@ var duplexify = require('duplexify'); var request = require('request'); var streamEvents = require('stream-events'); +/** + * @type module:storage/acl + * @private + */ +var Acl = require('./acl.js'); + /** * @type module:common/util * @private @@ -61,10 +67,34 @@ function File(bucket, name, metadata) { this.bucket = bucket; this.makeReq_ = bucket.makeReq_.bind(bucket); this.metadata = metadata || {}; + Object.defineProperty(this, 'name', { enumerable: true, value: name }); + + /** + * 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 + * users and allow other users to access your buckets and objects. + * + * An ACL consists of one or more entries, where each entry grants permissions + * to a scope. Permissions define the actions that can be performed against an + * object or bucket (for example, `READ` or `WRITE`); the scope defines who + * the permission applies to (for example, a specific user or group of users). + * + * For more detailed information, see + * [About Access Control Lists](http://goo.gl/6qBBPO). + * + * The `acl` object on a File instance provides methods to get you a list of + * the ACLs defined on your bucket, as well as set, update, and delete them. + * + * @mixes module:storage/acl + */ + this.acl = new Acl({ + makeReq: this.makeReq_, + pathPrefix: '/o/' + encodeURIComponent(this.name) + '/acl' + }); } /** diff --git a/lib/storage/index.js b/lib/storage/index.js index 81ed2dfd3b1..e94991643e8 100644 --- a/lib/storage/index.js +++ b/lib/storage/index.js @@ -110,6 +110,40 @@ function Storage(config) { this.projectId = config.projectId; } +/** + * 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 + * users and allow other users to access your buckets and objects. + * + * This object provides constants to refer to the three permission levels that + * can be granted to a scope: + * + * - `Storage.acl.OWNER_ROLE` - ("OWNER") + * - `Storage.acl.READER_ROLE` - ("READER") + * - `Storage.acl.WRITER_ROLE` - ("WRITER") + * + * For more detailed information, see + * [About Access Control Lists](http://goo.gl/6qBBPO). + * + * @type {object} + * + * @example + * var storage = gcloud.storage(); + * var albums = storage.bucket('albums'); + * + * albums.acl.add({ + * scope: 'user-useremail@example.com', + * permission: Storage.acl.OWNER_ROLE + * }, function(err, aclObject) {}); + */ +Storage.acl = { + OWNER_ROLE: 'OWNER', + READER_ROLE: 'READER', + WRITER_ROLE: 'WRITER' +}; + +Storage.prototype.acl = Storage.acl; + /** * Get a reference to a Google Cloud Storage bucket. * diff --git a/regression/storage.js b/regression/storage.js index 48ca3708fe4..4b2c6f60fa1 100644 --- a/regression/storage.js +++ b/regression/storage.js @@ -84,6 +84,158 @@ describe('storage', function() { }); }); + describe('acls', function() { + var USER_ACCOUNT = 'user-spsawchuk@gmail.com'; + + describe('buckets', function() { + it('should get access controls', function(done) { + bucket.acl.get(done, function(err, accessControls) { + assert.ifError(err); + assert(Array.isArray(accessControls)); + done(); + }); + }); + + it('should add entity to default access controls', function(done) { + bucket.acl.default.add({ + scope: USER_ACCOUNT, + role: storage.acl.OWNER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + bucket.acl.default.get({ + scope: USER_ACCOUNT + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + bucket.acl.default.update({ + scope: USER_ACCOUNT, + role: storage.acl.READER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.READER_ROLE); + + bucket.acl.default.delete({ scope: USER_ACCOUNT }, done); + }); + }); + }); + }); + + it('should get default access controls', function(done) { + bucket.acl.default.get(function(err, accessControls) { + assert.ifError(err); + assert(Array.isArray(accessControls)); + done(); + }); + }); + + it('should grant an account access', function(done) { + bucket.acl.add({ + scope: USER_ACCOUNT, + role: storage.acl.OWNER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + bucket.acl.get({ scope: USER_ACCOUNT }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + bucket.acl.delete({ scope: USER_ACCOUNT }, done); + }); + }); + }); + + it('should update an account', function(done) { + bucket.acl.add({ + scope: USER_ACCOUNT, + role: storage.acl.OWNER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + bucket.acl.update({ + scope: USER_ACCOUNT, + role: storage.acl.WRITER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.WRITER_ROLE); + + bucket.acl.delete({ scope: USER_ACCOUNT }, done); + }); + }); + }); + }); + + describe('files', function() { + var file; + + before(function(done) { + bucket.upload(files.logo.path, function(err, f) { + assert.ifError(err); + file = f; + done(); + }); + }); + + after(function(done) { + file.delete(done); + }); + + it('should get access controls', function(done) { + file.acl.get(done, function(err, accessControls) { + assert.ifError(err); + assert(Array.isArray(accessControls)); + done(); + }); + }); + + it('should not expose default api', function() { + assert.equal(typeof file.default, 'undefined'); + }); + + it('should grant an account access', function(done) { + file.acl.add({ + scope: USER_ACCOUNT, + role: storage.acl.OWNER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + file.acl.get({ scope: USER_ACCOUNT }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + file.acl.delete({ scope: USER_ACCOUNT }, done); + }); + }); + }); + + it('should update an account', function(done) { + file.acl.add({ + scope: USER_ACCOUNT, + role: storage.acl.OWNER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + assert.equal(accessControl.role, storage.acl.OWNER_ROLE); + + file.acl.update({ + scope: USER_ACCOUNT, + role: storage.acl.READER_ROLE + }, function(err, accessControl) { + assert.ifError(err); + + assert.equal(accessControl.role, storage.acl.READER_ROLE); + + file.acl.delete({ scope: USER_ACCOUNT }, done); + }); + }); + }); + }); + }); + describe('getting buckets', function() { var bucketsToCreate = [ generateBucketName(), generateBucketName(), generateBucketName() @@ -130,6 +282,23 @@ describe('storage', function() { }); }); + describe('bucket metadata', function() { + it('should allow setting metadata on a bucket', function(done) { + var metadata = { + website: { + mainPageSuffix: 'http://fakeuri', + notFoundPage: 'http://fakeuri/404.html' + } + }; + + bucket.setMetadata(metadata, function(err, meta) { + assert.ifError(err); + assert.deepEqual(meta.website, metadata.website); + done(); + }); + }); + }); + describe('write, read, and remove files', function() { before(function(done) { var doneCalled = 0; diff --git a/scripts/docs.sh b/scripts/docs.sh index 7892971d3f2..7c58f85cb95 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -31,6 +31,7 @@ ./node_modules/.bin/dox < lib/pubsub/subscription.js > docs/json/master/pubsub/subscription.json & ./node_modules/.bin/dox < lib/pubsub/topic.js > docs/json/master/pubsub/topic.json & +./node_modules/.bin/dox < lib/storage/acl.js > docs/json/master/storage/acl.json & ./node_modules/.bin/dox < lib/storage/bucket.js > docs/json/master/storage/bucket.json & ./node_modules/.bin/dox < lib/storage/file.js > docs/json/master/storage/file.json & ./node_modules/.bin/dox < lib/storage/index.js > docs/json/master/storage/index.json & diff --git a/test/storage/acl.js b/test/storage/acl.js new file mode 100644 index 00000000000..42594408348 --- /dev/null +++ b/test/storage/acl.js @@ -0,0 +1,323 @@ +/** + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*global describe, it, beforeEach */ + +'use strict'; + +var Acl = require('../../lib/storage/acl.js'); +var assert = require('assert'); +var storage = require('../../lib/storage/index.js'); +var util = require('../../lib/common/util.js'); + +describe('storage/acl', function() { + var acl; + var ERROR = new Error('Error.'); + var MAKE_REQ = util.noop; + var PATH_PREFIX = '/acl'; + var ROLE = storage.acl.OWNER_ROLE; + var SCOPE = 'user-user@example.com'; + + beforeEach(function() { + acl = new Acl({ makeReq: 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); + }); + }); + + 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: SCOPE, role: ROLE }); + + done(); + }; + + acl.add({ scope: SCOPE, role: ROLE }, assert.ifError); + }); + + it('executes the callback with an ACL object', function(done) { + var apiResponse = { scope: SCOPE, role: ROLE }; + var expectedAclObject = { scope: SCOPE, role: ROLE }; + + acl.makeAclObject_ = function (obj) { + assert.deepEqual(obj, apiResponse); + return expectedAclObject; + }; + + acl.makeReq_ = function(method, path, query, body, callback) { + callback(null, apiResponse); + }; + + acl.add({ scope: SCOPE, role: ROLE }, function(err, aclObject) { + assert.ifError(err); + assert.deepEqual(aclObject, expectedAclObject); + done(); + }); + }); + + it('executes the callback with an error', function(done) { + acl.makeReq_ = function(method, path, query, body, callback) { + callback(ERROR); + }; + + acl.add({ scope: SCOPE, role: ROLE }, function(err) { + assert.deepEqual(err, ERROR); + done(); + }); + }); + }); + + 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(SCOPE)); + assert.strictEqual(query, null); + assert.strictEqual(body, null); + + done(); + }; + + acl.delete({ scope: SCOPE }, assert.ifError); + }); + + it('should execute the callback with an error', function(done) { + acl.makeReq_ = function(method, path, query, body, callback) { + callback(ERROR); + }; + + acl.delete({ scope: SCOPE }, function(err) { + assert.deepEqual(err, ERROR); + done(); + }); + }); + }); + + 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); + + done(); + }; + + acl.get(assert.ifError); + }); + + it('should accept a configuration object', function(done) { + var generation = 1; + + acl.makeReq_ = function(method, path, query) { + assert.equal(query.generation, generation); + + done(); + }; + + acl.get({ generation: generation }, assert.ifError); + }); + + it('should pass an array of acl objects to the callback', function(done) { + var apiResponse = { + items: [ + { scope: SCOPE, role: ROLE }, + { scope: SCOPE, role: ROLE }, + { scope: SCOPE, role: ROLE } + ] + }; + + var expectedAclObjects = [ + { scope: SCOPE, role: ROLE }, + { scope: SCOPE, role: ROLE }, + { scope: SCOPE, role: ROLE } + ]; + + acl.makeAclObject_ = function (obj, index) { + return expectedAclObjects[index]; + }; + + acl.makeReq_ = function(method, path, query, body, callback) { + callback(null, apiResponse); + }; + + acl.get(function(err, aclObjects) { + assert.ifError(err); + assert.deepEqual(aclObjects, expectedAclObjects); + done(); + }); + }); + }); + + describe('ACL object for a scope', 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(SCOPE)); + assert.strictEqual(query, null); + assert.strictEqual(body, null); + + done(); + }; + + acl.get({ scope: SCOPE }, assert.ifError); + }); + + it('should accept a configuration object', function(done) { + var generation = 1; + + acl.makeReq_ = function(method, path, query) { + assert.equal(query.generation, generation); + + done(); + }; + + acl.get({ scope: SCOPE, generation: generation }, assert.ifError); + }); + + it('should pass an acl object to the callback', function(done) { + var apiResponse = { scope: SCOPE, role: ROLE }; + var expectedAclObject = { scope: SCOPE, role: ROLE }; + + acl.makeAclObject_ = function () { + return expectedAclObject; + }; + + acl.makeReq_ = function(method, path, query, body, callback) { + callback(null, apiResponse); + }; + + acl.get({ scope: SCOPE }, function(err, aclObject) { + assert.ifError(err); + assert.deepEqual(aclObject, expectedAclObject); + done(); + }); + }); + }); + + it('should execute the callback with an error', function(done) { + acl.makeReq_ = function(method, path, query, body, callback) { + callback(ERROR); + }; + + acl.get(function(err) { + assert.deepEqual(err, ERROR); + done(); + }); + }); + }); + + 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(SCOPE)); + assert.strictEqual(query, null); + assert.deepEqual(body, { role: ROLE }); + + done(); + }; + + acl.update({ scope: SCOPE, role: ROLE }, assert.ifError); + }); + + it('should pass an acl object to the callback', function(done) { + var apiResponse = { scope: SCOPE, role: ROLE }; + var expectedAclObject = { scope: SCOPE, role: ROLE }; + + acl.makeAclObject_ = function () { + return expectedAclObject; + }; + + acl.makeReq_ = function(method, path, query, body, callback) { + callback(null, apiResponse); + }; + + acl.update({ scope: SCOPE, role: ROLE }, function(err, aclObject) { + assert.ifError(err); + assert.deepEqual(aclObject, expectedAclObject); + done(); + }); + }); + + it('should execute the callback with an error', function(done) { + acl.makeReq_ = function(method, path, query, body, callback) { + callback(ERROR); + }; + + acl.update({ scope: SCOPE, role: ROLE }, function(err) { + assert.deepEqual(err, ERROR); + done(); + }); + }); + }); + + describe('makeAclObject_', function() { + it('should return an ACL object from an API response', function() { + var projectTeam = { + projectNumber: '283748374', + team: 'awesome' + }; + + var apiResponse = { + scope: SCOPE, + role: ROLE, + projectTeam: projectTeam, + extra: 'ignored', + things: true + }; + + assert.deepEqual(acl.makeAclObject_(apiResponse), { + scope: SCOPE, + role: ROLE, + projectTeam: projectTeam + }); + }); + }); + + 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); + }); + }); +}); diff --git a/test/storage/bucket.js b/test/storage/bucket.js index 0adfa9edf1e..13e0c243819 100644 --- a/test/storage/bucket.js +++ b/test/storage/bucket.js @@ -242,7 +242,7 @@ describe('Bucket', function() { it('should set metadata', function(done) { bucket.makeReq_ = function(method, path, query, body) { assert.equal(method, 'PATCH'); - assert.equal(path, '/b/' + bucket.name); + assert.equal(path, ''); assert.deepEqual(body, metadata); done(); };