From c92f9c630abf16d327498d47b74b27d0e24674d6 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 29 Sep 2015 12:41:18 -0400 Subject: [PATCH 01/49] implement Service and ServiceObject in BigQuery --- lib/bigquery/dataset.js | 96 ++++++------- lib/bigquery/index.js | 95 ++++++------- lib/bigquery/job.js | 35 ++--- lib/bigquery/table.js | 93 +++++++------ lib/common/service-object.js | 252 +++++++++++++++++++++++++++++++++++ lib/common/service.js | 75 +++++++++++ system-test/bigquery.js | 25 +--- 7 files changed, 483 insertions(+), 188 deletions(-) create mode 100644 lib/common/service-object.js create mode 100644 lib/common/service.js diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 80d8ce9fde6..7a652a89c71 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -22,12 +22,13 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** - * @type {module:bigquery/table} + * @type {module:common/serviceObject} * @private */ -var Table = require('./table.js'); +var ServiceObject = require('../common/service-object.js'); /** * @type {module:common/streamrouter} @@ -35,6 +36,12 @@ var Table = require('./table.js'); */ var streamRouter = require('../common/stream-router.js'); +/** + * @type {module:bigquery/table} + * @private + */ +var Table = require('./table.js'); + /*! Developer Documentation * * @param {module:bigquery} bigQuery - BigQuery instance. @@ -48,18 +55,26 @@ var streamRouter = require('../common/stream-router.js'); * @constructor */ function Dataset(bigQuery, id) { + ServiceObject.call(this, { + parent: bigQuery, + baseUrl: '/datasets', + id: id, + createMethod: bigQuery.createDataset.bind(bigQuery) + }); + this.bigQuery = bigQuery; this.id = id; - this.metadata = {}; } +nodeutil.inherits(Dataset, ServiceObject); + /** * Create a table given a tableId or configuration object. * * @resource [Tables: insert API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/tables/insert} * - * @param {object} options - Table id or configuration object. - * @param {string} options.id - The id of the table. + * @param {string} id - Table id. + * @param {object=} options - Configuration object. * @param {string|object} options.schema - A comma-separated list of name:type * pairs. Valid types are "string", "integer", "float", "boolean", and * "timestamp". If the type is omitted, it is assumed to be "string". @@ -86,14 +101,14 @@ function Dataset(bigQuery, id) { * * dataset.createTable(tableConfig, function(err, table, apiResponse) {}); */ -Dataset.prototype.createTable = function(options, callback) { +Dataset.prototype.createTable = function(id, options, callback) { var that = this; extend(true, options, { tableReference: { datasetId: this.id, projectId: this.bigQuery.projectId, - tableId: options.id + tableId: id } }); @@ -101,9 +116,11 @@ Dataset.prototype.createTable = function(options, callback) { options.schema = Table.createSchemaFromString_(options.schema); } - delete options.id; - - this.makeReq_('POST', '/tables', null, options, function(err, resp) { + this.request({ + method: 'POST', + uri: '/tables', + json: options + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -149,7 +166,11 @@ Dataset.prototype.delete = function(options, callback) { deleteContents: !!options.force }; - this.makeReq_('DELETE', '', query, null, callback); + this.request({ + method: 'DELETE', + uri: '', + qs: query + }, callback); }; /** @@ -165,18 +186,8 @@ Dataset.prototype.delete = function(options, callback) { * @example * dataset.getMetadata(function(err, metadata, apiResponse) {}); */ -Dataset.prototype.getMetadata = function(callback) { - var that = this; - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - that.metadata = resp; - - callback(null, that.metadata, resp); - }); +Dataset.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -231,7 +242,10 @@ Dataset.prototype.getTables = function(query, callback) { query = query || {}; - this.makeReq_('GET', '/tables', query, null, function(err, resp) { + this.request({ + uri: '/tables', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -293,19 +307,8 @@ Dataset.prototype.query = function(options, callback) { * * dataset.setMetadata(metadata, function(err, apiResponse) {}); */ -Dataset.prototype.setMetadata = function(metadata, callback) { - var that = this; - - this.makeReq_('PATCH', '', null, metadata, function(err, resp) { - if (err) { - callback(err, resp); - return; - } - - that.metadata = resp; - - callback(null, resp); - }); +Dataset.prototype.setMetadata = function() { + ServiceObject.prototype.setMetadata.apply(this, arguments); }; /** @@ -321,28 +324,11 @@ Dataset.prototype.table = function(id) { return new Table(this, id); }; -/** - * Pass through this request to BigQuery's request handler, first prepending the - * path with the dataset. - * - * @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. - */ -Dataset.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/datasets/' + this.id + path; - this.bigQuery.makeReq_(method, path, query, body, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object * stream. `streamRouter` is used to add this dual behavior. */ -streamRouter.extend(Dataset, 'getTables'); +streamRouter.extend(Dataset, ['getTables']); module.exports = Dataset; diff --git a/lib/bigquery/index.js b/lib/bigquery/index.js index f6ff063e7f6..ed62ee0a4bc 100644 --- a/lib/bigquery/index.js +++ b/lib/bigquery/index.js @@ -22,6 +22,7 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:bigquery/dataset} @@ -35,6 +36,12 @@ var Dataset = require('./dataset.js'); */ var Job = require('./job.js'); +/** + * @type {module:common/service} + * @private + */ +var Service = require('../common/service.js'); + /** * @type {module:common/streamrouter} * @private @@ -53,20 +60,6 @@ var Table = require('./table.js'); */ var util = require('../common/util.js'); -/** - * @const {string} Base URL for the BigQuery API. - * @private - */ -var BIGQUERY_BASE_URL = 'https://www.googleapis.com/bigquery/v2/projects/'; - -/** - * Required scopes for Google Cloud BigQuery API. - * - * @const {array} - * @private - */ -var SCOPES = ['https://www.googleapis.com/auth/bigquery']; - /** * The examples below will demonstrate the different usage patterns your app may * need to support to retrieve a BigQuery object. @@ -100,16 +93,16 @@ function BigQuery(options) { return new BigQuery(options); } - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - keyFile: options.keyFilename, - scopes: SCOPES, - email: options.email - }); + var config = { + baseUrl: 'https://www.googleapis.com/bigquery/v2/projects', + scopes: ['https://www.googleapis.com/auth/bigquery'] + }; - this.projectId = options.projectId; + Service.call(this, config, options); } +nodeutil.inherits(BigQuery, Service); + /** * Create a dataset. * @@ -133,7 +126,11 @@ BigQuery.prototype.createDataset = function(id, callback) { } }; - this.makeReq_('POST', '/datasets', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/datasets', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -232,7 +229,10 @@ BigQuery.prototype.getDatasets = function(query, callback) { query = query || {}; - this.makeReq_('GET', '/datasets', query, null, function(err, resp) { + this.request({ + uri: '/datasets', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -335,7 +335,10 @@ BigQuery.prototype.getJobs = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/jobs', options, null, function(err, resp) { + this.request({ + uri: '/jobs', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -460,11 +463,17 @@ BigQuery.prototype.query = function(options, callback) { if (job) { // Get results of the query. - var path = '/queries/' + job.id; - that.makeReq_('GET', path, requestQuery, null, responseHandler); + that.request({ + uri: '/queries/' + job.id, + qs: requestQuery + }, responseHandler); } else { // Create a job. - that.makeReq_('POST', '/queries', null, options, responseHandler); + that.request({ + method: 'POST', + uri: '/queries', + json: options + }, responseHandler); } function responseHandler(err, resp) { @@ -586,7 +595,11 @@ BigQuery.prototype.startQuery = function(options, callback) { } }; - this.makeReq_('POST', '/jobs', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/jobs', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -599,32 +612,6 @@ BigQuery.prototype.startQuery = 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. - */ -BigQuery.prototype.makeReq_ = function(method, path, query, body, callback) { - var reqOpts = { - method: method, - qs: query, - uri: BIGQUERY_BASE_URL + this.projectId + path - }; - - if (body) { - reqOpts.json = body; - } - - this.makeAuthenticatedRequest_(reqOpts, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/bigquery/job.js b/lib/bigquery/job.js index ebf2f9f9afd..6a67c47ffe7 100644 --- a/lib/bigquery/job.js +++ b/lib/bigquery/job.js @@ -21,6 +21,13 @@ 'use strict'; var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); /*! Developer Documentation * @@ -51,11 +58,18 @@ var is = require('is'); * @constructor */ function Job(bigQuery, id) { + ServiceObject.call(this, { + parent: bigQuery, + baseUrl: '/jobs', + id: id, + inherit: ['getMetadata'] + }); + this.bigQuery = bigQuery; - this.id = id; - this.metadata = {}; } +nodeutil.inherits(Job, ServiceObject); + /** * Get the metadata of the job. This will mostly be useful for checking the * status of a previously-run job. @@ -71,21 +85,8 @@ function Job(bigQuery, id) { * var job = bigquery.job('id'); * job.getMetadata(function(err, metadata, apiResponse) {}); */ -Job.prototype.getMetadata = function(callback) { - var that = this; - - var path = '/jobs/' + this.id; - - this.bigQuery.makeReq_('GET', path, null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - that.metadata = resp; - - callback(null, that.metadata, resp); - }); +Job.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index 2d4d9fc77d9..d5f3af2b881 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -27,6 +27,7 @@ var extend = require('extend'); var format = require('string-format-obj'); var fs = require('fs'); var is = require('is'); +var nodeutil = require('util'); var path = require('path'); var streamEvents = require('stream-events'); @@ -36,6 +37,12 @@ var streamEvents = require('stream-events'); */ var File = require('../storage/file'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/streamrouter} * @private @@ -72,12 +79,19 @@ var util = require('../common/util'); * var table = dataset.table('my-table'); */ function Table(dataset, id) { + ServiceObject.call(this, { + parent: dataset, + baseUrl: '/tables', + id: id, + createMethod: dataset.createTable.bind(dataset) + }); + this.bigQuery = dataset.bigQuery; this.dataset = dataset; - this.id = id; - this.metadata = {}; } +nodeutil.inherits(Table, ServiceObject); + /** * Convert a comma-separated name:type string to a table schema object. * @@ -190,7 +204,11 @@ Table.prototype.copy = function(destination, metadata, callback) { } }; - this.bigQuery.makeReq_('POST', '/jobs', null, body, function(err, resp) { + this.bigQuery.request({ + method: 'POST', + uri: '/jobs', + json: body, + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -311,7 +329,7 @@ Table.prototype.createWriteStream = function(metadata) { dup.once('writing', function() { util.makeWritableStream(dup, { - makeAuthenticatedRequest: that.bigQuery.makeAuthenticatedRequest_, + makeAuthenticatedRequest: that.bigQuery.makeAuthenticatedRequest, metadata: { configuration: { load: metadata @@ -346,8 +364,8 @@ Table.prototype.createWriteStream = function(metadata) { * @example * table.delete(function(err, apiResponse) {}); */ -Table.prototype.delete = function(callback) { - this.makeReq_('DELETE', '', null, null, callback); +Table.prototype.delete = function() { + return ServiceObject.prototype.delete.apply(this, arguments); }; /** @@ -462,7 +480,11 @@ Table.prototype.export = function(destination, options, callback) { } }; - this.bigQuery.makeReq_('POST', '/jobs', null, body, function(err, resp) { + this.bigQuery.request({ + method: 'POST', + uri: '/jobs', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -488,19 +510,8 @@ Table.prototype.export = function(destination, options, callback) { * @example * table.getMetadata(function(err, metadata, apiResponse) {}); */ -Table.prototype.getMetadata = function(callback) { - var that = this; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - that.metadata = resp; - - callback(null, that.metadata, resp); - }); +Table.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -571,7 +582,10 @@ Table.prototype.getRows = function(options, callback) { callback = callback || util.noop; - this.makeReq_('GET', '/data', options, null, function(err, resp) { + this.request({ + uri: '/data', + qs: options + }, function(err, resp) { if (err) { onComplete(err, null, null, resp); return; @@ -735,7 +749,11 @@ Table.prototype.import = function(source, metadata, callback) { }) }); - this.bigQuery.makeReq_('POST', '/jobs', null, body, function(err, resp) { + this.bigQuery.request({ + method: 'POST', + uri: '/jobs', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -819,7 +837,11 @@ Table.prototype.insert = function(rows, callback) { }) }; - this.makeReq_('POST', '/insertAll', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/insertAll', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -889,7 +911,11 @@ Table.prototype.setMetadata = function(metadata, callback) { metadata.schema = Table.createSchemaFromString_(metadata.schema); } - this.makeReq_('PUT', '', null, metadata, function(err, resp) { + this.request({ + method: 'PUT', + uri: '', + json: metadata + }, function(err, resp) { if (err) { callback(err, resp); return; @@ -901,28 +927,11 @@ Table.prototype.setMetadata = function(metadata, callback) { }); }; -/** - * Pass through this request to BigQuery's request handler, first prepending the - * path with the dataset. - * - * @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. - */ -Table.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/tables/' + this.id + path; - this.dataset.makeReq_(method, path, query, body, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object * stream. `streamRouter` is used to add this dual behavior. */ -streamRouter.extend(Table, 'getRows'); +streamRouter.extend(Table, ['getRows']); module.exports = Table; diff --git a/lib/common/service-object.js b/lib/common/service-object.js new file mode 100644 index 00000000000..5a0159063a8 --- /dev/null +++ b/lib/common/service-object.js @@ -0,0 +1,252 @@ +/*! + * Copyright 2015 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 common/serviceObject + */ + +'use strict'; + +var is = require('is'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('./util.js'); + +/** + * ServiceObject is a base class, meant to be inherited from by a "service + * object," like a BigQuery dataset or Storage bucket. + * + * Most of the time, these objects share common functionality; they can be + * created or deleted, and you can get or set their metadata. + * + * By inheriting from this class, a service object will be extended with these + * shared behaviors. Note that any method can be overridden when the service + * object requires specific behavior. + * + * @private + * + * @param {object} config - Configuration object. + * @param {string} config.baseUrl - The base URL to make API requests to. + * @param {string} config.createMethod - The method which creates this object. + * @param {string} config.id - The identifier of the object. For example, the + * name of a Storage bucket or Pub/Sub topic. + * @param {string[]=} config.inherit - If all of the methods don't apply to the + * service object, this is a whitelist of methods to implement. + * @param {object} config.parent - The parent service instance. For example, an + * instance of Storage if the object is Bucket. + */ +function ServiceObject(config) { + var self = this; + + this.metadata = {}; + + this.baseUrl = config.baseUrl; + this.parent = config.parent; // Parent class. + this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc.) + this.createMethod = config.createMethod; + + if (config.inherit) { + var allMethods = Object.keys(ServiceObject.prototype); + allMethods.splice(allMethods.indexOf('request'), 1); + + allMethods.forEach(function(methodName) { + if (config.inherit.indexOf(methodName) === -1) { + self[methodName] = undefined; + } + }); + } +} + +/** + * Create the object. + * + * @param {object=} options - Configuration object. + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.instance - The instance. + * @param {object} callback.apiResponse - The full API response. + */ +ServiceObject.prototype.create = function(options, callback) { + var self = this; + var args = [this.id]; + + if (is.fn(options)) { + callback = options; + } + + if (is.object(options)) { + args.push(options); + } + + // Wrap the callback to return *this* instance of the object, not the newly- + // created one. + function onCreate(err, instance, apiResponse) { + if (err) { + callback(err, null, apiResponse); + return; + } + + self.metadata = instance.metadata; + + callback(null, self, apiResponse); + } + + args.push(onCreate); + + this.createMethod.apply(null, args); +}; + +/** + * Delete the object. + * + * @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. + */ +ServiceObject.prototype.delete = function(callback) { + this.request({ + method: 'DELETE', + uri: '' + }, function(err, resp) { + callback(err, resp); + }); +}; + +/** + * Get the object if it exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.instance - The instance. + * @param {object} callback.apiResponse - The full API response. + */ +ServiceObject.prototype.get = function(callback) { + var self = this; + + this.getMetadata(function(err, metadata) { + if (err) { + callback(err, null, metadata); + return; + } + + self.metadata = metadata; + callback(null, self, metadata); + }); +}; + +/** + * Get the metadata of this object. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.metadata - The metadata for this object. + * @param {object} callback.apiResponse - The full API response. + */ +ServiceObject.prototype.getMetadata = function(callback) { + var self = this; + + this.request({ + uri: '' + }, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + self.metadata = resp; + + callback(null, self.metadata, resp); + }); +}; + +/** + * Get or create this object. If it doesn't exist, it will be created. + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.instance - The instance. + * @param {object} callback.apiResponse - The full API response. + */ +ServiceObject.prototype.getOrCreate = function(callback) { + var self = this; + + this.get(function(err, instance, apiResponse) { + if (err) { + self.create(callback); + return; + } + + callback(null, instance, apiResponse); + }); +}; + +/** + * Set the metadata for this object. + * + * @param {object} metadata - The metadata to set on this object. + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.instance - The instance. + * @param {object} callback.apiResponse - The full API response. + */ +ServiceObject.prototype.setMetadata = function(metadata, callback) { + var self = this; + + callback = callback || util.noop; + + this.request({ + method: 'PATCH', + uri: '', + json: metadata + }, function(err, resp) { + if (err) { + callback(err, resp); + return; + } + + self.metadata = resp; + + callback(null, resp); + }); +}; + +/** + * Make an authenticated API request. + * + * @private + * + * @param {object} reqOpts - Request options that are passed to `request`. + * @param {string} reqOpts.uri - A URI relative to the baseUrl. + * @param {function} callback - The callback function passed to `request`. + */ +ServiceObject.prototype.request = function(reqOpts, callback) { + var uriComponents = [ + this.baseUrl, + this.id, + reqOpts.uri + ]; + + reqOpts.uri = uriComponents.map(function(uriComponent) { + return uriComponent.replace(/^\/*|\/*$/g, ''); + }).join('/'); + + this.parent.request(reqOpts, callback); +}; + +module.exports = ServiceObject; diff --git a/lib/common/service.js b/lib/common/service.js new file mode 100644 index 00000000000..741f5e6ea96 --- /dev/null +++ b/lib/common/service.js @@ -0,0 +1,75 @@ +/*! + * Copyright 2015 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 common/service + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('./util.js'); + +/** + * Service is a base class, meant to be inherited from by a "service," like + * BigQuery or Storage. + * + * This handles making authenticated requests by exposing a `makeReq_` function. + * + * @param {object} config - Configuration object. + * @param {string} config.baseUrl - The base URL to make API requests to. + * @param {string[]} config.scopes - The scopes required for the request. + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + */ +function Service(config, options) { + this.baseUrl = config.baseUrl; + this.projectId = options.projectId; + + this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({ + credentials: options.credentials, + keyFile: options.keyFilename, + scopes: config.scopes, + email: options.email + }); +} + +/** + * Make an authenticated API request. + * + * @private + * + * @param {object} reqOpts - Request options that are passed to `request`. + * @param {string} reqOpts.uri - A URI relative to the baseUrl. + * @param {function} callback - The callback function passed to `request`. + */ +Service.prototype.request = function(reqOpts, callback) { + var uriComponents = [ + this.baseUrl, + this.projectId, + reqOpts.uri + ]; + + reqOpts.uri = uriComponents.map(function(uriComponent) { + return uriComponent.replace(/^\/*|\/*$/g, ''); + }).join('/'); + + this.makeAuthenticatedRequest(reqOpts, callback); +}; + +module.exports = Service; diff --git a/system-test/bigquery.js b/system-test/bigquery.js index bb0594cf268..a7b5238d965 100644 --- a/system-test/bigquery.js +++ b/system-test/bigquery.js @@ -41,37 +41,22 @@ describe('BigQuery', function() { before(function(done) { async.series([ - function(next) { - // Delete the test dataset, if it exists. - bigquery.dataset(DATASET_ID).delete({ force: true }, function() { - next(); - }); - }, - // Create the test dataset. function(next) { - bigquery.createDataset(DATASET_ID, function(err, ds) { + bigquery.dataset(DATASET_ID).create(function(err, dataset_) { if (err) { next(err); return; } - dataset = ds; - next(); - }); - }, - - // Delete the test table, if it exists. - function(next) { - dataset.table(TABLE_ID).delete(function() { + dataset = dataset_; next(); }); }, // Create the test table. function(next) { - dataset.createTable({ - id: TABLE_ID, + dataset.table(TABLE_ID).create({ schema: 'id:integer,breed,name,dob:timestamp' }, function(err, t) { if (err) { @@ -86,13 +71,13 @@ describe('BigQuery', function() { // Create a Bucket. function(next) { - storage.createBucket(BUCKET_NAME, function(err, b) { + storage.createBucket(BUCKET_NAME, function(err, bucket_) { if (err) { next(err); return; } - bucket = b; + bucket = bucket_; next(); }); } From edb8f7e920253bb5048bd75975f322ac899b28b9 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 1 Oct 2015 15:10:34 -0400 Subject: [PATCH 02/49] overhaul Compute Engine --- lib/bigquery/dataset.js | 3 +- lib/bigquery/job.js | 2 +- lib/common/service-object.js | 39 ++-- lib/common/service.js | 2 + lib/compute/address.js | 128 +++++++++---- lib/compute/disk.js | 163 +++++++++++++---- lib/compute/firewall.js | 174 ++++++++++++++---- lib/compute/index.js | 129 +++++++------ lib/compute/network.js | 150 +++++++++++---- lib/compute/operation.js | 83 +++++---- lib/compute/region.js | 91 ++++----- lib/compute/snapshot.js | 156 ++++++++++++---- lib/compute/vm.js | 344 ++++++++++++++++++++++++++++++----- lib/compute/zone.js | 102 ++++++----- system-test/compute.js | 107 ++++++++--- 15 files changed, 1221 insertions(+), 452 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 7a652a89c71..3faff3bdc2a 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -63,7 +63,6 @@ function Dataset(bigQuery, id) { }); this.bigQuery = bigQuery; - this.id = id; } nodeutil.inherits(Dataset, ServiceObject); @@ -97,7 +96,7 @@ nodeutil.inherits(Dataset, ServiceObject); * var bigquery = gcloud.bigquery({ * projectId: 'grape-spaceship-123' * }); - * var dataset = bigquery.dataset(); + * var dataset = bigquery.dataset('institutions'); * * dataset.createTable(tableConfig, function(err, table, apiResponse) {}); */ diff --git a/lib/bigquery/job.js b/lib/bigquery/job.js index 6a67c47ffe7..962255f38e2 100644 --- a/lib/bigquery/job.js +++ b/lib/bigquery/job.js @@ -62,7 +62,7 @@ function Job(bigQuery, id) { parent: bigQuery, baseUrl: '/jobs', id: id, - inherit: ['getMetadata'] + inherit: [] }); this.bigQuery = bigQuery; diff --git a/lib/common/service-object.js b/lib/common/service-object.js index 5a0159063a8..56114b308be 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -20,6 +20,7 @@ 'use strict'; +var exec = require('methmeth'); var is = require('is'); /** @@ -61,12 +62,10 @@ function ServiceObject(config) { this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc.) this.createMethod = config.createMethod; - if (config.inherit) { + if (config.exclude) { var allMethods = Object.keys(ServiceObject.prototype); - allMethods.splice(allMethods.indexOf('request'), 1); - allMethods.forEach(function(methodName) { - if (config.inherit.indexOf(methodName) === -1) { + if (config.exclude.indexOf(methodName) > -1) { self[methodName] = undefined; } }); @@ -120,10 +119,12 @@ ServiceObject.prototype.create = function(options, callback) { * @param {object} callback.apiResponse - The full API response. */ ServiceObject.prototype.delete = function(callback) { - this.request({ + var reqOpts = { method: 'DELETE', uri: '' - }, function(err, resp) { + }; + + ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { callback(err, resp); }); }; @@ -161,9 +162,11 @@ ServiceObject.prototype.get = function(callback) { ServiceObject.prototype.getMetadata = function(callback) { var self = this; - this.request({ + var reqOpts = { uri: '' - }, function(err, resp) { + }; + + ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -178,17 +181,18 @@ ServiceObject.prototype.getMetadata = function(callback) { /** * Get or create this object. If it doesn't exist, it will be created. * + * @param {object=} options - Configuration object. * @param {function=} callback - The callback function. * @param {?error} callback.err - An error returned while making this request. * @param {object} callback.instance - The instance. * @param {object} callback.apiResponse - The full API response. */ -ServiceObject.prototype.getOrCreate = function(callback) { +ServiceObject.prototype.getOrCreate = function(options, callback) { var self = this; this.get(function(err, instance, apiResponse) { if (err) { - self.create(callback); + self.create(options, callback); return; } @@ -210,11 +214,13 @@ ServiceObject.prototype.setMetadata = function(metadata, callback) { callback = callback || util.noop; - this.request({ + var reqOpts = { method: 'PATCH', uri: '', json: metadata - }, function(err, resp) { + }; + + ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, resp); return; @@ -242,9 +248,12 @@ ServiceObject.prototype.request = function(reqOpts, callback) { reqOpts.uri ]; - reqOpts.uri = uriComponents.map(function(uriComponent) { - return uriComponent.replace(/^\/*|\/*$/g, ''); - }).join('/'); + reqOpts.uri = uriComponents + .filter(exec('trim')) // Limit to non-empty strings. + .map(function(uriComponent) { + return uriComponent.replace(/^\/*|\/*$/g, ''); + }) + .join('/'); this.parent.request(reqOpts, callback); }; diff --git a/lib/common/service.js b/lib/common/service.js index 741f5e6ea96..b7c9022e9e6 100644 --- a/lib/common/service.js +++ b/lib/common/service.js @@ -47,6 +47,8 @@ function Service(config, options) { scopes: config.scopes, email: options.email }); + + this.authClient = this.makeAuthenticatedRequest.authClient; } /** diff --git a/lib/compute/address.js b/lib/compute/address.js index 45e3b3e8771..c7e41d62bf1 100644 --- a/lib/compute/address.js +++ b/lib/compute/address.js @@ -20,6 +20,14 @@ 'use strict'; +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private @@ -54,11 +62,88 @@ var util = require('../common/util.js'); * var address = region.address('address1'); */ function Address(region, name) { + ServiceObject.call(this, { + parent: region, + baseUrl: '/addresses', + id: name, + createMethod: region.createAddress.bind(region), + exclude: ['setMetadata'] + }); + this.region = region; - this.name = name; - this.metadata = {}; } +nodeutil.inherits(Address, ServiceObject); + +/** + * Create an address. + * + * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} + * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} + * @resource [Addresses: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/insert} + * + * @param {object=} options - See an + * [Address resource](https://cloud.google.com/compute/docs/reference/v1/addresses). + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/address} callback.address - The created Address + * object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * address.create(function(err, address, operation, apiResponse) { + * // `address` is an Address object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +Address.prototype.create = function() { + ServiceObject.prototype.create.apply(this, arguments); +}; + +/** + * Get an address if it exists. Also see {module:compute/address#getOrCreate}. + * + * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/address} callback.address - The Address object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * address.get(function(err, address, apiResponse) { + * // `address` is an Address object. + * }); + */ +Address.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + +/** + * Get an address if it exists, otherwise create one. + * + * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} + * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} + * @resource [Addresses: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/insert} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/address} callback.address - The Address object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * address.getOrCreate(function(err, address, apiResponse) { + * // `address` is an Address object. + * }); + */ +Address.prototype.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); +}; + /** * Delete the address. * @@ -81,7 +166,10 @@ Address.prototype.delete = function(callback) { var region = this.region; - this.makeReq_('DELETE', '', null, null, function(err, resp) { + this.request({ + method: 'DELETE', + uri: '' + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -108,38 +196,8 @@ Address.prototype.delete = function(callback) { * @example * address.getMetadata(function(err, metadata, apiResponse) {}); */ -Address.prototype.getMetadata = function(callback) { - callback = callback || util.noop; - - var self = this; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, 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. - */ -Address.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/addresses/' + this.name + path; - this.region.makeReq_(method, path, query, body, callback); +Address.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; module.exports = Address; diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 53fc2a1cbd0..3aae20d10a2 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -23,6 +23,19 @@ var extend = require('extend'); var format = require('string-format-obj'); var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + +/** + * @type {module:compute/snapshot} + * @private + */ +var Snapshot = require('./snapshot.js'); /** * @type {module:common/util} @@ -57,13 +70,22 @@ var util = require('../common/util.js'); * var disk = zone.disk('disk1'); */ function Disk(zone, name) { - this.zone = zone; + ServiceObject.call(this, { + parent: zone, + baseUrl: '/disks', + id: name, + createMethod: zone.createDisk.bind(zone), + exclude: ['setMetadata'] + }); + this.name = name; - this.metadata = {}; + this.zone = zone; this.formattedName = Disk.formatName_(zone, name); } +nodeutil.inherits(Disk, ServiceObject); + /** * Format a disk's name how the API expects. * @@ -81,6 +103,95 @@ Disk.formatName_ = function(zone, name) { }); }; +/** + * Create a persistent disk. + * + * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} + * @resource [Disks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/insert} + * + * @param {object} config - See a + * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). + * @param {string=} config.os - Specify the name of an OS, and we will use the + * latest version as the source image of a new boot disk. See + * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/disk} callback.disk - The created Disk object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * os: 'ubuntu', + * sizeGb: 10 + * }; + * + * disk.create(config, function(err, disk, operation, apiResponse) { + * // `disk` is a Disk object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +Disk.prototype.create = function() { + ServiceObject.prototype.create.apply(this, arguments); +}; + +/** + * Get a disk if it exists. Also see {module:compute/disk#getOrCreate}. + * + * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/disk} callback.disk - The Disk object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * disk.get(function(err, disk, apiResponse) { + * // `disk` is a Disk object. + * }); + */ +Disk.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + +/** + * Get a disk if it exists, otherwise create one. + * + * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} + * @resource [Disks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/insert} + * + * @param {object} config - See a + * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). + * @param {string=} config.os - Specify the name of an OS, and we will use the + * latest version as the source image of a new boot disk. See + * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/disk} callback.disk - The Disk object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * os: 'ubuntu', + * sizeGb: 10 + * }; + * + * disk.getOrCreate(config, function(err, disk, operation, apiResponse) { + * // `disk` is a Disk object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +Disk.prototype.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); +}; + /** * Create a snapshot of a disk. * @@ -110,6 +221,7 @@ Disk.formatName_ = function(zone, name) { * disk.createSnapshot('new-snapshot-name', callback); */ Disk.prototype.createSnapshot = function(name, options, callback) { + var self = this; var zone = this.zone; if (is.fn(options)) { @@ -121,13 +233,17 @@ Disk.prototype.createSnapshot = function(name, options, callback) { name: name }); - this.makeReq_('POST', '/createSnapshot', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/createSnapshot', + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; } - var snapshot = zone.compute.snapshot(name); + var snapshot = self.snapshot(name); var operation = zone.operation(resp.name); operation.metadata = resp; @@ -158,7 +274,7 @@ Disk.prototype.delete = function(callback) { callback = callback || util.noop; - this.makeReq_('DELETE', '', null, null, function(err, resp) { + ServiceObject.prototype.delete.call(this, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -185,38 +301,23 @@ Disk.prototype.delete = function(callback) { * @example * disk.getMetadata(function(err, metadata, apiResponse) {}); */ -Disk.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); +Disk.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** - * Make a new request object from the provided arguments and wrap the callback - * to intercept non-successful responses. + * Get a reference to a snapshot from this disk. * - * @private + * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} * - * @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. + * @param {string} name - Name of the existing snapshot. + * @return {module:compute/snapshot} + * + * @example + * var snapshot = disk.snapshot('snapshot-name'); */ -Disk.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/disks/' + this.name + path; - this.zone.makeReq_(method, path, query, body, callback); +Disk.prototype.snapshot = function(name) { + return new Snapshot(this, name); }; module.exports = Disk; diff --git a/lib/compute/firewall.js b/lib/compute/firewall.js index f36fb33f6b1..e0ddd9b66f8 100644 --- a/lib/compute/firewall.js +++ b/lib/compute/firewall.js @@ -20,6 +20,14 @@ 'use strict'; +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private @@ -52,14 +60,130 @@ var util = require('../common/util.js'); * var firewall = gce.firewall('tcp-3000'); */ function Firewall(compute, name) { + ServiceObject.call(this, { + parent: compute, + baseUrl: '/global/firewalls', + id: name, + createMethod: compute.createFirewall.bind(compute) + }); + this.compute = compute; this.name = name; - - this.metadata = { - network: 'global/networks/default' - }; + this.metadata.network = 'global/networks/default'; } +nodeutil.inherits(Firewall, ServiceObject); + +/** + * Create a firewall. + * + * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} + * @resource [Firewalls: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/insert} + * + * @param {object} config - See a + * [Firewall resource](https://cloud.google.com/compute/docs/reference/v1/firewalls#resource). + * @param {object} config.protocols - A map of protocol to port range. The keys + * of the object refer to a protocol (e.g. `tcp`, `udp`) and the value for + * the key are the ports/port-ranges that are allowed to make a connection. + * @param {string[]} config.ranges - The IP address blocks that this rule + * applies to, expressed in + * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) + * format. + * @param {string[]} config.tags - Instance tags which this rule applies to. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/firewall} callback.firewall - The created Firewall + * object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * protocols: { + * tcp: [3000], + * udp: [] // An empty array means all ports are allowed. + * }, + * + * ranges: ['0.0.0.0/0'] + * }; + * + * firewall.create(config, function(err, firewall, operation, apiResponse) { + * // `firewall` is a Firewall object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +Firewall.prototype.create = function() { + ServiceObject.prototype.create.apply(this, arguments); +}; + +/** + * Get a firewall if it exists. Also see {module:compute/firewall#getOrCreate}. + * + * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/firewall} callback.firewall - The Firewall object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * firewall.get(function(err, firewall, apiResponse) { + * // `firewall` is a Firewall object. + * }); + */ +Firewall.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + +/** + * Get a firewall if it exists, otherwise create one. + * + * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} + * @resource [Firewalls: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/insert} + * + * @param {object} config - See a + * [Firewall resource](https://cloud.google.com/compute/docs/reference/v1/firewalls#resource). + * @param {object} config.protocols - A map of protocol to port range. The keys + * of the object refer to a protocol (e.g. `tcp`, `udp`) and the value for + * the key are the ports/port-ranges that are allowed to make a connection. + * @param {string[]} config.ranges - The IP address blocks that this rule + * applies to, expressed in + * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) + * format. + * @param {string[]} config.tags - Instance tags which this rule applies to. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/firewall} callback.firewall - The Firewall object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * protocols: { + * tcp: [3000], + * udp: [] // An empty array means all ports are allowed. + * }, + * + * ranges: ['0.0.0.0/0'] + * }; + * + * function callback(err, firewall, operation, apiResponse) { + * // `firewall` is a Firewall object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * } + * + * firewall.getOrCreate(config, callback); + */ +Firewall.prototype.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); +}; + /** * Delete the firewall. * @@ -82,7 +206,7 @@ Firewall.prototype.delete = function(callback) { callback = callback || util.noop; - this.makeReq_('DELETE', '', null, null, function(err, resp) { + ServiceObject.prototype.delete.call(this, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -109,21 +233,8 @@ Firewall.prototype.delete = function(callback) { * @example * firewall.getMetadata(function(err, metadata, apiResponse) {}); */ -Firewall.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); +Firewall.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -158,7 +269,11 @@ Firewall.prototype.setMetadata = function(metadata, callback) { metadata.name = this.name; metadata.network = this.metadata.network; - this.makeReq_('PATCH', '', null, metadata, function(err, resp) { + this.request({ + method: 'PATCH', + uri: '', + json: metadata + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -171,21 +286,4 @@ Firewall.prototype.setMetadata = function(metadata, 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. - */ -Firewall.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/global/firewalls/' + this.name + path; - this.compute.makeReq_(method, path, query, body, callback); -}; - module.exports = Firewall; diff --git a/lib/compute/index.js b/lib/compute/index.js index 3c72196e055..34287a9aac2 100644 --- a/lib/compute/index.js +++ b/lib/compute/index.js @@ -23,6 +23,7 @@ var arrify = require('arrify'); var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:compute/firewall} @@ -48,6 +49,12 @@ var Operation = require('./operation.js'); */ var Region = require('./region.js'); +/** + * @type {module:common/service} + * @private + */ +var Service = require('../common/service.js'); + /** * @type {module:compute/snapshot} * @private @@ -72,19 +79,6 @@ var util = require('../common/util.js'); */ var Zone = require('./zone.js'); -/** - * @const {string} - * @private - */ -var COMPUTE_BASE_URL = 'https://www.googleapis.com/compute/v1/projects/'; - -/** - * Required scopes for Google Compute Engine API. - * @const {array} - * @private - */ -var SCOPES = ['https://www.googleapis.com/auth/compute']; - /** * A Compute object allows you to interact with the Google Compute Engine API. * Using this object, you can access your instances with {module:compute/vm}, @@ -110,16 +104,16 @@ function Compute(options) { return new Compute(options); } - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - keyFile: options.keyFilename, - scopes: SCOPES, - email: options.email - }); + var config = { + baseUrl: 'https://www.googleapis.com/compute/v1/projects', + scopes: ['https://www.googleapis.com/auth/compute'] + }; - this.projectId = options.projectId; + Service.call(this, config, options); } +nodeutil.inherits(Compute, Service); + /** * Create a firewall. * @@ -211,9 +205,11 @@ Compute.prototype.createFirewall = function(name, config, callback) { delete body.tags; } - var path = '/global/firewalls'; - - this.makeReq_('POST', path, null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/global/firewalls', + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -282,7 +278,11 @@ Compute.prototype.createNetwork = function(name, config, callback) { delete body.gateway; } - this.makeReq_('POST', '/global/networks', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/global/networks', + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -394,9 +394,10 @@ Compute.prototype.getAddresses = function(options, callback) { options = options || {}; - var path = '/aggregated/addresses'; - - this.makeReq_('GET', path, options, null, function(err, resp) { + this.request({ + uri: '/aggregated/addresses', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -506,7 +507,10 @@ Compute.prototype.getDisks = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/aggregated/disks', options, null, function(err, resp) { + this.request({ + uri: '/aggregated/disks', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -617,7 +621,10 @@ Compute.prototype.getFirewalls = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/global/firewalls', options, null, function(err, resp) { + this.request({ + uri: '/global/firewalls', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -710,6 +717,8 @@ Compute.prototype.getFirewalls = function(options, callback) { * }); */ Compute.prototype.getNetworks = function(options, callback) { + var self = this; + if (is.fn(options)) { callback = options; options = {}; @@ -717,8 +726,10 @@ Compute.prototype.getNetworks = function(options, callback) { options = options || {}; - var self = this; - this.makeReq_('GET', '/global/networks', options, null, function(err, resp) { + this.request({ + uri: '/global/networks', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -820,9 +831,10 @@ Compute.prototype.getOperations = function(options, callback) { options = options || {}; - var path = '/global/operations'; - - this.makeReq_('GET', path, options, null, function(err, resp) { + this.request({ + uri: '/global/operations', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -922,7 +934,10 @@ Compute.prototype.getRegions = function(options, callback) { options = {}; } - this.makeReq_('GET', '/regions', options, null, function(err, resp) { + this.request({ + uri: '/regions', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -1024,8 +1039,10 @@ Compute.prototype.getSnapshots = function(options, callback) { options = options || {}; - - this.makeReq_('GET', '/global/snapshots', options, null, function(err, resp) { + this.request({ + uri: '/global/snapshots', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -1126,9 +1143,10 @@ Compute.prototype.getVMs = function(options, callback) { options = options || {}; - var path = '/aggregated/instances'; - - this.makeReq_('GET', path, options, null, function(err, resp) { + this.request({ + uri: '/aggregated/instances', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -1237,7 +1255,10 @@ Compute.prototype.getZones = function(options, callback) { options = {}; } - this.makeReq_('GET', '/zones', options, null, function(err, resp) { + this.request({ + uri: '/zones', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -1336,32 +1357,6 @@ Compute.prototype.zone = function(name) { return new Zone(this, name); }; -/** - * 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. - */ -Compute.prototype.makeReq_ = function(method, path, query, body, callback) { - var reqOpts = { - method: method, - qs: query, - uri: COMPUTE_BASE_URL + this.projectId + path - }; - - if (body) { - reqOpts.json = body; - } - - this.makeAuthenticatedRequest_(reqOpts, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/compute/network.js b/lib/compute/network.js index 9158d79a6ba..6d704af7af3 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -23,6 +23,13 @@ var extend = require('extend'); var format = require('string-format-obj'); var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); /** * @type {module:common/util} @@ -55,13 +62,20 @@ var util = require('../common/util.js'); * var network = gce.network('network-name'); */ function Network(compute, name) { - this.compute = compute; - this.name = name; - this.metadata = {}; + ServiceObject.call(this, { + parent: compute, + baseUrl: '/global/networks', + id: name, + createMethod: compute.createNetwork.bind(compute), + exclude: ['setMetadata'] + }); + this.compute = compute; this.formattedName = Network.formatName_(compute, name); } +nodeutil.inherits(Network, ServiceObject); + /** * Format a network's name how the API expects. * @@ -78,6 +92,100 @@ Network.formatName_ = function(compute, name) { }); }; +/** + * Create a network. + * + * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} + * @resource [Networks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/insert} + * + * @param {object} config - See a + * [Network resource](https://cloud.google.com/compute/docs/reference/v1/networks#resource). + * @param {string} config.gateway - A gateway address for default routing to + * other networks. (Alias for `config.gatewayIPv4`) + * @param {string} config.range - + * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range + * of addresses that are legal on this network. (Alias for + * `config.IPv4Range`) + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/network} callback.network - The created Network + * object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * range: '10.240.0.0/16' + * }; + * + * network.create(config, function(err, network, operation, apiResponse) { + * // `network` is a Network object. + * + * // `operation` is an Operation object and can be used to check the status + * // of network creation. + * }); + */ +Network.prototype.create = function() { + ServiceObject.prototype.create.apply(this, arguments); +}; + +/** + * Get a network if it exists. Also see {module:compute/network#getOrCreate}. + * + * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/network} callback.network - The Network object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * network.get(function(err, network, apiResponse) { + * // `network` is a Network object. + * }); + */ +Network.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + +/** + * Get a network if it exists, otherwise create one. + * + * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} + * @resource [Networks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/insert} + * + * @param {object} config - See a + * [Network resource](https://cloud.google.com/compute/docs/reference/v1/networks#resource). + * @param {string} config.gateway - A gateway address for default routing to + * other networks. (Alias for `config.gatewayIPv4`) + * @param {string} config.range - + * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range + * of addresses that are legal on this network. (Alias for + * `config.IPv4Range`) + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/network} callback.network - The Network object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * range: '10.240.0.0/16' + * }; + * + * network.getOrCreate(config, function(err, network, operation, apiResponse) { + * // `network` is a Network object. + * + * // `operation` is an Operation object and can be used to check the status + * // of network creation. + * }); + */ +Network.prototype.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); +}; + /** * Create a firewall for this network. * @@ -151,7 +259,7 @@ Network.prototype.delete = function(callback) { callback = callback || util.noop; - this.makeReq_('DELETE', '', null, null, function(err, resp) { + ServiceObject.prototype.delete.call(this, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -272,38 +380,8 @@ Network.prototype.getFirewalls = function(options, callback) { * @example * network.getMetadata(function(err, metadata, apiResponse) {}); */ -Network.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, 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. - */ -Network.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/global/networks/' + this.name + path; - this.compute.makeReq_(method, path, query, body, callback); +Network.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; module.exports = Network; diff --git a/lib/compute/operation.js b/lib/compute/operation.js index a26c2cf412d..f07ec23d4fa 100644 --- a/lib/compute/operation.js +++ b/lib/compute/operation.js @@ -22,6 +22,13 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); /** * @type {module:common/util} @@ -74,11 +81,39 @@ var util = require('../common/util.js'); * var operation = zone.operation('operation-id'); */ function Operation(scope, name) { - this.scope = scope; - this.name = name; - this.metadata = {}; + var isCompute = scope.constructor.name === 'Compute'; + + ServiceObject.call(this, { + parent: scope, + baseUrl: isCompute ? '/global/operations' : '/operations', + id: name, + exclude: ['create', 'getOrCreate', 'setMetadata'] + }); } +nodeutil.inherits(Operation, ServiceObject); + +/** + * Get an operation if it exists. + * + * @resource [GlobalOperation Overview]{@link https://cloud.google.com/compute/docs/reference/v1/globalOperations} + * @resource [RegionOperation Overview]{@link https://cloud.google.com/compute/docs/reference/v1/regionOperations} + * @resource [ZoneOperations Overview]{@link https://cloud.google.com/compute/docs/reference/v1/zoneOperations} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/operation} callback.operation - The Operation object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * operation.get(function(err, operation, apiResponse) { + * // `operation` is an Operation object. + * }); + */ +Operation.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + /** * Delete the operation. * @@ -93,12 +128,8 @@ function Operation(scope, name) { * @example * operation.delete(function(err, apiResponse) {}); */ -Operation.prototype.delete = function(callback) { - callback = callback || util.noop; - - this.makeReq_('DELETE', '', null, null, function(err, resp) { - callback(err, resp); - }); +Operation.prototype.delete = function() { + ServiceObject.prototype.delete.apply(this, arguments); }; /** @@ -125,22 +156,22 @@ Operation.prototype.getMetadata = function(callback) { callback = callback || util.noop; - this.makeReq_('GET', '', null, null, function(err, resp) { + ServiceObject.prototype.getMetadata.call(this, function(err, apiResponse) { // An Operation entity contains a property named `error`. This makes - // `makeReq_` think the operation failed, and will return an ApiError to + // `request` think the operation failed, and will return an ApiError to // this callback. We have to make sure this isn't a false error by seeing if // the response body contains a property that wouldn't exist on a failed API // request (`name`). - var isActualError = err && (!resp || resp.name !== self.name); + var isActualError = err && (!apiResponse || apiResponse.name !== self.name); if (isActualError) { - callback(err, null, resp); + callback(err, null, apiResponse); return; } - self.metadata = resp; + self.metadata = apiResponse; - callback(null, self.metadata, resp); + callback(null, self.metadata, apiResponse); }); }; @@ -221,26 +252,4 @@ Operation.prototype.onComplete = function(options, callback) { checkMetadata(); }; -/** - * 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. - */ -Operation.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/operations/' + this.name + path; - - if (this.scope.constructor.name === 'Compute') { - path = '/global' + path; - } - - this.scope.makeReq_(method, path, query, body, callback); -}; - module.exports = Operation; diff --git a/lib/compute/region.js b/lib/compute/region.js index 50178338be3..d3aee955bd1 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -22,6 +22,7 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:compute/address} @@ -36,16 +37,16 @@ var Address = require('./address.js'); var Operation = require('./operation.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'); /*! Developer Documentation * @@ -72,11 +73,35 @@ var util = require('../common/util.js'); * var region = gce.region('us-central1'); */ function Region(compute, name) { - this.compute = compute; - this.name = name; - this.metadata = {}; + ServiceObject.call(this, { + parent: compute, + baseUrl: '/regions', + id: name, + exclude: ['create', 'delete', 'getOrCreate', 'setMetadata'] + }); } +nodeutil.inherits(Region, ServiceObject); + +/** + * Get a region. + * + * @resource [Regions & Zones Overview]{@link https://cloud.google.com/compute/docs/zones} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/region} callback.region - The Region object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * region.get(function(err, region, apiResponse) { + * // `region` is a Region object. + * }); + */ +Region.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + /** * Get a reference to a Google Compute Engine address in this region. * @@ -132,7 +157,11 @@ Region.prototype.createAddress = function(name, options, callback) { name: name }); - this.makeReq_('POST', '/addresses', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/addresses', + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -175,7 +204,7 @@ Region.prototype.createAddress = function(name, options, callback) { * @param {object} callback.apiResponse - The full API response. * * @example - * region.getAddresses(function (err, addresses) { + * region.getAddresses(function(err, addresses) { * // `addresses` is an array of `Address` objects. * }); * @@ -225,7 +254,10 @@ Region.prototype.getAddresses = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/addresses', options, null, function(err, resp) { + this.request({ + uri: '/addresses', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -263,21 +295,8 @@ Region.prototype.getAddresses = function(options, callback) { * @example * region.getMetadata(function(err, metadata, apiResponse) {}); */ -Region.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); +Region.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -358,7 +377,10 @@ Region.prototype.getOperations = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/operations', options, null, function(err, resp) { + this.request({ + uri: '/operations', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -397,23 +419,6 @@ Region.prototype.operation = function(name) { return new Operation(this, name); }; -/** - * 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. - */ -Region.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/regions/' + this.name + path; - this.compute.makeReq_(method, path, query, body, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index 03119618960..599181d203c 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -20,6 +20,14 @@ 'use strict'; +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private @@ -28,7 +36,8 @@ var util = require('../common/util.js'); /*! Developer Documentation * - * @param {module:compute} compute - Compute object this snapshot belongs to. + * @param {module:compute|module:compute/disk} scope - The parent scope this + * snapshot belongs to. If it's a Disk, we expose the `create` methods. * @param {string} name - Snapshot name. */ /** @@ -50,13 +59,114 @@ var util = require('../common/util.js'); * var gce = gcloud.compute(); * * var snapshot = gce.snapshot('snapshot-name'); + * + * //- + * // Or, access through a disk. + * //- + * var disk = gce.zone('us-central1-a').disk('disk-name'); + * var snapshot = disk.snapshot('disk-snapshot-name'); */ -function Snapshot(compute, name) { - this.compute = compute; - this.name = name; - this.metadata = {}; +function Snapshot(scope, name) { + var isCompute = scope.constructor.name === 'Compute'; + + var config = { + parent: scope, + baseUrl: '/global/snapshots', + id: name, + exclude: ['setMetadata'] + }; + + if (scope.createSnapshot) { + config.createMethod = scope.createSnapshot.bind(scope); + + /** + * Create a snapshot. + * + * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} + * @resource [Disks: createSnapshot API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot} + * + * @param {object=} options - See the + * [Disks: createSnapshot](https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot) + * request body. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {module:compute/snapshot} callback.snapshot - The created Snapshot + * object. + * @param {module:compute/operation} callback.operation - An operation + * object that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * snapshot.create(function(err, snapshot, operation, apiResponse) { + * // `snapshot` is a Snapshot object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + this.create = function() { + ServiceObject.prototype.create.apply(this, arguments); + }; + + /** + * Get a snapshot if it exists, otherwise create one. + * + * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} + * @resource [Disks: createSnapshot API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot} + * + * @param {object=} options - See the + * [Disks: createSnapshot](https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot) + * request body. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {module:compute/snapshot} callback.snapshot - The Snapshot object. + * @param {module:compute/operation} callback.operation - An operation + * object that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * snapshot.getOrCreate(function(err, snapshot, operation, apiResponse) { + * // `snapshot` is a Snapshot object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + this.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); + }; + } else { + config.exclude = config.exclude.concat(['create', 'getOrCreate']); + } + + ServiceObject.call(this, config); + + this.compute = isCompute ? scope : scope.compute; } +nodeutil.inherits(Snapshot, ServiceObject); + +/** + * Get a snapshot if it exists. + * + * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/snapshot} callback.snapshot - The Snapshot object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * snapshot.get(function(err, snapshot, apiResponse) { + * // `snapshot` is a Snapshot object. + * }); + */ +Snapshot.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + /** * Delete the snapshot. * @@ -79,7 +189,7 @@ Snapshot.prototype.delete = function(callback) { var compute = this.compute; - this.makeReq_('DELETE', '', null, null, function(err, resp) { + ServiceObject.prototype.delete.call(this, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -106,38 +216,8 @@ Snapshot.prototype.delete = function(callback) { * @example * snapshot.getMetadata(function(err, metadata, apiResponse) {}); */ -Snapshot.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, 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. - */ -Snapshot.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/global/snapshots/' + this.name + path; - this.compute.makeReq_(method, path, query, body, callback); +Snapshot.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; module.exports = Snapshot; diff --git a/lib/compute/vm.js b/lib/compute/vm.js index 7adfb1732c8..5d2905465f3 100644 --- a/lib/compute/vm.js +++ b/lib/compute/vm.js @@ -20,8 +20,10 @@ 'use strict'; +var createErrorClass = require('create-error-class'); var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:compute/disk} @@ -29,12 +31,28 @@ var is = require('is'); */ var Disk = require('./disk.js'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private */ var util = require('../common/util.js'); +/** + * Custom error type for errors related to detaching a disk. + * + * @param {string} message - Custom error message. + * @return {Error} + */ +var DetachDiskError = createErrorClass('DetachDiskError', function(message) { + this.message = message; +}); + /*! Developer Documentation * * @param {module:zone} zone - Zone object this instance belongs to. @@ -63,10 +81,206 @@ var util = require('../common/util.js'); * var vm = zone.vm('vm-name'); */ function VM(zone, name) { + ServiceObject.call(this, { + parent: zone, + baseUrl: '/instances', + id: name, + createMethod: zone.createVM.bind(zone), + exclude: ['setMetadata'] + }); + this.zone = zone; - this.name = name; } +nodeutil.inherits(VM, ServiceObject); + +/** + * Create a virtual machine. + * + * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} + * @resource [Instances: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/insert} + * + * @param {object} config - See an + * [Instance resource](https://cloud.google.com/compute/docs/reference/v1/instances). + * @param {object[]=} config.disks - See a + * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). + * @param {boolean=} config.http - Allow HTTP traffic. Default: `false` + * @param {boolean=} config.https - Allow HTTPS traffic. Default: `false` + * @param {object[]=} config.networkInterfaces - An array of configurations for + * this interface. This specifies how this interface should interact with + * other network services, such as connecting to the internet. Default: + * `[ { network: 'global/networks/default' } ]` + * @param {string=} config.machineType - The machine type resource to use. + * Provide only the name of the machine, e.g. `n1-standard-16`. Refer to + * [Available Machine Types](https://goo.gl/jrHEbo). Default: + * `n1-standard-1` + * @param {string=} config.os - Specify the name of an OS, and we will use the + * latest version as the source image of a new boot disk. See + * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). + * @param {string[]=} config.tags - An array of tags. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/vm} callback.vm - The created VM object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * //- + * // Create a new instance using the latest Debian version as the source image + * // for a new boot disk. + * //- + * var config = { + * os: 'debian', + * http: true, + * tags: ['debian-server'] + * }; + * + * //- + * // The above object will auto-expand behind the scenes to something like the + * // following. The Debian version may be different when you run the command. + * //- + * var config = { + * machineType: 'n1-standard-1', + * disks: [ + * { + * boot: true, + * initializeParams: { + * sourceImage: + * 'https://www.googleapis.com/compute/v1/projects' + + * '/debian-cloud/global/images/debian-7-wheezy-v20150710' + * } + * } + * ], + * networkInterfaces: [ + * { + * network: 'global/networks/default' + * } + * ], + * tags: [ + * { + * items: [ + * 'debian-server', + * 'http-server' + * ] + * } + * ] + * }; + * + * vm.create(config, function(err, vm, operation, apiResponse) { + * // `vm` is a VM object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +VM.prototype.create = function() { + ServiceObject.prototype.create.apply(this, arguments); +}; + +/** + * Get a virtual machine if it exists. Also see {module:compute/vm#getOrCreate}. + * + * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} + + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/vm} callback.vm - The VM object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * vm.get(function(err, vm, apiResponse) { + * // `vm` is a VM object. + * }); + */ +VM.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + +/** + * Get a virtual machine if it exists, otherwise create one. + * + * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} + * @resource [Instances: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/insert} + * + * @param {object} config - See an + * [Instance resource](https://cloud.google.com/compute/docs/reference/v1/instances). + * @param {object[]=} config.disks - See a + * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). + * @param {boolean=} config.http - Allow HTTP traffic. Default: `false` + * @param {boolean=} config.https - Allow HTTPS traffic. Default: `false` + * @param {object[]=} config.networkInterfaces - An array of configurations for + * this interface. This specifies how this interface should interact with + * other network services, such as connecting to the internet. Default: + * `[ { network: 'global/networks/default' } ]` + * @param {string=} config.machineType - The machine type resource to use. + * Provide only the name of the machine, e.g. `n1-standard-16`. Refer to + * [Available Machine Types](https://goo.gl/jrHEbo). Default: + * `n1-standard-1` + * @param {string=} config.os - Specify the name of an OS, and we will use the + * latest version as the source image of a new boot disk. See + * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). + * @param {string[]=} config.tags - An array of tags. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/vm} callback.vm - The created VM object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * //- + * // Create a new instance using the latest Debian version as the source image + * // for a new boot disk. + * //- + * var config = { + * os: 'debian', + * http: true, + * tags: ['debian-server'] + * }; + * + * //- + * // The above object will auto-expand behind the scenes to something like the + * // following. The Debian version may be different when you run the command. + * //- + * var config = { + * machineType: 'n1-standard-1', + * disks: [ + * { + * boot: true, + * initializeParams: { + * sourceImage: + * 'https://www.googleapis.com/compute/v1/projects' + + * '/debian-cloud/global/images/debian-7-wheezy-v20150710' + * } + * } + * ], + * networkInterfaces: [ + * { + * network: 'global/networks/default' + * } + * ], + * tags: [ + * { + * items: [ + * 'debian-server', + * 'http-server' + * ] + * } + * ] + * }; + * + * vm.getOrCreate(config, function(err, vm, operation, apiResponse) { + * // `vm` is a VM object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +VM.prototype.getOrCreate = function() { + ServiceObject.prototype.getOrCreate.apply(this, arguments); +}; + /** * Attach a disk to the instance. * @@ -118,7 +332,10 @@ VM.prototype.attachDisk = function(disk, options, callback) { options = {}; } - var body = extend({}, options, { + var body = extend({ + // Default the deviceName to the name of the disk, like the Console does. + deviceName: disk.name + }, options, { source: disk.formattedName }); @@ -127,7 +344,11 @@ VM.prototype.attachDisk = function(disk, options, callback) { delete body.readOnly; } - this.makeReq_('POST', '/attachDisk', null, body, callback); + this.request({ + method: 'POST', + uri: '/attachDisk', + json: body + }, callback); }; /** @@ -148,7 +369,10 @@ VM.prototype.attachDisk = function(disk, options, callback) { * }); */ VM.prototype.delete = function(callback) { - this.makeReq_('DELETE', '', null, null, callback || util.noop); + this.request({ + method: 'DELETE', + uri: '' + }, callback || util.noop); }; /** @@ -156,9 +380,9 @@ VM.prototype.delete = function(callback) { * * @resource [Instance: detachDisk API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/detachDisk} * - * @throws {Error} if a {module:compute/disk} is not provided. - * - * @param {module:compute/disk} disk - The disk to detach. + * @param {module:compute/disk|string} deviceName - The device name of the disk + * to detach. If a Disk object is provided, we try to find the device name + * automatically by searching through the attached disks on the instance. * @param {function=} callback - The callback function. * @param {?error} callback.err - An error returned while making this request. * @param {module:compute/operation} callback.operation - An operation object @@ -173,16 +397,53 @@ VM.prototype.delete = function(callback) { * // of the request. * }); */ -VM.prototype.detachDisk = function(disk, callback) { - if (!(disk instanceof Disk)) { - throw new Error('A Disk object must be provided.'); - } +VM.prototype.detachDisk = function(deviceName, callback) { + var self = this; - var query = { - deviceName: disk.name - }; + if (deviceName instanceof Disk) { + var disk = deviceName; - this.makeReq_('POST', '/detachDisk', query, null, callback || util.noop); + if (disk.metadata.deviceName) { + self.detachDisk(disk.metadata.deviceName, callback); + return; + } + + this.getMetadata(function(err, metadata) { + if (err) { + callback(new DetachDiskError(err.message)); + return; + } + + var deviceName; + var baseUrl = 'https://www.googleapis.com/compute/v1/'; + + // Try to find the deviceName by matching the source of the attached disks + // to the name of the disk provided by the user. + for (var i = 0; !deviceName && i < metadata.disks.length; i++) { + var attachedDisk = metadata.disks[i]; + var source = attachedDisk.source.replace(baseUrl, ''); + + if (source === disk.formattedName) { + deviceName = attachedDisk.deviceName; + } + } + + if (deviceName) { + self.detachDisk(deviceName, callback); + } else { + callback(new DetachDiskError('Could not find a deviceName for disk.')); + } + }); + return; + } + + this.request({ + method: 'POST', + uri: '/detachDisk', + qs: { + deviceName: deviceName + } + }, callback || util.noop); }; /** @@ -199,21 +460,8 @@ VM.prototype.detachDisk = function(disk, callback) { * @example * vm.getMetadata(function(err, metadata, apiResponse) {}); */ -VM.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, _, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); +VM.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -237,11 +485,14 @@ VM.prototype.getSerialPortOutput = function(port, callback) { port = 1; } - var query = { - port: port + var reqOpts = { + uri: '/serialPort', + qs: { + port: port + } }; - this.makeReq_('GET', '/serialPort', query, null, function(err, _, resp) { + ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -295,7 +546,10 @@ VM.prototype.getTags = function(callback) { * }); */ VM.prototype.reset = function(callback) { - this.makeReq_('POST', '/reset', null, null, callback || util.noop); + this.request({ + method: 'POST', + uri: '/reset' + }, callback || util.noop); }; /** @@ -328,7 +582,11 @@ VM.prototype.setTags = function(tags, fingerprint, callback) { fingerprint: fingerprint }; - this.makeReq_('POST', '/setTags', null, body, callback || util.noop); + this.request({ + method: 'POST', + uri: '/setTags', + json: body + }, callback || util.noop); }; /** @@ -349,7 +607,10 @@ VM.prototype.setTags = function(tags, fingerprint, callback) { * }); */ VM.prototype.start = function(callback) { - this.makeReq_('POST', '/start', null, null, callback || util.noop); + this.request({ + method: 'POST', + uri: '/start' + }, callback || util.noop); }; /** @@ -370,7 +631,10 @@ VM.prototype.start = function(callback) { * }); */ VM.prototype.stop = function(callback) { - this.makeReq_('POST', '/stop', null, null, callback || util.noop); + this.request({ + method: 'POST', + uri: '/stop' + }, callback || util.noop); }; /** @@ -390,12 +654,10 @@ VM.prototype.stop = function(callback) { * @param {*} body - Request body contents. * @param {function} callback - The callback function. */ -VM.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/instances/' + this.name + path; - +VM.prototype.request = function(reqOpts, callback) { var zone = this.zone; - zone.makeReq_(method, path, query, body, function(err, resp) { + ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; diff --git a/lib/compute/zone.js b/lib/compute/zone.js index 4d02e01a18f..a339f7f9bc6 100644 --- a/lib/compute/zone.js +++ b/lib/compute/zone.js @@ -25,6 +25,7 @@ var extend = require('extend'); var format = require('string-format-obj'); var gceImages = require('gce-images'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:compute/disk} @@ -39,16 +40,16 @@ var Disk = require('./disk.js'); var Operation = require('./operation.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'); /** * @type {module:compute/vm} @@ -81,15 +82,42 @@ var VM = require('./vm.js'); * var zone = gce.zone('us-central1-a'); */ function Zone(compute, name) { + ServiceObject.call(this, { + parent: compute, + baseUrl: '/zones', + id: name, + exclude: ['create', 'delete', 'getOrCreate', 'setMetadata'] + }); + this.compute = compute; this.name = name; - this.metadata = {}; this.gceImages = gceImages({ - authClient: compute.makeAuthenticatedRequest_.authClient + authClient: compute.authClient }); } +nodeutil.inherits(Zone, ServiceObject); + +/** + * Get a zone. + * + * @resource [Regions & Zones Overview]{@link https://cloud.google.com/compute/docs/zones} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/zone} callback.zone - The Zone object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * zone.get(function(err, zone, apiResponse) { + * // `zone` is a Zone object. + * }); + */ +Zone.prototype.get = function() { + ServiceObject.prototype.get.apply(this, arguments); +}; + /** * Create a persistent disk in this zone. * @@ -150,7 +178,12 @@ Zone.prototype.createDisk = function(name, config, callback) { return; } - this.makeReq_('POST', '/disks', query, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/disks', + qs: query, + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -345,7 +378,11 @@ Zone.prototype.createVM = function(name, config, callback) { return; } - this.makeReq_('POST', '/instances', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/instances', + json: body + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -452,7 +489,10 @@ Zone.prototype.getDisks = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/disks', options, null, function(err, resp) { + this.request({ + uri: '/disks', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -490,21 +530,8 @@ Zone.prototype.getDisks = function(options, callback) { * @example * zone.getMetadata(function(err, metadata, apiResponse) {}); */ -Zone.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); +Zone.prototype.getMetadata = function() { + ServiceObject.prototype.getMetadata.apply(this, arguments); }; /** @@ -585,7 +612,10 @@ Zone.prototype.getOperations = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/operations', options, null, function(err, resp) { + this.request({ + uri: '/operations', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -685,7 +715,10 @@ Zone.prototype.getVMs = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/instances', options, null, function(err, resp) { + this.request({ + uri: '/instances', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -779,23 +812,6 @@ Zone.prototype.createHttpsServerFirewall_ = function(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. - */ -Zone.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/zones/' + this.name + path; - this.compute.makeReq_(method, path, query, body, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/system-test/compute.js b/system-test/compute.js index 607336bd7a9..3b9c34ef904 100644 --- a/system-test/compute.js +++ b/system-test/compute.js @@ -60,7 +60,7 @@ describe('Compute', function() { before(function(done) { ADDRESS_NAME = generateName(); - region.createAddress(ADDRESS_NAME, function(err, address_, operation) { + region.address(ADDRESS_NAME).create(function(err, address_, operation) { assert.ifError(err); address = address_; operation.onComplete(done); @@ -113,7 +113,7 @@ describe('Compute', function() { os: 'ubuntu' }; - zone.createDisk(DISK_NAME, config, function(err, disk_, operation) { + zone.disk(DISK_NAME).create(config, function(err, disk_, operation) { assert.ifError(err); disk = disk_; operation.onComplete(done); @@ -155,7 +155,13 @@ describe('Compute', function() { }); it('should take a snapshot', function(done) { - disk.createSnapshot(generateName(), done); + var MAX_TIME_ALLOWED = 90000; + this.timeout(MAX_TIME_ALLOWED); + + disk.snapshot(generateName()).create(function(err, snapshot, operation) { + assert.ifError(err); + operation.onComplete(getOperationOptions(MAX_TIME_ALLOWED), done); + }); }); }); @@ -190,8 +196,9 @@ describe('Compute', function() { before(function(done) { FIREWALL_NAME = generateName(); - compute.createFirewall( - FIREWALL_NAME, CONFIG, function(err, firewall_, operation) { + compute + .firewall(FIREWALL_NAME) + .create(CONFIG, function(err, firewall_, operation) { assert.ifError(err); firewall = firewall_; operation.onComplete(done); @@ -242,8 +249,9 @@ describe('Compute', function() { before(function(done) { NETWORK_NAME = generateName(); - compute.createNetwork( - NETWORK_NAME, CONFIG, function(err, network_, operation) { + compute + .network(NETWORK_NAME) + .create(CONFIG, function(err, network_, operation) { assert.ifError(err); network = network_; operation.onComplete(done); @@ -409,7 +417,7 @@ describe('Compute', function() { http: true }; - zone.createVM(VM_NAME, config, function(err, vm_, operation) { + zone.vm(VM_NAME).create(config, function(err, vm_, operation) { assert.ifError(err); vm = vm_; operation.onComplete(done); @@ -430,10 +438,7 @@ describe('Compute', function() { return; } - operation.onComplete({ - maxAttempts: MAX_TIME_ALLOWED / 10000, - interval: 10000 - }, done); + operation.onComplete(getOperationOptions(MAX_TIME_ALLOWED), done); }); }); @@ -472,20 +477,48 @@ describe('Compute', function() { }); it('should attach and detach a disk', function(done) { - compute.getDisks() - .on('error', done) - .once('data', function(disk) { - this.end(); + var disk; - vm.attachDisk(disk, function(err) { - assert.ifError(err); + // This test waits on a lot of operations. + this.timeout(90000); - vm.detachDisk(disk, function(err, operation) { - assert.ifError(err); - operation.onComplete(done); - }); + async.series([ + createDisk, + attachDisk, + detachDisk + ], done); + + function createDisk(callback) { + var diskName = generateName(); + var config = { + os: 'ubuntu' + }; + + zone.disk(diskName).create(config, function(err, disk_, operation) { + if (err) { + callback(err); + return; + } + + operation.onComplete(function(err) { + if (err) { + callback(err); + return; + } + + disk = disk_; + callback(); }); }); + } + + function attachDisk(callback) { + vm.attachDisk(disk, execAfterOperationComplete(callback)); + } + + function detachDisk(callback) { + vm.detachDisk(disk, execAfterOperationComplete(callback)); + } }); it('should get serial port output', function(done) { @@ -517,15 +550,19 @@ describe('Compute', function() { }); it('should reset', function(done) { - vm.reset(done); + vm.reset(execAfterOperationComplete(done)); }); it('should start', function(done) { - vm.start(done); + vm.start(execAfterOperationComplete(done)); }); it('should stop', function(done) { - vm.stop(done); + var MAX_TIME_ALLOWED = 90000 * 2; + this.timeout(MAX_TIME_ALLOWED); + + var options = getOperationOptions(MAX_TIME_ALLOWED); + vm.stop(execAfterOperationComplete(options, done)); }); }); @@ -624,4 +661,24 @@ describe('Compute', function() { async.each(objects, exec('delete'), callback); }); } + + function getOperationOptions(maxTimeAllowed) { + var interval = 10000; + + return { + maxAttempts: maxTimeAllowed / interval, + interval: interval + }; + } + + function execAfterOperationComplete(options, callback) { + return function(err, operation) { + if (err) { + callback(err); + return; + } + + operation.onComplete(options || {}, callback); + }; + } }); From b5c7d4006c6c58f2f312e6779e09a64ad8c0cfd6 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 22 Oct 2015 13:34:20 -0400 Subject: [PATCH 03/49] use new inheritance style --- lib/bigquery/dataset.js | 114 +++++++++++------ lib/bigquery/job.js | 53 +++++--- lib/bigquery/table.js | 108 +++++++++++----- lib/common/service-object.js | 43 ++----- lib/compute/address.js | 144 +++++++++------------ lib/compute/disk.js | 176 ++++++++++--------------- lib/compute/firewall.js | 201 +++++++++++------------------ lib/compute/index.js | 2 +- lib/compute/network.js | 184 +++++++++++---------------- lib/compute/operation.js | 70 +++++----- lib/compute/region.js | 68 +++++----- lib/compute/snapshot.js | 113 ++++++----------- lib/compute/vm.js | 240 ++++++++--------------------------- lib/compute/zone.js | 68 +++++----- 14 files changed, 642 insertions(+), 942 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 3faff3bdc2a..b5ca551d19c 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -55,11 +55,85 @@ var Table = require('./table.js'); * @constructor */ function Dataset(bigQuery, id) { + var methods = { + /** + * Create a dataset. + * + * @example + * dataset.create(function(err, dataset, apiResponse) { + * // `dataset.metadata` has been populated. + * }); + */ + create: true, + + /** + * Get a dataset if it exists. Also see + * {module:bigquery/dataset#getOrCreate}. + * + * @example + * dataset.get(function(err, dataset, apiResponse) { + * if (!err) { + * // `dataset.metadata` has been populated. + * } + * }); + */ + get: true, + + /** + * Get the metadata for the Dataset. + * + * @resource [Datasets: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The dataset's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * dataset.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get a dataset if it exists, otherwise create one. + * + * @example + * dataset.getOrCreate(function(err, dataset, apiResponse) { + * if (!err) { + * // `dataset.metadata` has been populated. + * } + * }); + */ + getOrCreate: true, + + /** + * Sets the metadata of the Dataset object. + * + * @resource [Datasets: patch API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/patch} + * + * @param {object} metadata - Metadata to save on the Dataset. + * @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 metadata = { + * description: 'Info for every institution in the 2013 IPEDS universe' + * }; + * + * dataset.setMetadata(metadata, function(err, apiResponse) {}); + */ + setMetadata: true + }; + ServiceObject.call(this, { parent: bigQuery, baseUrl: '/datasets', id: id, - createMethod: bigQuery.createDataset.bind(bigQuery) + createMethod: bigQuery.createDataset.bind(bigQuery), + methods: methods }); this.bigQuery = bigQuery; @@ -172,23 +246,6 @@ Dataset.prototype.delete = function(options, callback) { }, callback); }; -/** - * Get the metadata for the Dataset. - * - * @resource [Datasets: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The dataset's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * dataset.getMetadata(function(err, metadata, apiResponse) {}); - */ -Dataset.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Get a list of tables. * @@ -289,27 +346,6 @@ Dataset.prototype.query = function(options, callback) { return this.bigQuery.query(options, callback); }; -/** - * Sets the metadata of the Dataset object. - * - * @resource [Datasets: patch API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/patch} - * - * @param {object} metadata - Metadata to save on the Dataset. - * @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 metadata = { - * description: 'Information for every institution in the 2013 IPEDS universe' - * }; - * - * dataset.setMetadata(metadata, function(err, apiResponse) {}); - */ -Dataset.prototype.setMetadata = function() { - ServiceObject.prototype.setMetadata.apply(this, arguments); -}; - /** * Return a new instance of reference to an existing Table object. * diff --git a/lib/bigquery/job.js b/lib/bigquery/job.js index 962255f38e2..95827f106b9 100644 --- a/lib/bigquery/job.js +++ b/lib/bigquery/job.js @@ -58,11 +58,43 @@ var ServiceObject = require('../common/service-object.js'); * @constructor */ function Job(bigQuery, id) { + var methods = { + /** + * Get a job if it exists. + * + * @example + * job.get(function(err, job, apiResponse) { + * if (!err) { + * // `job.metadata` has been populated. + * } + * }); + */ + get: true, + + /** + * Get the metadata of the job. This will mostly be useful for checking the + * status of a previously-run job. + * + * @resource [Jobs: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The metadata of the job. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var job = bigquery.job('id'); + * job.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; + ServiceObject.call(this, { parent: bigQuery, baseUrl: '/jobs', id: id, - inherit: [] + methods: methods }); this.bigQuery = bigQuery; @@ -70,25 +102,6 @@ function Job(bigQuery, id) { nodeutil.inherits(Job, ServiceObject); -/** - * Get the metadata of the job. This will mostly be useful for checking the - * status of a previously-run job. - * - * @resource [Jobs: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The metadata of the job. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var job = bigquery.job('id'); - * job.getMetadata(function(err, metadata, apiResponse) {}); - */ -Job.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Get the results of a job. * diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index d5f3af2b881..18dbf700401 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -79,11 +79,83 @@ var util = require('../common/util'); * var table = dataset.table('my-table'); */ function Table(dataset, id) { + var methods = { + /** + * Create a table. + * + * @param {object} config - See {module:bigquery/dataset#createTable}. + * + * @example + * var config = { + * // ... + * }; + * + * table.create(config, function(err, table, apiResponse) { + * // `table.metadata` has been populated. + * }); + */ + create: true, + + /** + * Delete a table and all its data. + * + * @resource [Tables: delete API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/tables/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 + * table.delete(function(err, apiResponse) {}); + */ + delete: true, + + /** + * Get a table if it exists. Also see {module:bigquery/table#getOrCreate}. + * + * @example + * table.get(function(err, table, apiResponse) { + * // `table.metadata` has been populated. + * }); + */ + get: true, + + /** + * Return the metadata associated with the Table. + * + * @resource [Tables: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/tables/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The metadata of the Table. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * table.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get a table if it exists, otherwise create one. + * + * @param {object=} config - See {module:bigquery/dataset#createTable}. + * + * @example + * table.getOrCreate(config, function(err, table, apiResonse) { + * // `table.metadata` has been populated. + * }); + */ + getOrCreate: true + }; + ServiceObject.call(this, { parent: dataset, baseUrl: '/tables', id: id, - createMethod: dataset.createTable.bind(dataset) + createMethod: dataset.createTable.bind(dataset), + methods: methods }); this.bigQuery = dataset.bigQuery; @@ -352,22 +424,6 @@ Table.prototype.createWriteStream = function(metadata) { return dup; }; -/** - * Delete a table and all its data. - * - * @resource [Tables: delete API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/tables/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 - * table.delete(function(err, apiResponse) {}); - */ -Table.prototype.delete = function() { - return ServiceObject.prototype.delete.apply(this, arguments); -}; - /** * Export table to Google Cloud Storage. * @@ -497,23 +553,6 @@ Table.prototype.export = function(destination, options, callback) { }); }; -/** - * Return the metadata associated with the Table. - * - * @resource [Tables: get API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/tables/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The metadata of the Table. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * table.getMetadata(function(err, metadata, apiResponse) {}); - */ -Table.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Retrieves table data from a specified set of rows. The rows are returned to * your callback as an array of objects matching your table's schema. @@ -897,6 +936,7 @@ Table.prototype.query = function(query, callback) { * description: 'A table for storing my recipes.', * schema: 'name:string, servings:integer, cookingTime:float, quick:boolean' * }; + * * table.setMetadata(metadata, function(err, metadata, apiResponse) {}); */ Table.prototype.setMetadata = function(metadata, callback) { diff --git a/lib/common/service-object.js b/lib/common/service-object.js index 56114b308be..540463ed4ea 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -47,8 +47,8 @@ var util = require('./util.js'); * @param {string} config.createMethod - The method which creates this object. * @param {string} config.id - The identifier of the object. For example, the * name of a Storage bucket or Pub/Sub topic. - * @param {string[]=} config.inherit - If all of the methods don't apply to the - * service object, this is a whitelist of methods to implement. + * @param {object} config.methods - A map of each method name that should be + * inherited. * @param {object} config.parent - The parent service instance. For example, an * instance of Storage if the object is Bucket. */ @@ -62,14 +62,17 @@ function ServiceObject(config) { this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc.) this.createMethod = config.createMethod; - if (config.exclude) { - var allMethods = Object.keys(ServiceObject.prototype); - allMethods.forEach(function(methodName) { - if (config.exclude.indexOf(methodName) > -1) { - self[methodName] = undefined; - } + // Itera + var allMethodNames = Object.keys(ServiceObject.prototype); + allMethodNames + .filter(function(methodName) { + return methodName !== 'request' && // All ServiceObjects need `request`. + !self[methodName] && // The ServiceObject didn't redefine the method. + !config.methods[methodName]; // This method wasn't asked to be extended. + }) + .forEach(function(methodName) { + self[methodName] = undefined; }); - } } /** @@ -178,28 +181,6 @@ ServiceObject.prototype.getMetadata = function(callback) { }); }; -/** - * Get or create this object. If it doesn't exist, it will be created. - * - * @param {object=} options - Configuration object. - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {object} callback.instance - The instance. - * @param {object} callback.apiResponse - The full API response. - */ -ServiceObject.prototype.getOrCreate = function(options, callback) { - var self = this; - - this.get(function(err, instance, apiResponse) { - if (err) { - self.create(options, callback); - return; - } - - callback(null, instance, apiResponse); - }); -}; - /** * Set the metadata for this object. * diff --git a/lib/compute/address.js b/lib/compute/address.js index c7e41d62bf1..a1522b08257 100644 --- a/lib/compute/address.js +++ b/lib/compute/address.js @@ -62,12 +62,68 @@ var util = require('../common/util.js'); * var address = region.address('address1'); */ function Address(region, name) { + var methods = { + /** + * Create an address. + * + * @param {object=} options - See {module:compute#createAddress}. + * + * @example + * address.create(function(err, address, operation, apiResponse) { + * // `address` is an Address object. + * + * // `operation` is an Operation object that can be used to check the + * // of the request. + * }); + */ + create: true, + + /** + * Get an address if it exists. + * + * @example + * address.get(function(err, address, apiResponse) { + * // `address` is an Address object. + * }); + */ + get: true, + + /** + * Get the metadata of this address. + * + * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} + * @resource [Addresses: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The address's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * address.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get an address if it exists, otherwise create one. + * + * @param {object=} options - See {module:compute#createAdresss}. + * + * @example + * address.getOrCreate(function(err, address, apiResponse) { + * // `address` is an Address object. + * }); + */ + getOrCreate: true + }; + ServiceObject.call(this, { parent: region, baseUrl: '/addresses', id: name, createMethod: region.createAddress.bind(region), - exclude: ['setMetadata'] + methods: methods }); this.region = region; @@ -75,74 +131,6 @@ function Address(region, name) { nodeutil.inherits(Address, ServiceObject); -/** - * Create an address. - * - * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} - * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} - * @resource [Addresses: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/insert} - * - * @param {object=} options - See an - * [Address resource](https://cloud.google.com/compute/docs/reference/v1/addresses). - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/address} callback.address - The created Address - * object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * address.create(function(err, address, operation, apiResponse) { - * // `address` is an Address object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -Address.prototype.create = function() { - ServiceObject.prototype.create.apply(this, arguments); -}; - -/** - * Get an address if it exists. Also see {module:compute/address#getOrCreate}. - * - * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/address} callback.address - The Address object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * address.get(function(err, address, apiResponse) { - * // `address` is an Address object. - * }); - */ -Address.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Get an address if it exists, otherwise create one. - * - * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} - * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} - * @resource [Addresses: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/insert} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/address} callback.address - The Address object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * address.getOrCreate(function(err, address, apiResponse) { - * // `address` is an Address object. - * }); - */ -Address.prototype.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); -}; /** * Delete the address. @@ -182,22 +170,4 @@ Address.prototype.delete = function(callback) { }); }; -/** - * Get the metadata of this address. - * - * @resource [Address Resource]{@link https://cloud.google.com/compute/docs/reference/v1/addresses} - * @resource [Addresses: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/addresses/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The address's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * address.getMetadata(function(err, metadata, apiResponse) {}); - */ -Address.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - module.exports = Address; diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 3aae20d10a2..931f538336b 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -70,12 +70,79 @@ var util = require('../common/util.js'); * var disk = zone.disk('disk1'); */ function Disk(zone, name) { + var methods = { + /** + * Create a persistent disk. + * + * @param {object} config - See {module:compute#createDisk}. + * + * @example + * var config = { + * // ... + * }; + * + * disk.create(config, function(err, disk, operation, apiResponse) { + * // `disk` is a Disk object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + create: true, + + /** + * Get a disk if it exists. Also see {module:compute/disk#getOrCreate}. + * + * @example + * disk.get(function(err, disk, apiResponse) { + * // `disk` is a Disk object. + * }); + */ + get: true, + + /** + * Get the disk's metadata. + * + * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} + * @resource [Disks: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The disk's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * disk.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get a disk if it exists, otherwise create one. + * + * @param {object=} config - See {module:compute#createDisk}. + * + * @example + * var config = { + * // ... + * }; + * + * disk.getOrCreate(config, function(err, disk, operation, apiResponse) { + * // `disk` is a Disk object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + getOrCreate: true + }; + ServiceObject.call(this, { parent: zone, baseUrl: '/disks', id: name, createMethod: zone.createDisk.bind(zone), - exclude: ['setMetadata'] + methods: methods }); this.name = name; @@ -103,95 +170,6 @@ Disk.formatName_ = function(zone, name) { }); }; -/** - * Create a persistent disk. - * - * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} - * @resource [Disks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/insert} - * - * @param {object} config - See a - * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). - * @param {string=} config.os - Specify the name of an OS, and we will use the - * latest version as the source image of a new boot disk. See - * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/disk} callback.disk - The created Disk object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * os: 'ubuntu', - * sizeGb: 10 - * }; - * - * disk.create(config, function(err, disk, operation, apiResponse) { - * // `disk` is a Disk object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -Disk.prototype.create = function() { - ServiceObject.prototype.create.apply(this, arguments); -}; - -/** - * Get a disk if it exists. Also see {module:compute/disk#getOrCreate}. - * - * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/disk} callback.disk - The Disk object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * disk.get(function(err, disk, apiResponse) { - * // `disk` is a Disk object. - * }); - */ -Disk.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Get a disk if it exists, otherwise create one. - * - * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} - * @resource [Disks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/insert} - * - * @param {object} config - See a - * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). - * @param {string=} config.os - Specify the name of an OS, and we will use the - * latest version as the source image of a new boot disk. See - * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/disk} callback.disk - The Disk object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * os: 'ubuntu', - * sizeGb: 10 - * }; - * - * disk.getOrCreate(config, function(err, disk, operation, apiResponse) { - * // `disk` is a Disk object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -Disk.prototype.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); -}; - /** * Create a snapshot of a disk. * @@ -287,24 +265,6 @@ Disk.prototype.delete = function(callback) { }); }; -/** - * Get the disk's metadata. - * - * @resource [Disk Resource]{@link https://cloud.google.com/compute/docs/reference/v1/disks} - * @resource [Disks: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The disk's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * disk.getMetadata(function(err, metadata, apiResponse) {}); - */ -Disk.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Get a reference to a snapshot from this disk. * diff --git a/lib/compute/firewall.js b/lib/compute/firewall.js index e0ddd9b66f8..d172c0f7893 100644 --- a/lib/compute/firewall.js +++ b/lib/compute/firewall.js @@ -60,11 +60,82 @@ var util = require('../common/util.js'); * var firewall = gce.firewall('tcp-3000'); */ function Firewall(compute, name) { + var methods = { + /** + * Create a firewall. + * + * @param {object} config - See {module:compute#createFirewall}. + * + * @example + * var config = { + * // ... + * }; + * + * firewall.create(config, function(err, firewall, operation, apiResponse) { + * // `firewall` is a Firewall object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + create: true, + + /** + * Get a firewall if it exists. Also see + * {module:compute/firewall#getOrCreate}. + * + * @example + * firewall.get(function(err, firewall, apiResponse) { + * // `firewall` is a Firewall object. + * }); + */ + get: true, + + /** + * Get the firewall's metadata. + * + * @resource [Firewalls: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/get} + * @resource [Firewall Resource]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The firewall's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * firewall.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get a firewall if it exists, otherwise create one. + * + * @param {object=} config - See {module:compute#createFireall}. + * + * @example + * var config = { + * // ... + * }; + * + * function callback(err, firewall, operation, apiResponse) { + * // `firewall` is a Firewall object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * } + * + * firewall.getOrCreate(config, callback); + */ + getOrCreate: true + }; + ServiceObject.call(this, { parent: compute, baseUrl: '/global/firewalls', id: name, - createMethod: compute.createFirewall.bind(compute) + createMethod: compute.createFirewall.bind(compute), + methods: methods, }); this.compute = compute; @@ -74,116 +145,6 @@ function Firewall(compute, name) { nodeutil.inherits(Firewall, ServiceObject); -/** - * Create a firewall. - * - * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} - * @resource [Firewalls: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/insert} - * - * @param {object} config - See a - * [Firewall resource](https://cloud.google.com/compute/docs/reference/v1/firewalls#resource). - * @param {object} config.protocols - A map of protocol to port range. The keys - * of the object refer to a protocol (e.g. `tcp`, `udp`) and the value for - * the key are the ports/port-ranges that are allowed to make a connection. - * @param {string[]} config.ranges - The IP address blocks that this rule - * applies to, expressed in - * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) - * format. - * @param {string[]} config.tags - Instance tags which this rule applies to. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/firewall} callback.firewall - The created Firewall - * object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * protocols: { - * tcp: [3000], - * udp: [] // An empty array means all ports are allowed. - * }, - * - * ranges: ['0.0.0.0/0'] - * }; - * - * firewall.create(config, function(err, firewall, operation, apiResponse) { - * // `firewall` is a Firewall object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -Firewall.prototype.create = function() { - ServiceObject.prototype.create.apply(this, arguments); -}; - -/** - * Get a firewall if it exists. Also see {module:compute/firewall#getOrCreate}. - * - * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/firewall} callback.firewall - The Firewall object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * firewall.get(function(err, firewall, apiResponse) { - * // `firewall` is a Firewall object. - * }); - */ -Firewall.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Get a firewall if it exists, otherwise create one. - * - * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} - * @resource [Firewalls: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/insert} - * - * @param {object} config - See a - * [Firewall resource](https://cloud.google.com/compute/docs/reference/v1/firewalls#resource). - * @param {object} config.protocols - A map of protocol to port range. The keys - * of the object refer to a protocol (e.g. `tcp`, `udp`) and the value for - * the key are the ports/port-ranges that are allowed to make a connection. - * @param {string[]} config.ranges - The IP address blocks that this rule - * applies to, expressed in - * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) - * format. - * @param {string[]} config.tags - Instance tags which this rule applies to. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/firewall} callback.firewall - The Firewall object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * protocols: { - * tcp: [3000], - * udp: [] // An empty array means all ports are allowed. - * }, - * - * ranges: ['0.0.0.0/0'] - * }; - * - * function callback(err, firewall, operation, apiResponse) { - * // `firewall` is a Firewall object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * } - * - * firewall.getOrCreate(config, callback); - */ -Firewall.prototype.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); -}; - /** * Delete the firewall. * @@ -219,24 +180,6 @@ Firewall.prototype.delete = function(callback) { }); }; -/** - * Get the firewall's metadata. - * - * @resource [Firewalls: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls/get} - * @resource [Firewall Resource]{@link https://cloud.google.com/compute/docs/reference/v1/firewalls} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The firewall's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * firewall.getMetadata(function(err, metadata, apiResponse) {}); - */ -Firewall.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Set the firewall's metadata. * diff --git a/lib/compute/index.js b/lib/compute/index.js index 34287a9aac2..e2d95c174f9 100644 --- a/lib/compute/index.js +++ b/lib/compute/index.js @@ -255,7 +255,7 @@ Compute.prototype.createFirewall = function(name, config, callback) { * function callback(err, network, operation, apiResponse) { * // `network` is a Network object. * - * // `operation` is an Operation object and can be used to check the status + * // `operation` is an Operation object that can be used to check the status * // of network creation. * } * diff --git a/lib/compute/network.js b/lib/compute/network.js index 6d704af7af3..ec53553b5e0 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -62,12 +62,82 @@ var util = require('../common/util.js'); * var network = gce.network('network-name'); */ function Network(compute, name) { + var methods = { + /** + * Create a network. + * + * @param {object} config - See {module:compute#createNetwork}. + * + * @example + * var config = { + * // ... + * }; + * + * network.create(config, function(err, network, operation, apiResponse) { + * // `network` is a Network object. + * + * // `operation` is an Operation object that can be used to check the + * // status of network creation. + * }); + */ + create: true, + + /** + * Get a network if it exists. Also see + * {module:compute/network#getOrCreate}. + * + * @example + * network.get(function(err, network, apiResponse) { + * // `network` is a Network object. + * }); + */ + get: true, + + /** + * Get the network's metadata. + * + * @resource [Network Resource]{@link https://cloud.google.com/compute/docs/reference/v1/networks} + * @resource [Networks: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/delete} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The network's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * network.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Get a network if it exists, otherwise create one. + * + * @param {object=} config - See {module:compute#createNetwork}. + * + * @example + * var config = { + * // ... + * }; + * + * function callback(err, network, operation, apiResponse) { + * // `network` is a Network object. + * + * // `operation` is an Operation object that can be used to check the + * // status of network creation. + * } + * + * network.getOrCreate(config, callback); + */ + getOrCreate: true, + }; + ServiceObject.call(this, { parent: compute, baseUrl: '/global/networks', id: name, createMethod: compute.createNetwork.bind(compute), - exclude: ['setMetadata'] + methods: methods, }); this.compute = compute; @@ -92,100 +162,6 @@ Network.formatName_ = function(compute, name) { }); }; -/** - * Create a network. - * - * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} - * @resource [Networks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/insert} - * - * @param {object} config - See a - * [Network resource](https://cloud.google.com/compute/docs/reference/v1/networks#resource). - * @param {string} config.gateway - A gateway address for default routing to - * other networks. (Alias for `config.gatewayIPv4`) - * @param {string} config.range - - * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range - * of addresses that are legal on this network. (Alias for - * `config.IPv4Range`) - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/network} callback.network - The created Network - * object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * range: '10.240.0.0/16' - * }; - * - * network.create(config, function(err, network, operation, apiResponse) { - * // `network` is a Network object. - * - * // `operation` is an Operation object and can be used to check the status - * // of network creation. - * }); - */ -Network.prototype.create = function() { - ServiceObject.prototype.create.apply(this, arguments); -}; - -/** - * Get a network if it exists. Also see {module:compute/network#getOrCreate}. - * - * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/network} callback.network - The Network object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * network.get(function(err, network, apiResponse) { - * // `network` is a Network object. - * }); - */ -Network.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Get a network if it exists, otherwise create one. - * - * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} - * @resource [Networks: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/insert} - * - * @param {object} config - See a - * [Network resource](https://cloud.google.com/compute/docs/reference/v1/networks#resource). - * @param {string} config.gateway - A gateway address for default routing to - * other networks. (Alias for `config.gatewayIPv4`) - * @param {string} config.range - - * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range - * of addresses that are legal on this network. (Alias for - * `config.IPv4Range`) - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/network} callback.network - The Network object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * var config = { - * range: '10.240.0.0/16' - * }; - * - * network.getOrCreate(config, function(err, network, operation, apiResponse) { - * // `network` is a Network object. - * - * // `operation` is an Operation object and can be used to check the status - * // of network creation. - * }); - */ -Network.prototype.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); -}; - /** * Create a firewall for this network. * @@ -366,22 +342,4 @@ Network.prototype.getFirewalls = function(options, callback) { return this.compute.getFirewalls(options, callback); }; -/** - * Get the network's metadata. - * - * @resource [Network Resource]{@link https://cloud.google.com/compute/docs/reference/v1/networks} - * @resource [Networks: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/networks/delete} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {object} callback.metadata - The network's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * network.getMetadata(function(err, metadata, apiResponse) {}); - */ -Network.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - module.exports = Network; diff --git a/lib/compute/operation.js b/lib/compute/operation.js index f07ec23d4fa..8954a592b1b 100644 --- a/lib/compute/operation.js +++ b/lib/compute/operation.js @@ -83,55 +83,45 @@ var util = require('../common/util.js'); function Operation(scope, name) { var isCompute = scope.constructor.name === 'Compute'; + var methods = { + /** + * Delete the operation. + * + * @resource [GlobalOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/globalOperations/delete} + * @resource [RegionOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/regionOperations/delete} + * @resource [ZoneOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/zoneOperations/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 + * operation.delete(function(err, apiResponse) {}); + */ + delete: true, + + /** + * Get an operation if it exists. + * + * @example + * operation.get(function(err, operation, apiResponse) { + * // `operation` is an Operation object. + * }); + */ + get: true + }; + ServiceObject.call(this, { parent: scope, baseUrl: isCompute ? '/global/operations' : '/operations', id: name, - exclude: ['create', 'getOrCreate', 'setMetadata'] + methods: methods }); } nodeutil.inherits(Operation, ServiceObject); -/** - * Get an operation if it exists. - * - * @resource [GlobalOperation Overview]{@link https://cloud.google.com/compute/docs/reference/v1/globalOperations} - * @resource [RegionOperation Overview]{@link https://cloud.google.com/compute/docs/reference/v1/regionOperations} - * @resource [ZoneOperations Overview]{@link https://cloud.google.com/compute/docs/reference/v1/zoneOperations} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/operation} callback.operation - The Operation object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * operation.get(function(err, operation, apiResponse) { - * // `operation` is an Operation object. - * }); - */ -Operation.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Delete the operation. - * - * @resource [GlobalOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/globalOperations/delete} - * @resource [RegionOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/regionOperations/delete} - * @resource [ZoneOperations: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/zoneOperations/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 - * operation.delete(function(err, apiResponse) {}); - */ -Operation.prototype.delete = function() { - ServiceObject.prototype.delete.apply(this, arguments); -}; - /** * Get the operation's metadata. For a detailed description of metadata see * [Operation resource](https://goo.gl/sWm1rt). diff --git a/lib/compute/region.js b/lib/compute/region.js index d3aee955bd1..5c0bfabf70d 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -73,35 +73,45 @@ var streamRouter = require('../common/stream-router.js'); * var region = gce.region('us-central1'); */ function Region(compute, name) { + var methods = { + /** + * Get a region. + * + * @example + * region.get(function(err, region, apiResponse) { + * // `region` is a Region object. + * }); + */ + get: true, + + /** + * Get the region's metadata. + * + * @resource [Region Resource]{@link https://cloud.google.com/compute/docs/reference/v1/regions} + * @resource [Regions: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/regions/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The region's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * region.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; + ServiceObject.call(this, { parent: compute, baseUrl: '/regions', id: name, - exclude: ['create', 'delete', 'getOrCreate', 'setMetadata'] + methods: methods }); } nodeutil.inherits(Region, ServiceObject); -/** - * Get a region. - * - * @resource [Regions & Zones Overview]{@link https://cloud.google.com/compute/docs/zones} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/region} callback.region - The Region object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * region.get(function(err, region, apiResponse) { - * // `region` is a Region object. - * }); - */ -Region.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - /** * Get a reference to a Google Compute Engine address in this region. * @@ -281,24 +291,6 @@ Region.prototype.getAddresses = function(options, callback) { }); }; -/** - * Get the region's metadata. - * - * @resource [Region Resource]{@link https://cloud.google.com/compute/docs/reference/v1/regions} - * @resource [Regions: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/regions/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The region's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * region.getMetadata(function(err, metadata, apiResponse) {}); - */ -Region.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Get a list of operations for this region. * diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index 599181d203c..4c5f5c812a2 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -67,35 +67,51 @@ var util = require('../common/util.js'); * var snapshot = disk.snapshot('disk-snapshot-name'); */ function Snapshot(scope, name) { - var isCompute = scope.constructor.name === 'Compute'; + var isDisk = scope.constructor.name === 'Disk'; + + var methods = { + /** + * Get a snapshot if it exists. + * + * @example + * snapshot.get(function(err, snapshot, apiResponse) { + * // `snapshot` is a Snapshot object. + * }); + */ + get: true, + + /** + * Get the snapshots's metadata. + * + * @resource [Snapshot Resource]{@link https://cloud.google.com/compute/docs/reference/v1/snapshots} + * @resource [Snapshots: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/snapshots/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The snapshot's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * snapshot.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; var config = { parent: scope, baseUrl: '/global/snapshots', id: name, - exclude: ['setMetadata'] + methods: methods }; - if (scope.createSnapshot) { + if (isDisk) { config.createMethod = scope.createSnapshot.bind(scope); /** * Create a snapshot. * - * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} - * @resource [Disks: createSnapshot API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot} - * - * @param {object=} options - See the - * [Disks: createSnapshot](https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot) - * request body. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this - * request. - * @param {module:compute/snapshot} callback.snapshot - The created Snapshot - * object. - * @param {module:compute/operation} callback.operation - An operation - * object that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. + * @param {object} config - See {module:compute/disk#createSnapshot}. * * @example * snapshot.create(function(err, snapshot, operation, apiResponse) { @@ -105,26 +121,12 @@ function Snapshot(scope, name) { * // status of the request. * }); */ - this.create = function() { - ServiceObject.prototype.create.apply(this, arguments); - }; + config.methods.create = true; /** * Get a snapshot if it exists, otherwise create one. * - * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} - * @resource [Disks: createSnapshot API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot} - * - * @param {object=} options - See the - * [Disks: createSnapshot](https://cloud.google.com/compute/docs/reference/v1/disks/createSnapshot) - * request body. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this - * request. - * @param {module:compute/snapshot} callback.snapshot - The Snapshot object. - * @param {module:compute/operation} callback.operation - An operation - * object that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. + * @param {object=} config - See {module:compute/disk#createSnapshot}. * * @example * snapshot.getOrCreate(function(err, snapshot, operation, apiResponse) { @@ -134,39 +136,16 @@ function Snapshot(scope, name) { * // status of the request. * }); */ - this.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); - }; - } else { - config.exclude = config.exclude.concat(['create', 'getOrCreate']); + config.methods.getOrCreate = true; } ServiceObject.call(this, config); - this.compute = isCompute ? scope : scope.compute; + this.compute = isDisk ? scope.compute : scope; } nodeutil.inherits(Snapshot, ServiceObject); -/** - * Get a snapshot if it exists. - * - * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/snapshot} callback.snapshot - The Snapshot object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * snapshot.get(function(err, snapshot, apiResponse) { - * // `snapshot` is a Snapshot object. - * }); - */ -Snapshot.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - /** * Delete the snapshot. * @@ -202,22 +181,4 @@ Snapshot.prototype.delete = function(callback) { }); }; -/** - * Get the snapshots's metadata. - * - * @resource [Snapshot Resource]{@link https://cloud.google.com/compute/docs/reference/v1/snapshots} - * @resource [Snapshots: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/snapshots/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The snapshot's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * snapshot.getMetadata(function(err, metadata, apiResponse) {}); - */ -Snapshot.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - module.exports = Snapshot; diff --git a/lib/compute/vm.js b/lib/compute/vm.js index 5d2905465f3..3af336e57ce 100644 --- a/lib/compute/vm.js +++ b/lib/compute/vm.js @@ -81,12 +81,63 @@ var DetachDiskError = createErrorClass('DetachDiskError', function(message) { * var vm = zone.vm('vm-name'); */ function VM(zone, name) { + var methods = { + /** + * Create a virtual machine. + * + * @param {object} config - See {module:compute/zone#createVM}. + * + * @example + * var config = { + * // ... + * }; + * + * vm.create(config, function(err, vm, operation, apiResponse) { + * // `vm` is a VM object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + create: true, + + /** + * Get a virtual machine if it exists. Also see + * {module:compute/vm#getOrCreate}. + * + * @example + * vm.get(function(err, vm, apiResponse) { + * // `vm` is a VM object. + * }); + */ + get: true, + + /** + * Get a virtual machine if it exists, otherwise create one. + * + * @param {object=} config - See {module:compute/zone#createVM}. + * + * @example + * var config = { + * // ... + * }; + * + * vm.getOrCreate(config, function(err, vm, operation, apiResponse) { + * // `vm` is a VM object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * }); + */ + getOrCreate: true + }; + ServiceObject.call(this, { parent: zone, baseUrl: '/instances', id: name, createMethod: zone.createVM.bind(zone), - exclude: ['setMetadata'] + methods: methods }); this.zone = zone; @@ -94,193 +145,6 @@ function VM(zone, name) { nodeutil.inherits(VM, ServiceObject); -/** - * Create a virtual machine. - * - * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} - * @resource [Instances: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/insert} - * - * @param {object} config - See an - * [Instance resource](https://cloud.google.com/compute/docs/reference/v1/instances). - * @param {object[]=} config.disks - See a - * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). - * @param {boolean=} config.http - Allow HTTP traffic. Default: `false` - * @param {boolean=} config.https - Allow HTTPS traffic. Default: `false` - * @param {object[]=} config.networkInterfaces - An array of configurations for - * this interface. This specifies how this interface should interact with - * other network services, such as connecting to the internet. Default: - * `[ { network: 'global/networks/default' } ]` - * @param {string=} config.machineType - The machine type resource to use. - * Provide only the name of the machine, e.g. `n1-standard-16`. Refer to - * [Available Machine Types](https://goo.gl/jrHEbo). Default: - * `n1-standard-1` - * @param {string=} config.os - Specify the name of an OS, and we will use the - * latest version as the source image of a new boot disk. See - * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). - * @param {string[]=} config.tags - An array of tags. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/vm} callback.vm - The created VM object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * //- - * // Create a new instance using the latest Debian version as the source image - * // for a new boot disk. - * //- - * var config = { - * os: 'debian', - * http: true, - * tags: ['debian-server'] - * }; - * - * //- - * // The above object will auto-expand behind the scenes to something like the - * // following. The Debian version may be different when you run the command. - * //- - * var config = { - * machineType: 'n1-standard-1', - * disks: [ - * { - * boot: true, - * initializeParams: { - * sourceImage: - * 'https://www.googleapis.com/compute/v1/projects' + - * '/debian-cloud/global/images/debian-7-wheezy-v20150710' - * } - * } - * ], - * networkInterfaces: [ - * { - * network: 'global/networks/default' - * } - * ], - * tags: [ - * { - * items: [ - * 'debian-server', - * 'http-server' - * ] - * } - * ] - * }; - * - * vm.create(config, function(err, vm, operation, apiResponse) { - * // `vm` is a VM object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -VM.prototype.create = function() { - ServiceObject.prototype.create.apply(this, arguments); -}; - -/** - * Get a virtual machine if it exists. Also see {module:compute/vm#getOrCreate}. - * - * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} - - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/vm} callback.vm - The VM object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * vm.get(function(err, vm, apiResponse) { - * // `vm` is a VM object. - * }); - */ -VM.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - -/** - * Get a virtual machine if it exists, otherwise create one. - * - * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} - * @resource [Instances: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/insert} - * - * @param {object} config - See an - * [Instance resource](https://cloud.google.com/compute/docs/reference/v1/instances). - * @param {object[]=} config.disks - See a - * [Disk resource](https://cloud.google.com/compute/docs/reference/v1/disks). - * @param {boolean=} config.http - Allow HTTP traffic. Default: `false` - * @param {boolean=} config.https - Allow HTTPS traffic. Default: `false` - * @param {object[]=} config.networkInterfaces - An array of configurations for - * this interface. This specifies how this interface should interact with - * other network services, such as connecting to the internet. Default: - * `[ { network: 'global/networks/default' } ]` - * @param {string=} config.machineType - The machine type resource to use. - * Provide only the name of the machine, e.g. `n1-standard-16`. Refer to - * [Available Machine Types](https://goo.gl/jrHEbo). Default: - * `n1-standard-1` - * @param {string=} config.os - Specify the name of an OS, and we will use the - * latest version as the source image of a new boot disk. See - * [this list of accepted OS names](https://github.com/stephenplusplus/gce-images#accepted-os-names). - * @param {string[]=} config.tags - An array of tags. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/vm} callback.vm - The created VM object. - * @param {module:compute/operation} callback.operation - An operation object - * that can be used to check the status of the request. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * //- - * // Create a new instance using the latest Debian version as the source image - * // for a new boot disk. - * //- - * var config = { - * os: 'debian', - * http: true, - * tags: ['debian-server'] - * }; - * - * //- - * // The above object will auto-expand behind the scenes to something like the - * // following. The Debian version may be different when you run the command. - * //- - * var config = { - * machineType: 'n1-standard-1', - * disks: [ - * { - * boot: true, - * initializeParams: { - * sourceImage: - * 'https://www.googleapis.com/compute/v1/projects' + - * '/debian-cloud/global/images/debian-7-wheezy-v20150710' - * } - * } - * ], - * networkInterfaces: [ - * { - * network: 'global/networks/default' - * } - * ], - * tags: [ - * { - * items: [ - * 'debian-server', - * 'http-server' - * ] - * } - * ] - * }; - * - * vm.getOrCreate(config, function(err, vm, operation, apiResponse) { - * // `vm` is a VM object. - * - * // `operation` is an Operation object that can be used to check the status - * // of the request. - * }); - */ -VM.prototype.getOrCreate = function() { - ServiceObject.prototype.getOrCreate.apply(this, arguments); -}; - /** * Attach a disk to the instance. * diff --git a/lib/compute/zone.js b/lib/compute/zone.js index a339f7f9bc6..8f30ba3ffd6 100644 --- a/lib/compute/zone.js +++ b/lib/compute/zone.js @@ -82,11 +82,40 @@ var VM = require('./vm.js'); * var zone = gce.zone('us-central1-a'); */ function Zone(compute, name) { + var methods = { + /** + * Get a zone. + * + * @example + * zone.get(function(err, zone, apiResponse) { + * // `zone` is a Zone object. + * }); + */ + get: true, + + /** + * Get the zone's metadata. + * + * @resource [Zone Resource]{@link https://cloud.google.com/compute/docs/reference/v1/zones} + * @resource [Zones: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/zones/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The zone's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * zone.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; + ServiceObject.call(this, { parent: compute, baseUrl: '/zones', id: name, - exclude: ['create', 'delete', 'getOrCreate', 'setMetadata'] + methods: methods }); this.compute = compute; @@ -99,25 +128,6 @@ function Zone(compute, name) { nodeutil.inherits(Zone, ServiceObject); -/** - * Get a zone. - * - * @resource [Regions & Zones Overview]{@link https://cloud.google.com/compute/docs/zones} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {module:compute/zone} callback.zone - The Zone object. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * zone.get(function(err, zone, apiResponse) { - * // `zone` is a Zone object. - * }); - */ -Zone.prototype.get = function() { - ServiceObject.prototype.get.apply(this, arguments); -}; - /** * Create a persistent disk in this zone. * @@ -516,24 +526,6 @@ Zone.prototype.getDisks = function(options, callback) { }); }; -/** - * Get the zone's metadata. - * - * @resource [Zone Resource]{@link https://cloud.google.com/compute/docs/reference/v1/zones} - * @resource [Zones: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/zones/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The zone's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * zone.getMetadata(function(err, metadata, apiResponse) {}); - */ -Zone.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Get a list of operations for this zone. * From 2b786d143db459cb5a2f09d092d5fffdd3ec9ff4 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Oct 2015 14:16:14 -0400 Subject: [PATCH 04/49] remove getorcreate --- lib/bigquery/dataset.js | 24 ++++----- lib/bigquery/table.js | 25 ++++------ lib/common/service-object.js | 27 +++++++++- lib/compute/address.js | 23 ++++----- lib/compute/disk.js | 32 ++++-------- lib/compute/firewall.js | 35 ++++--------- lib/compute/network.js | 35 ++++--------- lib/compute/snapshot.js | 24 ++++----- lib/compute/vm.js | 31 ++++-------- system-test/bigquery.js | 26 ++-------- system-test/compute.js | 97 +++++++++++------------------------- 11 files changed, 141 insertions(+), 238 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index b5ca551d19c..333a39c1eb3 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -67,8 +67,16 @@ function Dataset(bigQuery, id) { create: true, /** - * Get a dataset if it exists. Also see - * {module:bigquery/dataset#getOrCreate}. + * Get a dataset 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 * dataset.get(function(err, dataset, apiResponse) { @@ -95,18 +103,6 @@ function Dataset(bigQuery, id) { */ getMetadata: true, - /** - * Get a dataset if it exists, otherwise create one. - * - * @example - * dataset.getOrCreate(function(err, dataset, apiResponse) { - * if (!err) { - * // `dataset.metadata` has been populated. - * } - * }); - */ - getOrCreate: true, - /** * Sets the metadata of the Dataset object. * diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index 18dbf700401..49bc1230aee 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -112,7 +112,16 @@ function Table(dataset, id) { delete: true, /** - * Get a table if it exists. Also see {module:bigquery/table#getOrCreate}. + * Get a table 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 * table.get(function(err, table, apiResponse) { @@ -135,19 +144,7 @@ function Table(dataset, id) { * @example * table.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Get a table if it exists, otherwise create one. - * - * @param {object=} config - See {module:bigquery/dataset#createTable}. - * - * @example - * table.getOrCreate(config, function(err, table, apiResonse) { - * // `table.metadata` has been populated. - * }); - */ - getOrCreate: true + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/common/service-object.js b/lib/common/service-object.js index 540463ed4ea..2522f1c20d6 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -127,6 +127,8 @@ ServiceObject.prototype.delete = function(callback) { uri: '' }; + // The `request` method may have been overridden to hold any special behavior. + // Ensure we call the original `request` method. ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { callback(err, resp); }); @@ -140,11 +142,30 @@ ServiceObject.prototype.delete = function(callback) { * @param {object} callback.instance - The instance. * @param {object} callback.apiResponse - The full API response. */ -ServiceObject.prototype.get = function(callback) { +ServiceObject.prototype.get = function(config, callback) { var self = this; + if (is.fn(config)) { + callback = config; + config = {}; + } + + config = config || {}; + + var autoCreate = config.autoCreate; + delete config.autoCreate; + this.getMetadata(function(err, metadata) { if (err) { + if (err.code === 404 && autoCreate) { + if (!is.empty(config)) { + self.create(config, callback); + } else { + self.create(callback); + } + return; + } + callback(err, null, metadata); return; } @@ -169,6 +190,8 @@ ServiceObject.prototype.getMetadata = function(callback) { uri: '' }; + // The `request` method may have been overridden to hold any special behavior. + // Ensure we call the original `request` method. ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); @@ -201,6 +224,8 @@ ServiceObject.prototype.setMetadata = function(metadata, callback) { json: metadata }; + // The `request` method may have been overridden to hold any special behavior. + // Ensure we call the original `request` method. ServiceObject.prototype.request.call(this, reqOpts, function(err, resp) { if (err) { callback(err, resp); diff --git a/lib/compute/address.js b/lib/compute/address.js index a1522b08257..efa9077a716 100644 --- a/lib/compute/address.js +++ b/lib/compute/address.js @@ -81,6 +81,15 @@ function Address(region, name) { /** * Get an address 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 * address.get(function(err, address, apiResponse) { * // `address` is an Address object. @@ -103,19 +112,7 @@ function Address(region, name) { * @example * address.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Get an address if it exists, otherwise create one. - * - * @param {object=} options - See {module:compute#createAdresss}. - * - * @example - * address.getOrCreate(function(err, address, apiResponse) { - * // `address` is an Address object. - * }); - */ - getOrCreate: true + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 931f538336b..01a4b644e30 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -91,7 +91,16 @@ function Disk(zone, name) { create: true, /** - * Get a disk if it exists. Also see {module:compute/disk#getOrCreate}. + * Get a disk 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 * disk.get(function(err, disk, apiResponse) { @@ -115,26 +124,7 @@ function Disk(zone, name) { * @example * disk.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Get a disk if it exists, otherwise create one. - * - * @param {object=} config - See {module:compute#createDisk}. - * - * @example - * var config = { - * // ... - * }; - * - * disk.getOrCreate(config, function(err, disk, operation, apiResponse) { - * // `disk` is a Disk object. - * - * // `operation` is an Operation object that can be used to check the - * // status of the request. - * }); - */ - getOrCreate: true + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/compute/firewall.js b/lib/compute/firewall.js index d172c0f7893..f14d8d0cf69 100644 --- a/lib/compute/firewall.js +++ b/lib/compute/firewall.js @@ -81,8 +81,16 @@ function Firewall(compute, name) { create: true, /** - * Get a firewall if it exists. Also see - * {module:compute/firewall#getOrCreate}. + * Get a firewall 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 * firewall.get(function(err, firewall, apiResponse) { @@ -106,28 +114,7 @@ function Firewall(compute, name) { * @example * firewall.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Get a firewall if it exists, otherwise create one. - * - * @param {object=} config - See {module:compute#createFireall}. - * - * @example - * var config = { - * // ... - * }; - * - * function callback(err, firewall, operation, apiResponse) { - * // `firewall` is a Firewall object. - * - * // `operation` is an Operation object that can be used to check the - * // status of the request. - * } - * - * firewall.getOrCreate(config, callback); - */ - getOrCreate: true + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/compute/network.js b/lib/compute/network.js index ec53553b5e0..ddb2e0e2866 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -83,8 +83,16 @@ function Network(compute, name) { create: true, /** - * Get a network if it exists. Also see - * {module:compute/network#getOrCreate}. + * Get a network 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 * network.get(function(err, network, apiResponse) { @@ -108,28 +116,7 @@ function Network(compute, name) { * @example * network.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Get a network if it exists, otherwise create one. - * - * @param {object=} config - See {module:compute#createNetwork}. - * - * @example - * var config = { - * // ... - * }; - * - * function callback(err, network, operation, apiResponse) { - * // `network` is a Network object. - * - * // `operation` is an Operation object that can be used to check the - * // status of network creation. - * } - * - * network.getOrCreate(config, callback); - */ - getOrCreate: true, + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index 4c5f5c812a2..67c7d9fabe4 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -73,6 +73,15 @@ function Snapshot(scope, name) { /** * Get a snapshot if it exists. * + * If you access this snapshot through a Disk object, this can be used as a + * "get or create" method. Pass 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 * snapshot.get(function(err, snapshot, apiResponse) { * // `snapshot` is a Snapshot object. @@ -122,21 +131,6 @@ function Snapshot(scope, name) { * }); */ config.methods.create = true; - - /** - * Get a snapshot if it exists, otherwise create one. - * - * @param {object=} config - See {module:compute/disk#createSnapshot}. - * - * @example - * snapshot.getOrCreate(function(err, snapshot, operation, apiResponse) { - * // `snapshot` is a Snapshot object. - * - * // `operation` is an Operation object that can be used to check the - * // status of the request. - * }); - */ - config.methods.getOrCreate = true; } ServiceObject.call(this, config); diff --git a/lib/compute/vm.js b/lib/compute/vm.js index 3af336e57ce..059a30233ad 100644 --- a/lib/compute/vm.js +++ b/lib/compute/vm.js @@ -102,34 +102,23 @@ function VM(zone, name) { create: true, /** - * Get a virtual machine if it exists. Also see - * {module:compute/vm#getOrCreate}. + * Get a virtual machine if it exists. * - * @example - * vm.get(function(err, vm, apiResponse) { - * // `vm` is a VM object. - * }); - */ - get: true, - - /** - * Get a virtual machine if it exists, otherwise create one. + * 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 {object=} config - See {module:compute/zone#createVM}. + * @param {options=} options - Configuration object. + * @param {boolean} options.autoCreate - Automatically create the object if + * it does not exist. Default: `false` * * @example - * var config = { - * // ... - * }; - * - * vm.getOrCreate(config, function(err, vm, operation, apiResponse) { + * vm.get(function(err, vm, apiResponse) { * // `vm` is a VM object. - * - * // `operation` is an Operation object that can be used to check the - * // status of the request. * }); */ - getOrCreate: true + get: true }; ServiceObject.call(this, { diff --git a/system-test/bigquery.js b/system-test/bigquery.js index a7b5238d965..0a89611c04c 100644 --- a/system-test/bigquery.js +++ b/system-test/bigquery.js @@ -31,9 +31,9 @@ var storage = gcloud.storage(); describe('BigQuery', function() { var DATASET_ID = ('gcloud_test_dataset_temp' + uuid.v1()).replace(/-/g, '_'); - var dataset; + var dataset = bigquery.dataset(DATASET_ID); var TABLE_ID = 'myKittens'; - var table; + var table = dataset.table(TABLE_ID); var BUCKET_NAME = 'gcloud-test-bucket-temp-' + uuid.v1(); var bucket; @@ -43,30 +43,12 @@ describe('BigQuery', function() { async.series([ // Create the test dataset. function(next) { - bigquery.dataset(DATASET_ID).create(function(err, dataset_) { - if (err) { - next(err); - return; - } - - dataset = dataset_; - next(); - }); + dataset.create(next); }, // Create the test table. function(next) { - dataset.table(TABLE_ID).create({ - schema: 'id:integer,breed,name,dob:timestamp' - }, function(err, t) { - if (err) { - next(err); - return; - } - - table = t; - next(); - }); + table.create({ schema: 'id:integer,breed,name,dob:timestamp' }, next); }, // Create a Bucket. diff --git a/system-test/compute.js b/system-test/compute.js index 3b9c34ef904..de324ded4c7 100644 --- a/system-test/compute.js +++ b/system-test/compute.js @@ -54,15 +54,12 @@ describe('Compute', function() { }); describe('addresses', function() { - var ADDRESS_NAME; - var address; + var ADDRESS_NAME = generateName(); + var address = region.address(ADDRESS_NAME); before(function(done) { - ADDRESS_NAME = generateName(); - - region.address(ADDRESS_NAME).create(function(err, address_, operation) { + address.create(function(err, disk, operation) { assert.ifError(err); - address = address_; operation.onComplete(done); }); }); @@ -103,19 +100,16 @@ describe('Compute', function() { }); describe('disks', function() { - var DISK_NAME; - var disk; + var DISK_NAME = generateName(); + var disk = zone.disk(DISK_NAME); before(function(done) { - DISK_NAME = generateName(); - var config = { os: 'ubuntu' }; - zone.disk(DISK_NAME).create(config, function(err, disk_, operation) { + disk.create(config, function(err, disk, operation) { assert.ifError(err); - disk = disk_; operation.onComplete(done); }); }); @@ -166,7 +160,8 @@ describe('Compute', function() { }); describe('firewalls', function() { - var FIREWALL_NAME; + var FIREWALL_NAME = generateName(); + var firewall = compute.firewall(FIREWALL_NAME); var CONFIG = { protocols: { @@ -191,18 +186,11 @@ describe('Compute', function() { sourceRanges: CONFIG.ranges }; - var firewall; - before(function(done) { - FIREWALL_NAME = generateName(); - - compute - .firewall(FIREWALL_NAME) - .create(CONFIG, function(err, firewall_, operation) { - assert.ifError(err); - firewall = firewall_; - operation.onComplete(done); - }); + firewall.create(CONFIG, function(err, firewall, operation) { + assert.ifError(err); + operation.onComplete(done); + }); }); it('should have opened the correct connections', function(done) { @@ -238,24 +226,18 @@ describe('Compute', function() { }); describe('networks', function() { - var NETWORK_NAME; + var NETWORK_NAME = generateName(); + var network = compute.network(NETWORK_NAME); var CONFIG = { range: '10.240.0.0/16' }; - var network; - before(function(done) { - NETWORK_NAME = generateName(); - - compute - .network(NETWORK_NAME) - .create(CONFIG, function(err, network_, operation) { - assert.ifError(err); - network = network_; - operation.onComplete(done); - }); + network.create(CONFIG, function(err, network, operation) { + assert.ifError(err); + operation.onComplete(done); + }); }); it('should have opened the correct range', function(done) { @@ -406,20 +388,17 @@ describe('Compute', function() { }); describe('vms', function() { - var VM_NAME; - var vm; + var VM_NAME = generateName(); + var vm = zone.vm(VM_NAME); before(function(done) { - VM_NAME = generateName(); - var config = { os: 'ubuntu', http: true }; - zone.vm(VM_NAME).create(config, function(err, vm_, operation) { + vm.create(config, function(err, vm, operation) { assert.ifError(err); - vm = vm_; operation.onComplete(done); }); }); @@ -477,7 +456,7 @@ describe('Compute', function() { }); it('should attach and detach a disk', function(done) { - var disk; + var disk = zone.disk(generateName()); // This test waits on a lot of operations. this.timeout(90000); @@ -489,27 +468,11 @@ describe('Compute', function() { ], done); function createDisk(callback) { - var diskName = generateName(); var config = { os: 'ubuntu' }; - zone.disk(diskName).create(config, function(err, disk_, operation) { - if (err) { - callback(err); - return; - } - - operation.onComplete(function(err) { - if (err) { - callback(err); - return; - } - - disk = disk_; - callback(); - }); - }); + disk.create(config, execAfterOperationComplete(callback)); } function attachDisk(callback) { @@ -533,19 +496,15 @@ describe('Compute', function() { tags.push(newTagName); - vm.setTags(tags, fingerprint, function(err, operation) { + vm.setTags(tags, fingerprint, execAfterOperationComplete(function(err) { assert.ifError(err); - operation.onComplete(function(err) { + vm.getTags(function(err, tags) { assert.ifError(err); - - vm.getTags(function(err, tags) { - assert.ifError(err); - assert(tags.indexOf(newTagName) > -1); - done(); - }); + assert(tags.indexOf(newTagName) > -1); + done(); }); - }); + })); }); }); From 55c63fd9d7b68cc9077710234697dace8e0be67d Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Oct 2015 14:38:21 -0400 Subject: [PATCH 05/49] add exists() --- lib/bigquery/dataset.js | 13 +++++++++++++ lib/bigquery/job.js | 13 +++++++++++++ lib/bigquery/table.js | 13 +++++++++++++ lib/common/service-object.js | 30 +++++++++++++++++++++++++++++- lib/compute/address.js | 13 +++++++++++++ lib/compute/disk.js | 13 +++++++++++++ lib/compute/firewall.js | 13 +++++++++++++ lib/compute/network.js | 13 +++++++++++++ lib/compute/operation.js | 13 +++++++++++++ lib/compute/region.js | 13 +++++++++++++ lib/compute/snapshot.js | 13 +++++++++++++ lib/compute/vm.js | 13 +++++++++++++ lib/compute/zone.js | 13 +++++++++++++ 13 files changed, 185 insertions(+), 1 deletion(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 333a39c1eb3..580963c0116 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -66,6 +66,19 @@ function Dataset(bigQuery, id) { */ create: true, + /** + * Check if the dataset exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the dataset exists or not. + * + * @example + * dataset.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a dataset if it exists. * diff --git a/lib/bigquery/job.js b/lib/bigquery/job.js index 95827f106b9..ba17e7bb796 100644 --- a/lib/bigquery/job.js +++ b/lib/bigquery/job.js @@ -59,6 +59,19 @@ var ServiceObject = require('../common/service-object.js'); */ function Job(bigQuery, id) { var methods = { + /** + * Check if the job exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the job exists or not. + * + * @example + * job.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a job if it exists. * diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index 49bc1230aee..acf7b72ef37 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -111,6 +111,19 @@ function Table(dataset, id) { */ delete: true, + /** + * Check if the table exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the table exists or not. + * + * @example + * table.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a table if it exists. * diff --git a/lib/common/service-object.js b/lib/common/service-object.js index 2522f1c20d6..379c5e9bfeb 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -135,10 +135,38 @@ ServiceObject.prototype.delete = function(callback) { }; /** - * Get the object if it exists. + * Check if the object exists. * * @param {function} callback - The callback function. * @param {?error} callback.err - An error returned while making this request. + * @param {boolean} callback.exists - Whether the object exists or not. + */ +ServiceObject.prototype.exists = function(callback) { + this.get(function(err) { + if (err) { + if (err.code === 404) { + callback(null, false); + } else { + callback(err); + } + + return; + } + + callback(true); + }); +}; + +/** + * Get the object if it exists. Optionally have the object created if an options + * object is provided with `autoCreate: true`. + * + * @param {object=} config - The configuration object that will be used to + * create the object if necessary. + * @param {boolean} config.autoCreate - Create the object if it doesn't already + * exist. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. * @param {object} callback.instance - The instance. * @param {object} callback.apiResponse - The full API response. */ diff --git a/lib/compute/address.js b/lib/compute/address.js index efa9077a716..7f09a739f6f 100644 --- a/lib/compute/address.js +++ b/lib/compute/address.js @@ -78,6 +78,19 @@ function Address(region, name) { */ create: true, + /** + * Check if the address exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the address exists or not. + * + * @example + * address.exists(function(err, exists) {}); + */ + exists: true, + /** * Get an address if it exists. * diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 01a4b644e30..9e3b103c94b 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -90,6 +90,19 @@ function Disk(zone, name) { */ create: true, + /** + * Check if the disk exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the disk exists or not. + * + * @example + * disk.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a disk if it exists. * diff --git a/lib/compute/firewall.js b/lib/compute/firewall.js index f14d8d0cf69..8832cfc4ca5 100644 --- a/lib/compute/firewall.js +++ b/lib/compute/firewall.js @@ -80,6 +80,19 @@ function Firewall(compute, name) { */ create: true, + /** + * Check if the firewall exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the firewall exists or not. + * + * @example + * firewall.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a firewall if it exists. * diff --git a/lib/compute/network.js b/lib/compute/network.js index ddb2e0e2866..8071946704f 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -82,6 +82,19 @@ function Network(compute, name) { */ create: true, + /** + * Check if the network exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the network exists or not. + * + * @example + * network.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a network if it exists. * diff --git a/lib/compute/operation.js b/lib/compute/operation.js index 8954a592b1b..5eebd483ddd 100644 --- a/lib/compute/operation.js +++ b/lib/compute/operation.js @@ -101,6 +101,19 @@ function Operation(scope, name) { */ delete: true, + /** + * Check if the operation exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the operation exists or not. + * + * @example + * operation.exists(function(err, exists) {}); + */ + exists: true, + /** * Get an operation if it exists. * diff --git a/lib/compute/region.js b/lib/compute/region.js index 5c0bfabf70d..575bc432986 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -74,6 +74,19 @@ var streamRouter = require('../common/stream-router.js'); */ function Region(compute, name) { var methods = { + /** + * Check if the region exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the region exists or not. + * + * @example + * region.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a region. * diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index 67c7d9fabe4..fc1ff745ff5 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -70,6 +70,19 @@ function Snapshot(scope, name) { var isDisk = scope.constructor.name === 'Disk'; var methods = { + /** + * Check if the snapshot exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the snapshot exists or not. + * + * @example + * snapshot.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a snapshot if it exists. * diff --git a/lib/compute/vm.js b/lib/compute/vm.js index 059a30233ad..3256c18f7db 100644 --- a/lib/compute/vm.js +++ b/lib/compute/vm.js @@ -101,6 +101,19 @@ function VM(zone, name) { */ create: true, + /** + * Check if the vm exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the vm exists or not. + * + * @example + * vm.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a virtual machine if it exists. * diff --git a/lib/compute/zone.js b/lib/compute/zone.js index 8f30ba3ffd6..4a7db88ff41 100644 --- a/lib/compute/zone.js +++ b/lib/compute/zone.js @@ -83,6 +83,19 @@ var VM = require('./vm.js'); */ function Zone(compute, name) { var methods = { + /** + * Check if the zone exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the zone exists or not. + * + * @example + * zone.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a zone. * From 4700d8077cddffcf971d75f418ef134fc4473c23 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 23 Oct 2015 16:30:45 -0400 Subject: [PATCH 06/49] dns --- lib/bigquery/dataset.js | 4 +- lib/bigquery/table.js | 4 +- lib/dns/change.js | 107 ++++++++++++++++----------- lib/dns/index.js | 76 +++++++------------- lib/dns/zone.js | 155 ++++++++++++++++++++++++++-------------- system-test/dns.js | 125 +++++++++++++------------------- 6 files changed, 249 insertions(+), 222 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 580963c0116..99caed4286c 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -61,7 +61,9 @@ function Dataset(bigQuery, id) { * * @example * dataset.create(function(err, dataset, apiResponse) { - * // `dataset.metadata` has been populated. + * if (!err) { + * // The dataset was created successfully. + * } * }); */ create: true, diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index acf7b72ef37..3b9d9a1a22f 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -91,7 +91,9 @@ function Table(dataset, id) { * }; * * table.create(config, function(err, table, apiResponse) { - * // `table.metadata` has been populated. + * if (!err) { + * // The table was created successfully. + * } * }); */ create: true, diff --git a/lib/dns/change.js b/lib/dns/change.js index f069cadd866..85062b5b76f 100644 --- a/lib/dns/change.js +++ b/lib/dns/change.js @@ -20,6 +20,14 @@ 'use strict'; +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @constructor * @alias module:dns/change @@ -39,51 +47,66 @@ * var change = zone.change('change-id'); */ function Change(zone, id) { - this.zoneName = zone.name; - this.id = id; - - this.metadata = {}; - this.makeReq_ = zone.dns.makeReq_.bind(zone.dns); -} - -/** - * Get the metadata for the change in the zone. - * - * @resource [Changes: get API Documentation]{@link https://cloud.google.com/dns/api/v1/changes/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An API error. - * @param {?object} callback.metadata - Metadata of the change from the API. - * @param {object} callback.apiResponse - Raw API response. - * - * @example - * change.getMetadata(function(err, metadata, apiResponse) { - * if (!err) { - * // metadata = { - * // kind: 'dns#change', - * // additions: [{...}], - * // deletions: [{...}], - * // startTime: '2015-07-21T14:40:06.056Z', - * // id: '1', - * // status: 'done' - * // } - * } - * }); - */ -Change.prototype.getMetadata = function(callback) { - var self = this; - var path = '/managedZones/' + this.zoneName + '/changes/' + this.id; + var methods = { + /** + * Check if the change exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the change exists or not. + * + * @example + * change.exists(function(err, exists) {}); + */ + exists: true, - this.makeReq_('GET', path, null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + /** + * Get a change if it exists. + * + * @example + * change.get(function(err, change, apiResponse) { + * // `change.metadata` has been populated. + * }); + */ + get: true, - self.metadata = resp; + /** + * Get the metadata for the change in the zone. + * + * @resource [Changes: get API Documentation]{@link https://cloud.google.com/dns/api/v1/changes/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An API error. + * @param {?object} callback.metadata - Metadata of the change from the API. + * @param {object} callback.apiResponse - Raw API response. + * + * @example + * change.getMetadata(function(err, metadata, apiResponse) { + * if (!err) { + * // metadata = { + * // kind: 'dns#change', + * // additions: [{...}], + * // deletions: [{...}], + * // startTime: '2015-07-21T14:40:06.056Z', + * // id: '1', + * // status: 'done' + * // } + * } + * }); + */ + getMetadata: true + }; - callback(null, self.metadata, resp); + ServiceObject.call(this, { + parent: zone, + baseUrl: '/changes', + id: id, + createMethod: zone.createChange.bind(zone), + methods: methods }); -}; +} + +nodeutil.inherits(Change, ServiceObject); module.exports = Change; diff --git a/lib/dns/index.js b/lib/dns/index.js index 24a81b5f942..3dfa96031dd 100644 --- a/lib/dns/index.js +++ b/lib/dns/index.js @@ -22,6 +22,13 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/service} + * @private + */ +var Service = require('../common/service.js'); /** * @type {module:common/streamrouter} @@ -41,21 +48,6 @@ var util = require('../common/util.js'); */ var Zone = require('./zone.js'); -/** - * @const {string} Base URL for DNS API. - * @private - */ -var DNS_BASE_URL = 'https://www.googleapis.com/dns/v1/projects/'; - -/** - * @const {array} Required scopes for the DNS API. - * @private - */ -var SCOPES = [ - 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', - 'https://www.googleapis.com/auth/cloud-platform' -]; - /** * [Google Cloud DNS](https://cloud.google.com/dns/what-is-cloud-dns) is a high- * performance, resilient, global DNS service that provides a cost-effective way @@ -82,16 +74,19 @@ function DNS(options) { return new DNS(options); } - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - keyFile: options.keyFilename, - scopes: SCOPES, - email: options.email - }); + var config = { + baseUrl: 'https://www.googleapis.com/dns/v1/projects', + scopes: [ + 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', + 'https://www.googleapis.com/auth/cloud-platform' + ] + }; - this.projectId_ = options.projectId; + Service.call(this, config, options); } +nodeutil.inherits(DNS, Service); + /** * Create a managed zone. * @@ -135,7 +130,11 @@ DNS.prototype.createZone = function(name, config, callback) { // Required by the API. config.description = config.description || ''; - this.makeReq_('POST', '/managedZones', null, config, function(err, resp) { + this.request({ + method: 'POST', + uri: '/managedZones', + json: config + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -173,7 +172,10 @@ DNS.prototype.getZones = function(query, callback) { query = {}; } - this.makeReq_('GET', '/managedZones', query, null, function(err, resp) { + this.request({ + uri: '/managedZones', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -216,32 +218,6 @@ DNS.prototype.zone = function(name) { return new Zone(this, name); }; -/** - * 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. - */ -DNS.prototype.makeReq_ = function(method, path, query, body, callback) { - var reqOpts = { - method: method, - qs: query, - uri: DNS_BASE_URL + this.projectId_ + path - }; - - if (body) { - reqOpts.json = body; - } - - this.makeAuthenticatedRequest_(reqOpts, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/dns/zone.js b/lib/dns/zone.js index af93c55852e..7f473ff23aa 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -25,6 +25,7 @@ var exec = require('methmeth'); var extend = require('extend'); var fs = require('fs'); var is = require('is'); +var nodeutil = require('util'); var zonefile = require('dns-zonefile'); /** @@ -39,6 +40,12 @@ var Change = require('./change.js'); */ var Record = require('./record.js'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/streamrouter} * @private @@ -64,13 +71,82 @@ var streamRouter = require('../common/stream-router.js'); * var zone = dns.zone('zone-id'); */ function Zone(dns, name) { - this.dns = dns; - this.name = name; - this.metadata = {}; + var methods = { + /** + * Create a zone. + * + * @param {object} config - See {module:dns#createZone}. + * + * @example + * zone.create(function(err, zone, apiResponse) { + * if (!err) { + * // The zone was created successfully. + * } + * }); + */ + create: true, + + /** + * Check if the zone exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the zone exists or not. + * + * @example + * zone.exists(function(err, exists) {}); + */ + exists: true, + + /** + * Get a zone 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 + * zone.get(function(err, zone, apiResponse) { + * // `zone.metadata` has been populated. + * }); + */ + get: true, + + /** + * Get the metadata for the zone. + * + * @resource [ManagedZones: get API Documentation]{@link https://cloud.google.com/dns/api/v1/managedZones/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An API error. + * @param {?object} callback.metadata - Metadata of the zone from the API. + * @param {object} callback.apiResponse - Raw API response. + * + * @example + * zone.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; + + ServiceObject.call(this, { + parent: dns, + baseUrl: '/managedZones', + id: name, + createMethod: dns.createZone.bind(dns), + methods: methods + }); - this.makeReq_ = this.dns.makeReq_.bind(dns); + this.name = name; } +nodeutil.inherits(Zone, ServiceObject); + /** * Add records to this zone. This is a convenience wrapper around * {module:dns/zone#createChange}. @@ -113,10 +189,10 @@ Zone.prototype.change = function(id) { * * @resource [ManagedZones: create API Documentation]{@link https://cloud.google.com/dns/api/v1/managedZones/create} * - * @param {object} options - The configuration object. - * @param {module:dns/record|module:dns/record[]} options.add - Record objects + * @param {object} config - The configuration object. + * @param {module:dns/record|module:dns/record[]} config.add - Record objects * to add to this zone. - * @param {module:dns/record|module:dns/record[]} options.delete - Record + * @param {module:dns/record|module:dns/record[]} config.delete - Record * objects to delete from this zone. Be aware that the resource records here * must match exactly to be deleted. * @param {function} callback - The callback function. @@ -146,24 +222,26 @@ Zone.prototype.change = function(id) { * } * }); */ -Zone.prototype.createChange = function(options, callback) { +Zone.prototype.createChange = function(config, callback) { var self = this; - if (!options || !options.add && !options.delete) { + if (!config || !config.add && !config.delete) { throw new Error('Cannot create a change with no additions or deletions.'); } - var body = extend({}, options, { - additions: arrify(options.add).map(exec('toJSON')), - deletions: arrify(options.delete).map(exec('toJSON')) + var body = extend({}, config, { + additions: arrify(config.add).map(exec('toJSON')), + deletions: arrify(config.delete).map(exec('toJSON')) }); delete body.add; delete body.delete; - var path = '/managedZones/' + this.name + '/changes'; - - this.makeReq_('POST', path, null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/changes', + json: body + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -221,10 +299,7 @@ Zone.prototype.delete = function(options, callback) { return; } - var path = '/managedZones/' + this.name; - this.makeReq_('DELETE', path, null, null, function(err, resp) { - callback(err, resp); - }); + ServiceObject.prototype.delete.call(this, callback); }; /** @@ -444,9 +519,10 @@ Zone.prototype.getChanges = function(query, callback) { delete query.sort; } - var path = '/managedZones/' + this.name + '/changes'; - - this.makeReq_('GET', path, query, null, function(err, resp) { + this.request({ + uri: '/changes', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -469,35 +545,6 @@ Zone.prototype.getChanges = function(query, callback) { }); }; -/** - * Get the metadata for the zone. - * - * @resource [ManagedZones: get API Documentation]{@link https://cloud.google.com/dns/api/v1/managedZones/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An API error. - * @param {?object} callback.metadata - Metadata of the zone from the API. - * @param {object} callback.apiResponse - Raw API response. - * - * @example - * zone.getMetadata(function(err, metadata, apiResponse) {}); - */ -Zone.prototype.getMetadata = function(callback) { - var self = this; - var path = '/managedZones/' + this.name; - - this.makeReq_('GET', path, null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); -}; - /** * Get the list of records for this zone. * @@ -606,8 +653,10 @@ Zone.prototype.getRecords = function(query, callback) { var requestQuery = extend({}, query); delete requestQuery.filterByTypes_; - var path = '/managedZones/' + this.name + '/rrsets'; - this.makeReq_('GET', path, requestQuery, true, function(err, resp) { + this.request({ + uri: '/rrsets', + qs: requestQuery + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; diff --git a/system-test/dns.js b/system-test/dns.js index 136ba3655f8..66e5f3268c8 100644 --- a/system-test/dns.js +++ b/system-test/dns.js @@ -32,61 +32,59 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; // Only run the tests if there is a domain to test with. (DNS_DOMAIN ? describe : describe.skip)('dns', function() { - var ZONE; - var ZONENAME = 'test-zone-' + uuid.v4().substr(0, 18); + var ZONE_NAME = 'test-zone-' + uuid.v4().substr(0, 18); + var ZONE = dns.zone(ZONE_NAME); - var records = {}; - - function createRecords() { - records.a = ZONE.record('a', { + var records = { + a: ZONE.record('a', { ttl: 86400, name: DNS_DOMAIN, data: '1.2.3.4' - }); + }), - records.aaaa = ZONE.record('aaaa', { + aaaa: ZONE.record('aaaa', { ttl: 86400, name: DNS_DOMAIN, data: '2607:f8b0:400a:801::1005' - }); + }), - records.cname = ZONE.record('cname', { + cname: ZONE.record('cname', { ttl: 86400, name: 'mail.' + DNS_DOMAIN, data: 'example.com.' - }); + }), - records.mx = ZONE.record('mx', { + mx: ZONE.record('mx', { ttl: 86400, name: DNS_DOMAIN, data: [ '10 mail.' + DNS_DOMAIN, '20 mail2.' + DNS_DOMAIN ] - }); + }), - records.naptr = ZONE.record('naptr', { + naptr: ZONE.record('naptr', { ttl: 300, name: '2.1.2.1.5.5.5.0.7.7.1.e164.arpa.', data: [ '100 10 \"u\" \"sip+E2U\" \"!^.*$!sip:information@foo.se!i\" .', '102 10 \"u\" \"smtp+E2U\" \"!^.*$!mailto:information@foo.se!i\" .' ] - }); + }), - records.ns = ZONE.record('ns', { + ns: ZONE.record('ns', { ttl: 86400, name: DNS_DOMAIN, data: 'ns-cloud1.googledomains.com.' - }); + }), - records.ptr = ZONE.record('ptr', { + ptr: ZONE.record('ptr', { ttl: 60, name: '2.1.0.10.in-addr.arpa.', data: 'server.' + DNS_DOMAIN - }); + }), - records.soa = ZONE.record('soa', { + soa: ZONE.record('soa', { ttl: 21600, name: DNS_DOMAIN, data: [ @@ -94,26 +92,26 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; 'dns-admin.google.com.', '1 21600 3600 1209600 300' ].join(' ') - }); + }), - records.spf = ZONE.record('spf', { + spf: ZONE.record('spf', { ttl: 21600, name: DNS_DOMAIN, data: 'v=spf1 mx:' + DNS_DOMAIN.replace(/.$/, '') + ' -all' - }); + }), - records.srv = ZONE.record('srv', { + srv: ZONE.record('srv', { ttl: 21600, name: 'sip.' + DNS_DOMAIN, data: '0 5 5060 sip.' + DNS_DOMAIN - }); + }), - records.txt = ZONE.record('txt', { + txt: ZONE.record('txt', { ttl: 21600, name: DNS_DOMAIN, data: 'google-site-verification=xxxxxxxxxxxxYYYYYYXXX' - }); - } + }) + }; before(function(done) { dns.getZones(function(err, zones) { @@ -128,12 +126,7 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; return; } - dns.createZone(ZONENAME, { dnsName: DNS_DOMAIN }, function(err, zone) { - assert.ifError(err); - ZONE = zone; - createRecords(); - done(); - }); + ZONE.create({ dnsName: DNS_DOMAIN }, done); }); }); }); @@ -142,16 +135,6 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; ZONE.delete({ force: true }, done); }); - it('should create a zone', function(done) { - var tempName = 'test-zone-' + uuid.v4().substr(0, 18); - - dns.createZone(tempName, { dnsName: DNS_DOMAIN }, function(err, zone) { - assert.ifError(err); - assert.equal(zone.name, tempName); - zone.delete(done); - }); - }); - it('should return 0 or more zones', function(done) { dns.getZones(function(err, zones) { assert.ifError(err); @@ -164,20 +147,11 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; it('should get the metadata for a zone', function(done) { ZONE.getMetadata(function(err, metadata) { assert.ifError(err); - assert.equal(metadata.name, ZONENAME); + assert.equal(metadata.name, ZONE_NAME); done(); }); }); - it('should delete a zone', function(done) { - var name = 'test-zone-' + uuid.v4().substr(0, 18); - - dns.createZone(name, { dnsName: DNS_DOMAIN }, function(err, zone) { - assert.ifError(err); - zone.delete(done); - }); - }); - it('should support all types of records', function(done) { var recordsToCreate = [ records.a, @@ -249,23 +223,23 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; assert.ifError(err); async.series([ - function(next) { - ZONE.empty(next); - }, - - function(next) { - var recordsToCreate = [ - records.spf, - records.srv - ]; - - ZONE.addRecords(recordsToCreate, next); - }, - - function(next) { - ZONE.export(tmpFilename, next); - } - ], done); + function(next) { + ZONE.empty(next); + }, + + function(next) { + var recordsToCreate = [ + records.spf, + records.srv + ]; + + ZONE.addRecords(recordsToCreate, next); + }, + + function(next) { + ZONE.export(tmpFilename, next); + } + ], done); }); }); @@ -343,25 +317,26 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; ZONE.replaceRecords('cname', newRecords, function(err) { assert.ifError(err); - var callback = function(err, records, nextQuery) { + function onRecordsReceived(err, records, nextQuery) { if (nextQuery) { - ZONE.getRecords(nextQuery, callback); + ZONE.getRecords(nextQuery, onRecordsReceived); return; } ZONE.deleteRecords(newRecords, done); - }; + } ZONE.getRecords({ types: 'cname', maxResults: 2 - }, callback); + }, onRecordsReceived); }); }); it('should replace records', function(done) { var name = 'test-zone-' + uuid.v4().substr(0, 18); + // Do this in a new zone so no existing records are affected. dns.createZone(name, { dnsName: DNS_DOMAIN }, function(err, zone) { assert.ifError(err); From cb00b8a6015153be366ff368ab07714c00b24396 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Sat, 24 Oct 2015 15:43:55 -0400 Subject: [PATCH 07/49] pubsub --- lib/bigquery/index.js | 2 +- lib/common/service-object.js | 25 +- lib/common/service.js | 10 +- lib/compute/index.js | 2 +- lib/dns/index.js | 2 +- lib/pubsub/iam.js | 61 +++-- lib/pubsub/index.js | 434 +++++++++++++++++------------------ lib/pubsub/subscription.js | 126 ++++++---- lib/pubsub/topic.js | 109 +++++---- package.json | 1 + system-test/pubsub.js | 46 ++-- 11 files changed, 439 insertions(+), 379 deletions(-) diff --git a/lib/bigquery/index.js b/lib/bigquery/index.js index ed62ee0a4bc..c071e22db36 100644 --- a/lib/bigquery/index.js +++ b/lib/bigquery/index.js @@ -94,7 +94,7 @@ function BigQuery(options) { } var config = { - baseUrl: 'https://www.googleapis.com/bigquery/v2/projects', + baseUrl: 'https://www.googleapis.com/bigquery/v2', scopes: ['https://www.googleapis.com/auth/bigquery'] }; diff --git a/lib/common/service-object.js b/lib/common/service-object.js index 379c5e9bfeb..db2643d5742 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -47,7 +47,7 @@ var util = require('./util.js'); * @param {string} config.createMethod - The method which creates this object. * @param {string} config.id - The identifier of the object. For example, the * name of a Storage bucket or Pub/Sub topic. - * @param {object} config.methods - A map of each method name that should be + * @param {object=} config.methods - A map of each method name that should be * inherited. * @param {object} config.parent - The parent service instance. For example, an * instance of Storage if the object is Bucket. @@ -62,17 +62,18 @@ function ServiceObject(config) { this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc.) this.createMethod = config.createMethod; - // Itera - var allMethodNames = Object.keys(ServiceObject.prototype); - allMethodNames - .filter(function(methodName) { - return methodName !== 'request' && // All ServiceObjects need `request`. - !self[methodName] && // The ServiceObject didn't redefine the method. - !config.methods[methodName]; // This method wasn't asked to be extended. - }) - .forEach(function(methodName) { - self[methodName] = undefined; - }); + if (config.methods) { + var allMethodNames = Object.keys(ServiceObject.prototype); + allMethodNames + .filter(function(methodName) { + return methodName !== 'request' && // All ServiceObjects need `request`. + !self[methodName] && // The ServiceObject didn't redefine the method. + !config.methods[methodName]; // This method isn't wanted. + }) + .forEach(function(methodName) { + self[methodName] = undefined; + }); + } } /** diff --git a/lib/common/service.js b/lib/common/service.js index b7c9022e9e6..fd924605962 100644 --- a/lib/common/service.js +++ b/lib/common/service.js @@ -63,13 +63,17 @@ function Service(config, options) { Service.prototype.request = function(reqOpts, callback) { var uriComponents = [ this.baseUrl, + 'projects', this.projectId, reqOpts.uri ]; - reqOpts.uri = uriComponents.map(function(uriComponent) { - return uriComponent.replace(/^\/*|\/*$/g, ''); - }).join('/'); + reqOpts.uri = uriComponents + .map(function(uriComponent) { + return uriComponent.replace(/^\/*|\/*$/g, ''); + }) + .join('/') + .replace(/\/:/g, ':'); this.makeAuthenticatedRequest(reqOpts, callback); }; diff --git a/lib/compute/index.js b/lib/compute/index.js index e2d95c174f9..ff14a46f474 100644 --- a/lib/compute/index.js +++ b/lib/compute/index.js @@ -105,7 +105,7 @@ function Compute(options) { } var config = { - baseUrl: 'https://www.googleapis.com/compute/v1/projects', + baseUrl: 'https://www.googleapis.com/compute/v1', scopes: ['https://www.googleapis.com/auth/compute'] }; diff --git a/lib/dns/index.js b/lib/dns/index.js index 3dfa96031dd..908c4fb6b0c 100644 --- a/lib/dns/index.js +++ b/lib/dns/index.js @@ -75,7 +75,7 @@ function DNS(options) { } var config = { - baseUrl: 'https://www.googleapis.com/dns/v1/projects', + baseUrl: 'https://www.googleapis.com/dns/v1', scopes: [ 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', 'https://www.googleapis.com/auth/cloud-platform' diff --git a/lib/pubsub/iam.js b/lib/pubsub/iam.js index 67c1b76171f..37e79d08583 100644 --- a/lib/pubsub/iam.js +++ b/lib/pubsub/iam.js @@ -20,8 +20,15 @@ 'use strict'; -var is = require('is'); var arrify = require('arrify'); +var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); /*! Developer Documentation * @@ -66,11 +73,17 @@ var arrify = require('arrify'); * var subscription = pubsub.subscription('my-subscription'); * // subscription.iam */ -function IAM(pubsub, resource) { - this.resource = resource; - this.makeReq_ = pubsub.makeReq_.bind(pubsub); +function IAM(pubsub, scope) { + ServiceObject.call(this, { + parent: pubsub, + baseUrl: scope.baseUrl, + id: scope.id, + methods: {} + }); } +nodeutil.inherits(IAM, ServiceObject); + /** * Get the IAM policy * @@ -90,9 +103,9 @@ function IAM(pubsub, resource) { * subscription.iam.getPolicy(function(err, policy, apiResponse) {}); */ IAM.prototype.getPolicy = function(callback) { - var path = this.resource + ':getIamPolicy'; - - this.makeReq_('GET', path, null, null, function(err, resp) { + this.request({ + uri: ':getIamPolicy' + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -138,15 +151,16 @@ IAM.prototype.getPolicy = function(callback) { */ IAM.prototype.setPolicy = function(policy, callback) { if (!is.object(policy)) { - throw new Error('A policy is required'); + throw new Error('A policy object is required.'); } - var path = this.resource + ':setIamPolicy'; - var body = { - policy: policy - }; - - this.makeReq_('POST', path, null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: ':setIamPolicy', + json: { + policy: policy + } + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -207,23 +221,26 @@ IAM.prototype.setPolicy = function(policy, callback) { */ IAM.prototype.testPermissions = function(permissions, callback) { if (!is.array(permissions) && !is.string(permissions)) { - throw new Error('Permissions are required'); + throw new Error('Permissions are required.'); } - var path = this.resource + ':testIamPermissions'; - var body = { - permissions: arrify(permissions) - }; + permissions = arrify(permissions); - this.makeReq_('POST', path, null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: ':testIamPermissions', + json: { + permissions: permissions + } + }, function(err, resp) { if (err) { callback(err, null, resp); return; } - var availablePermissions = resp.permissions || []; + var availablePermissions = arrify(resp.permissions); - var permissionsHash = body.permissions.reduce(function(acc, permission) { + var permissionsHash = permissions.reduce(function(acc, permission) { acc[permission] = availablePermissions.indexOf(permission) > -1; return acc; }, {}); diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js index 9a9cf551716..0e0303bc76c 100644 --- a/lib/pubsub/index.js +++ b/lib/pubsub/index.js @@ -20,8 +20,15 @@ 'use strict'; -var format = require('string-format-obj'); +var arrify = require('arrify'); var is = require('is'); +var nodeutil = require('util'); + +/** + * @type {module:common/service} + * @private + */ +var Service = require('../common/service.js'); /** * @type {module:pubsub/subscription} @@ -47,21 +54,6 @@ var Topic = require('./topic.js'); */ var util = require('../common/util.js'); -/** - * @const {string} Base URL for Pub/Sub API. - * @private - */ -var PUBSUB_BASE_URL = 'https://pubsub.googleapis.com/v1/'; - -/** - * @const {array} Required scopes for Pub/Sub API. - * @private - */ -var SCOPES = [ - 'https://www.googleapis.com/auth/pubsub', - 'https://www.googleapis.com/auth/cloud-platform' -]; - /** * [Google Cloud Pub/Sub](https://developers.google.com/pubsub/overview) is a * reliable, many-to-many, asynchronous messaging service from Google Cloud @@ -86,17 +78,191 @@ function PubSub(options) { return new PubSub(options); } - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - keyFile: options.keyFilename, - scopes: SCOPES, - email: options.email - }); + var config = { + baseUrl: 'https://pubsub.googleapis.com/v1', + scopes: [ + 'https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/cloud-platform' + ] + }; - this.projectId = options.projectId; - this.projectName = 'projects/' + this.projectId; + Service.call(this, config, options); } +nodeutil.inherits(PubSub, Service); + +/** + * Create a topic with the given name. + * + * @resource [Topics: create API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create} + * + * @param {string} name - Name of the topic. + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error from the API call, may be null. + * @param {module:pubsub/topic} callback.topic - The newly created topic. + * @param {object} callback.apiResponse - The full API response from the + * service. + * + * @example + * pubsub.createTopic('my-new-topic', function(err, topic, apiResponse) { + * if (!err) { + * // The topic was created successfully. + * } + * }); + */ +PubSub.prototype.createTopic = function(name, callback) { + var self = this; + + callback = callback || util.noop; + + this.request({ + method: 'PUT', + uri: '/topics/' + name, + }, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + var topic = self.topic(name); + topic.metadata = resp; + + callback(null, topic, resp); + }); +}; + +/** + * Get a list of the subscriptions registered to all of your project's topics. + * You may optionally provide a query object as the first argument to customize + * the response. + * + * Your provided callback will be invoked with an error object if an API error + * occurred or an array of {@linkcode module:pubsub/subscription} objects. + * + * To get subscriptions for a topic, see {module:pubsub/topic}. + * + * @resource [Subscriptions: list API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/list} + * + * @param {object=} options - Configuration object. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {string|module:pubsub/topic} options.topic - The name of the topic to + * list subscriptions from. + * @param {number} options.pageSize - Maximum number of results to return. + * @param {string} options.pageToken - Page token. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error from the API call, may be null. + * @param {module:pubsub/subscription[]} callback.subscriptions - The list of + * subscriptions returned. + * @param {?object} callback.nextQuery - A query object representing the next + * page of topics. + * @param {object} callback.apiResponse - The full API response from the + * service. + * + * @example + * pubsub.getSubscriptions(function(err, subscriptions) { + * if (!err) { + * // subscriptions is an array of Subscription objects. + * } + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, subscriptions, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * pubsub.getSubscriptions(nextQuery, callback); + * } + * }; + * + * pubsub.getSubscriptions({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the subscriptions as a readable object stream. + * //- + * pubsub.getSubscriptions() + * .on('error', console.error) + * .on('data', function(subscription) { + * // subscription is a Subscription object. + * }) + * .on('end', function() { + * // All subscriptions retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * pubsub.getSubscriptions() + * .on('data', function(topic) { + * this.end(); + * }); + */ +PubSub.prototype.getSubscriptions = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + var topicName; + + if (is.string(options.topic)) { + topicName = options.topic; + } else if (options.topic instanceof Topic) { + topicName = options.topic.unformattedName; + } + + var query = {}; + + if (options.pageSize) { + query.pageSize = options.pageSize; + } + + if (options.pageToken) { + query.pageToken = options.pageToken; + } + + this.request({ + uri: (topicName ? '/topics/' + topicName : '') + '/subscriptions', + qs: query + }, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var subscriptions = arrify(resp.subscriptions).map(function(sub) { + // Depending on if we're using a subscriptions.list or + // topics.subscriptions.list API endpoint, we will get back a + // Subscription resource or just the name of the subscription. + var subscriptionInstance = self.subscription(sub.name || sub); + + if (sub.name) { + subscriptionInstance.metadata = sub; + } + + return subscriptionInstance; + }); + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = options; + nextQuery.pageToken = resp.nextPageToken; + } + + callback(null, subscriptions, nextQuery, resp); + }); +}; + /** * Get a list of the topics registered to your project. You may optionally * provide a query object as the first argument to customize the response. @@ -168,59 +334,34 @@ function PubSub(options) { */ PubSub.prototype.getTopics = function(query, callback) { var self = this; + if (!callback) { callback = query; query = {}; } - var path = this.projectName + '/topics'; - this.makeReq_('GET', path, query, true, function(err, result) { + + this.request({ + uri: '/topics', + qs: query + }, function(err, result) { if (err) { callback(err, null, null, result); return; } - var topics = (result.topics || []).map(function(item) { - var topicInstance = self.topic(item.name); - topicInstance.metadata = item; + + var topics = arrify(result.topics).map(function(topic) { + var topicInstance = self.topic(topic.name); + topicInstance.metadata = topic; return topicInstance; }); + var nextQuery = null; if (result.nextPageToken) { nextQuery = query; nextQuery.pageToken = result.nextPageToken; } - callback(null, topics, nextQuery, result); - }); -}; -/** - * Create a topic with the given name. - * - * @resource [Topics: create API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create} - * - * @param {string} name - Name of the topic. - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error from the API call, may be null. - * @param {module:pubsub/topic} callback.topic - The newly created topic. - * @param {object} callback.apiResponse - The full API response from the - * service. - * - * @example - * pubsub.createTopic('my-new-topic', function(err, topic, apiResponse) { - * if (!err) { - * // The topic was created successfully. - * } - * }); - */ -PubSub.prototype.createTopic = function(name, callback) { - callback = callback || util.noop; - var topic = this.topic(name); - var path = this.projectName + '/topics/' + name; - this.makeReq_('PUT', path, null, null, function(err, result) { - if (err) { - callback(err, null, result); - return; - } - callback(null, topic, result); + callback(null, topics, nextQuery, result); }); }; @@ -285,6 +426,8 @@ PubSub.prototype.createTopic = function(name, callback) { * pubsub.subscribe(topic, name, function(err, subscription, apiResponse) {}); */ PubSub.prototype.subscribe = function(topic, subName, options, callback) { + var self = this; + if (!is.string(topic) && !(topic instanceof Topic)) { throw new Error('A Topic is required for a new subscription.'); } @@ -312,15 +455,18 @@ PubSub.prototype.subscribe = function(topic, subName, options, callback) { body.ackDeadlineSeconds = options.ackDeadlineSeconds; } - var subscription = this.subscription(subName, options); - - this.makeReq_('PUT', subscription.name, null, body, function(err, result) { + this.request({ + method: 'PUT', + uri: '/subscriptions/' + subName, + json: body + }, function(err, resp) { if (err && !(err.code === 409 && options.reuseExisting)) { - callback(err, null, result); + callback(err, null, resp); return; } - callback(null, subscription, result); + var subscription = self.subscription(resp.name, options); + callback(null, subscription, resp); }); }; @@ -385,162 +531,6 @@ PubSub.prototype.topic = function(name) { return new Topic(this, name); }; -/** - * Get a list of the subscriptions registered to all of your project's topics. - * You may optionally provide a query object as the first argument to customize - * the response. - * - * Your provided callback will be invoked with an error object if an API error - * occurred or an array of {@linkcode module:pubsub/subscription} objects. - * - * To get subscriptions for a topic, see {module:pubsub/topic}. - * - * @resource [Subscriptions: list API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/list} - * - * @param {object=} options - Configuration object. - * @param {boolean} options.autoPaginate - Have pagination handled - * automatically. Default: true. - * @param {string|module:pubsub/topic} options.topic - The name of the topic to - * list subscriptions from. - * @param {number} options.pageSize - Maximum number of results to return. - * @param {string} options.pageToken - Page token. - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error from the API call, may be null. - * @param {module:pubsub/subscription[]} callback.subscriptions - The list of - * subscriptions returned. - * @param {?object} callback.nextQuery - A query object representing the next - * page of topics. - * @param {object} callback.apiResponse - The full API response from the - * service. - * - * @example - * pubsub.getSubscriptions(function(err, subscriptions) { - * if (!err) { - * // subscriptions is an array of Subscription objects. - * } - * }); - * - * //- - * // To control how many API requests are made and page through the results - * // manually, set `autoPaginate` to `false`. - * //- - * var callback = function(err, subscriptions, nextQuery, apiResponse) { - * if (nextQuery) { - * // More results exist. - * pubsub.getSubscriptions(nextQuery, callback); - * } - * }; - * - * pubsub.getSubscriptions({ - * autoPaginate: false - * }, callback); - * - * //- - * // Get the subscriptions as a readable object stream. - * //- - * pubsub.getSubscriptions() - * .on('error', console.error) - * .on('data', function(subscription) { - * // subscription is a Subscription object. - * }) - * .on('end', function() { - * // All subscriptions retrieved. - * }); - * - * //- - * // If you anticipate many results, you can end a stream early to prevent - * // unnecessary processing and API requests. - * //- - * pubsub.getSubscriptions() - * .on('data', function(topic) { - * this.end(); - * }); - */ -PubSub.prototype.getSubscriptions = function(options, callback) { - var self = this; - - if (is.fn(options)) { - callback = options; - options = {}; - } - - options = options || {}; - - var topicName; - - if (is.string(options.topic)) { - topicName = options.topic; - } else if (options.topic instanceof Topic) { - topicName = options.topic.unformattedName; - } - - var query = {}; - - if (options.pageSize) { - query.pageSize = options.pageSize; - } - - if (options.pageToken) { - query.pageToken = options.pageToken; - } - - var apiPath = format('{projectPath}{topicPath}/subscriptions', { - projectPath: 'projects/' + this.projectId, - topicPath: topicName ? '/topics/' + topicName : '' - }); - - this.makeReq_('GET', apiPath, query, null, function(err, result) { - if (err) { - callback(err, null, null, result); - return; - } - - var subscriptions = (result.subscriptions || []).map(function(sub) { - return new Subscription(self, { - // Depending on if we're using a subscriptions.list or - // topics.subscriptions.list API endpoint, we will get back a - // Subscription resource or just the name of the subscription. - name: sub.name || sub - }); - }); - - var nextQuery = null; - - if (result.nextPageToken) { - nextQuery = options; - nextQuery.pageToken = result.nextPageToken; - } - - callback(null, subscriptions, nextQuery, result); - }); -}; - -/** - * 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. - */ -PubSub.prototype.makeReq_ = function(method, path, q, body, callback) { - var reqOpts = { - method: method, - qs: q, - uri: PUBSUB_BASE_URL + path - }; - - if (body) { - reqOpts.json = body; - } - - this.makeAuthenticatedRequest_(reqOpts, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/pubsub/subscription.js b/lib/pubsub/subscription.js index 4ff223f6514..0899813186d 100644 --- a/lib/pubsub/subscription.js +++ b/lib/pubsub/subscription.js @@ -23,19 +23,26 @@ var arrify = require('arrify'); var events = require('events'); var is = require('is'); -var nodeutil = require('util'); +var modelo = require('modelo'); +var prop = require('propprop'); /** - * @type {module:common/util} + * @type {module:pubsub/iam} * @private */ -var util = require('../common/util.js'); +var IAM = require('./iam.js'); /** - * @type {module:pubsub/iam} + * @type {module:common/serviceObject} * @private */ -var IAM = require('./iam'); +var ServiceObject = require('../common/service-object.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); /*! Developer Documentation * @@ -135,12 +142,35 @@ var IAM = require('./iam'); * subscription.removeListener('message', onMessage); */ function Subscription(pubsub, options) { + // Make sure we only have the subscription's name. + var unformattedName = options.name.replace(/^.*\//, ''); + var baseUrl = '/subscriptions'; + + var methods = { + exists: true, + + get: true + }; + + var config = { + parent: pubsub, + baseUrl: baseUrl, + id: unformattedName, + methods: methods + }; + + if (options.topic) { + // Only a subscription with knowledge of its topic can be created. + config.methods.create = true; + config.createMethod = pubsub.subscribe.bind(pubsub, options.topic); + delete options.topic; + } + + ServiceObject.call(this, config); events.EventEmitter.call(this); this.name = Subscription.formatName_(pubsub.projectId, options.name); - this.makeReq_ = pubsub.makeReq_.bind(pubsub); - this.autoAck = is.boolean(options.autoAck) ? options.autoAck : false; this.closed = true; this.interval = is.number(options.interval) ? options.interval : 10; @@ -176,12 +206,15 @@ function Subscription(pubsub, options) { * console.log(policy); * }); */ - this.iam = new IAM(pubsub, this.name); + this.iam = new IAM(pubsub, { + baseUrl: baseUrl, + id: unformattedName + }); this.listenForEvents_(); } -nodeutil.inherits(Subscription, events.EventEmitter); +modelo.inherits(Subscription, ServiceObject, events.EventEmitter); /** * Format the name of a subscription. A subscription's full name is in the @@ -194,6 +227,7 @@ Subscription.formatName_ = function(projectId, name) { if (name.indexOf('/') > -1) { return name; } + return 'projects/' + projectId + '/subscriptions/' + name; }; @@ -326,26 +360,28 @@ Subscription.prototype.startPulling_ = function() { Subscription.prototype.ack = function(ackIds, callback) { var self = this; - if (!ackIds || ackIds.length === 0) { - throw new Error( - 'At least one ID must be specified before it can be acknowledged.'); - } - ackIds = arrify(ackIds); - var body = { - ackIds: ackIds - }; + if (ackIds.length === 0) { + throw new Error([ + 'At least one ID must be specified before it can be acknowledged.' + ].join('')); + } callback = callback || util.noop; - var path = this.name + ':acknowledge'; - - this.makeReq_('POST', path, null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: ':acknowledge', + json: { + ackIds: ackIds + } + }, function(err, resp) { if (!err) { ackIds.forEach(function(ackId) { delete self.inProgressAckIds[ackId]; }); + self.refreshPausedStatus_(); } @@ -366,15 +402,19 @@ Subscription.prototype.ack = function(ackIds, callback) { */ Subscription.prototype.delete = function(callback) { var self = this; + callback = callback || util.noop; - this.makeReq_('DELETE', this.name, null, true, function(err, result) { + + ServiceObject.prototype.delete.call(this, function(err, resp) { if (err) { - callback(err, result); + callback(err, resp); return; } + self.closed = true; self.removeAllListeners(); - callback(null, result); + + callback(null, resp); }); }; @@ -440,29 +480,27 @@ Subscription.prototype.pull = function(options, callback) { options.maxResults = MAX_EVENTS_LIMIT; } - var body = { - returnImmediately: !!options.returnImmediately, - maxMessages: options.maxResults - }; - - var path = this.name + ':pull'; - this.makeReq_('POST', path, null, body, function(err, response) { + this.request({ + method: 'POST', + uri: ':pull', + json: { + returnImmediately: !!options.returnImmediately, + maxMessages: options.maxResults + } + }, function(err, response) { if (err) { callback(err, null, response); return; } - var messages = response.receivedMessages || []; - messages = messages + var messages = arrify(response.receivedMessages) .map(Subscription.formatMessage_) .map(self.decorateMessage_.bind(self)); self.refreshPausedStatus_(); if (self.autoAck && messages.length !== 0) { - var ackIds = messages.map(function(message) { - return message.ackId; - }); + var ackIds = messages.map(prop('ackId')); self.ack(ackIds, function(err) { callback(err, messages, response); @@ -496,15 +534,18 @@ Subscription.prototype.pull = function(options, callback) { * subscription.setAckDeadline(options, function(err, apiResponse) {}); */ Subscription.prototype.setAckDeadline = function(options, callback) { - var body = { - ackIds: arrify(options.ackIds), - ackDeadlineSeconds: options.seconds - }; - callback = callback || util.noop; - var path = this.name + ':modifyAckDeadline'; - this.makeReq_('POST', path, null, body, callback); + this.request({ + method: 'POST', + uri: ':modifyAckDeadline', + json: { + ackIds: arrify(options.ackIds), + ackDeadlineSeconds: options.seconds + } + }, function(err, resp) { + callback(err, resp); + }); }; /** @@ -551,6 +592,7 @@ Subscription.prototype.decorateMessage_ = function(message) { Subscription.prototype.refreshPausedStatus_ = function() { var isCurrentlyPaused = this.paused; var inProgress = Object.keys(this.inProgressAckIds).length; + this.paused = inProgress >= this.maxInProgress; if (isCurrentlyPaused && !this.paused && this.messageListeners > 0) { diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js index 20df59b5123..49536c28060 100644 --- a/lib/pubsub/topic.js +++ b/lib/pubsub/topic.js @@ -22,6 +22,7 @@ var arrify = require('arrify'); var is = require('is'); +var nodeutil = require('util'); var prop = require('propprop'); /** @@ -36,6 +37,12 @@ var util = require('../common/util.js'); */ var IAM = require('./iam'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /*! Developer Documentation * * @param {module:pubsub} pubsub - PubSub object. @@ -55,14 +62,49 @@ var IAM = require('./iam'); * var topic = pubsub.topic('my-topic'); */ function Topic(pubsub, name) { + var baseUrl = '/topics'; + + var methods = { + /** + * Delete the topic. This will not delete subscriptions to this topic. + * + * @resource [Topics: delete API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/delete} + * + * @param {function=} callback - The callback function. + * + * @example + * topic.delete(function(err, apiResponse) {}); + */ + delete: true, + + /** + * Get the official representation of this topic from the API. + * + * @resource [Topics: get API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The metadata of the Topic. + * @param {object} callback.apiResponse - The full API response. + */ + getMetadata: true + }; + + ServiceObject.call(this, { + parent: pubsub, + baseUrl: baseUrl, + id: name, + createMethod: pubsub.createTopic.bind(pubsub), + methods: methods + }); + this.name = Topic.formatName_(pubsub.projectId, name); this.projectId = pubsub.projectId; this.pubsub = pubsub; this.unformattedName = name; - this.makeReq_ = this.pubsub.makeReq_.bind(this.pubsub); - /** * [IAM (Identity and Access Management)](https://cloud.google.com/pubsub/access_control) * allows you to set permissions on invidual resources and offers a wider @@ -89,9 +131,14 @@ function Topic(pubsub, name) { * console.log(policy); * }); */ - this.iam = new IAM(pubsub, this.name); + this.iam = new IAM(pubsub, { + baseUrl: baseUrl, + id: name + }); } +nodeutil.inherits(Topic, ServiceObject); + /** * Format a message object as the upstream API expects it. * @@ -191,56 +238,19 @@ Topic.prototype.publish = function(messages, callback) { callback = callback || util.noop; - var body = { - messages: messages.map(Topic.formatMessage_) - }; - - var path = this.name + ':publish'; - this.makeReq_('POST', path, null, body, function(err, result) { - if (err) { - callback(err, null, result); - return; + this.request({ + method: 'POST', + uri: ':publish', + json: { + messages: messages.map(Topic.formatMessage_) } - callback(null, result && result.messageIds || [], result); - }); -}; - -/** - * Delete the topic. This will not delete subscriptions to this topic. - * - * @resource [Topics: delete API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/delete} - * - * @param {function=} callback - The callback function. - * - * @example - * topic.delete(function(err, apiResponse) {}); - */ -Topic.prototype.delete = function(callback) { - callback = callback || util.noop; - this.makeReq_('DELETE', this.name, null, null, callback); -}; - -/** - * Get the official representation of this topic from the API. - * - * @resource [Topics: get API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get} - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {object} callback.metadata - The metadata of the Topic. - * @param {object} callback.apiResponse - The full API response. - */ -Topic.prototype.getMetadata = function(callback) { - var self = this; - - this.makeReq_('GET', this.name, null, null, function(err, resp) { + }, function(err, result) { if (err) { - callback(err, null, resp); + callback(err, null, result); return; } - self.metadata = resp; - callback(null, self.metadata, resp); + callback(null, arrify(result.messageIds), result); }); }; @@ -369,6 +379,9 @@ Topic.prototype.subscribe = function(subName, options, callback) { * }); */ Topic.prototype.subscription = function(name, options) { + options = options || {}; + options.topic = this; + return this.pubsub.subscription(name, options); }; diff --git a/package.json b/package.json index 601a807a954..d9fc91d8235 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "is": "^3.0.1", "methmeth": "^1.0.0", "mime-types": "^2.0.8", + "modelo": "^4.2.0", "once": "^1.3.1", "prop-assign": "^1.0.0", "propprop": "^0.3.0", diff --git a/system-test/pubsub.js b/system-test/pubsub.js index 4fbf4c91431..5ae8b14a183 100644 --- a/system-test/pubsub.js +++ b/system-test/pubsub.js @@ -42,16 +42,22 @@ describe('pubsub', function() { generateTopicName() ]; - var TOPICS = TOPIC_NAMES.map(pubsub.topic.bind(pubsub)); + var TOPICS = [ + pubsub.topic(TOPIC_NAMES[0]), + pubsub.topic(TOPIC_NAMES[1]), + pubsub.topic(TOPIC_NAMES[2]) + ]; - var TOPIC_FULL_NAMES = TOPICS.map(function(topic) { - return topic.name; - }); + var TOPIC_FULL_NAMES = [ + TOPICS[0].name, + TOPICS[1].name, + TOPICS[2].name + ]; before(function(done) { // create all needed topics - async.each(TOPIC_NAMES, function(name, cb) { - pubsub.createTopic(name, cb); + async.each(TOPICS, function(topic, cb) { + topic.create(cb); }, done); }); @@ -136,6 +142,7 @@ describe('pubsub', function() { describe('Subscription', function() { var TOPIC_NAME = generateTopicName(); + var topic = pubsub.topic(TOPIC_NAME); var SUB_NAMES = [ generateSubName(), @@ -143,25 +150,16 @@ describe('pubsub', function() { ]; var SUBSCRIPTIONS = [ - { - name: SUB_NAMES[0], - options: { ackDeadlineSeconds: 30 } - }, - { - name: SUB_NAMES[1], - options: { ackDeadlineSeconds: 60 } - } + topic.subscription(SUB_NAMES[0], { ackDeadlineSeconds: 30 }), + topic.subscription(SUB_NAMES[1], { ackDeadlineSeconds: 60 }) ]; - var topic; - before(function(done) { - pubsub.createTopic(TOPIC_NAME, function(err, newTopic) { + topic.create(function(err) { assert.ifError(err); - topic = newTopic; function createSubscription(subscription, callback) { - topic.subscribe(subscription.name, subscription.options, callback); + subscription.create(callback); } async.each(SUBSCRIPTIONS, createSubscription, function(err) { @@ -178,12 +176,8 @@ describe('pubsub', function() { }); after(function(done) { - var SUBS = SUB_NAMES.map(function(name) { - return topic.subscription(name); - }); - // Delete subscriptions - async.each(SUBS, function(sub, callback) { + async.each(SUBSCRIPTIONS, function(sub, callback) { sub.delete(callback); }, function(err) { assert.ifError(err); @@ -194,8 +188,8 @@ describe('pubsub', function() { it('should list all subscriptions registered to the topic', function(done) { topic.getSubscriptions(function(err, subs) { assert.ifError(err); - assert(subs[0] instanceof Subscription); assert.equal(subs.length, SUBSCRIPTIONS.length); + assert(subs[0] instanceof Subscription); done(); }); }); @@ -322,7 +316,6 @@ describe('pubsub', function() { }); describe('IAM', function() { - it('should get a policy', function(done) { var topic = pubsub.topic(TOPIC_NAMES[0]); @@ -365,6 +358,5 @@ describe('pubsub', function() { done(); }); }); - }); }); From b7d2e611ad3b9bdd307ed04b81294f3d52c7e16e Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Oct 2015 11:37:39 -0400 Subject: [PATCH 08/49] catching up on docs --- lib/compute/snapshot.js | 3 ++ lib/pubsub/iam.js | 4 +- lib/pubsub/subscription.js | 90 +++++++++++++++++++++++++++++++------- lib/pubsub/topic.js | 58 +++++++++++++++++++++--- 4 files changed, 133 insertions(+), 22 deletions(-) diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index fc1ff745ff5..2efa2f4156e 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -133,6 +133,9 @@ function Snapshot(scope, name) { /** * Create a snapshot. * + * **This is only available if you accessed this object through + * {module:compute/disk#snapshot}.** + * * @param {object} config - See {module:compute/disk#createSnapshot}. * * @example diff --git a/lib/pubsub/iam.js b/lib/pubsub/iam.js index 37e79d08583..14adad8364c 100644 --- a/lib/pubsub/iam.js +++ b/lib/pubsub/iam.js @@ -78,7 +78,9 @@ function IAM(pubsub, scope) { parent: pubsub, baseUrl: scope.baseUrl, id: scope.id, - methods: {} + methods: { + // Nothing needed other than the `request` method. + } }); } diff --git a/lib/pubsub/subscription.js b/lib/pubsub/subscription.js index 0899813186d..b9ef36f289d 100644 --- a/lib/pubsub/subscription.js +++ b/lib/pubsub/subscription.js @@ -142,14 +142,58 @@ var util = require('../common/util.js'); * subscription.removeListener('message', onMessage); */ function Subscription(pubsub, options) { - // Make sure we only have the subscription's name. - var unformattedName = options.name.replace(/^.*\//, ''); var baseUrl = '/subscriptions'; + var unformattedName = options.name.split('/').pop(); var methods = { + /** + * Check if the subscription exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the subscription exists or + * not. + * + * @example + * subscription.exists(function(err, exists) {}); + */ exists: true, - get: true + /** + * Get a subscription 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 + * subscription.get(function(err, subscription, apiResponse) { + * // `subscription.metadata` has been populated. + * }); + */ + get: true, + + /** + * Get the metadata for the subscription. + * + * @resource [Subscriptions: get API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An API error. + * @param {?object} callback.metadata - Metadata of the subscription from + * the API. + * @param {object} callback.apiResponse - Raw API response. + * + * @example + * subscription.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true }; var config = { @@ -161,25 +205,30 @@ function Subscription(pubsub, options) { if (options.topic) { // Only a subscription with knowledge of its topic can be created. - config.methods.create = true; config.createMethod = pubsub.subscribe.bind(pubsub, options.topic); delete options.topic; + + /** + * Create a subscription. + * + * **This is only available if you accessed this object through + * {module:pubsub/topic#subscription}.** + * + * @param {object} config - See {module:pubsub#subscribe}. + * + * @example + * subscription.create(function(err, subscription, apiResponse) { + * if (!err) { + * // The subscription was created successfully. + * } + * }); + */ + config.methods.create = true; } ServiceObject.call(this, config); events.EventEmitter.call(this); - this.name = Subscription.formatName_(pubsub.projectId, options.name); - - this.autoAck = is.boolean(options.autoAck) ? options.autoAck : false; - this.closed = true; - this.interval = is.number(options.interval) ? options.interval : 10; - this.inProgressAckIds = {}; - this.maxInProgress = - is.number(options.maxInProgress) ? options.maxInProgress : Infinity; - this.messageListeners = 0; - this.paused = false; - /** * [IAM (Identity and Access Management)](https://cloud.google.com/pubsub/access_control) * allows you to set permissions on invidual resources and offers a wider @@ -211,6 +260,17 @@ function Subscription(pubsub, options) { id: unformattedName }); + this.name = Subscription.formatName_(pubsub.projectId, options.name); + + this.autoAck = is.boolean(options.autoAck) ? options.autoAck : false; + this.closed = true; + this.interval = is.number(options.interval) ? options.interval : 10; + this.inProgressAckIds = {}; + this.maxInProgress = + is.number(options.maxInProgress) ? options.maxInProgress : Infinity; + this.messageListeners = 0; + this.paused = false; + this.listenForEvents_(); } diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js index 49536c28060..2b72ffea058 100644 --- a/lib/pubsub/topic.js +++ b/lib/pubsub/topic.js @@ -65,6 +65,20 @@ function Topic(pubsub, name) { var baseUrl = '/topics'; var methods = { + /** + * Create a topic. + * + * @param {object} config - See {module:pubsub#createTopic}. + * + * @example + * topic.create(function(err, topic, apiResponse) { + * if (!err) { + * // The topic was created successfully. + * } + * }); + */ + create: true, + /** * Delete the topic. This will not delete subscriptions to this topic. * @@ -77,6 +91,38 @@ function Topic(pubsub, name) { */ delete: true, + /** + * Check if the topic exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the topic exists or not. + * + * @example + * topic.exists(function(err, exists) {}); + */ + exists: true, + + /** + * Get a topic 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 + * topic.get(function(err, topic, apiResponse) { + * // `topic.metadata` has been populated. + * }); + */ + get: true, + /** * Get the official representation of this topic from the API. * @@ -99,12 +145,6 @@ function Topic(pubsub, name) { methods: methods }); - this.name = Topic.formatName_(pubsub.projectId, name); - - this.projectId = pubsub.projectId; - this.pubsub = pubsub; - this.unformattedName = name; - /** * [IAM (Identity and Access Management)](https://cloud.google.com/pubsub/access_control) * allows you to set permissions on invidual resources and offers a wider @@ -135,6 +175,12 @@ function Topic(pubsub, name) { baseUrl: baseUrl, id: name }); + + this.name = Topic.formatName_(pubsub.projectId, name); + + this.projectId = pubsub.projectId; + this.pubsub = pubsub; + this.unformattedName = name; } nodeutil.inherits(Topic, ServiceObject); From d34ae7a020f7f869a550f699fcde6dc122853d66 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Oct 2015 12:43:04 -0400 Subject: [PATCH 09/49] implement resource --- lib/common/service-object.js | 3 +- lib/common/service.js | 24 ++-- lib/dns/zone.js | 28 ++-- lib/resource/index.js | 82 ++++-------- lib/resource/project.js | 252 +++++++++++++++++++---------------- 5 files changed, 194 insertions(+), 195 deletions(-) diff --git a/lib/common/service-object.js b/lib/common/service-object.js index db2643d5742..b5b6365dd8b 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -286,7 +286,8 @@ ServiceObject.prototype.request = function(reqOpts, callback) { reqOpts.uri = uriComponents .filter(exec('trim')) // Limit to non-empty strings. .map(function(uriComponent) { - return uriComponent.replace(/^\/*|\/*$/g, ''); + var trimSlashesRegex = /^\/*|\/*$/g; + return uriComponent.replace(trimSlashesRegex, ''); }) .join('/'); diff --git a/lib/common/service.js b/lib/common/service.js index fd924605962..beea62dd616 100644 --- a/lib/common/service.js +++ b/lib/common/service.js @@ -38,9 +38,6 @@ var util = require('./util.js'); * @param {object} options - [Configuration object](#/docs/?method=gcloud). */ function Service(config, options) { - this.baseUrl = config.baseUrl; - this.projectId = options.projectId; - this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({ credentials: options.credentials, keyFile: options.keyFilename, @@ -49,6 +46,9 @@ function Service(config, options) { }); this.authClient = this.makeAuthenticatedRequest.authClient; + this.baseUrl = config.baseUrl; + this.projectId = options.projectId; + this.projectIdRequired = config.projectIdRequired !== false; } /** @@ -62,17 +62,25 @@ function Service(config, options) { */ Service.prototype.request = function(reqOpts, callback) { var uriComponents = [ - this.baseUrl, - 'projects', - this.projectId, - reqOpts.uri + this.baseUrl ]; + if (this.projectIdRequired) { + uriComponents.push('projects'); + uriComponents.push(this.projectId); + } + + uriComponents.push(reqOpts.uri); + reqOpts.uri = uriComponents .map(function(uriComponent) { - return uriComponent.replace(/^\/*|\/*$/g, ''); + var trimSlashesRegex = /^\/*|\/*$/g; + return uriComponent.replace(trimSlashesRegex, ''); }) .join('/') + // Some URIs have colon separators. + // Bad: https://.../projects/:list + // Good: https://.../projects:list .replace(/\/:/g, ':'); this.makeAuthenticatedRequest(reqOpts, callback); diff --git a/lib/dns/zone.js b/lib/dns/zone.js index 7f473ff23aa..95c84ff4145 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -86,19 +86,6 @@ function Zone(dns, name) { */ create: true, - /** - * Check if the zone exists. - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this - * request. - * @param {boolean} callback.exists - Whether the zone exists or not. - * - * @example - * zone.exists(function(err, exists) {}); - */ - exists: true, - /** * Get a zone if it exists. * @@ -131,7 +118,20 @@ function Zone(dns, name) { * @example * zone.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true + getMetadata: true, + + /** + * Check if the zone exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the zone exists or not. + * + * @example + * zone.exists(function(err, exists) {}); + */ + exists: true }; ServiceObject.call(this, { diff --git a/lib/resource/index.js b/lib/resource/index.js index ec0f06cc2eb..28d6cd3737f 100644 --- a/lib/resource/index.js +++ b/lib/resource/index.js @@ -22,6 +22,7 @@ var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:resource/project} @@ -30,31 +31,22 @@ var is = require('is'); var Project = require('./project.js'); /** - * @type {module:common/streamrouter} - * @private - */ -var streamRouter = require('../common/stream-router.js'); - -/** - * @type {module:common/util} + * @type {module:common/service} * @private */ -var util = require('../common/util.js'); +var Service = require('../common/service.js'); /** - * @const {string} + * @type {module:common/streamrouter} * @private */ -var BASE_URL = 'https://cloudresourcemanager.googleapis.com/v1beta1/projects'; +var streamRouter = require('../common/stream-router.js'); /** - * Required scopes for Google Cloud Resource Manager API. - * @const {array} + * @type {module:common/util} * @private */ -var SCOPES = [ - 'https://www.googleapis.com/auth/cloud-platform' -]; +var util = require('../common/util.js'); /** * [The Cloud Resource Manager](https://cloud.google.com/resource-manager/) @@ -88,16 +80,19 @@ function Resource(options) { return new Resource(options); } - this.defaultProjectId_ = options.projectId; + var config = { + baseUrl: 'https://cloudresourcemanager.googleapis.com/v1beta1', + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + projectIdRequired: false + }; - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - keyFile: options.keyFilename, - scopes: SCOPES, - email: options.email - }); + Service.call(this, config, options); + + this.defaultProjectId_ = options.projectId; } +nodeutil.inherits(Resource, Service); + /** * Create a project. * @@ -130,11 +125,13 @@ Resource.prototype.createProject = function(id, options, callback) { options = {}; } - var body = extend({}, options, { - projectId: id - }); - - this.makeReq_('POST', '/', null, body, function(err, resp) { + this.request({ + method: 'POST', + uri: '/projects', + json: extend({}, options, { + projectId: id + }) + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -219,7 +216,10 @@ Resource.prototype.getProjects = function(options, callback) { options = options || {}; - this.makeReq_('GET', '/', options, null, function(err, resp) { + this.request({ + uri: '/projects', + qs: options + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -265,32 +265,6 @@ Resource.prototype.project = function(id) { return new Project(this, id); }; -/** - * 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. - */ -Resource.prototype.makeReq_ = function(method, path, query, body, callback) { - var reqOpts = { - method: method, - qs: query, - uri: BASE_URL + path - }; - - if (body) { - reqOpts.json = body; - } - - this.makeAuthenticatedRequest_(reqOpts, callback); -}; - /*! Developer Documentation * * These methods can be used with either a callback or as a readable object diff --git a/lib/resource/project.js b/lib/resource/project.js index d52dfdc6547..729ac850f67 100644 --- a/lib/resource/project.js +++ b/lib/resource/project.js @@ -20,6 +20,14 @@ 'use strict'; +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private @@ -59,66 +67,130 @@ var util = require('../common/util.js'); * var project = resource.project(); */ function Project(resource, id) { - this.resource = resource; - this.id = id; - this.metadata = {}; -} - -/** - * Delete the project. - * - * @resource [projects: delete API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/delete} - * - * @private - * - * @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 - * project.delete(function(err, apiResponse) { - * if (!err) { - * // The project was deleted! - * } - * }); - */ -Project.prototype.delete = function(callback) { - callback = callback || util.noop; - - this.makeReq_('DELETE', '', null, null, function(err, resp) { - callback(err, resp); + var methods = { + /** + * Create a project. + * + * @param {object} config - See {module:resource#createProject}. + * + * @example + * project.create(function(err, zone, apiResponse) { + * if (!err) { + * // The zone was created successfully. + * } + * }); + */ + create: true, + + /** + * Delete the project. + * + * @resource [projects: delete API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/delete} + * + * @private + * + * @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 + * project.delete(function(err, apiResponse) { + * if (!err) { + * // The project was deleted! + * } + * }); + */ + delete: true, + + /** + * Get a project 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 + * project.get(function(err, project, apiResponse) { + * // `project.metadata` has been populated. + * }); + */ + get: true, + + /** + * Get the metadata for the project. + * + * @resource [projects: get API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {?object} callback.metadata - Metadata of the project from the + * API. + * @param {object} callback.apiResponse - Raw API response. + * + * @example + * project.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true, + + /** + * Check if the project exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the project exists or not. + * + * @example + * project.exists(function(err, exists) {}); + */ + exists: true, + + /** + * Set the project's metadata. + * + * @resource [projects: update API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/update} + * @resource [Project Resource]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project} + * + * @private + * + * @param {object} metadata - See a + * [Project resource](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project). + * @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 metadata = { + * name: 'New name' + * }; + * + * project.setMetadata(metadata, function(err, apiResponse) { + * if (!err) { + * // The project has been successfully updated. + * } + * }); + */ + setMetadata: true + }; + + ServiceObject.call(this, { + parent: resource, + baseUrl: '/projects', + id: id, + createMethod: resource.createProject.bind(resource), + methods: methods }); -}; - -/** - * Get the metadata for the project. - * - * @resource [projects: get API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request. - * @param {?object} callback.metadata - Metadata of the project from the API. - * @param {object} callback.apiResponse - Raw API response. - * - * @example - * project.getMetadata(function(err, metadata, apiResponse) {}); - */ -Project.prototype.getMetadata = function(callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('GET', '', null, null, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } - - self.metadata = resp; +} - callback(null, self.metadata, resp); - }); -}; +nodeutil.inherits(Project, ServiceObject); /** * Restore a project. @@ -141,68 +213,12 @@ Project.prototype.getMetadata = function(callback) { Project.prototype.restore = function(callback) { callback = callback || util.noop; - this.makeReq_('POST', ':undelete', null, null, function(err, resp) { + this.request({ + method: 'POST', + uri: ':undelete' + }, function(err, resp) { callback(err, resp); }); }; -/** - * Set the project's metadata. - * - * @resource [projects: update API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/update} - * @resource [Project Resource]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project} - * - * @private - * - * @param {object} metadata - See a - * [Project resource](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project). - * @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 metadata = { - * name: 'New name' - * }; - * - * project.setMetadata(metadata, function(err, apiResponse) { - * if (!err) { - * // The project has been successfully updated. - * } - * }); - */ -Project.prototype.setMetadata = function(metadata, callback) { - var self = this; - - callback = callback || util.noop; - - this.makeReq_('PUT', '', null, metadata, function(err, resp) { - if (err) { - callback(err, resp); - return; - } - - self.metadata = resp; - - callback(null, 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. - */ -Project.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/' + this.id + path; - this.resource.makeReq_(method, path, query, body, callback); -}; - module.exports = Project; From e116084be8502905203f99022cea10c52f5cae9e Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Oct 2015 14:36:00 -0400 Subject: [PATCH 10/49] search --- lib/common/service-object.js | 2 +- lib/dns/zone.js | 6 +- lib/search/document.js | 106 ++++++++++++++++++++++++----------- lib/search/index-class.js | 82 ++++++++++++++++++--------- lib/search/index.js | 79 +++++++++----------------- system-test/search.js | 49 ++++++++-------- 6 files changed, 185 insertions(+), 139 deletions(-) diff --git a/lib/common/service-object.js b/lib/common/service-object.js index b5b6365dd8b..d48d67faccb 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -186,7 +186,7 @@ ServiceObject.prototype.get = function(config, callback) { this.getMetadata(function(err, metadata) { if (err) { - if (err.code === 404 && autoCreate) { + if (err.code === 404 && autoCreate && self.create) { if (!is.empty(config)) { self.create(config, callback); } else { diff --git a/lib/dns/zone.js b/lib/dns/zone.js index 95c84ff4145..16313eb7c09 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -78,7 +78,11 @@ function Zone(dns, name) { * @param {object} config - See {module:dns#createZone}. * * @example - * zone.create(function(err, zone, apiResponse) { + * var config = { + * // ... + * }; + * + * zone.create(config, function(err, zone, apiResponse) { * if (!err) { * // The zone was created successfully. * } diff --git a/lib/search/document.js b/lib/search/document.js index 29aea20e398..72500a4b8e2 100644 --- a/lib/search/document.js +++ b/lib/search/document.js @@ -21,6 +21,7 @@ 'use strict'; var is = require('is'); +var nodeutil = require('util'); /** * @type {module:search/field} @@ -28,6 +29,12 @@ var is = require('is'); */ var Field = require('./field.js'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/util} * @private @@ -52,13 +59,68 @@ var util = require('../common/util.js'); * var document = search.index('records').document('stephen'); */ function Document(index, id) { - this.search_ = index.search_; - this.index_ = index; + var methods = { + /** + * Delete this document. + * + * @resource [Documents: delete API Documentation]{@link https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/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 + * document.delete(function(err, apiResponse) {}); + */ + delete: true, + + /** + * Check if the document exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the zone exists or not. + * + * @example + * document.exists(function(err, exists) {}); + */ + exists: true, + + /** + * Get a document 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 + * document.get(function(err, document, apiResponse) { + * // `document.fields` has been populated. + * }); + */ + get: true + }; + + ServiceObject.call(this, { + parent: index, + baseUrl: '/documents', + id: id, + methods: methods + }); this.id = id; this.fields = {}; } +nodeutil.inherits(Document, ServiceObject); + /** * Add a field to this document. * @@ -82,21 +144,19 @@ Document.prototype.addField = function(name) { }; /** - * Delete this document. + * Create a document. * - * @resource [Documents: delete API Documentation]{@link https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/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. + * @param {object} config - See {module:search/index#createDocument}. * * @example - * document.delete(function(err, apiResponse) {}); + * document.create(function(err, document, apiResponse) { + * if (!err) { + * // The document was created successfully. + * } + * }); */ -Document.prototype.delete = function(callback) { - this.makeReq_('DELETE', '', null, null, function(err, resp) { - (callback || util.noop)(err, resp); - }); +Document.prototype.create = function(callback) { + this.parent.createDocument(this, callback); }; /** @@ -129,7 +189,7 @@ Document.prototype.getMetadata = function(callback) { callback = callback || util.noop; - this.makeReq_('GET', '/', null, null, function(err, resp) { + ServiceObject.prototype.getMetadata.call(this, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -203,22 +263,4 @@ Document.prototype.toJSON = function() { return documentObject; }; -/** - * 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. - */ -Document.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/documents/' + this.id + path; - - this.index_.makeReq_(method, path, query, body, callback); -}; - module.exports = Document; diff --git a/lib/search/index-class.js b/lib/search/index-class.js index 07db1bcf83a..6fb9d73cd32 100644 --- a/lib/search/index-class.js +++ b/lib/search/index-class.js @@ -20,8 +20,10 @@ 'use strict'; +var arrify = require('arrify'); var extend = require('extend'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:search/document} @@ -29,6 +31,12 @@ var is = require('is'); */ var Document = require('./document.js'); +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + /** * @type {module:common/streamrouter} * @private @@ -53,14 +61,21 @@ var streamRouter = require('../common/stream-router.js'); * var index = search.index('records'); */ function Index(search, id) { + ServiceObject.call(this, { + parent: search, + baseUrl: '/indexes', + id: id, + methods: { + // Nothing needed other than the `request` method. + } + }); + this.search_ = search; this.id = id; - - if (!this.id) { - throw new Error('An ID is needed to access a Google Cloud Search index.'); - } } +nodeutil.inherits(Index, ServiceObject); + /** * Create a document in the index. * @@ -121,7 +136,11 @@ Index.prototype.createDocument = function(documentObj, callback) { document = this.documentFromObject_(documentObj); } - this.makeReq_('POST', '/documents', null, documentObj, function(err, resp) { + this.request({ + method: 'POST', + uri: '/documents', + json: documentObj + }, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -139,8 +158,29 @@ Index.prototype.createDocument = function(documentObj, callback) { * * @example * var myDocument = index.document('my-document'); + * + * //- + * // Documents can also be created from objects. + * //- + * var myDocument = index.document({ + * docId: 'my-document', + * fields: { + * person: { + * values: [ + * { + * stringFormat: 'TEXT', + * stringValue: 'Stephen' + * } + * ] + * } + * } + * }); */ Index.prototype.document = function(id) { + if (is.object(id)) { + return this.documentFromObject_(id); + } + return new Document(this, id); }; @@ -222,7 +262,10 @@ Index.prototype.getDocuments = function(query, callback) { query = {}; } - this.makeReq_('GET', '/documents', query, null, function(err, resp) { + this.request({ + uri: '/documents', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -236,7 +279,7 @@ Index.prototype.getDocuments = function(query, callback) { }); } - var documents = (resp.documents || []) + var documents = arrify(resp.documents) .map(self.documentFromObject_.bind(self)); callback(null, documents, nextQuery, resp); @@ -322,7 +365,10 @@ Index.prototype.search = function(query, callback) { throw new Error('A query must be either a string or object.'); } - this.makeReq_('GET', '/search', query, null, function(err, resp) { + this.request({ + uri: '/search', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -336,7 +382,7 @@ Index.prototype.search = function(query, callback) { }); } - var documents = (resp.results || []) + var documents = arrify(resp.results) .map(self.documentFromObject_.bind(self)); callback(null, documents, nextQuery, resp); @@ -385,24 +431,6 @@ Index.prototype.documentFromObject_ = function(documentObj) { return document; }; -/** - * 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. - */ -Index.prototype.makeReq_ = function(method, path, query, body, callback) { - path = '/indexes/' + this.id + path; - - this.search_.makeReq_(method, path, query, body, callback); -}; - /*! Developer Documentation * * {module:search/index#getDocuments} and {module:search/index#search} can be diff --git a/lib/search/index.js b/lib/search/index.js index 20ea50212eb..358b0402aaa 100644 --- a/lib/search/index.js +++ b/lib/search/index.js @@ -21,8 +21,8 @@ 'use strict'; var extend = require('extend'); -var format = require('string-format-obj'); var is = require('is'); +var nodeutil = require('util'); /** * @type {module:search/index} @@ -31,32 +31,22 @@ var is = require('is'); var Index = require('./index-class.js'); /** - * @type {module:common/streamrouter} - * @private - */ -var streamRouter = require('../common/stream-router.js'); - -/** - * @type {module:common/util} + * @type {module:common/service} * @private */ -var util = require('../common/util.js'); +var Service = require('../common/service.js'); /** - * @const {array} Required scopes for the Search API. + * @type {module:common/streamrouter} * @private */ -var SCOPES = [ - 'https://www.googleapis.com/auth/cloud-platform', - 'https://www.googleapis.com/auth/cloudsearch', - 'https://www.googleapis.com/auth/userinfo.email' -]; +var streamRouter = require('../common/stream-router.js'); /** - * @const {string} Base URL for the Search API. + * @type {module:common/util} * @private */ -var SEARCH_BASE_URL = 'https://cloudsearch.googleapis.com/v1/'; +var util = require('../common/util.js'); /** * Create a Search object to Interact with the Cloud Search API. Using this @@ -85,16 +75,20 @@ function Search(options) { return new Search(options); } - this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ - credentials: options.credentials, - email: options.email, - keyFile: options.keyFilename, - scopes: SCOPES - }); + var config = { + baseUrl: 'https://cloudsearch.googleapis.com/v1', + scopes: [ + 'https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/cloudsearch', + 'https://www.googleapis.com/auth/userinfo.email' + ] + }; - this.projectId_ = options.projectId; + Service.call(this, config, options); } +nodeutil.inherits(Search, Service); + /** * Get {module:search/index} objects for all of the indexes in your project. * @@ -180,7 +174,10 @@ Search.prototype.getIndexes = function(query, callback) { delete query.prefix; } - this.makeReq_('GET', '/indexes', query, null, function(err, resp) { + this.request({ + uri: '/indexes', + qs: query + }, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -215,37 +212,11 @@ Search.prototype.getIndexes = function(query, callback) { * @return {module:search/index} */ Search.prototype.index = function(id) { - return new Index(this, id); -}; - -/** - * 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. - */ -Search.prototype.makeReq_ = function(method, path, query, body, callback) { - var reqOpts = { - method: method, - qs: query, - uri: format('{base}projects/{projectId}{path}', { - base: SEARCH_BASE_URL, - projectId: this.projectId_, - path: path - }) - }; - - if (body) { - reqOpts.json = body; + if (!id) { + throw new Error('An ID is needed to access a Google Cloud Search index.'); } - this.makeAuthenticatedRequest_(reqOpts, callback); + return new Index(this, id); }; /*! Developer Documentation diff --git a/system-test/search.js b/system-test/search.js index 74fa42731d8..7a2035f113a 100644 --- a/system-test/search.js +++ b/system-test/search.js @@ -65,7 +65,9 @@ describe('Search', function() { var newIndexName = generateIndexName(); var newIndex = search.index(newIndexName); - newIndex.createDocument(TEST_DOCUMENT_JSON, function(err, document) { + var document = newIndex.document(TEST_DOCUMENT_JSON); + + document.create(function(err) { assert.ifError(err); document.delete(done); }); @@ -80,7 +82,9 @@ describe('Search', function() { var newIndexName = generateIndexName(); var newIndex = search.index(newIndexName); - newIndex.createDocument(TEST_DOCUMENT_JSON, function(err, document) { + var document = newIndex.document(TEST_DOCUMENT_JSON); + + document.create(function(err) { if (err) { done(err); return; @@ -114,18 +118,10 @@ describe('Search', function() { }); describe('listing documents', function() { - var document; + var document = index.document(TEST_DOCUMENT_JSON); before(function(done) { - index.createDocument(TEST_DOCUMENT_JSON, function(err, doc) { - if (err) { - done(err); - return; - } - - document = doc; - done(); - }); + document.create(done); }); after(function(done) { @@ -186,25 +182,31 @@ describe('Search', function() { }); }); - index.createDocument(newDocument, function(err, document) { + newDocument.create(function(err) { assert.ifError(err); - document.getMetadata(function(err) { + + newDocument.getMetadata(function(err) { assert.ifError(err); - assert.deepEqual(document.toJSON(), TEST_DOCUMENT_JSON); - document.delete(done); + assert.deepEqual(newDocument.toJSON(), TEST_DOCUMENT_JSON); + + newDocument.delete(done); }); }); }); it('should create a document from JSON', function(done) { - index.createDocument(TEST_DOCUMENT_JSON, function(err, document) { + var newDocument = index.document(TEST_DOCUMENT_JSON); + + newDocument.create(function(err) { assert.ifError(err); - document.getMetadata(function(err) { + + newDocument.getMetadata(function(err) { assert.ifError(err); - assert.deepEqual(document.toJSON(), TEST_DOCUMENT_JSON); - document.delete(done); + assert.deepEqual(newDocument.toJSON(), TEST_DOCUMENT_JSON); + + newDocument.delete(done); }); }); }); @@ -213,16 +215,15 @@ describe('Search', function() { describe('search', function() { var query = 'ryan'; var DOCUMENT_NAME = generateDocumentName(); - var document; + var document = index.document(DOCUMENT_NAME); before(function(done) { - document = index.document(DOCUMENT_NAME); - var questions = document.addField('question'); + questions.addTextValue('Where did Ryan go?'); questions.addTextValue('Where did Silvano go?'); - index.createDocument(document, done); + document.create(done); }); after(function(done) { From bf6924eceb571aae350c63f4fd6dca3ff721322b Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Oct 2015 16:33:49 -0400 Subject: [PATCH 11/49] storage --- lib/common/service.js | 3 + lib/dns/zone.js | 28 ++-- lib/storage/acl.js | 81 ++++++----- lib/storage/bucket.js | 310 ++++++++++++++++++++++------------------- lib/storage/file.js | 154 +++++++++++++------- lib/storage/index.js | 107 +++++++------- system-test/storage.js | 36 +++-- 7 files changed, 401 insertions(+), 318 deletions(-) diff --git a/lib/common/service.js b/lib/common/service.js index beea62dd616..5a54160562f 100644 --- a/lib/common/service.js +++ b/lib/common/service.js @@ -47,6 +47,7 @@ function Service(config, options) { this.authClient = this.makeAuthenticatedRequest.authClient; this.baseUrl = config.baseUrl; + this.getCredentials = this.makeAuthenticatedRequest.getCredentials; this.projectId = options.projectId; this.projectIdRequired = config.projectIdRequired !== false; } @@ -83,6 +84,8 @@ Service.prototype.request = function(reqOpts, callback) { // Good: https://.../projects:list .replace(/\/:/g, ':'); + // console.log(reqOpts) + this.makeAuthenticatedRequest(reqOpts, callback); }; diff --git a/lib/dns/zone.js b/lib/dns/zone.js index 16313eb7c09..cf0813fda8d 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -90,6 +90,19 @@ function Zone(dns, name) { */ create: true, + /** + * Check if the zone exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the zone exists or not. + * + * @example + * zone.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a zone if it exists. * @@ -122,20 +135,7 @@ function Zone(dns, name) { * @example * zone.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true, - - /** - * Check if the zone exists. - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this - * request. - * @param {boolean} callback.exists - Whether the zone exists or not. - * - * @example - * zone.exists(function(err, exists) {}); - */ - exists: true + getMetadata: true }; ServiceObject.call(this, { diff --git a/lib/storage/acl.js b/lib/storage/acl.js index 2267bbbca96..7c6fd374f8f 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,30 @@ nodeutil.inherits(Acl, AclRoleAccessorMethods); * }, function(err, aclObject, apiResponse) {}); */ Acl.prototype.add = function(options, callback) { - var that = this; - - var body = { - entity: options.entity, - role: options.role.toUpperCase() - }; - - var query = null; + var self = this; + var query = {}; if (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 +290,20 @@ 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 }; } - 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 +353,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; @@ -363,18 +370,21 @@ Acl.prototype.get = function(options, callback) { } } - 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 +430,29 @@ 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 }; } - 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 +485,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 +535,7 @@ AclRoleAccessorMethods.roles = [ ]; AclRoleAccessorMethods.prototype._assignAccessMethods = function(role) { - var that = this; + var self = this; var accessMethods = AclRoleAccessorMethods.accessMethods; var entities = AclRoleAccessorMethods.entities; @@ -553,7 +566,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..d3cb28a1e33 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 @@ -92,15 +93,133 @@ var RESUMABLE_THRESHOLD = 5000000; * * var albums = 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 +257,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 +328,8 @@ function Bucket(storage, name) { /* jshint ignore:end */ } +nodeutil.inherits(Bucket, ServiceObject); + /** * Combine mutliple files into one new file. * @@ -269,13 +390,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 +428,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 +544,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 +648,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 +766,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 +904,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 +1123,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..bcbd0f9b4ae 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -28,6 +28,7 @@ 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 streamEvents = require('stream-events'); @@ -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 @@ -79,16 +92,43 @@ var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b'; * @constructor */ 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, @@ -129,11 +169,13 @@ function File(bucket, name, options) { * }, 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,25 +275,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) { + newFile = newFile || destBucket.file(destName); + + 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, resp); return; } - callback(null, newFile || destBucket.file(destName), resp); + callback(null, newFile, resp); }); }; @@ -459,9 +503,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 +526,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... @@ -846,15 +891,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 +977,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 +1127,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 +1254,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; @@ -1305,23 +1349,27 @@ File.prototype.getSignedUrl = function(options, callback) { * }); */ File.prototype.setMetadata = function(metadata, callback) { + var self = this; + 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) { + 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); }); @@ -1352,27 +1400,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); }); @@ -1415,7 +1473,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 +1510,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 +1521,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..49b79c886a7 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 @@ -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..76b841201e4 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) { @@ -92,7 +88,7 @@ describe('storage', function() { }); }); - describe('acls', function() { + describe.only('acls', function() { var USER_ACCOUNT = 'user-spsawchuk@gmail.com'; describe('buckets', function() { @@ -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; From 337c70fe39cf446eb357c43bae27419b2df198b1 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 27 Oct 2015 20:25:26 -0400 Subject: [PATCH 12/49] fix storage test --- system-test/storage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-test/storage.js b/system-test/storage.js index 76b841201e4..5c46a55f6ae 100644 --- a/system-test/storage.js +++ b/system-test/storage.js @@ -88,7 +88,7 @@ describe('storage', function() { }); }); - describe.only('acls', function() { + describe('acls', function() { var USER_ACCOUNT = 'user-spsawchuk@gmail.com'; describe('buckets', function() { From 8f2d1690c6cdf5cfb0ebc74252f10d3d377159a3 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 29 Oct 2015 15:09:18 -0400 Subject: [PATCH 13/49] test dataset --- lib/bigquery/dataset.js | 5 +- test/bigquery/dataset.js | 411 +++++++++++++++++---------------------- 2 files changed, 177 insertions(+), 239 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 99caed4286c..452abfdf2cb 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -186,7 +186,7 @@ nodeutil.inherits(Dataset, ServiceObject); * dataset.createTable(tableConfig, function(err, table, apiResponse) {}); */ Dataset.prototype.createTable = function(id, options, callback) { - var that = this; + var self = this; extend(true, options, { tableReference: { @@ -210,7 +210,7 @@ Dataset.prototype.createTable = function(id, options, callback) { return; } - var table = that.table(resp.tableReference.tableId); + var table = self.table(resp.tableReference.tableId); table.metadata = resp; callback(null, table, resp); @@ -319,7 +319,6 @@ Dataset.prototype.getTables = function(query, callback) { } var nextQuery = null; - if (resp.nextPageToken) { nextQuery = extend({}, query, { pageToken: resp.nextPageToken diff --git a/test/bigquery/dataset.js b/test/bigquery/dataset.js index 40af0bb74b8..c2eaad2f2c5 100644 --- a/test/bigquery/dataset.js +++ b/test/bigquery/dataset.js @@ -18,8 +18,12 @@ var arrify = require('arrify'); var assert = require('assert'); -var util = require('../../lib/common/util'); +var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); var extended = false; var fakeStreamRouter = { @@ -35,8 +39,18 @@ var fakeStreamRouter = { } }; +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('BigQuery/Dataset', function() { - var BIGQUERY = { projectId: 'my-project' }; + var BIGQUERY = { + projectId: 'my-project', + createDataset: util.noop + }; var DATASET_ID = 'kittens'; var Dataset; var Table; @@ -44,6 +58,7 @@ describe('BigQuery/Dataset', function() { before(function() { mockery.registerMock('../common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.enable({ useCleanCache: true, warnOnUnregistered: false @@ -66,6 +81,32 @@ describe('BigQuery/Dataset', function() { it('should extend the correct methods', function() { assert(extended); // See `fakeStreamRouter.extend` }); + + it('should inherit from ServiceObject', function(done) { + var bigQueryInstance = extend({}, BIGQUERY, { + createDataset: { + bind: function(context) { + assert.strictEqual(context, bigQueryInstance); + done(); + } + } + }); + + var ds = new Dataset(bigQueryInstance, DATASET_ID); + + var calledWith = ds.calledWith_[0]; + + assert.strictEqual(calledWith.parent, bigQueryInstance); + assert.strictEqual(calledWith.baseUrl, '/datasets'); + assert.strictEqual(calledWith.id, DATASET_ID); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true, + setMetadata: true + }); + }); }); describe('createTable', function() { @@ -80,45 +121,62 @@ describe('BigQuery/Dataset', function() { var SCHEMA_STRING = 'id:integer,breed,name,dob:timestamp'; var TABLE_ID = 'kittens'; + var API_RESPONSE = { + tableReference: { + tableId: TABLE_ID + } + }; + it('should create a table', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/tables'); - assert.strictEqual(query, null); + var options = { + schema: SCHEMA_OBJECT + }; + + ds.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/tables'); + + var body = reqOpts.json; + assert.strictEqual(body, options); assert.deepEqual(body.schema, SCHEMA_OBJECT); assert.equal(body.tableReference.datasetId, DATASET_ID); assert.equal(body.tableReference.projectId, ds.bigQuery.projectId); assert.equal(body.tableReference.tableId, TABLE_ID); + done(); }; - ds.createTable({ id: TABLE_ID, schema: SCHEMA_OBJECT }, assert.ifError); + + ds.createTable(TABLE_ID, options, assert.ifError); }); it('should create a schema object from a string', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.schema, SCHEMA_OBJECT); + ds.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.schema, SCHEMA_OBJECT); done(); }; - ds.createTable({ id: TABLE_ID, schema: SCHEMA_STRING }, assert.ifError); + + ds.createTable(TABLE_ID, { schema: SCHEMA_STRING }, assert.ifError); }); it('should return an error to the callback', function(done) { var error = new Error('Error.'); - ds.makeReq_ = function(method, path, query, body, callback) { + + ds.request = function(reqOpts, callback) { callback(error); }; - ds.createTable({ id: TABLE_ID, schema: SCHEMA_OBJECT }, function(err) { - assert.equal(err, error); + + ds.createTable(TABLE_ID, { schema: SCHEMA_OBJECT }, function(err) { + assert.strictEqual(err, error); done(); }); }); it('should return a Table object', function(done) { - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, { tableReference: { tableId: TABLE_ID } }); + ds.request = function(reqOpts, callback) { + callback(null, API_RESPONSE); }; - var options = { id: TABLE_ID, schema: SCHEMA_OBJECT }; - ds.createTable(options, function(err, table) { + + ds.createTable(TABLE_ID, { schema: SCHEMA_OBJECT }, function(err, table) { assert.ifError(err); assert(table instanceof Table); done(); @@ -126,31 +184,32 @@ describe('BigQuery/Dataset', function() { }); it('should return an apiResponse', function(done) { - var resp = { tableReference: { tableId: TABLE_ID } }; - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, resp); + var opts = { id: TABLE_ID, schema: SCHEMA_OBJECT }; + + ds.request = function(reqOpts, callback) { + callback(null, API_RESPONSE); }; - var options = { id: TABLE_ID, schema: SCHEMA_OBJECT }; - ds.createTable(options, function(err, table, apiResponse) { + + ds.createTable(TABLE_ID, opts, function(err, table, apiResponse) { assert.ifError(err); - assert.deepEqual(apiResponse, resp); + assert.strictEqual(apiResponse, API_RESPONSE); done(); }); }); it('should assign metadata to the Table object', function(done) { - var metadata = { + var apiResponse = extend({ a: 'b', - c: 'd', - tableReference: { tableId: TABLE_ID } - }; - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, metadata); + c: 'd' + }, API_RESPONSE); + + ds.request = function(reqOpts, callback) { + callback(null, apiResponse); }; - var options = { id: TABLE_ID, schema: SCHEMA_OBJECT }; - ds.createTable(options, function(e, table) { - assert.ifError(e); - assert.deepEqual(table.metadata, metadata); + + ds.createTable(TABLE_ID, { schema: SCHEMA_OBJECT }, function(err, table) { + assert.ifError(err); + assert.strictEqual(table.metadata, apiResponse); done(); }); }); @@ -158,189 +217,153 @@ describe('BigQuery/Dataset', function() { describe('delete', function() { it('should delete the dataset via the api', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'DELETE'); - assert.equal(path, ''); - assert.deepEqual(query, { deleteContents: false }); - assert.strictEqual(body, null); + ds.request = function(reqOpts) { + assert.equal(reqOpts.method, 'DELETE'); + assert.equal(reqOpts.uri, ''); + assert.deepEqual(reqOpts.qs, { deleteContents: false }); done(); }; + ds.delete(assert.ifError); }); it('should allow a force delete', function(done) { - ds.makeReq_ = function(method, path, query) { - assert.deepEqual(query, { deleteContents: true }); + ds.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, { deleteContents: true }); done(); }; + ds.delete({ force: true }, assert.ifError); }); it('should execute callback when done', function(done) { - ds.makeReq_ = function(method, path, query, body, callback) { + ds.request = function(reqOpts, callback) { callback(); }; + ds.delete(done); }); it('should pass error to callback', function(done) { var error = new Error('Error.'); - ds.makeReq_ = function(method, path, query, body, callback) { + + ds.request = function(reqOpts, callback) { callback(error); }; + ds.delete(function(err) { - assert.equal(err, error); + assert.strictEqual(err, error); done(); }); }); it('should pass apiResponse to callback', function(done) { - var resp = { success: true }; - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, resp); - }; - ds.delete(function(err, apiResponse) { - assert.deepEqual(apiResponse, { success: true }); - done(); - }); - }); - }); + var apiResponse = {}; - describe('getMetadata', function() { - it('should get metadata from api', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - done(); + ds.request = function(reqOpts, callback) { + callback(null, apiResponse); }; - ds.getMetadata(assert.ifError); - }); - it('should execute callback with error', function(done) { - var error = new Error('Error.'); - ds.makeReq_ = function(method, path, query, body, callback) { - callback(error); - }; - ds.getMetadata(function(err) { - assert.equal(err, error); + ds.delete(function(err, apiResponse_) { + assert.strictEqual(apiResponse_, apiResponse); done(); }); }); - - describe('metadata', function() { - var METADATA = { a: 'b', c: 'd' }; - - beforeEach(function() { - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, METADATA); - }; - }); - - it('should update metadata on Dataset object', function(done) { - ds.getMetadata(function(err) { - assert.ifError(err); - assert.deepEqual(ds.metadata, METADATA); - done(); - }); - }); - - it('should execute callback with metadata', function(done) { - ds.getMetadata(function(err, metadata) { - assert.ifError(err); - assert.deepEqual(metadata, METADATA); - done(); - }); - }); - - it('should execute callback with apiResponse', function(done) { - ds.getMetadata(function(err, metadata, apiResponse) { - assert.ifError(err); - assert.deepEqual(apiResponse, METADATA); - done(); - }); - }); - }); }); describe('getTables', function() { it('should get tables from the api', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/tables'); - assert.deepEqual(query, {}); - assert.strictEqual(body, null); + ds.request = function(reqOpts) { + assert.equal(reqOpts.uri, '/tables'); + assert.deepEqual(reqOpts.qs, {}); done(); }; + ds.getTables(assert.ifError); }); - it('should accept query', function(done) { - var queryObject = { maxResults: 8, pageToken: 'token' }; - ds.makeReq_ = function(method, path, query) { - assert.deepEqual(query, queryObject); + it('should accept a query', function(done) { + var query = { + maxResults: 8, + pageToken: 'token' + }; + + ds.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs, query); done(); }; - ds.getTables(queryObject, assert.ifError); + + ds.getTables(query, assert.ifError); }); it('should return error to callback', function(done) { var error = new Error('Error.'); - ds.makeReq_ = function(method, path, query, body, callback) { + + ds.request = function(reqOpts, callback) { callback(error); }; + ds.getTables(function(err) { - assert.equal(err, error); + assert.strictEqual(err, error); done(); }); }); - it('should return Table objects', function(done) { - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, { tables: [{ id: 'tableName' }] }); + describe('success', function() { + var apiResponse = { + tables: [ + { + a: 'b', + c: 'd', + id: 'tableName' + } + ] }; - ds.getTables(function(err, tables) { - assert.ifError(err); - assert(tables[0] instanceof Table); - done(); + + beforeEach(function() { + ds.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; }); - }); - it('should return apiResponse', function(done) { - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, { tables: [{ id: 'tableName' }] }); - }; - ds.getTables(function(err, tables, nextQuery, apiResponse) { - assert.ifError(err); - assert.deepEqual(apiResponse, { tables: [{ id: 'tableName' }] }); - done(); + it('should return Table & apiResponse', function(done) { + ds.getTables(function(err, tables, nextQuery, apiResponse_) { + assert.ifError(err); + assert(tables[0] instanceof Table); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); }); - }); - it('should assign metadata to the Table objects', function(done) { - var tableObjects = [{ a: 'b', c: 'd', id: 'tableName' }]; - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, { tables: tableObjects }); - }; - ds.getTables(function(err, tables) { - assert.ifError(err); - assert(tables[0].metadata, tableObjects[0]); - done(); + it('should assign metadata to the Table objects', function(done) { + ds.getTables(function(err, tables) { + assert.ifError(err); + assert.strictEqual(tables[0].metadata, apiResponse.tables[0]); + done(); + }); }); - }); - it('should return token if more results exist', function(done) { - var token = 'token'; - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, { nextPageToken: token }); - }; - ds.getTables({ maxResults: 5 }, function(err, tables, nextQuery) { - assert.deepEqual(nextQuery, { - pageToken: token, + it('should return token if more results exist', function(done) { + var pageToken = 'token'; + + var query = { maxResults: 5 + }; + + var expectedNextQuery = { + maxResults: 5, + pageToken: pageToken + }; + + ds.request = function(reqOpts, callback) { + callback(null, { nextPageToken: pageToken }); + }; + + ds.getTables(query, function(err, tables, nextQuery) { + assert.ifError(err); + assert.deepEqual(nextQuery, expectedNextQuery); + done(); }); - done(); }); }); }); @@ -420,60 +443,6 @@ describe('BigQuery/Dataset', function() { }); }); - describe('setMetadata', function() { - var METADATA = { a: 'b', c: 'd' }; - - it('should send request to the api', function(done) { - ds.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'PATCH'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.deepEqual(body, METADATA); - done(); - }; - ds.setMetadata(METADATA, assert.ifError); - }); - - it('should execute callback with error & API response', function(done) { - var error = new Error('Error.'); - var apiResponse = {}; - - ds.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - - ds.setMetadata(METADATA, function(err, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - describe('metadata', function() { - beforeEach(function() { - ds.makeReq_ = function(method, path, query, body, callback) { - callback(null, METADATA); - }; - }); - - it('should update metadata on Dataset object', function(done) { - ds.setMetadata(METADATA, function(err) { - assert.ifError(err); - assert.deepEqual(ds.metadata, METADATA); - done(); - }); - }); - - it('should execute callback with apiResponse', function(done) { - ds.setMetadata(METADATA, function(err, apiResponse) { - assert.ifError(err); - assert.deepEqual(apiResponse, METADATA); - done(); - }); - }); - }); - }); - describe('table', function() { it('should return a Table object', function() { var tableId = 'tableId'; @@ -482,34 +451,4 @@ describe('BigQuery/Dataset', function() { assert.equal(table.id, tableId); }); }); - - describe('makeReq_', function() { - it('should prefix the path', function(done) { - var path = '/test-path'; - - ds.bigQuery.makeReq_ = function(method, p) { - assert.equal(p, '/datasets/' + ds.id + path); - done(); - }; - - ds.makeReq_('POST', path); - }); - - it('should pass through arguments', function(done) { - var method = 'POST'; - var query = { a: 'b', c: 'd', e: { f: 'g' } }; - var body = { a: 'b', c: 'd', e: { f: 'g' } }; - var callback = util.noop; - - ds.bigQuery.makeReq_ = function(m, p, q, b, c) { - assert.equal(m, method); - assert.deepEqual(q, query); - assert.deepEqual(b, body); - assert.equal(c, callback); - done(); - }; - - ds.makeReq_(method, '/path', query, body, callback); - }); - }); }); From faf158fe62434c81092765a903a45c31add7a4e3 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 29 Oct 2015 15:57:05 -0400 Subject: [PATCH 14/49] test job --- test/bigquery/job.js | 104 ++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 66 deletions(-) diff --git a/test/bigquery/job.js b/test/bigquery/job.js index 1d02ca47485..a9adf5d2c90 100644 --- a/test/bigquery/job.js +++ b/test/bigquery/job.js @@ -17,17 +17,42 @@ 'use strict'; var assert = require('assert'); -var Job = require('../../lib/bigquery/job'); -var util = require('../../lib/common/util'); +var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('BigQuery/Job', function() { var BIGQUERY = { - projectId: 'test-project', - makeReq_: util.noop + projectId: 'my-project' }; var JOB_ID = 'job_XYrk_3z'; + var Job; var job; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Job = require('../../lib/bigquery/job.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { job = new Job(BIGQUERY, JOB_ID); }); @@ -37,69 +62,16 @@ describe('BigQuery/Job', function() { assert.deepEqual(job.bigQuery, BIGQUERY); }); - it('should assign the given id', function() { - assert.equal(job.id, JOB_ID); - }); - - it('should assign empty metadata object', function() { - assert.equal(JSON.stringify(job.metadata), '{}'); - }); - }); - - describe('getMetadata', function() { - it('should get metadata from api', function(done) { - job.bigQuery.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/jobs/' + job.id); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - done(); - }; - job.getMetadata(assert.ifError); - }); - - it('should execute callback with error', function(done) { - var error = new Error('Error.'); - job.bigQuery.makeReq_ = function(method, path, query, body, callback) { - callback(error); - }; - job.getMetadata(function(err) { - assert.equal(err, error); - done(); - }); - }); - - describe('metadata', function() { - var METADATA = { a: 'b', c: 'd' }; - - beforeEach(function() { - job.bigQuery.makeReq_ = function(method, path, query, body, callback) { - callback(null, METADATA); - }; - }); - - it('should update metadata on Dataset object', function(done) { - job.getMetadata(function(err) { - assert.ifError(err); - assert.deepEqual(job.metadata, METADATA); - done(); - }); - }); - - it('should execute callback with metadata', function(done) { - job.getMetadata(function(err, metadata) { - assert.ifError(err); - assert.deepEqual(metadata, METADATA); - done(); - }); - }); + it('should inherit from ServiceObject', function() { + var calledWith = job.calledWith_[0]; - it('should execute callback with apiResponse', function(done) { - job.getMetadata(function(err, metadata, apiResponse) { - assert.ifError(err); - assert.deepEqual(apiResponse, METADATA); - done(); - }); + assert.strictEqual(calledWith.parent, BIGQUERY); + assert.strictEqual(calledWith.baseUrl, '/jobs'); + assert.strictEqual(calledWith.id, JOB_ID); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true }); }); }); From f028d39b8f4fddad95777598ed961eedfcb09f3a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 29 Oct 2015 17:10:38 -0400 Subject: [PATCH 15/49] bigquery.table --- lib/bigquery/table.js | 4 +- test/bigquery/table.js | 315 ++++++++++++++++------------------------- 2 files changed, 121 insertions(+), 198 deletions(-) diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index 3b9d9a1a22f..43994c107d6 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -35,7 +35,7 @@ var streamEvents = require('stream-events'); * @type {module:storage/file} * @private */ -var File = require('../storage/file'); +var File = require('../storage/file.js'); /** * @type {module:common/serviceObject} @@ -53,7 +53,7 @@ var streamRouter = require('../common/stream-router.js'); * @type {module:common/util} * @private */ -var util = require('../common/util'); +var util = require('../common/util.js'); /*! Developer Documentation * diff --git a/test/bigquery/table.js b/test/bigquery/table.js index 397e415488b..76daf67c4e6 100644 --- a/test/bigquery/table.js +++ b/test/bigquery/table.js @@ -20,12 +20,16 @@ var arrify = require('arrify'); var assert = require('assert'); var crypto = require('crypto'); var extend = require('extend'); -var File = require('../../lib/storage/file'); var mockery = require('mockery'); +var nodeutil = require('util'); var stream = require('stream'); -var util = require('../../lib/common/util'); + +var File = require('../../lib/storage/file.js'); +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); function FakeFile(a, b) { + this.request = util.noop; File.call(this, a, b); } @@ -51,15 +55,22 @@ var fakeStreamRouter = { } }; +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('BigQuery/Table', function() { var DATASET = { id: 'dataset-id', + createTable: util.noop, bigQuery: { - makeReq_: util.noop, + projectId: 'project-id', job: function(id) { return { id: id }; - }, - projectId: 'project-id' + } } }; @@ -79,15 +90,16 @@ describe('BigQuery/Table', function() { var tableOverrides = {}; before(function() { - mockery.registerMock('../storage/file', FakeFile); + mockery.registerMock('../storage/file.js', FakeFile); + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util', fakeUtil); + mockery.registerMock('../common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); - Table = require('../../lib/bigquery/table'); + Table = require('../../lib/bigquery/table.js'); var tableCached = extend(true, {}, Table); @@ -120,6 +132,32 @@ describe('BigQuery/Table', function() { it('should extend the correct methods', function() { assert(extended); // See `fakeStreamRouter.extend` }); + + it('should inherit from ServiceObject', function(done) { + var datasetInstance = extend({}, DATASET, { + createTable: { + bind: function(context) { + assert.strictEqual(context, datasetInstance); + done(); + } + } + }); + + var table = new Table(datasetInstance, TABLE_ID); + + var calledWith = table.calledWith_[0]; + + assert.strictEqual(calledWith.parent, datasetInstance); + assert.strictEqual(calledWith.baseUrl, '/tables'); + assert.strictEqual(calledWith.id, TABLE_ID); + assert.deepEqual(calledWith.methods, { + create: true, + delete: true, + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('createSchemaFromString_', function() { @@ -179,11 +217,10 @@ describe('BigQuery/Table', function() { }); it('should send correct request to the API', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/jobs'); - assert.strictEqual(query, null); - assert.deepEqual(body, { + table.bigQuery.request = function(reqOpts) { + assert.equal(reqOpts.method, 'POST'); + assert.equal(reqOpts.uri, '/jobs'); + assert.deepEqual(reqOpts.json, { configuration: { copy: { a: 'b', @@ -201,6 +238,7 @@ describe('BigQuery/Table', function() { } } }); + done(); }; @@ -210,7 +248,7 @@ describe('BigQuery/Table', function() { it('should create and return a Job', function(done) { var jobId = 'job-id'; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, { jobReference: { jobId: jobId } }); }; @@ -224,7 +262,7 @@ describe('BigQuery/Table', function() { it('should assign metadata on the job', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -236,7 +274,7 @@ describe('BigQuery/Table', function() { }); it('should accept just a destination and callback', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, { jobReference: { jobId: 'job-id' } }); }; @@ -246,7 +284,7 @@ describe('BigQuery/Table', function() { it('should pass an error to the callback', function(done) { var error = new Error('Error.'); - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(error); }; @@ -259,7 +297,7 @@ describe('BigQuery/Table', function() { it('should pass an apiResponse to the callback', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -412,36 +450,9 @@ describe('BigQuery/Table', function() { }); }); - describe('delete', function() { - it('should send the correct API request to delete', function(done) { - table.makeReq_ = function(method, path, query, body, callback) { - assert.equal(method, 'DELETE'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - callback(); - }; - - table.delete(done); - }); - - it('should return apiResponse in callback', function(done) { - var resp = { success: true }; - table.makeReq_ = function(method, path, query, body, callback) { - callback(null, resp); - }; - - table.delete(function(err, apiResponse) { - assert.deepEqual(apiResponse, resp); - done(); - }); - }); - }); - describe('export', function() { var FILE = new FakeFile({ - name: 'bucket-name', - makeReq_: util.noop + name: 'bucket-name' }, 'file.json'); beforeEach(function() { @@ -451,15 +462,15 @@ describe('BigQuery/Table', function() { }); it('should send the correct API request', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/jobs'); - assert.strictEqual(query, null); - assert.deepEqual(body.configuration.extract.sourceTable, { + table.bigQuery.request = function(reqOpts) { + assert.equal(reqOpts.method, 'POST'); + assert.equal(reqOpts.uri, '/jobs'); + assert.deepEqual(reqOpts.json.configuration.extract.sourceTable, { datasetId: table.dataset.id, projectId: table.bigQuery.projectId, tableId: table.id }); + done(); }; @@ -467,7 +478,7 @@ describe('BigQuery/Table', function() { }); it('should accept just a destination and a callback', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, { jobReference: { jobId: 'job-id' }}); }; @@ -475,8 +486,8 @@ describe('BigQuery/Table', function() { }); it('should parse out full gs:// urls from files', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.configuration.extract.destinationUris, [ + table.bigQuery.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.configuration.extract.destinationUris, [ 'gs://' + FILE.bucket.name + '/' + FILE.name ]); done(); @@ -496,9 +507,9 @@ describe('BigQuery/Table', function() { }); it('should detect file format if a format is not provided', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - var destinationFormat = body.configuration.extract.destinationFormat; - assert.equal(destinationFormat, 'NEWLINE_DELIMITED_JSON'); + table.bigQuery.request = function(reqOpts) { + var destFormat = reqOpts.json.configuration.extract.destinationFormat; + assert.equal(destFormat, 'NEWLINE_DELIMITED_JSON'); done(); }; @@ -506,9 +517,10 @@ describe('BigQuery/Table', function() { }); it('should assign the provided format if matched', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - assert.equal(body.configuration.extract.destinationFormat, 'CSV'); - assert.strictEqual(body.configuration.extract.format, undefined); + table.bigQuery.request = function(reqOpts) { + var extract = reqOpts.json.configuration.extract; + assert.equal(extract.destinationFormat, 'CSV'); + assert.strictEqual(extract.format, undefined); done(); }; @@ -522,9 +534,9 @@ describe('BigQuery/Table', function() { }); it('should assign GZIP compression with gzip: true', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - assert.equal(body.configuration.extract.compression, 'GZIP'); - assert.strictEqual(body.configuration.extract.gzip, undefined); + table.bigQuery.request = function(reqOpts) { + assert.equal(reqOpts.json.configuration.extract.compression, 'GZIP'); + assert.strictEqual(reqOpts.json.configuration.extract.gzip, undefined); done(); }; @@ -534,12 +546,12 @@ describe('BigQuery/Table', function() { it('should execute the callback with error', function(done) { var error = new Error('Error.'); - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(error); }; table.export(FILE, function(err) { - assert.equal(err, error); + assert.strictEqual(err, error); done(); }); }); @@ -547,7 +559,7 @@ describe('BigQuery/Table', function() { it('should create a Job and returns it to the callback', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -561,7 +573,7 @@ describe('BigQuery/Table', function() { it('should return apiResponse to callback', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -573,67 +585,9 @@ describe('BigQuery/Table', function() { }); }); - describe('getMetadata', function() { - it('should get metadata from api', function(done) { - table.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - done(); - }; - table.getMetadata(assert.ifError); - }); - - it('should execute callback with error', function(done) { - var error = new Error('Error.'); - table.makeReq_ = function(method, path, query, body, callback) { - callback(error); - }; - table.getMetadata(function(err) { - assert.equal(err, error); - done(); - }); - }); - - describe('metadata', function() { - var METADATA = { a: 'b', c: 'd' }; - - beforeEach(function() { - table.makeReq_ = function(method, path, query, body, callback) { - callback(null, METADATA); - }; - }); - - it('should update metadata on Table object', function(done) { - table.getMetadata(function(err) { - assert.ifError(err); - assert.deepEqual(table.metadata, METADATA); - done(); - }); - }); - - it('should execute callback with metadata', function(done) { - table.getMetadata(function(err, metadata) { - assert.ifError(err); - assert.deepEqual(metadata, METADATA); - done(); - }); - }); - - it('should execute callback with apiResponse', function(done) { - table.getMetadata(function(err, metadata, apiResponse) { - assert.ifError(err); - assert.deepEqual(apiResponse, METADATA); - done(); - }); - }); - }); - }); - describe('getRows', function() { it('should accept just a callback', function(done) { - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, {}); }; table.getRows(done); @@ -642,11 +596,9 @@ describe('BigQuery/Table', function() { it('should make correct API request', function(done) { var options = { a: 'b', c: 'd' }; - table.makeReq_ = function(method, path, query, body, callback) { - assert.equal(method, 'GET'); - assert.equal(path, '/data'); - assert.deepEqual(query, options); - assert.strictEqual(body, null); + table.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.uri, '/data'); + assert.strictEqual(reqOpts.qs, options); callback(null, {}); }; @@ -657,7 +609,7 @@ describe('BigQuery/Table', function() { var apiResponse = {}; var error = new Error('Error.'); - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -677,7 +629,7 @@ describe('BigQuery/Table', function() { var schema = { fields: [{ name: 'name', type: 'string' }] }; beforeEach(function() { - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { // Respond with a row, so it grabs the schema. // Use setImmediate to let our getMetadata overwrite process. setImmediate(callback, null, { rows: rows }); @@ -730,7 +682,7 @@ describe('BigQuery/Table', function() { var schema = { fields: [{ name: 'name', type: 'string' }] }; table.metadata = { schema: schema }; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, { rows: rows }); }; @@ -746,7 +698,7 @@ describe('BigQuery/Table', function() { var schema = { fields: [{ name: 'name', type: 'string' }] }; table.metadata = { schema: schema }; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, { rows: rows }); }; @@ -764,7 +716,7 @@ describe('BigQuery/Table', function() { // Set a schema so it doesn't try to refresh the metadata. table.metadata = { schema: {} }; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, { pageToken: pageToken }); }; @@ -797,8 +749,8 @@ describe('BigQuery/Table', function() { return ws; }; - table.import(FILEPATH, function(error, job) { - assert.strictEqual(error, null); + table.import(FILEPATH, function(err, job) { + assert.strictEqual(err, null); assert.deepEqual(job, mockJob); done(); }); @@ -847,8 +799,8 @@ describe('BigQuery/Table', function() { }); it('should convert File objects to gs:// urls', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - var sourceUri = body.configuration.load.sourceUris[0]; + table.bigQuery.request = function(reqOpts) { + var sourceUri = reqOpts.json.configuration.load.sourceUris[0]; assert.equal(sourceUri, 'gs://' + FILE.bucket.name + '/' + FILE.name); done(); }; @@ -857,8 +809,8 @@ describe('BigQuery/Table', function() { }); it('should infer the file format from a File object', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - var sourceFormat = body.configuration.load.sourceFormat; + table.bigQuery.request = function(reqOpts) { + var sourceFormat = reqOpts.json.configuration.load.sourceFormat; assert.equal(sourceFormat, 'NEWLINE_DELIMITED_JSON'); done(); }; @@ -867,8 +819,8 @@ describe('BigQuery/Table', function() { }); it('should not override a provided format with a File', function(done) { - table.bigQuery.makeReq_ = function(method, path, query, body) { - var sourceFormat = body.configuration.load.sourceFormat; + table.bigQuery.request = function(reqOpts) { + var sourceFormat = reqOpts.json.configuration.load.sourceFormat; assert.equal(sourceFormat, 'CSV'); done(); }; @@ -879,7 +831,7 @@ describe('BigQuery/Table', function() { it('should execute the callback with error', function(done) { var error = new Error('Error.'); - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(error); }; @@ -892,7 +844,7 @@ describe('BigQuery/Table', function() { it('should create a Job and return it to the callback', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -906,7 +858,7 @@ describe('BigQuery/Table', function() { it('should return apiResponse to callback', function(done) { var jobMetadata = { jobReference: { jobId: 'job-id' }, a: 'b', c: 'd' }; - table.bigQuery.makeReq_ = function(method, path, query, body, callback) { + table.bigQuery.request = function(reqOpts, callback) { callback(null, jobMetadata); }; @@ -939,11 +891,10 @@ describe('BigQuery/Table', function() { }; it('should save data', function(done) { - table.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/insertAll'); - assert.strictEqual(query, null); - assert.deepEqual(body, dataApiFormat); + table.request = function(reqOpts) { + assert.equal(reqOpts.method, 'POST'); + assert.equal(reqOpts.uri, '/insertAll'); + assert.deepEqual(reqOpts.json, dataApiFormat); done(); }; @@ -953,7 +904,7 @@ describe('BigQuery/Table', function() { it('should execute callback with API response', function(done) { var apiResponse = { insertErrors: [] }; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -969,7 +920,7 @@ describe('BigQuery/Table', function() { var error = new Error('Error.'); var apiResponse = {}; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -985,7 +936,7 @@ describe('BigQuery/Table', function() { var row0Error = { message: 'Error.', reason: 'notFound' }; var row1Error = { message: 'Error.', reason: 'notFound' }; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, { insertErrors: [ { index: 0, errors: [row0Error] }, @@ -1023,28 +974,30 @@ describe('BigQuery/Table', function() { var METADATA = { a: 'b', c: 'd' }; it('should send request to the api', function(done) { - table.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'PUT'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.deepEqual(body, METADATA); + table.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'PUT'); + assert.strictEqual(reqOpts.uri, ''); + assert.deepEqual(reqOpts.json, METADATA); done(); }; + table.setMetadata(METADATA, assert.ifError); }); it('should convert a name to a friendly name', function(done) { var name = 'a new name'; - table.makeReq_ = function(method, path, query, body) { - assert.equal(body.friendlyName, name); + + table.request = function(reqOpts) { + assert.equal(reqOpts.json.friendlyName, name); done(); }; + table.setMetadata({ name: name }, assert.ifError); }); it('should accept a schema', function(done) { - table.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.schema, { + table.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.schema, { fields: [{ name: 'schema', type: 'string' }] }); done(); @@ -1056,7 +1009,7 @@ describe('BigQuery/Table', function() { var error = new Error('Error.'); var apiResponse = {}; - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -1069,7 +1022,7 @@ describe('BigQuery/Table', function() { describe('metadata', function() { beforeEach(function() { - table.makeReq_ = function(method, path, query, body, callback) { + table.request = function(reqOpts, callback) { callback(null, METADATA); }; }); @@ -1099,34 +1052,4 @@ describe('BigQuery/Table', function() { }); }); }); - - describe('makeReq_', function() { - it('should prefix the path', function(done) { - var path = '/test-path'; - - table.dataset.makeReq_ = function(method, p) { - assert.equal(p, '/tables/' + table.id + path); - done(); - }; - - table.makeReq_('POST', path); - }); - - it('should pass through arguments', function(done) { - var method = 'POST'; - var query = { a: 'b', c: 'd', e: { f: 'g' } }; - var body = { a: 'b', c: 'd', e: { f: 'g' } }; - var callback = util.noop; - - table.dataset.makeReq_ = function(m, p, q, b, c) { - assert.equal(m, method); - assert.deepEqual(q, query); - assert.deepEqual(b, body); - assert.equal(c, callback); - done(); - }; - - table.makeReq_(method, '/path', query, body, callback); - }); - }); }); From b8167a84c287a34153ed54699f5045629c1c74c3 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 29 Oct 2015 17:35:30 -0400 Subject: [PATCH 16/49] bigquery.index --- test/bigquery/dataset.js | 2 + test/bigquery/index.js | 222 ++++++++++++++++++++++----------------- test/bigquery/job.js | 2 + test/bigquery/table.js | 2 + 4 files changed, 131 insertions(+), 97 deletions(-) diff --git a/test/bigquery/dataset.js b/test/bigquery/dataset.js index c2eaad2f2c5..0a1328ef2c7 100644 --- a/test/bigquery/dataset.js +++ b/test/bigquery/dataset.js @@ -83,6 +83,8 @@ describe('BigQuery/Dataset', function() { }); it('should inherit from ServiceObject', function(done) { + assert(ds instanceof ServiceObject); + var bigQueryInstance = extend({}, BIGQUERY, { createDataset: { bind: function(context) { diff --git a/test/bigquery/index.js b/test/bigquery/index.js index 82f4cd81178..743ce56e46d 100644 --- a/test/bigquery/index.js +++ b/test/bigquery/index.js @@ -18,9 +18,12 @@ var arrify = require('arrify'); var assert = require('assert'); -var mockery = require('mockery'); var extend = require('extend'); -var util = require('../../lib/common/util'); +var mockery = require('mockery'); +var nodeutil = require('util'); + +var Service = require('../../lib/common/service.js'); +var util = require('../../lib/common/util.js'); var Table = require('../../lib/bigquery/table.js'); var fakeUtil = extend({}, util); @@ -50,6 +53,13 @@ var fakeStreamRouter = { } }; +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + describe('BigQuery', function() { var JOB_ID = 'JOB_ID'; var PROJECT_ID = 'test-project'; @@ -59,13 +69,14 @@ describe('BigQuery', function() { before(function() { mockery.registerMock('./table.js', FakeTable); + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('../common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); - BigQuery = require('../../lib/bigquery'); + BigQuery = require('../../lib/bigquery/index.js'); }); after(function() { @@ -100,31 +111,46 @@ describe('BigQuery', function() { fakeUtil.normalizeArguments = normalizeArguments; }); + + it('should inherit from Service', function() { + assert(bq instanceof Service); + + var calledWith = bq.calledWith_[0]; + + var baseUrl = 'https://www.googleapis.com/bigquery/v2'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/bigquery' + ]); + }); }); describe('createDataset', function() { var DATASET_ID = 'kittens'; it('should create a dataset', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/datasets'); - assert.strictEqual(query, null); - assert.deepEqual(body, { + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/datasets'); + assert.deepEqual(reqOpts.json, { datasetReference: { datasetId: DATASET_ID } }); + done(); }; + bq.createDataset(DATASET_ID, assert.ifError); }); it('should return an error to the callback', function(done) { var error = new Error('Error.'); - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(error); }; + bq.createDataset(DATASET_ID, function(err) { assert.equal(err, error); done(); @@ -132,9 +158,10 @@ describe('BigQuery', function() { }); it('should return a Dataset object', function(done) { - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, {}); }; + bq.createDataset(DATASET_ID, function(err, dataset) { assert.ifError(err); assert.equal(dataset.constructor.name, 'Dataset'); @@ -144,9 +171,11 @@ describe('BigQuery', function() { it('should return an apiResponse', function(done) { var resp = { success: true }; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, resp); }; + bq.createDataset(DATASET_ID, function(err, dataset, apiResponse) { assert.ifError(err); assert.deepEqual(apiResponse, resp); @@ -156,9 +185,11 @@ describe('BigQuery', function() { it('should assign metadata to the Dataset object', function(done) { var metadata = { a: 'b', c: 'd' }; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, metadata); }; + bq.createDataset(DATASET_ID, function(err, dataset) { assert.ifError(err); assert.deepEqual(dataset.metadata, metadata); @@ -184,57 +215,64 @@ describe('BigQuery', function() { describe('getDatasets', function() { it('should get datasets from the api', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/datasets'); - assert.deepEqual(query, {}); - assert.strictEqual(body, null); + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/datasets'); + assert.deepEqual(reqOpts.qs, {}); + done(); }; + bq.getDatasets(assert.ifError); }); it('should accept query', function(done) { var queryObject = { all: true, maxResults: 8, pageToken: 'token' }; - bq.makeReq_ = function(method, path, query) { - assert.deepEqual(query, queryObject); + + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs, queryObject); done(); }; + bq.getDatasets(queryObject, assert.ifError); }); it('should return error to callback', function(done) { var error = new Error('Error.'); - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(error); }; + bq.getDatasets(function(err) { - assert.equal(err, error); + assert.strictEqual(err, error); done(); }); }); it('should return Dataset objects', function(done) { - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { datasets: [{ datasetReference: { datasetId: 'datasetName' } }] }); }; + bq.getDatasets(function(err, datasets) { assert.ifError(err); - assert.equal(datasets[0].constructor.name, 'Dataset'); + assert.strictEqual(datasets[0].constructor.name, 'Dataset'); done(); }); }); it('should return Dataset objects', function(done) { var resp = { success: true }; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, resp); }; + bq.getDatasets(function(err, datasets, nextQuery, apiResponse) { assert.ifError(err); - assert.equal(apiResponse, resp); + assert.strictEqual(apiResponse, resp); done(); }); }); @@ -249,21 +287,25 @@ describe('BigQuery', function() { } } ]; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, { datasets: datasetObjects }); }; + bq.getDatasets(function(err, datasets) { assert.ifError(err); - assert(datasets[0].metadata, datasetObjects[0]); + assert.strictEqual(datasets[0].metadata, datasetObjects[0]); done(); }); }); it('should return token if more results exist', function(done) { var token = 'token'; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, { nextPageToken: token }); }; + bq.getDatasets(function(err, datasets, nextQuery) { assert.deepEqual(nextQuery, { pageToken: token @@ -275,13 +317,13 @@ describe('BigQuery', function() { describe('getJobs', function() { it('should get jobs from the api', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/jobs'); - assert.deepEqual(query, {}); - assert.strictEqual(body, null); + bq.request = function(reqOpts) { + assert.equal(reqOpts.uri, '/jobs'); + assert.deepEqual(reqOpts.qs, {}); + done(); }; + bq.getJobs(assert.ifError); }); @@ -293,65 +335,77 @@ describe('BigQuery', function() { projection: 'full', stateFilter: 'done' }; - bq.makeReq_ = function(method, path, query) { - assert.deepEqual(query, queryObject); + + bq.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, queryObject); done(); }; + bq.getJobs(queryObject, assert.ifError); }); it('should return error to callback', function(done) { var error = new Error('Error.'); - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(error); }; + bq.getJobs(function(err) { - assert.equal(err, error); + assert.strictEqual(err, error); done(); }); }); it('should return Job objects', function(done) { - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { jobs: [{ id: JOB_ID }] }); }; + bq.getJobs(function(err, jobs) { assert.ifError(err); - assert.equal(jobs[0].constructor.name, 'Job'); + assert.strictEqual(jobs[0].constructor.name, 'Job'); done(); }); }); it('should return apiResponse', function(done) { var resp = { jobs: [{ id: JOB_ID }] }; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, resp); }; + bq.getJobs(function(err, jobs, nextQuery, apiResponse) { assert.ifError(err); - assert.equal(resp, apiResponse); + assert.strictEqual(resp, apiResponse); done(); }); }); it('should assign metadata to the Job objects', function(done) { var jobObjects = [{ a: 'b', c: 'd', id: JOB_ID }]; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, { jobs: jobObjects }); }; + bq.getJobs(function(err, jobs) { assert.ifError(err); - assert(jobs[0].metadata, jobObjects[0]); + assert.strictEqual(jobs[0].metadata, jobObjects[0]); done(); }); }); it('should return token if more results exist', function(done) { var token = 'token'; - bq.makeReq_ = function(method, path, query, body, callback) { + + bq.request = function(reqOpts, callback) { callback(null, { nextPageToken: token }); }; + bq.getJobs(function(err, jobs, nextQuery) { + assert.ifError(err); assert.deepEqual(nextQuery, { pageToken: token }); @@ -377,8 +431,8 @@ describe('BigQuery', function() { var QUERY_STRING = 'SELECT * FROM [dataset.table]'; it('should accept a string for a query', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.equal(body.query, QUERY_STRING); + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.query, QUERY_STRING); done(); }; @@ -392,8 +446,8 @@ describe('BigQuery', function() { c: 'd' }; - bq.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body, options); + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.json, options); done(); }; @@ -412,10 +466,9 @@ describe('BigQuery', function() { timeoutMs: 8 }; - bq.makeReq_ = function(method, path, query) { - assert.equal(method, 'GET'); - assert.equal(path, '/queries/' + JOB_ID); - assert.deepEqual(query, expectedRequestQuery); + bq.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/queries/' + JOB_ID); + assert.deepEqual(reqOpts.qs, expectedRequestQuery); done(); }; @@ -426,7 +479,7 @@ describe('BigQuery', function() { var options = {}; beforeEach(function() { - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { jobComplete: false, jobReference: { jobId: JOB_ID } @@ -468,7 +521,7 @@ describe('BigQuery', function() { var pageToken = 'token'; beforeEach(function() { - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { pageToken: pageToken, jobReference: { jobId: JOB_ID } @@ -506,7 +559,7 @@ describe('BigQuery', function() { done(); }; - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { jobReference: { jobId: JOB_ID }, rows: rows, @@ -520,7 +573,7 @@ describe('BigQuery', function() { it('should pass errors to the callback', function(done) { var error = new Error('Error.'); - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(error); }; @@ -533,7 +586,7 @@ describe('BigQuery', function() { it('should return rows to the callback', function(done) { var ROWS = [{ a: 'b' }, { c: 'd' }]; - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, { jobReference: { jobId: JOB_ID }, rows: [], @@ -571,7 +624,8 @@ describe('BigQuery', function() { beforeEach(function() { dataset = { bigQuery: bq, - id: 'dataset-id' + id: 'dataset-id', + createTable: util.noop }; }); @@ -585,8 +639,8 @@ describe('BigQuery', function() { }); it('should assign destination table to request body', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.configuration.query.destinationTable, { + bq.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.configuration.query.destinationTable, { datasetId: dataset.id, projectId: dataset.bigQuery.projectId, tableId: TABLE_ID @@ -602,7 +656,8 @@ describe('BigQuery', function() { }); it('should delete `destination` prop from request body', function(done) { - bq.makeReq_ = function(method, path, query, body) { + bq.request = function(reqOpts) { + var body = reqOpts.json; assert.strictEqual(body.configuration.query.destination, undefined); done(); }; @@ -617,8 +672,8 @@ describe('BigQuery', function() { it('should pass options to the request body', function(done) { var options = { a: 'b', c: 'd', query: 'query' }; - bq.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.configuration.query, options); + bq.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.configuration.query, options); done(); }; @@ -626,11 +681,10 @@ describe('BigQuery', function() { }); it('should make the correct api request', function(done) { - bq.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/jobs'); - assert.strictEqual(query, null); - assert.deepEqual(body.configuration.query, { query: 'query' }); + bq.request = function(reqOpts) { + assert.equal(reqOpts.method, 'POST'); + assert.equal(reqOpts.uri, '/jobs'); + assert.deepEqual(reqOpts.json.configuration.query, { query: 'query' }); done(); }; @@ -640,7 +694,7 @@ describe('BigQuery', function() { it('should execute the callback with error', function(done) { var error = new Error('Error.'); - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(error); }; @@ -653,7 +707,7 @@ describe('BigQuery', function() { it('should execute the callback with Job', function(done) { var jobsResource = { jobReference: { jobId: JOB_ID }, a: 'b', c: 'd' }; - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, jobsResource); }; @@ -669,7 +723,7 @@ describe('BigQuery', function() { it('should execute the callback with apiResponse', function(done) { var jobsResource = { jobReference: { jobId: JOB_ID }, a: 'b', c: 'd' }; - bq.makeReq_ = function(method, path, query, body, callback) { + bq.request = function(reqOpts, callback) { callback(null, jobsResource); }; @@ -680,30 +734,4 @@ describe('BigQuery', 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) { - bq.makeAuthenticatedRequest_ = function(request) { - var basePath = 'https://www.googleapis.com/bigquery/v2/projects/'; - assert.equal(request.method, method); - assert.equal(request.uri, basePath + bq.projectId + path); - assert.deepEqual(request.qs, query); - assert.deepEqual(request.json, body); - done(); - }; - bq.makeReq_(method, path, query, body, assert.ifError); - }); - - it('should execute callback', function(done) { - bq.makeAuthenticatedRequest_ = function(request, callback) { - callback(); - }; - bq.makeReq_(method, path, query, body, done); - }); - }); }); diff --git a/test/bigquery/job.js b/test/bigquery/job.js index a9adf5d2c90..902fbb80fce 100644 --- a/test/bigquery/job.js +++ b/test/bigquery/job.js @@ -63,6 +63,8 @@ describe('BigQuery/Job', function() { }); it('should inherit from ServiceObject', function() { + assert(job instanceof ServiceObject); + var calledWith = job.calledWith_[0]; assert.strictEqual(calledWith.parent, BIGQUERY); diff --git a/test/bigquery/table.js b/test/bigquery/table.js index 76daf67c4e6..48862305f40 100644 --- a/test/bigquery/table.js +++ b/test/bigquery/table.js @@ -134,6 +134,8 @@ describe('BigQuery/Table', function() { }); it('should inherit from ServiceObject', function(done) { + assert(table instanceof ServiceObject); + var datasetInstance = extend({}, DATASET, { createTable: { bind: function(context) { From 1851e4657415673e7f637c48099887b4d86b55ee Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 29 Oct 2015 21:02:18 -0400 Subject: [PATCH 17/49] compute.address --- lib/compute/address.js | 2 +- test/bigquery/dataset.js | 3 +- test/bigquery/table.js | 3 +- test/compute/address.js | 174 ++++++++++++++------------------------- 4 files changed, 64 insertions(+), 118 deletions(-) diff --git a/lib/compute/address.js b/lib/compute/address.js index 7f09a739f6f..e03cda0b0db 100644 --- a/lib/compute/address.js +++ b/lib/compute/address.js @@ -136,12 +136,12 @@ function Address(region, name) { methods: methods }); + this.name = name; this.region = region; } nodeutil.inherits(Address, ServiceObject); - /** * Delete the address. * diff --git a/test/bigquery/dataset.js b/test/bigquery/dataset.js index 0a1328ef2c7..51a27038a40 100644 --- a/test/bigquery/dataset.js +++ b/test/bigquery/dataset.js @@ -83,8 +83,6 @@ describe('BigQuery/Dataset', function() { }); it('should inherit from ServiceObject', function(done) { - assert(ds instanceof ServiceObject); - var bigQueryInstance = extend({}, BIGQUERY, { createDataset: { bind: function(context) { @@ -95,6 +93,7 @@ describe('BigQuery/Dataset', function() { }); var ds = new Dataset(bigQueryInstance, DATASET_ID); + assert(ds instanceof ServiceObject); var calledWith = ds.calledWith_[0]; diff --git a/test/bigquery/table.js b/test/bigquery/table.js index 48862305f40..6413ded2943 100644 --- a/test/bigquery/table.js +++ b/test/bigquery/table.js @@ -134,8 +134,6 @@ describe('BigQuery/Table', function() { }); it('should inherit from ServiceObject', function(done) { - assert(table instanceof ServiceObject); - var datasetInstance = extend({}, DATASET, { createTable: { bind: function(context) { @@ -146,6 +144,7 @@ describe('BigQuery/Table', function() { }); var table = new Table(datasetInstance, TABLE_ID); + assert(table instanceof ServiceObject); var calledWith = table.calledWith_[0]; diff --git a/test/compute/address.js b/test/compute/address.js index 17d6bbb8d4f..3775b314661 100644 --- a/test/compute/address.js +++ b/test/compute/address.js @@ -17,13 +17,43 @@ 'use strict'; var assert = require('assert'); -var Address = require('../../lib/compute/address'); +var extend = require('extend'); +var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('Address', function() { + var Address; var address; var ADDRESS_NAME = 'us-central1'; - var REGION = {}; + var REGION = { + createAddress: util.noop + }; + + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Address = require('../../lib/compute/address.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); beforeEach(function() { address = new Address(REGION, ADDRESS_NAME); @@ -38,19 +68,38 @@ describe('Address', function() { assert.strictEqual(address.name, ADDRESS_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof address.metadata, 'object'); - assert.strictEqual(Object.keys(address.metadata).length, 0); + it('should inherit from ServiceObject', function(done) { + var regionInstance = extend({}, REGION, { + createAddress: { + bind: function(context) { + assert.strictEqual(context, regionInstance); + done(); + } + } + }); + + var address = new Address(regionInstance, ADDRESS_NAME); + assert(address instanceof ServiceObject); + + var calledWith = address.calledWith_[0]; + + assert.strictEqual(calledWith.parent, regionInstance); + assert.strictEqual(calledWith.baseUrl, '/addresses'); + assert.strictEqual(calledWith.id, ADDRESS_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); }); }); describe('delete', function() { it('should make the correct API request', function(done) { - address.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + address.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'DELETE'); + assert.strictEqual(reqOpts.uri, ''); done(); }; @@ -62,7 +111,7 @@ describe('Address', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - address.makeReq_ = function(method, path, query, body, callback) { + address.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -89,7 +138,7 @@ describe('Address', function() { }; beforeEach(function() { - address.makeReq_ = function(method, path, query, body, callback) { + address.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -117,105 +166,4 @@ describe('Address', function() { }); }); }); - - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - address.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - address.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - address.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - address.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - address.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - address.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - address.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(address.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - address.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - address.getMetadata(); - }); - }); - }); - }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/addresses/' + address.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - address.region.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - address.makeReq_(method, path, query, body, done); - }); - }); }); From b7846f67dc2f4a3a63d49829614cbb89e628d157 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 10:25:57 -0400 Subject: [PATCH 18/49] compute.disk --- lib/compute/disk.js | 8 +- test/compute/disk.js | 212 ++++++++++++++++++------------------------- 2 files changed, 92 insertions(+), 128 deletions(-) diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 9e3b103c94b..58729bae5c4 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -210,14 +210,12 @@ Disk.prototype.createSnapshot = function(name, options, callback) { options = {}; } - var body = extend({}, options, { - name: name - }); - this.request({ method: 'POST', uri: '/createSnapshot', - json: body + json: extend({}, options, { + name: name + }) }, function(err, resp) { if (err) { callback(err, null, null, resp); diff --git a/test/compute/disk.js b/test/compute/disk.js index 27486da5361..49a21ddc217 100644 --- a/test/compute/disk.js +++ b/test/compute/disk.js @@ -17,16 +17,39 @@ 'use strict'; var assert = require('assert'); +var extend = require('extend'); var format = require('string-format-obj'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Disk = require('../../lib/compute/disk'); -var util = require('../../lib/common/util'); +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeSnapshot() { + this.calledWith_ = [].slice.call(arguments); +} + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('Disk', function() { + var Disk; var disk; - var COMPUTE = { projectId: 'project-id' }; - var ZONE = { compute: COMPUTE, name: 'us-central1-a' }; + var COMPUTE = { + projectId: 'project-id' + }; + + var ZONE = { + compute: COMPUTE, + name: 'us-central1-a', + createDisk: util.noop + }; + var DISK_NAME = 'disk-name'; var DISK_FULL_NAME = format('projects/{pId}/zones/{zName}/disks/{dName}', { pId: COMPUTE.projectId, @@ -34,6 +57,22 @@ describe('Disk', function() { dName: DISK_NAME }); + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock('./snapshot.js', FakeSnapshot); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Disk = require('../../lib/compute/disk.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { disk = new Disk(ZONE, DISK_NAME); }); @@ -47,11 +86,6 @@ describe('Disk', function() { assert.strictEqual(disk.name, DISK_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof disk.metadata, 'object'); - assert.strictEqual(Object.keys(disk.metadata).length, 0); - }); - it('should format the disk name', function() { var formatName_ = Disk.formatName_; var formattedName = 'projects/a/zones/b/disks/c'; @@ -68,6 +102,32 @@ describe('Disk', function() { var disk = new Disk(ZONE, DISK_NAME); assert(disk.formattedName, formattedName); }); + + it('should inherit from ServiceObject', function(done) { + var zoneInstance = extend({}, ZONE, { + createDisk: { + bind: function(context) { + assert.strictEqual(context, zoneInstance); + done(); + } + } + }); + + var disk = new Disk(zoneInstance, DISK_NAME); + assert(disk instanceof ServiceObject); + + var calledWith = disk.calledWith_[0]; + + assert.strictEqual(calledWith.parent, zoneInstance); + assert.strictEqual(calledWith.baseUrl, '/disks'); + assert.strictEqual(calledWith.id, DISK_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('formatName_', function() { @@ -79,11 +139,10 @@ describe('Disk', function() { describe('createSnapshot', function() { it('should make the correct API request', function(done) { - disk.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/createSnapshot'); - assert.strictEqual(query, null); - assert.deepEqual(body, { name: 'test', a: 'b' }); + disk.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/createSnapshot'); + assert.deepEqual(reqOpts.json, { name: 'test', a: 'b' }); done(); }; @@ -95,7 +154,7 @@ describe('Disk', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { + disk.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -123,7 +182,7 @@ describe('Disk', function() { }; beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { + disk.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -132,7 +191,7 @@ describe('Disk', function() { var snapshot = {}; var operation = {}; - disk.zone.compute.snapshot = function(name) { + disk.snapshot = function(name) { assert.strictEqual(name, 'test'); return snapshot; }; @@ -163,16 +222,13 @@ describe('Disk', function() { }); describe('delete', function() { - it('should make the correct API request', function(done) { - disk.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.delete = function() { + assert.strictEqual(this, disk); done(); }; - disk.delete(assert.ifError); + disk.delete(); }); describe('error', function() { @@ -180,7 +236,7 @@ describe('Disk', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(error, apiResponse); }; }); @@ -207,7 +263,7 @@ describe('Disk', function() { }; beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(null, apiResponse); }; }); @@ -237,104 +293,14 @@ describe('Disk', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - disk.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - disk.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - disk.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - disk.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - disk.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - disk.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(disk.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - disk.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - disk.getMetadata(); - }); - }); - }); - }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/disks/' + disk.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - disk.zone.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; + describe('snapshot', function() { + var NAME = 'snapshot-name'; - disk.makeReq_(method, path, query, body, done); + it('should return a Snapshot object', function() { + var snapshot = disk.snapshot(NAME); + assert(snapshot instanceof FakeSnapshot); + assert.strictEqual(snapshot.calledWith_[0], disk); + assert.strictEqual(snapshot.calledWith_[1], NAME); }); }); }); From 345548fbb908ea351e8af51cca0440e2d16fa9fa Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 10:38:08 -0400 Subject: [PATCH 19/49] compute.firewall --- test/compute/firewall.js | 203 +++++++++++++++------------------------ 1 file changed, 79 insertions(+), 124 deletions(-) diff --git a/test/compute/firewall.js b/test/compute/firewall.js index f6a8b4c776d..af5e98bcd9a 100644 --- a/test/compute/firewall.js +++ b/test/compute/firewall.js @@ -17,15 +17,46 @@ 'use strict'; var assert = require('assert'); -var Firewall = require('../../lib/compute/firewall'); +var extend = require('extend'); +var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('Firewall', function() { + var Firewall; var firewall; - var COMPUTE = { projectId: 'project-id' }; + var COMPUTE = { + projectId: 'project-id', + createFirewall: util.noop + }; var FIREWALL_NAME = 'tcp-3000'; var FIREWALL_NETWORK = 'global/networks/default'; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Firewall = require('../../lib/compute/firewall.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { firewall = new Firewall(COMPUTE, FIREWALL_NAME); }); @@ -39,22 +70,45 @@ describe('Firewall', function() { assert.strictEqual(firewall.name, FIREWALL_NAME); }); - it('should create default metadata', function() { + it('should default to the global network', function() { assert.deepEqual(firewall.metadata, { network: FIREWALL_NETWORK }); }); + + it('should inherit from ServiceObject', function(done) { + var computeInstance = extend({}, COMPUTE, { + createFirewall: { + bind: function(context) { + assert.strictEqual(context, computeInstance); + done(); + } + } + }); + + var firewall = new Firewall(computeInstance, FIREWALL_NAME); + assert(firewall instanceof ServiceObject); + + var calledWith = firewall.calledWith_[0]; + + assert.strictEqual(calledWith.parent, computeInstance); + assert.strictEqual(calledWith.baseUrl, '/global/firewalls'); + assert.strictEqual(calledWith.id, FIREWALL_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('delete', function() { - it('should make the correct API request', function(done) { - firewall.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.delete = function() { + assert.strictEqual(this, firewall); done(); }; - firewall.delete(assert.ifError); + firewall.delete(); }); describe('error', function() { @@ -62,7 +116,7 @@ describe('Firewall', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(error, apiResponse); }; }); @@ -89,7 +143,7 @@ describe('Firewall', function() { }; beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(null, apiResponse); }; }); @@ -105,6 +159,7 @@ describe('Firewall', function() { firewall.delete(function(err, operation_, apiResponse_) { assert.ifError(err); assert.strictEqual(operation_, operation); + assert.strictEqual(operation_.metadata, apiResponse); assert.strictEqual(apiResponse_, apiResponse); done(); }); @@ -118,96 +173,23 @@ describe('Firewall', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - firewall.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - firewall.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - firewall.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - firewall.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - firewall.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(firewall.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - firewall.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - firewall.getMetadata(); - }); - }); - }); - }); - describe('setMetadata', function() { it('should make the correct API request', function(done) { - firewall.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'PATCH'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.deepEqual(body, { - name: FIREWALL_NAME, - network: FIREWALL_NETWORK, - a: 'b' + var metadata = {}; + + firewall.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'PATCH'); + assert.strictEqual(reqOpts.uri, ''); + assert.strictEqual(reqOpts.json, metadata); + assert.deepEqual(metadata, { + name: firewall.name, + network: FIREWALL_NETWORK }); done(); }; - firewall.setMetadata({ a: 'b' }, assert.ifError); + firewall.setMetadata(metadata, assert.ifError); }); describe('error', function() { @@ -215,7 +197,7 @@ describe('Firewall', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { + firewall.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -236,7 +218,7 @@ describe('Firewall', function() { }; beforeEach(function() { - firewall.makeReq_ = function(method, path, query, body, callback) { + firewall.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -266,31 +248,4 @@ describe('Firewall', function() { }); }); }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/global/firewalls/' + firewall.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - firewall.compute.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - firewall.makeReq_(method, path, query, body, done); - }); - }); }); From 1567c6e8b9dda57ac74488e92426ab67c127b8ee Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 10:53:15 -0400 Subject: [PATCH 20/49] compute.network --- lib/compute/network.js | 1 + test/compute/network.js | 180 ++++++++++++++-------------------------- 2 files changed, 64 insertions(+), 117 deletions(-) diff --git a/lib/compute/network.js b/lib/compute/network.js index 8071946704f..db7a922ddad 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -142,6 +142,7 @@ function Network(compute, name) { this.compute = compute; this.formattedName = Network.formatName_(compute, name); + this.name = name; } nodeutil.inherits(Network, ServiceObject); diff --git a/test/compute/network.js b/test/compute/network.js index 893f46fb344..f4d3334001c 100644 --- a/test/compute/network.js +++ b/test/compute/network.js @@ -19,19 +19,48 @@ var assert = require('assert'); var extend = require('extend'); var format = require('string-format-obj'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Network = require('../../lib/compute/network.js'); +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('Network', function() { + var Network; var network; - var COMPUTE = { projectId: 'project-id' }; + var COMPUTE = { + projectId: 'project-id', + createNetwork: util.noop + }; var NETWORK_NAME = 'network-name'; var NETWORK_FULL_NAME = format('projects/{pId}/global/networks/{name}', { pId: COMPUTE.projectId, name: NETWORK_NAME }); + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Network = require('../../lib/compute/network.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { network = new Network(COMPUTE, NETWORK_NAME); }); @@ -45,11 +74,6 @@ describe('Network', function() { assert.strictEqual(network.name, NETWORK_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof network.metadata, 'object'); - assert.strictEqual(Object.keys(network.metadata).length, 0); - }); - it('should format the network name', function() { var formatName_ = Network.formatName_; var formattedName = 'projects/a/global/networks/b'; @@ -66,6 +90,32 @@ describe('Network', function() { var network = new Network(COMPUTE, NETWORK_NAME); assert(network.formattedName, formattedName); }); + + it('should inherit from ServiceObject', function(done) { + var computeInstance = extend({}, COMPUTE, { + createNetwork: { + bind: function(context) { + assert.strictEqual(context, computeInstance); + done(); + } + } + }); + + var network = new Network(computeInstance, NETWORK_NAME); + assert(network instanceof ServiceObject); + + var calledWith = network.calledWith_[0]; + + assert.strictEqual(calledWith.parent, computeInstance); + assert.strictEqual(calledWith.baseUrl, '/global/networks'); + assert.strictEqual(calledWith.id, NETWORK_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('formatName_', function() { @@ -94,16 +144,13 @@ describe('Network', function() { }); describe('delete', function() { - it('should make the correct API request', function(done) { - network.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.delete = function() { + assert.strictEqual(this, network); done(); }; - network.delete(assert.ifError); + network.delete(); }); describe('error', function() { @@ -111,7 +158,7 @@ describe('Network', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - network.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(error, apiResponse); }; }); @@ -138,7 +185,7 @@ describe('Network', function() { }; beforeEach(function() { - network.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(null, apiResponse); }; }); @@ -230,105 +277,4 @@ describe('Network', function() { assert.strictEqual(network.getFirewalls(), resultOfGetFirewalls); }); }); - - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - network.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - network.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - network.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - network.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - network.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - network.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - network.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(network.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - network.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - network.getMetadata(); - }); - }); - }); - }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/global/networks/' + network.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - network.compute.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - network.makeReq_(method, path, query, body, done); - }); - }); }); From 63a51dff4fe5ca06d5518a10b4f4b1d06c2175e4 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 11:17:12 -0400 Subject: [PATCH 21/49] compute.operation --- lib/compute/operation.js | 2 + lib/compute/zone.js | 2 +- test/compute/operation.js | 172 ++++++++++++-------------------------- 3 files changed, 56 insertions(+), 120 deletions(-) diff --git a/lib/compute/operation.js b/lib/compute/operation.js index 5eebd483ddd..6c143834c1f 100644 --- a/lib/compute/operation.js +++ b/lib/compute/operation.js @@ -131,6 +131,8 @@ function Operation(scope, name) { id: name, methods: methods }); + + this.name = name; } nodeutil.inherits(Operation, ServiceObject); diff --git a/lib/compute/zone.js b/lib/compute/zone.js index 4a7db88ff41..d8f25178a25 100644 --- a/lib/compute/zone.js +++ b/lib/compute/zone.js @@ -756,7 +756,7 @@ Zone.prototype.getVMs = function(options, callback) { * @return {module:compute/operation} * * @example - * var operation = zone.operation('operation-name'); + * var operation = zone.operation('operation-1445532685163-8b137d2a-1822afe7'); */ Zone.prototype.operation = function(name) { return new Operation(this, name); diff --git a/test/compute/operation.js b/test/compute/operation.js index 54ad6935eb2..02312256349 100644 --- a/test/compute/operation.js +++ b/test/compute/operation.js @@ -17,111 +17,86 @@ 'use strict'; var assert = require('assert'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Operation = require('../../lib/compute/operation.js'); +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('Operation', function() { + var Operation; + var operation; + var SCOPE = {}; var OPERATION_NAME = 'operation-name'; - var operation; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Operation = require('../../lib/compute/operation.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); beforeEach(function() { operation = new Operation(SCOPE, OPERATION_NAME); }); describe('instantiation', function() { - it('should localize the scope', function() { - assert.strictEqual(operation.scope, SCOPE); - }); - it('should localize the name', function() { assert.strictEqual(operation.name, OPERATION_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof operation.metadata, 'object'); - assert.strictEqual(Object.keys(operation.metadata).length, 0); - }); - }); + it('should inherit from ServiceObject', function() { + var operation = new Operation(SCOPE, OPERATION_NAME); + assert(operation instanceof ServiceObject); - describe('delete', function() { - it('should make the correct API request', function(done) { - operation.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + var calledWith = operation.calledWith_[0]; - done(); - }; - - operation.delete(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = {}; - - beforeEach(function() { - operation.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error & API resp', function(done) { - operation.delete(function(err, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - operation.delete(); - }); + assert.strictEqual(calledWith.parent, SCOPE); + assert.strictEqual(calledWith.baseUrl, '/operations'); + assert.strictEqual(calledWith.id, OPERATION_NAME); + assert.deepEqual(calledWith.methods, { + delete: true, + exists: true, + get: true }); }); - describe('success', function() { - var apiResponse = {}; + it('should give the right baseUrl for a global Operation', function() { + var operation = new Operation({ + constructor: { + name: 'Compute' + } + }, OPERATION_NAME); - beforeEach(function() { - operation.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should execute callback with error & API resp', function(done) { - operation.delete(function(err, apiResponse_) { - assert.ifError(err); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - operation.delete(); - }); - }); + var calledWith = operation.calledWith_[0]; + assert.strictEqual(calledWith.baseUrl, '/global/operations'); }); }); describe('getMetadata', function() { - it('should make the correct API request', function(done) { - operation.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.getMetadata = function() { + assert.strictEqual(this, operation); done(); }; - operation.getMetadata(assert.ifError); + operation.getMetadata(); }); describe('error', function() { @@ -129,7 +104,7 @@ describe('Operation', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - operation.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(error, apiResponse); }; }); @@ -142,7 +117,7 @@ describe('Operation', function() { } }; - operation.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(apiResponse.error, apiResponse); }; @@ -174,7 +149,7 @@ describe('Operation', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - operation.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, apiResponse); }; }); @@ -337,45 +312,4 @@ describe('Operation', function() { }); }); }); - - describe('makeReq_', function() { - it('should make the correct request to Scope', function(done) { - var expectedPathPrefix = '/operations/' + operation.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - operation.scope.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - operation.makeReq_(method, path, query, body, done); - }); - - it('should prefix the path with /global if Compute', function(done) { - var expectedPathPrefix = '/global/operations/' + operation.name; - - function Compute() {} - operation.scope = new Compute(); - - operation.scope.makeReq_ = function(method, path) { - assert.strictEqual(path, expectedPathPrefix + '/test'); - done(); - }; - - operation.makeReq_(null, '/test'); - }); - }); }); From cf4e0b0302bd6599e57a2dc91e15aae8be71ebaa Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 11:30:44 -0400 Subject: [PATCH 22/49] compute.region --- lib/compute/region.js | 10 +-- test/compute/region.js | 183 +++++++++++------------------------------ 2 files changed, 51 insertions(+), 142 deletions(-) diff --git a/lib/compute/region.js b/lib/compute/region.js index 575bc432986..80ba730f7f0 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -121,6 +121,8 @@ function Region(compute, name) { id: name, methods: methods }); + + this.name = name; } nodeutil.inherits(Region, ServiceObject); @@ -176,14 +178,12 @@ Region.prototype.createAddress = function(name, options, callback) { options = {}; } - var body = extend({}, options, { - name: name - }); - this.request({ method: 'POST', uri: '/addresses', - json: body + json: extend({}, options, { + name: name + }) }, function(err, resp) { if (err) { callback(err, null, null, resp); diff --git a/test/compute/region.js b/test/compute/region.js index f52373d5bb7..384fe3e6914 100644 --- a/test/compute/region.js +++ b/test/compute/region.js @@ -20,6 +20,9 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); function FakeAddress() { this.calledWith_ = [].slice.call(arguments); @@ -29,6 +32,13 @@ function FakeOperation() { this.calledWith_ = [].slice.call(arguments); } +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + var extended = false; var fakeStreamRouter = { extend: function(Class, methods) { @@ -53,6 +63,7 @@ describe('Region', function() { var REGION_NAME = 'us-central1'; before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('./address.js', FakeAddress); mockery.registerMock('./operation.js', FakeOperation); @@ -79,17 +90,21 @@ describe('Region', function() { assert(extended); // See `fakeStreamRouter.extend` }); - it('should localize the compute instance', function() { - assert.strictEqual(region.compute, COMPUTE); - }); - it('should localize the name', function() { assert.strictEqual(region.name, REGION_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof region.metadata, 'object'); - assert.strictEqual(Object.keys(region.metadata).length, 0); + it('should inherit from ServiceObject', function() { + var calledWith = region.calledWith_[0]; + + assert.strictEqual(calledWith.parent, COMPUTE); + assert.strictEqual(calledWith.baseUrl, '/regions'); + assert.strictEqual(calledWith.id, REGION_NAME); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true + }); }); }); @@ -112,8 +127,8 @@ describe('Region', function() { it('should not require any options', function(done) { var expectedBody = { name: NAME }; - region.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body, expectedBody); + region.request = function(reqOpts) { + assert.deepEqual(reqOpts.json, expectedBody); done(); }; @@ -121,11 +136,10 @@ describe('Region', function() { }); it('should make the correct API request', function(done) { - region.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/addresses'); - assert.strictEqual(query, null); - assert.deepEqual(body, EXPECTED_BODY); + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/addresses'); + assert.deepEqual(reqOpts.json, EXPECTED_BODY); done(); }; @@ -138,7 +152,7 @@ describe('Region', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -158,7 +172,7 @@ describe('Region', function() { var apiResponse = { name: 'operation-name' }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -194,8 +208,8 @@ describe('Region', function() { describe('getAddresses', function() { it('should accept only a callback', function(done) { - region.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + region.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -205,11 +219,9 @@ describe('Region', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - region.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/addresses'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/addresses'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -222,7 +234,7 @@ describe('Region', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -246,7 +258,7 @@ describe('Region', function() { }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -260,7 +272,7 @@ describe('Region', function() { pageToken: nextPageToken }; - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -295,84 +307,10 @@ describe('Region', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - region.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - region.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - region.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - region.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - region.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(region.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - region.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - region.getMetadata(); - }); - }); - }); - }); - describe('getOperations', function() { it('should accept only a callback', function(done) { - region.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + region.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -382,11 +320,9 @@ describe('Region', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - region.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/operations'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/operations'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -399,7 +335,7 @@ describe('Region', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -423,7 +359,7 @@ describe('Region', function() { }; beforeEach(function() { - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -437,7 +373,7 @@ describe('Region', function() { pageToken: nextPageToken }; - region.makeReq_ = function(method, path, query, body, callback) { + region.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -482,31 +418,4 @@ describe('Region', function() { assert.strictEqual(operation.calledWith_[1], NAME); }); }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/regions/' + region.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - region.compute.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - region.makeReq_(method, path, query, body, done); - }); - }); }); From 90b691534ae8abd9c79ec7bcff29657a66dfa760 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 12:18:14 -0400 Subject: [PATCH 23/49] compute.snapshot --- lib/compute/snapshot.js | 1 + test/compute/snapshot.js | 180 ++++++++++++++------------------------- 2 files changed, 65 insertions(+), 116 deletions(-) diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js index 2efa2f4156e..9ab5e96b1d8 100644 --- a/lib/compute/snapshot.js +++ b/lib/compute/snapshot.js @@ -152,6 +152,7 @@ function Snapshot(scope, name) { ServiceObject.call(this, config); this.compute = isDisk ? scope.compute : scope; + this.name = name; } nodeutil.inherits(Snapshot, ServiceObject); diff --git a/test/compute/snapshot.js b/test/compute/snapshot.js index 6dc6dbb9ecc..d00c63deddd 100644 --- a/test/compute/snapshot.js +++ b/test/compute/snapshot.js @@ -17,13 +17,39 @@ 'use strict'; var assert = require('assert'); -var Snapshot = require('../../lib/compute/snapshot.js'); +var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('Snapshot', function() { + var Snapshot; + var snapshot; + var COMPUTE = {}; var SNAPSHOT_NAME = 'snapshot-name'; - var snapshot; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Snapshot = require('../../lib/compute/snapshot.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); beforeEach(function() { snapshot = new Snapshot(COMPUTE, SNAPSHOT_NAME); @@ -38,24 +64,47 @@ describe('Snapshot', function() { assert.strictEqual(snapshot.name, SNAPSHOT_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof snapshot.metadata, 'object'); - assert.strictEqual(Object.keys(snapshot.metadata).length, 0); + it('should inherit from ServiceObject', function() { + var calledWith = snapshot.calledWith_[0]; + + assert.strictEqual(calledWith.parent, COMPUTE); + assert.strictEqual(calledWith.baseUrl, '/global/snapshots'); + assert.strictEqual(calledWith.id, SNAPSHOT_NAME); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true + }); + }); + + it('should allow creating for a Disk object snapshot', function(done) { + var scope = { + constructor: { + name: 'Disk' + }, + createSnapshot: function() { + assert.strictEqual(this, scope); + done(); + } + }; + + var snapshot = new Snapshot(scope, SNAPSHOT_NAME); + + var calledWith = snapshot.calledWith_[0]; + assert.strictEqual(calledWith.methods.create, true); + + calledWith.createMethod(); // (scope.createSnapshot) }); }); describe('delete', function() { - it('should make the correct API request', function(done) { - snapshot.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.delete = function() { + assert.strictEqual(this, snapshot); done(); }; - snapshot.delete(assert.ifError); + snapshot.delete(); }); describe('error', function() { @@ -63,7 +112,7 @@ describe('Snapshot', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - snapshot.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(error, apiResponse); }; }); @@ -88,7 +137,7 @@ describe('Snapshot', function() { var apiResponse = { name: 'operation-name' }; beforeEach(function() { - snapshot.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.delete = function(callback) { callback(null, apiResponse); }; }); @@ -119,105 +168,4 @@ describe('Snapshot', function() { }); }); }); - - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - snapshot.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - snapshot.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - snapshot.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - snapshot.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - snapshot.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - snapshot.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - snapshot.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(snapshot.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - snapshot.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - snapshot.getMetadata(); - }); - }); - }); - }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/global/snapshots/' + snapshot.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - snapshot.compute.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - snapshot.makeReq_(method, path, query, body, done); - }); - }); }); From 47840aa6017c24772689ed3f5f51beabf0d74a17 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 12:57:36 -0400 Subject: [PATCH 24/49] compute.vm --- lib/compute/vm.js | 92 +++--------- test/compute/vm.js | 342 ++++++++++++++++++++------------------------- 2 files changed, 177 insertions(+), 257 deletions(-) diff --git a/lib/compute/vm.js b/lib/compute/vm.js index 3256c18f7db..b65e5327ebf 100644 --- a/lib/compute/vm.js +++ b/lib/compute/vm.js @@ -20,7 +20,6 @@ 'use strict'; -var createErrorClass = require('create-error-class'); var extend = require('extend'); var is = require('is'); var nodeutil = require('util'); @@ -43,16 +42,6 @@ var ServiceObject = require('../common/service-object.js'); */ var util = require('../common/util.js'); -/** - * Custom error type for errors related to detaching a disk. - * - * @param {string} message - Custom error message. - * @return {Error} - */ -var DetachDiskError = createErrorClass('DetachDiskError', function(message) { - this.message = message; -}); - /*! Developer Documentation * * @param {module:zone} zone - Zone object this instance belongs to. @@ -131,7 +120,24 @@ function VM(zone, name) { * // `vm` is a VM object. * }); */ - get: true + get: true, + + /** + * Get the instances's metadata. + * + * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} + * @resource [Instance: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The instance's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * vm.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true }; ServiceObject.call(this, { @@ -142,6 +148,7 @@ function VM(zone, name) { methods: methods }); + this.name = name; this.zone = zone; } @@ -263,73 +270,20 @@ VM.prototype.delete = function(callback) { * // of the request. * }); */ -VM.prototype.detachDisk = function(deviceName, callback) { - var self = this; - - if (deviceName instanceof Disk) { - var disk = deviceName; - - if (disk.metadata.deviceName) { - self.detachDisk(disk.metadata.deviceName, callback); - return; - } - - this.getMetadata(function(err, metadata) { - if (err) { - callback(new DetachDiskError(err.message)); - return; - } - - var deviceName; - var baseUrl = 'https://www.googleapis.com/compute/v1/'; - - // Try to find the deviceName by matching the source of the attached disks - // to the name of the disk provided by the user. - for (var i = 0; !deviceName && i < metadata.disks.length; i++) { - var attachedDisk = metadata.disks[i]; - var source = attachedDisk.source.replace(baseUrl, ''); - - if (source === disk.formattedName) { - deviceName = attachedDisk.deviceName; - } - } - - if (deviceName) { - self.detachDisk(deviceName, callback); - } else { - callback(new DetachDiskError('Could not find a deviceName for disk.')); - } - }); - return; +VM.prototype.detachDisk = function(disk, callback) { + if (!(disk instanceof Disk)) { + throw new Error('A Disk object must be provided.'); } this.request({ method: 'POST', uri: '/detachDisk', qs: { - deviceName: deviceName + deviceName: disk.name } }, callback || util.noop); }; -/** - * Get the instances's metadata. - * - * @resource [Instance Resource]{@link https://cloud.google.com/compute/docs/reference/v1/instances} - * @resource [Instance: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/instances/get} - * - * @param {function=} callback - The callback function. - * @param {?error} callback.err - An error returned while making this request - * @param {object} callback.metadata - The instance's metadata. - * @param {object} callback.apiResponse - The full API response. - * - * @example - * vm.getMetadata(function(err, metadata, apiResponse) {}); - */ -VM.prototype.getMetadata = function() { - ServiceObject.prototype.getMetadata.apply(this, arguments); -}; - /** * Returns the serial port output for the instance. * diff --git a/test/compute/vm.js b/test/compute/vm.js index fa8792a3089..c32fea6e537 100644 --- a/test/compute/vm.js +++ b/test/compute/vm.js @@ -18,22 +18,54 @@ var assert = require('assert'); var extend = require('extend'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Disk = require('../../lib/compute/disk.js'); var util = require('../../lib/common/util.js'); -var VM = require('../../lib/compute/vm.js'); +var ServiceObject = require('../../lib/common/service-object.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('VM', function() { + var VM; var vm; + var Disk; + var DISK; + var COMPUTE = { projectId: 'project-id' }; - var ZONE = { compute: COMPUTE, name: 'us-central1-a' }; + var ZONE = { + compute: COMPUTE, + name: 'us-central1-a', + createDisk: util.noop, + createVM: util.noop + }; var VM_NAME = 'vm-name'; - var DISK = new Disk(ZONE, 'disk-name'); + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Disk = require('../../lib/compute/disk.js'); + VM = require('../../lib/compute/vm.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); beforeEach(function() { vm = new VM(ZONE, VM_NAME); + DISK = new Disk(ZONE, 'disk-name'); }); describe('instantiation', function() { @@ -44,11 +76,44 @@ describe('VM', function() { it('should localize the name', function() { assert.strictEqual(vm.name, VM_NAME); }); + + it('should inherit from ServiceObject', function(done) { + var zoneInstance = extend({}, ZONE, { + createVM: { + bind: function(context) { + assert.strictEqual(context, zoneInstance); + done(); + } + } + }); + + var vm = new VM(zoneInstance, VM_NAME); + assert(vm instanceof ServiceObject); + + var calledWith = vm.calledWith_[0]; + + assert.strictEqual(calledWith.parent, zoneInstance); + assert.strictEqual(calledWith.baseUrl, '/instances'); + assert.strictEqual(calledWith.id, VM_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('attachDisk', function() { var CONFIG = {}; - var EXPECTED_BODY = { source: DISK.formattedName }; + var EXPECTED_BODY; + + beforeEach(function() { + EXPECTED_BODY = { + deviceName: DISK.name, + source: DISK.formattedName + }; + }); it('should throw if a Disk object is not provided', function() { assert.throws(function() { @@ -56,14 +121,14 @@ describe('VM', function() { }, /A Disk object must be provided/); assert.doesNotThrow(function() { - vm.makeReq_ = util.noop; + vm.request = util.noop; vm.attachDisk(DISK, CONFIG, assert.ifError); }); }); it('should not require an options object', function(done) { - vm.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body, EXPECTED_BODY); + vm.request = function(reqOpts) { + assert.deepEqual(reqOpts.json, EXPECTED_BODY); done(); }; @@ -76,8 +141,8 @@ describe('VM', function() { it('should set the correct mode', function(done) { var expectedBody = extend({}, EXPECTED_BODY, { mode: 'READ_ONLY' }); - vm.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body, expectedBody); + vm.request = function(reqOpts) { + assert.deepEqual(reqOpts.json, expectedBody); done(); }; @@ -85,8 +150,8 @@ describe('VM', function() { }); it('should delete the readOnly property', function(done) { - vm.makeReq_ = function(method, path, query, body) { - assert.strictEqual(typeof body.readOnly, 'undefined'); + vm.request = function(reqOpts) { + assert.strictEqual(typeof reqOpts.json.readOnly, 'undefined'); done(); }; @@ -95,11 +160,10 @@ describe('VM', function() { }); it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/attachDisk'); - assert.strictEqual(query, null); - assert.deepEqual(body, EXPECTED_BODY); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/attachDisk'); + assert.deepEqual(reqOpts.json, EXPECTED_BODY); callback(); }; @@ -110,11 +174,9 @@ describe('VM', function() { describe('delete', function() { it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'DELETE'); + assert.strictEqual(reqOpts.uri, ''); callback(); }; @@ -123,7 +185,7 @@ describe('VM', function() { }); it('should not require a callback', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { + vm.request = function(reqOpts, callback) { assert.doesNotThrow(function() { callback(); done(); @@ -141,17 +203,16 @@ describe('VM', function() { }, /A Disk object must be provided/); assert.doesNotThrow(function() { - vm.makeReq_ = util.noop; + vm.request = util.noop; vm.detachDisk(DISK); }); }); it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/detachDisk'); - assert.deepEqual(query, { deviceName: DISK.name }); - assert.strictEqual(body, null); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/detachDisk'); + assert.deepEqual(reqOpts.qs, { deviceName: DISK.name }); callback(); }; @@ -160,7 +221,7 @@ describe('VM', function() { }); it('should not require a callback', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { + vm.request = function(reqOpts, callback) { assert.doesNotThrow(function() { callback(); done(); @@ -171,86 +232,12 @@ describe('VM', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - vm.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - vm.makeReq_ = function(method, path, query, body, callback) { - callback(error, null/*usually an operation*/, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - vm.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - vm.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - vm.makeReq_ = function(method, path, query, body, callback) { - callback(null, null/*usually an operation*/, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - vm.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(vm.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - vm.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - vm.getMetadata(); - }); - }); - }); - }); - describe('getSerialPortOutput', function() { var EXPECTED_QUERY = { port: 1 }; it('should default to port 1', function(done) { - vm.makeReq_ = function(method, path, query) { - assert.strictEqual(query.port, 1); + FakeServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.port, 1); done(); }; @@ -260,8 +247,8 @@ describe('VM', function() { it('should override the default port', function(done) { var port = 8001; - vm.makeReq_ = function(method, path, query) { - assert.strictEqual(query.port, port); + FakeServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.port, port); done(); }; @@ -269,11 +256,9 @@ describe('VM', function() { }); it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/serialPort'); - assert.deepEqual(query, EXPECTED_QUERY); - assert.strictEqual(body, null); + FakeServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/serialPort'); + assert.deepEqual(reqOpts.qs, EXPECTED_QUERY); done(); }; @@ -286,8 +271,8 @@ describe('VM', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - vm.makeReq_ = function(method, path, query, body, callback) { - callback(error, null/*usually an operation*/, apiResponse); + FakeServiceObject.prototype.request = function(reqOpts, callback) { + callback(error, apiResponse); }; }); @@ -306,8 +291,8 @@ describe('VM', function() { var apiResponse = { contents: 'contents' }; beforeEach(function() { - vm.makeReq_ = function(method, path, query, body, callback) { - callback(null, null/*usually an operation*/, apiResponse); + FakeServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); }; }); @@ -386,11 +371,9 @@ describe('VM', function() { describe('reset', function() { it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/reset'); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/reset'); callback(); }; @@ -399,7 +382,7 @@ describe('VM', function() { }); it('should not require a callback', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { + vm.request = function(reqOpts, callback) { assert.doesNotThrow(function() { callback(); done(); @@ -412,11 +395,9 @@ describe('VM', function() { describe('start', function() { it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/start'); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/start'); callback(); }; @@ -425,7 +406,7 @@ describe('VM', function() { }); it('should not require a callback', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { + vm.request = function(reqOpts, callback) { assert.doesNotThrow(function() { callback(); done(); @@ -438,11 +419,9 @@ describe('VM', function() { describe('stop', function() { it('should make the correct API request', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/stop'); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + vm.request = function(reqOpts, callback) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/stop'); callback(); }; @@ -451,7 +430,7 @@ describe('VM', function() { }); it('should not require a callback', function(done) { - vm.makeReq_ = function(method, path, query, body, callback) { + vm.request = function(reqOpts, callback) { assert.doesNotThrow(function() { callback(); done(); @@ -462,76 +441,63 @@ describe('VM', function() { }); }); - describe('makeReq_', function() { + describe('request', function() { it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/instances/' + vm.name; + var reqOpts = {}; - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - vm.zone.makeReq_ = function(method_, path_, query_, body_) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); + FakeServiceObject.prototype.request = function(reqOpts_) { + assert.strictEqual(this, vm); + assert.strictEqual(reqOpts_, reqOpts); done(); }; - vm.makeReq_(method, path, query, body, assert.ifError); + vm.request(reqOpts, assert.ifError); }); - }); - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { a: 'b', c: 'd' }; - beforeEach(function() { - vm.zone.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); + beforeEach(function() { + FakeServiceObject.prototype.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); - it('should execute callback with error & API response', function(done) { - vm.makeReq_('POST', '/', {}, {}, function(err, operation, resp) { - assert.strictEqual(err, error); - assert.strictEqual(operation, null); - assert.strictEqual(resp, apiResponse); - done(); + it('should execute callback with error & API response', function(done) { + vm.request({}, function(err, operation, resp) { + assert.strictEqual(err, error); + assert.strictEqual(operation, null); + assert.strictEqual(resp, apiResponse); + done(); + }); }); }); - }); - describe('success', function() { - var apiResponse = { name: 'operation-name' }; + describe('success', function() { + var apiResponse = { name: 'operation-name' }; - beforeEach(function() { - vm.zone.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); + beforeEach(function() { + FakeServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); - it('should execute callback with a Zone object & API resp', function(done) { - var operation = {}; + it('should execute callback with Zone object & API resp', function(done) { + var operation = {}; - vm.zone.operation = function(name) { - assert.strictEqual(name, apiResponse.name); - return operation; - }; + vm.zone.operation = function(name) { + assert.strictEqual(name, apiResponse.name); + return operation; + }; - vm.makeReq_('POST', '/', {}, {}, function(err, operation_, resp) { - assert.ifError(err); - assert.strictEqual(operation_, operation); - assert.strictEqual(resp, apiResponse); - done(); + vm.request({}, function(err, operation_, resp) { + assert.ifError(err); + assert.strictEqual(operation_, operation); + assert.strictEqual(resp, apiResponse); + done(); + }); }); }); }); From d3c9e1bb4950617d1adfcf2806480a5713776b36 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 13:11:38 -0400 Subject: [PATCH 25/49] compute.zone --- test/compute/zone.js | 254 +++++++++++++++---------------------------- 1 file changed, 85 insertions(+), 169 deletions(-) diff --git a/test/compute/zone.js b/test/compute/zone.js index e7fc250189d..5a27624ca8c 100644 --- a/test/compute/zone.js +++ b/test/compute/zone.js @@ -21,7 +21,9 @@ var assert = require('assert'); var extend = require('extend'); var gceImages = require('gce-images'); var mockery = require('mockery'); +var nodeutil = require('util'); +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); var gceImagesOverride = null; @@ -41,6 +43,13 @@ function FakeVM() { this.calledWith_ = [].slice.call(arguments); } +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + var extended = false; var fakeStreamRouter = { extend: function(Class, methods) { @@ -60,17 +69,13 @@ describe('Zone', function() { var zone; var COMPUTE = { - makeAuthenticatedRequest_: { - authClient: { - a: 'b', - c: 'd' - } - } + authClient: {} }; var ZONE_NAME = 'us-central1-a'; before(function() { mockery.registerMock('gce-images', fakeGceImages); + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('./disk.js', FakeDisk); mockery.registerMock('./operation.js', FakeOperation); @@ -107,30 +112,37 @@ describe('Zone', function() { assert.strictEqual(zone.name, ZONE_NAME); }); - it('should default metadata to an empty object', function() { - assert.strictEqual(typeof zone.metadata, 'object'); - assert.strictEqual(Object.keys(zone.metadata).length, 0); - }); - it('should create a gceImages instance', function() { var gceVal = 'ok'; gceImagesOverride = function(authConfig) { - var expectedAuthClient = COMPUTE.makeAuthenticatedRequest_.authClient; - assert.strictEqual(authConfig.authClient, expectedAuthClient); + assert.strictEqual(authConfig.authClient, COMPUTE.authClient); return gceVal; }; var newZone = new Zone(COMPUTE, ZONE_NAME); assert.strictEqual(newZone.gceImages, gceVal); }); + + it('should inherit from ServiceObject', function() { + var calledWith = zone.calledWith_[0]; + + assert.strictEqual(calledWith.parent, COMPUTE); + assert.strictEqual(calledWith.baseUrl, '/zones'); + assert.strictEqual(calledWith.id, ZONE_NAME); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true + }); + }); }); describe('createDisk', function() { var NAME = 'disk-name'; beforeEach(function() { - zone.makeReq_ = util.noop; + zone.request = util.noop; }); it('should use the image property as qs.sourceImages', function(done) { @@ -138,8 +150,8 @@ describe('Zone', function() { image: 'abc' }; - zone.makeReq_ = function(method, path, query) { - assert.strictEqual(query.sourceImage, config.image); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.sourceImage, config.image); done(); }; @@ -216,11 +228,11 @@ describe('Zone', function() { }; it('should make the correct API request', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/disks'); - assert.deepEqual(query, {}); - assert.deepEqual(body, expectedBody); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/disks'); + assert.deepEqual(reqOpts.qs, {}); + assert.deepEqual(reqOpts.json, expectedBody); done(); }; @@ -233,7 +245,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -253,7 +265,7 @@ describe('Zone', function() { var apiResponse = { name: 'operation-name' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -309,9 +321,9 @@ describe('Zone', function() { }; it('should format a given machine type', function(done) { - zone.makeReq_ = function(method, path, query, body) { + zone.request = function(reqOpts) { assert.strictEqual( - body.machineType, + reqOpts.json.machineType, 'zones/' + ZONE_NAME + '/machineTypes/' + CONFIG.machineType ); done(); @@ -327,8 +339,8 @@ describe('Zone', function() { }; it('should accept an array of tags', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.tags.items, CONFIG.tags); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.tags.items, CONFIG.tags); done(); }; @@ -356,8 +368,8 @@ describe('Zone', function() { }); it('should add a network interface accessConfig', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.networkInterfaces[0].accessConfigs[0], { + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.networkInterfaces[0].accessConfigs[0], { type: 'ONE_TO_ONE_NAT' }); done(); @@ -367,8 +379,8 @@ describe('Zone', function() { }); it('should add an http tag', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert(body.tags.items.indexOf('http-server') > -1); + zone.request = function(reqOpts) { + assert(reqOpts.json.tags.items.indexOf('http-server') > -1); done(); }; @@ -385,8 +397,8 @@ describe('Zone', function() { var expectedTags = ['a', 'b', 'http-server']; - zone.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.tags.items, expectedTags); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.tags.items, expectedTags); done(); }; @@ -394,8 +406,8 @@ describe('Zone', function() { }); it('should delete the https property', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.https, undefined); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.https, undefined); done(); }; @@ -423,8 +435,8 @@ describe('Zone', function() { }); it('should add a network interface accessConfig', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.networkInterfaces[0].accessConfigs[0], { + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.networkInterfaces[0].accessConfigs[0], { type: 'ONE_TO_ONE_NAT' }); done(); @@ -434,8 +446,8 @@ describe('Zone', function() { }); it('should add an https tag', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert(body.tags.items.indexOf('https-server') > -1); + zone.request = function(reqOpts) { + assert(reqOpts.json.tags.items.indexOf('https-server') > -1); done(); }; @@ -452,8 +464,8 @@ describe('Zone', function() { var expectedTags = ['a', 'b', 'https-server']; - zone.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.tags.items, expectedTags); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.tags.items, expectedTags); done(); }; @@ -461,8 +473,8 @@ describe('Zone', function() { }); it('should delete the https property', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.https, undefined); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.https, undefined); done(); }; @@ -535,11 +547,10 @@ describe('Zone', function() { describe('API request', function() { it('should make the correct API request', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/instances'); - assert.deepEqual(query, null); - assert.deepEqual(body, EXPECTED_CONFIG); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/instances'); + assert.deepEqual(reqOpts.json, EXPECTED_CONFIG); done(); }; @@ -552,7 +563,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -572,7 +583,7 @@ describe('Zone', function() { var apiResponse = { name: 'operation-name' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -620,8 +631,8 @@ describe('Zone', function() { describe('getDisks', function() { it('should accept only a callback', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -631,11 +642,9 @@ describe('Zone', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - zone.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/disks'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/disks'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -648,7 +657,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -672,7 +681,7 @@ describe('Zone', function() { }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -686,7 +695,7 @@ describe('Zone', function() { pageToken: nextPageToken }; - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -721,72 +730,10 @@ describe('Zone', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - zone.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - zone.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - zone.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(zone.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - zone.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - }); - describe('getOperations', function() { it('should accept only a callback', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -796,11 +743,9 @@ describe('Zone', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - zone.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/operations'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/operations'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -813,7 +758,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -836,7 +781,7 @@ describe('Zone', function() { }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -850,7 +795,7 @@ describe('Zone', function() { pageToken: nextPageToken }; - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -887,8 +832,8 @@ describe('Zone', function() { describe('getVMs', function() { it('should accept only a callback', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -898,11 +843,9 @@ describe('Zone', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - zone.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/instances'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/instances'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -915,7 +858,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -938,7 +881,7 @@ describe('Zone', function() { }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -952,7 +895,7 @@ describe('Zone', function() { pageToken: nextPageToken }; - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -1102,31 +1045,4 @@ describe('Zone', function() { }); }); }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var expectedPathPrefix = '/zones/' + zone.name; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - zone.compute.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - zone.makeReq_(method, path, query, body, done); - }); - }); }); From 9d60b92f430d8f3d00d25eb802fe353034ec9195 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 13:28:00 -0400 Subject: [PATCH 26/49] compute.index --- test/compute/index.js | 280 ++++++++++++++++++------------------------ 1 file changed, 120 insertions(+), 160 deletions(-) diff --git a/test/compute/index.js b/test/compute/index.js index d5bf538a607..f8c2dc96863 100644 --- a/test/compute/index.js +++ b/test/compute/index.js @@ -16,12 +16,15 @@ 'use strict'; +var arrify = require('arrify'); var assert = require('assert'); -var mockery = require('mockery'); var extend = require('extend'); -var arrify = require('arrify'); +var mockery = require('mockery'); +var nodeutil = require('util'); +var Service = require('../../lib/common/service.js'); var util = require('../../lib/common/util.js'); + var slice = Array.prototype.slice; var fakeUtil = extend({}, util, { @@ -78,6 +81,13 @@ function FakeZone() { this.vm = function() { return {}; }; } +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + describe('Compute', function() { var Compute; var compute; @@ -86,6 +96,7 @@ describe('Compute', function() { before(function() { mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('./firewall.js', FakeFirewall); mockery.registerMock('./network.js', FakeNetwork); @@ -144,24 +155,16 @@ describe('Compute', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - it('should create a makeAuthenticatedRequest method', function(done) { - fakeUtil.makeAuthenticatedRequestFactory = function(options_) { - assert.deepEqual(options_, { - credentials: options.credentials, - email: options.email, - keyFile: options.keyFilename, - scopes: ['https://www.googleapis.com/auth/compute'] - }); - fakeUtil.makeAuthenticatedRequestFactory = util.noop; - return done; - }; + it('should inherit from Service', function() { + assert(compute instanceof Service); - var compute = new Compute(options); - compute.makeAuthenticatedRequest_(); - }); + var calledWith = compute.calledWith_[0]; - it('should localize the project id', function() { - assert.strictEqual(compute.projectId, PROJECT_ID); + var baseUrl = 'https://www.googleapis.com/compute/v1'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/compute' + ]); }); }); @@ -192,14 +195,14 @@ describe('Compute', function() { } }; - compute.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.allowed, [ + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.allowed, [ { IPProtocol: 'http', ports: [8000] }, { IPProtocol: 'https', ports: [8080, 9000] }, { IPProtocol: 'ssh', ports: [22] }, { IPProtocol: 'ftp' } ]); - assert.strictEqual(body.protocols, undefined); + assert.strictEqual(reqOpts.json.protocols, undefined); done(); }; @@ -213,9 +216,9 @@ describe('Compute', function() { ranges: '0.0.0.0/0' // non-array to test that it's arrified. }; - compute.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.sourceRanges, [options.ranges]); - assert.strictEqual(body.ranges, undefined); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.sourceRanges, [options.ranges]); + assert.strictEqual(reqOpts.json.ranges, undefined); done(); }; @@ -229,9 +232,9 @@ describe('Compute', function() { tags: 'tag' // non-array to test that it's arrified. }; - compute.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body.sourceTags, [options.tags]); - assert.strictEqual(body.tags, undefined); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.sourceTags, [options.tags]); + assert.strictEqual(reqOpts.json.tags, undefined); done(); }; @@ -242,11 +245,10 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var name = 'new-firewall-name'; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/global/firewalls'); - assert.strictEqual(query, null); - assert.deepEqual(body, { name: name }); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/global/firewalls'); + assert.deepEqual(reqOpts.json, { name: name }); done(); }; @@ -258,7 +260,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -280,7 +282,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -319,9 +321,9 @@ describe('Compute', function() { range: '10.240.0.0/16' }; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.IPv4Range, options.range); - assert.strictEqual(body.range, undefined); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.IPv4Range, options.range); + assert.strictEqual(reqOpts.json.range, undefined); done(); }; @@ -335,9 +337,9 @@ describe('Compute', function() { gateway: '10.1.1.1' }; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.gatewayIPv4, options.gateway); - assert.strictEqual(body.gateway, undefined); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.gatewayIPv4, options.gateway); + assert.strictEqual(reqOpts.json.gateway, undefined); done(); }; @@ -348,11 +350,10 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var name = 'new-network'; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/global/networks'); - assert.strictEqual(query, null); - assert.deepEqual(body, { name: name }); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/global/networks'); + assert.deepEqual(reqOpts.json, { name: name }); done(); }; @@ -364,7 +365,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -387,7 +388,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -431,8 +432,8 @@ describe('Compute', function() { describe('getAddresses', function() { it('should accept only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -442,11 +443,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/aggregated/addresses'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/aggregated/addresses'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -458,7 +457,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -489,7 +488,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -519,7 +518,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -540,8 +539,8 @@ describe('Compute', function() { describe('getDisks', function() { it('should accept only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -551,11 +550,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/aggregated/disks'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/aggregated/disks'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -567,7 +564,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -598,7 +595,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -628,7 +625,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -649,8 +646,8 @@ describe('Compute', function() { describe('getFirewalls', function() { it('should accept only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -660,11 +657,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/global/firewalls'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/global/firewalls'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -676,7 +671,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -700,7 +695,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -723,7 +718,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -744,8 +739,8 @@ describe('Compute', function() { describe('getNetworks', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -755,11 +750,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/global/networks'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/global/networks'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -771,7 +764,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -795,7 +788,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -818,7 +811,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -839,8 +832,8 @@ describe('Compute', function() { describe('getOperations', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -850,11 +843,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/global/operations'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/global/operations'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -866,7 +857,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -890,7 +881,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -913,7 +904,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -934,8 +925,8 @@ describe('Compute', function() { describe('getRegions', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -945,11 +936,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/regions'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/regions'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -961,7 +950,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -985,7 +974,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -1008,7 +997,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -1029,8 +1018,8 @@ describe('Compute', function() { describe('getSnapshots', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -1040,11 +1029,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/global/snapshots'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/global/snapshots'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -1056,7 +1043,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -1080,7 +1067,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -1103,7 +1090,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -1124,8 +1111,8 @@ describe('Compute', function() { describe('getVMs', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -1135,11 +1122,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/aggregated/instances'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/aggregated/instances'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -1151,7 +1136,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -1182,7 +1167,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -1212,7 +1197,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -1233,8 +1218,8 @@ describe('Compute', function() { describe('getZones', function() { it('should work with only a callback', function(done) { - compute.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -1244,11 +1229,9 @@ describe('Compute', function() { it('should make the correct API request', function(done) { var options = {}; - compute.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/zones'); - assert.strictEqual(query, options); - assert.strictEqual(body, null); + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/zones'); + assert.strictEqual(reqOpts.qs, options); done(); }; @@ -1260,7 +1243,7 @@ describe('Compute', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -1284,7 +1267,7 @@ describe('Compute', function() { }; beforeEach(function() { - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -1307,7 +1290,7 @@ describe('Compute', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - compute.makeReq_ = function(method, path, query, body, callback) { + compute.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -1380,27 +1363,4 @@ describe('Compute', function() { assert.strictEqual(zone.calledWith_[1], NAME); }); }); - - describe('makeReq_', function() { - it('should make the correct request to Compute', function(done) { - var method = 'POST'; - var path = '/'; - var query = 'query'; - var body = 'body'; - - compute.makeAuthenticatedRequest_ = function(reqOpts, callback) { - assert.equal(reqOpts.method, method); - assert.equal(reqOpts.qs, query); - - var baseUri = 'https://www.googleapis.com/compute/v1/'; - assert.equal(reqOpts.uri, baseUri + 'projects/' + PROJECT_ID + path); - - assert.equal(reqOpts.json, body); - - callback(); - }; - - compute.makeReq_(method, path, query, body, done); - }); - }); }); From 38ca0c62590e3f1e0a8bb996cb054e47a9f45caf Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 13:29:46 -0400 Subject: [PATCH 27/49] add missing instanceof tests --- test/compute/region.js | 2 ++ test/compute/snapshot.js | 1 + test/compute/zone.js | 2 ++ 3 files changed, 5 insertions(+) diff --git a/test/compute/region.js b/test/compute/region.js index 384fe3e6914..4af69e41f42 100644 --- a/test/compute/region.js +++ b/test/compute/region.js @@ -95,6 +95,8 @@ describe('Region', function() { }); it('should inherit from ServiceObject', function() { + assert(region instanceof ServiceObject); + var calledWith = region.calledWith_[0]; assert.strictEqual(calledWith.parent, COMPUTE); diff --git a/test/compute/snapshot.js b/test/compute/snapshot.js index d00c63deddd..55016042b8f 100644 --- a/test/compute/snapshot.js +++ b/test/compute/snapshot.js @@ -89,6 +89,7 @@ describe('Snapshot', function() { }; var snapshot = new Snapshot(scope, SNAPSHOT_NAME); + assert(snapshot instanceof ServiceObject); var calledWith = snapshot.calledWith_[0]; assert.strictEqual(calledWith.methods.create, true); diff --git a/test/compute/zone.js b/test/compute/zone.js index 5a27624ca8c..a21fe7ca7af 100644 --- a/test/compute/zone.js +++ b/test/compute/zone.js @@ -125,6 +125,8 @@ describe('Zone', function() { }); it('should inherit from ServiceObject', function() { + assert(zone instanceof ServiceObject); + var calledWith = zone.calledWith_[0]; assert.strictEqual(calledWith.parent, COMPUTE); From 8a2ebd9f4b5c99876548fe458cf401fc83663167 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 13:52:46 -0400 Subject: [PATCH 28/49] fix test for not working diskAttach --- system-test/compute.js | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/system-test/compute.js b/system-test/compute.js index de324ded4c7..3811450375e 100644 --- a/system-test/compute.js +++ b/system-test/compute.js @@ -456,32 +456,16 @@ describe('Compute', function() { }); it('should attach and detach a disk', function(done) { - var disk = zone.disk(generateName()); - - // This test waits on a lot of operations. - this.timeout(90000); - - async.series([ - createDisk, - attachDisk, - detachDisk - ], done); - - function createDisk(callback) { - var config = { - os: 'ubuntu' - }; - - disk.create(config, execAfterOperationComplete(callback)); - } - - function attachDisk(callback) { - vm.attachDisk(disk, execAfterOperationComplete(callback)); - } + compute.getDisks() + .on('error', done) + .once('data', function(disk) { + this.end(); - function detachDisk(callback) { - vm.detachDisk(disk, execAfterOperationComplete(callback)); - } + vm.attachDisk(disk, function(err) { + assert.ifError(err); + vm.detachDisk(disk, done); + }); + }); }); it('should get serial port output', function(done) { From 801b7b3776a6ce14185a239784be00399cd53e7f Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 14:35:02 -0400 Subject: [PATCH 29/49] dns.change --- test/dns/change.js | 128 ++++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 84 deletions(-) diff --git a/test/dns/change.js b/test/dns/change.js index 82714d3d275..a26aba89965 100644 --- a/test/dns/change.js +++ b/test/dns/change.js @@ -18,113 +18,73 @@ var assert = require('assert'); var extend = require('extend'); -var format = require('string-format-obj'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Change = require('../../lib/dns/change.js'); +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('Change', function() { + var Change; + var change; + var ZONE = { name: 'zone-name', - dns: { - makeReq_: util.noop - } + createChange: util.noop }; var CHANGE_ID = 'change-id'; - var change; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Change = require('../../lib/dns/change.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); beforeEach(function() { change = new Change(ZONE, CHANGE_ID); }); describe('instantiation', function() { - it('should localize the zone name', function() { - assert.strictEqual(change.zoneName, ZONE.name); - }); - - it('should localize the id', function() { - assert.strictEqual(change.id, CHANGE_ID); - }); - - it('should create a makeReq_ function from the Zone', function(done) { - var zone = extend({}, ZONE, { - dns: { - makeReq_: function() { - assert.strictEqual(this, zone.dns); + it('should inherit from ServiceObject', function(done) { + var zoneInstance = extend({}, ZONE, { + createChange: { + bind: function(context) { + assert.strictEqual(context, zoneInstance); done(); } } }); - new Change(zone, CHANGE_ID).makeReq_(); - }); - }); + var change = new Change(zoneInstance, CHANGE_ID); + assert(change instanceof ServiceObject); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - change.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - - var expectedPath = format('/managedZones/{z}/changes/{c}', { - z: ZONE.name, - c: CHANGE_ID - }); - assert.strictEqual(path, expectedPath); - - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - change.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - change.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - change.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - - describe('success', function() { - var metadata = { e: 'f', g: 'h' }; - - beforeEach(function() { - change.makeReq_ = function(method, path, query, body, callback) { - callback(null, metadata, metadata); - }; - }); - - it('should update the metadata', function(done) { - change.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(change.metadata, metadata); - done(); - }); - }); + var calledWith = change.calledWith_[0]; - it('should execute callback with metadata & API resp', function(done) { - change.getMetadata(function(err, metadata_, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata_, metadata); - assert.strictEqual(apiResponse_, metadata); - done(); - }); + assert.strictEqual(calledWith.parent, zoneInstance); + assert.strictEqual(calledWith.baseUrl, '/changes'); + assert.strictEqual(calledWith.id, CHANGE_ID); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true }); }); }); From 34d570f00bb2533231f21fbdfdcd9b64ec2c9377 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 14:43:18 -0400 Subject: [PATCH 30/49] dns.index --- test/dns/index.js | 123 ++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 82 deletions(-) diff --git a/test/dns/index.js b/test/dns/index.js index be0e71520f4..8b96f479bfc 100644 --- a/test/dns/index.js +++ b/test/dns/index.js @@ -20,9 +20,15 @@ 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, { + makeAuthenticatedRequestFactory: util.noop +}); + var extended = false; var fakeStreamRouter = { extend: function(Class, methods) { @@ -37,21 +43,17 @@ var fakeStreamRouter = { } }; -var makeAuthenticatedRequestFactoryOverride; -var fakeUtil = extend({}, util, { - makeAuthenticatedRequestFactory: function() { - if (makeAuthenticatedRequestFactoryOverride) { - return makeAuthenticatedRequestFactoryOverride.apply(null, arguments); - } else { - return util.makeAuthenticatedRequestFactory.apply(null, arguments); - } - } -}); - function FakeZone() { this.calledWith_ = arguments; } +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + describe('DNS', function() { var DNS; var dns; @@ -59,6 +61,7 @@ describe('DNS', function() { var PROJECT_ID = 'project-id'; before(function() { + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('../common/util.js', fakeUtil); mockery.registerMock('./zone.js', FakeZone); @@ -76,8 +79,6 @@ describe('DNS', function() { }); beforeEach(function() { - makeAuthenticatedRequestFactoryOverride = null; - dns = new DNS({ projectId: PROJECT_ID }); @@ -107,33 +108,17 @@ describe('DNS', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - it('should create an authenticated request function', function(done) { - var options = { - projectId: 'projectId', - credentials: 'credentials', - email: 'email', - keyFilename: 'keyFile' - }; + it('should inherit from Service', function() { + assert(dns instanceof Service); - makeAuthenticatedRequestFactoryOverride = function(options_) { - assert.deepEqual(options_, { - credentials: options.credentials, - email: options.email, - keyFile: options.keyFilename, - scopes: [ - 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', - 'https://www.googleapis.com/auth/cloud-platform' - ] - }); - return done; - }; - - var dns = new DNS(options); - dns.makeAuthenticatedRequest_(); - }); + var calledWith = dns.calledWith_[0]; - it('should localize the projectId', function() { - assert.equal(dns.projectId_, PROJECT_ID); + var baseUrl = 'https://www.googleapis.com/dns/v1'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', + 'https://www.googleapis.com/auth/cloud-platform' + ]); }); }); @@ -160,8 +145,8 @@ describe('DNS', function() { it('should use a provided description', function(done) { var cfg = extend({}, config, { description: 'description' }); - dns.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.description, cfg.description); + dns.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.description, cfg.description); done(); }; @@ -169,8 +154,8 @@ describe('DNS', function() { }); it('should default a description to ""', function(done) { - dns.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.description, ''); + dns.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.description, ''); done(); }; @@ -178,16 +163,15 @@ describe('DNS', function() { }); it('should make the correct API request', function(done) { - dns.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/managedZones'); - assert.strictEqual(query, null); + dns.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/managedZones'); var expectedBody = extend({}, config, { name: zoneName, description: '' }); - assert.deepEqual(body, expectedBody); + assert.deepEqual(reqOpts.json, expectedBody); done(); }; @@ -200,7 +184,7 @@ describe('DNS', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - dns.makeReq_ = function(method, path, query, body, callback) { + dns.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -220,7 +204,7 @@ describe('DNS', function() { var zone = {}; beforeEach(function() { - dns.makeReq_ = function(method, path, query, body, callback) { + dns.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -262,11 +246,9 @@ describe('DNS', function() { it('should make the correct request', function(done) { var query = { a: 'b', c: 'd' }; - dns.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/managedZones'); - assert.strictEqual(query, query); - assert.strictEqual(body, null); + dns.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/managedZones'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -275,8 +257,8 @@ describe('DNS', function() { }); it('should use an empty query if one was not provided', function(done) { - dns.makeReq_ = function(method, path, query) { - assert.equal(Object.keys(query).length, 0); + dns.request = function(reqOpts) { + assert.equal(Object.keys(reqOpts.qs).length, 0); done(); }; @@ -288,7 +270,7 @@ describe('DNS', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - dns.makeReq_ = function(method, path, query, body, callback) { + dns.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -310,7 +292,7 @@ describe('DNS', function() { var apiResponse = { managedZones: [zone] }; beforeEach(function() { - dns.makeReq_ = function(method, path, query, body, callback) { + dns.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -337,7 +319,7 @@ describe('DNS', function() { var query = { a: 'b', c: 'd' }; var originalQuery = extend({}, query); - dns.makeReq_ = function(method, path, query, body, callback) { + dns.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -393,27 +375,4 @@ describe('DNS', function() { assert.strictEqual(newZone.calledWith_[1], newZoneName); }); }); - - describe('makeReq_', function() { - it('should make correct authenticated request', function(done) { - var method = 'POST'; - var path = '/'; - var query = 'query'; - var body = 'body'; - - dns.makeAuthenticatedRequest_ = function(reqOpts, callback) { - assert.equal(reqOpts.method, method); - assert.equal(reqOpts.qs, query); - - var baseUri = 'https://www.googleapis.com/dns/v1/'; - assert.equal(reqOpts.uri, baseUri + 'projects/' + PROJECT_ID + path); - - assert.equal(reqOpts.json, body); - - callback(); - }; - - dns.makeReq_(method, path, query, body, done); - }); - }); }); From eb8e76d7ab1950b3723fcfc8d01fd8f9a32985c6 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 14:57:12 -0400 Subject: [PATCH 31/49] dns.zone --- test/dns/zone.js | 188 +++++++++++++++++------------------------------ 1 file changed, 67 insertions(+), 121 deletions(-) diff --git a/test/dns/zone.js b/test/dns/zone.js index be12e2036c5..9af1672a136 100644 --- a/test/dns/zone.js +++ b/test/dns/zone.js @@ -20,8 +20,18 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + var parseOverride; var fakeDnsZonefile = { parse: function() { @@ -72,13 +82,14 @@ describe('Zone', function() { var zone; var DNS = { - makeReq_: function() {} + createZone: util.noop }; var ZONE_NAME = 'zone-name'; before(function() { mockery.registerMock('dns-zonefile', fakeDnsZonefile); mockery.registerMock('fs', fakeFs); + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('./change.js', FakeChange); mockery.registerMock('./record.js', FakeRecord); @@ -107,24 +118,34 @@ describe('Zone', function() { assert(extended); // See `fakeStreamRouter.extend` }); - it('should localize the DNS instance', function() { - assert.strictEqual(zone.dns, DNS); - }); - it('should localize the name', function() { assert.strictEqual(zone.name, ZONE_NAME); }); - it('should create a makeReq_ function', function(done) { - var dns = { - makeReq_: function() { - assert.strictEqual(this, dns); - done(); + it('should inherit from ServiceObject', function(done) { + var dnsInstance = extend({}, DNS, { + createZone: { + bind: function(context) { + assert.strictEqual(context, dnsInstance); + done(); + } } - }; + }); + + var zone = new Zone(dnsInstance, ZONE_NAME); + assert(zone instanceof ServiceObject); - var zone = new Zone(dns, ZONE_NAME); - zone.makeReq_(); + var calledWith = zone.calledWith_[0]; + + assert.strictEqual(calledWith.parent, dnsInstance); + assert.strictEqual(calledWith.baseUrl, '/managedZones'); + assert.strictEqual(calledWith.id, ZONE_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); }); }); @@ -173,9 +194,9 @@ describe('Zone', function() { ]; var expectedAdditions = ['a', 'a']; - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.add, undefined); - assert.deepEqual(body.additions, expectedAdditions); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.add, undefined); + assert.deepEqual(reqOpts.json.additions, expectedAdditions); done(); }; @@ -189,9 +210,9 @@ describe('Zone', function() { ]; var expectedDeletions = ['a', 'a']; - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.delete, undefined); - assert.deepEqual(body.deletions, expectedDeletions); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.delete, undefined); + assert.deepEqual(reqOpts.json.deletions, expectedDeletions); done(); }; @@ -199,10 +220,10 @@ describe('Zone', function() { }); it('should make correct API request', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/managedZones/' + ZONE_NAME + '/changes'); - assert.strictEqual(query, null); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/changes'); + done(); }; @@ -214,7 +235,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -232,7 +253,7 @@ describe('Zone', function() { var apiResponse = { id: 1, a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -270,7 +291,7 @@ describe('Zone', function() { }); it('should try to delete again after emptying', function(done) { - zone.makeReq_ = function() { + FakeServiceObject.prototype.delete = function() { done(); }; @@ -283,24 +304,12 @@ describe('Zone', function() { }); it('should make the correct API request', function(done) { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - var ignoreThisArgument = { e: 'f', g: 'h' }; - - zone.makeReq_ = function(method, path, query, body, callback) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, '/managedZones/' + ZONE_NAME); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - callback(error, apiResponse, ignoreThisArgument); + FakeServiceObject.prototype.delete = function(callback) { + assert.strictEqual(this, zone); + callback(); }; - zone.delete(function(err, apiResponse_) { - assert.strictEqual(arguments.length, 2); - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); + zone.delete(done); }); }); @@ -492,8 +501,8 @@ describe('Zone', function() { describe('getChanges', function() { it('should accept only a callback', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.strictEqual(Object.keys(query).length, 0); + zone.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -503,9 +512,9 @@ describe('Zone', function() { it('should accept a sort', function(done) { var query = { sort: 'desc' }; - zone.makeReq_ = function(method, path, query) { - assert.strictEqual(query.sortOrder, 'descending'); - assert.strictEqual(query.sort, undefined); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.sortOrder, 'descending'); + assert.strictEqual(reqOpts.qs.sort, undefined); done(); }; @@ -516,11 +525,9 @@ describe('Zone', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - zone.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/managedZones/' + ZONE_NAME + '/changes'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/changes'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -533,7 +540,7 @@ describe('Zone', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -553,7 +560,7 @@ describe('Zone', function() { }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -567,7 +574,7 @@ describe('Zone', function() { pageToken: nextPageToken }; - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -602,74 +609,13 @@ describe('Zone', function() { }); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - zone.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/managedZones/' + ZONE_NAME); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - zone.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - zone.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should update the metadata to the API response', function(done) { - zone.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(zone.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata and API response', function(done) { - zone.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - }); - describe('getRecords', function() { describe('error', function() { var error = new Error('Error.'); var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -696,7 +642,7 @@ describe('Zone', function() { }; beforeEach(function() { - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -708,7 +654,7 @@ describe('Zone', function() { }); var expectedNextQuery = { pageToken: nextPageToken }; - zone.makeReq_ = function(method, path, query, body, callback) { + zone.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -771,8 +717,8 @@ describe('Zone', function() { }); it('should not send filterByTypes_ in API request', function(done) { - zone.makeReq_ = function(method, path, query) { - assert.strictEqual(query.filterByTypes_, undefined); + zone.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.filterByTypes_, undefined); done(); }; From f2f532b5dbc6d2ac85ebfb5f41831387c5f98389 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 15:34:48 -0400 Subject: [PATCH 32/49] pubsub.iam --- lib/pubsub/iam.js | 6 +- test/pubsub/iam.js | 199 ++++++++++++++++++++++++--------------------- 2 files changed, 111 insertions(+), 94 deletions(-) diff --git a/lib/pubsub/iam.js b/lib/pubsub/iam.js index 14adad8364c..66b77164303 100644 --- a/lib/pubsub/iam.js +++ b/lib/pubsub/iam.js @@ -32,8 +32,10 @@ var ServiceObject = require('../common/service-object.js'); /*! Developer Documentation * - * @param {module:pubsub} pubsub - PubSub Object - * @param {string} resource - topic or subscription name + * @param {module:pubsub} pubsub - PubSub Object. + * @param {object} config - Configuration object. + * @param {string} config.baseUrl - The base URL to apply to API requests. + * @param {string} config.id - The name of the topic or subscription. */ /** * [IAM (Identity and Access Management)](https://cloud.google.com/pubsub/access_control) diff --git a/test/pubsub/iam.js b/test/pubsub/iam.js index fff0a3cb05a..90a2b874b32 100644 --- a/test/pubsub/iam.js +++ b/test/pubsub/iam.js @@ -17,36 +17,66 @@ 'use strict'; var assert = require('assert'); -var IAM = require('../../lib/pubsub/iam'); -var noop = function() {}; +var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); describe('IAM', function() { - var RESOURCE = 'projects/test-project/topics/test-topic'; - var pubsubMock = { - makeReq_: noop - }; + var IAM; var iam; + var PUBSUB = {}; + var CONFIG = { + baseUrl: '/baseurl', + id: 'id' + }; + + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + IAM = require('../../lib/pubsub/iam.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { - iam = new IAM(pubsubMock, RESOURCE); + iam = new IAM(PUBSUB, CONFIG); }); describe('initialization', function() { - it('should localize the resource', function() { - assert.strictEqual(iam.resource, RESOURCE); + it('should inherit from ServiceObject', function() { + assert(iam instanceof ServiceObject); + + var calledWith = iam.calledWith_[0]; + + assert.strictEqual(calledWith.parent, PUBSUB); + assert.strictEqual(calledWith.baseUrl, CONFIG.baseUrl); + assert.strictEqual(calledWith.id, CONFIG.id); + assert.deepEqual(calledWith.methods, {}); }); }); describe('getPolicy', function() { it('should make the correct API request', function(done) { - iam.makeReq_ = function(method, path, q, body) { - assert.strictEqual(method, 'GET'); - - var expectedPath = RESOURCE + ':getIamPolicy'; - assert.strictEqual(path, expectedPath); - - assert.strictEqual(q, null); - assert.strictEqual(body, null); + iam.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, ':getIamPolicy'); done(); }; @@ -54,37 +84,35 @@ describe('IAM', function() { iam.getPolicy(assert.ifError); }); - it('should pass the callback the expected params', function(done) { - var _policy = { - bindings: [{ yo: 'yo' }] - }; + it('should handle errors properly', function(done) { + var apiResponse = {}; + var error = new Error('Error.'); - iam.makeReq_ = function(method, path, q, body, callback) { - callback(null, _policy, _policy); + iam.request = function(reqOpts, callback) { + callback(error, apiResponse); }; - iam.getPolicy(function(err, policy, apiResponse) { - assert.ifError(err); - assert.deepEqual(policy, _policy); - assert.deepEqual(apiResponse, _policy); + iam.getPolicy(function(err, policy, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(policy, null); + assert.strictEqual(apiResponse_, apiResponse); done(); }); }); - it('should handle errors properly', function(done) { - var fakeResponse = { - error: 'Ohnoes' + it('should pass the callback the expected params', function(done) { + var apiResponse = { + bindings: [{ yo: 'yo' }] }; - var error = new Error(fakeResponse.error); - iam.makeReq_ = function(method, path, q, body, callback) { - callback(error, fakeResponse); + iam.request = function(reqOpts, callback) { + callback(null, apiResponse); }; - iam.getPolicy(function(err, policy, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(policy, null); - assert.strictEqual(apiResponse, fakeResponse); + iam.getPolicy(function(err, policy, apiResponse_) { + assert.ifError(err); + assert.strictEqual(policy, apiResponse); + assert.strictEqual(apiResponse_, apiResponse); done(); }); }); @@ -93,23 +121,17 @@ describe('IAM', function() { describe('setPolicy', function() { it('should throw an error if a policy is not supplied', function() { assert.throws(function() { - iam.setPolicy(noop); - }, /A policy is required/); + iam.setPolicy(util.noop); + }, /A policy object is required/); }); it('should make the correct API request', function(done) { var policy = { etag: 'ACAB' }; - iam.makeReq_ = function(method, path, q, body) { - assert.strictEqual(method, 'POST'); - - var expectedPath = RESOURCE + ':setIamPolicy'; - assert.strictEqual(path, expectedPath); - - assert.strictEqual(q, null); - - var expectedBody = { policy: policy }; - assert.deepEqual(body, expectedBody); + iam.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':setIamPolicy'); + assert.deepEqual(reqOpts.json, { policy: policy }); done(); }; @@ -117,37 +139,35 @@ describe('IAM', function() { iam.setPolicy(policy, assert.ifError); }); - it('should pass the callback the expected params', function(done) { - var _policy = { - bindings: [{ yo: 'yo' }] - }; + it('should handle errors properly', function(done) { + var apiResponse = {}; + var error = new Error('Error.'); - iam.makeReq_ = function(method, path, q, body, callback) { - callback(null, body.policy, body.policy); + iam.request = function(reqOpts, callback) { + callback(error, apiResponse); }; - iam.setPolicy(_policy, function(err, policy, apiResponse) { - assert.ifError(err); - assert.deepEqual(_policy, policy); - assert.deepEqual(_policy, apiResponse); + iam.setPolicy({}, function(err, policy, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(policy, null); + assert.strictEqual(apiResponse_, apiResponse); done(); }); }); - it('should handle errors properly', function(done) { - var fakeResponse = { - error: 'Ohnoes' + it('should pass the callback the expected params', function(done) { + var apiResponse = { + bindings: [{ yo: 'yo' }] }; - var error = new Error(fakeResponse.error); - iam.makeReq_ = function(method, path, q, body, callback) { - callback(error, fakeResponse); + iam.request = function(reqOpts, callback) { + callback(null, apiResponse); }; - iam.setPolicy({}, function(err, policy, apiResponse) { - assert.strictEqual(err, error); - assert.strictEqual(policy, null); - assert.strictEqual(apiResponse, fakeResponse); + iam.setPolicy({}, function(err, policy, apiResponse_) { + assert.ifError(err); + assert.strictEqual(policy, apiResponse); + assert.strictEqual(apiResponse_, apiResponse); done(); }); }); @@ -156,23 +176,17 @@ describe('IAM', function() { describe('testPermissions', function() { it('should throw an error if permissions are missing', function() { assert.throws(function() { - iam.testPermissions(noop); + iam.testPermissions(util.noop); }, /Permissions are required/); }); it('should make the correct API request', function(done) { var permissions = 'storage.bucket.list'; - iam.makeReq_ = function(method, path, q, body) { - assert.strictEqual(method, 'POST'); - - var expectedPath = RESOURCE + ':testIamPermissions'; - assert.strictEqual(path, expectedPath); - - assert.strictEqual(q, null); - - var expectedBody = { permissions: [permissions] }; - assert.deepEqual(body, expectedBody); + iam.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':testIamPermissions'); + assert.deepEqual(reqOpts.json, { permissions: [permissions] }); done(); }; @@ -183,16 +197,16 @@ describe('IAM', function() { it('should send an error back if the request fails', function(done) { var permissions = ['storage.bucket.list']; var error = new Error('Error.'); - var fakeResponse = {}; + var apiResponse = {}; - iam.makeReq_ = function(method, path, q, body, callback) { - callback(error, fakeResponse); + iam.request = function(reqOpts, callback) { + callback(error, apiResponse); }; - iam.testPermissions(permissions, function(err, perms, resp) { + iam.testPermissions(permissions, function(err, permissions, apiResp) { assert.strictEqual(err, error); - assert.strictEqual(perms, null); - assert.strictEqual(resp, fakeResponse); + assert.strictEqual(permissions, null); + assert.strictEqual(apiResp, apiResponse); done(); }); }); @@ -202,21 +216,22 @@ describe('IAM', function() { 'storage.bucket.list', 'storage.bucket.consume' ]; - var fakeResponse = { + var apiResponse = { permissions: ['storage.bucket.consume'] }; - iam.makeReq_ = function(method, path, q, body, callback) { - callback(null, fakeResponse); + iam.request = function(reqOpts, callback) { + callback(null, apiResponse); }; - iam.testPermissions(permissions, function(err, perms, resp) { + iam.testPermissions(permissions, function(err, permissions, apiResp) { assert.ifError(err); - assert.deepEqual(perms, { + assert.deepEqual(permissions, { 'storage.bucket.list': false, 'storage.bucket.consume': true }); - assert.strictEqual(resp, fakeResponse); + assert.strictEqual(apiResp, apiResponse); + done(); }); }); From f324f915052b09c6a5d12177da3b853bbf5b3f75 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Fri, 30 Oct 2015 17:07:38 -0400 Subject: [PATCH 33/49] pubsub.subscription --- lib/pubsub/subscription.js | 214 ++++++------ test/pubsub/subscription.js | 644 ++++++++++++++++++++---------------- 2 files changed, 460 insertions(+), 398 deletions(-) diff --git a/lib/pubsub/subscription.js b/lib/pubsub/subscription.js index b9ef36f289d..a15d3b79bab 100644 --- a/lib/pubsub/subscription.js +++ b/lib/pubsub/subscription.js @@ -276,21 +276,6 @@ function Subscription(pubsub, options) { modelo.inherits(Subscription, ServiceObject, events.EventEmitter); -/** - * Format the name of a subscription. A subscription's full name is in the - * format of projects/{projectId}/subscriptions/{subName}. - * - * @private - */ -Subscription.formatName_ = function(projectId, name) { - // Simple check if the name is already formatted. - if (name.indexOf('/') > -1) { - return name; - } - - return 'projects/' + projectId + '/subscriptions/' + name; -}; - /** * Simplify a message from an API response to have three properties, `id`, * `data` and `attributes`. `data` is always converted to a string. @@ -323,84 +308,18 @@ Subscription.formatMessage_ = function(msg) { }; /** - * Begin listening for events on the subscription. This method keeps track of - * how many message listeners are assigned, and then removed, making sure - * polling is handled automatically. - * - * As long as there is one active message listener, the connection is open. As - * soon as there are no more message listeners, the connection is closed. - * - * @private - * - * @example - * subscription.listenForEvents_(); - */ -Subscription.prototype.listenForEvents_ = function() { - var self = this; - - this.on('newListener', function(event) { - if (event === 'message') { - self.messageListeners++; - if (self.closed) { - self.closed = false; - self.startPulling_(); - } - } - }); - - this.on('removeListener', function(event) { - if (event === 'message' && --self.messageListeners === 0) { - self.closed = true; - } - }); -}; - -/** - * Poll the backend for new messages. This runs a loop to ping the API at the - * provided interval from the subscription's instantiation. If one wasn't - * provided, the default value is 10 milliseconds. - * - * If messages are received, they are emitted on the `message` event. - * - * Note: This method is automatically called once a message event handler is - * assigned to the description. - * - * To stop pulling, see {@linkcode module:pubsub/subscription#close}. + * Format the name of a subscription. A subscription's full name is in the + * format of projects/{projectId}/subscriptions/{subName}. * * @private - * - * @example - * subscription.startPulling_(); */ -Subscription.prototype.startPulling_ = function() { - var self = this; - - if (this.closed || this.paused) { - return; - } - - var maxResults; - - if (this.maxInProgress < Infinity) { - maxResults = this.maxInProgress - Object.keys(this.inProgressAckIds).length; +Subscription.formatName_ = function(projectId, name) { + // Simple check if the name is already formatted. + if (name.indexOf('/') > -1) { + return name; } - this.pull({ - returnImmediately: false, - maxResults: maxResults - }, function(err, messages, apiResponse) { - if (err) { - self.emit('error', err, apiResponse); - } - - if (messages) { - messages.forEach(function(message) { - self.emit('message', message, apiResponse); - }); - } - - setTimeout(self.startPulling_.bind(self), self.interval); - }); + return 'projects/' + projectId + '/subscriptions/' + name; }; /** @@ -449,6 +368,36 @@ Subscription.prototype.ack = function(ackIds, callback) { }); }; +/** + * Add functionality on top of a message returned from the API, including the + * ability to `ack` and `skip` the message. + * + * This also records the message as being "in progress". See + * {module:subscription#refreshPausedStatus_}. + * + * @private + * + * @param {object} message - A message object. + * @return {object} message - The original message after being decorated. + * @param {function} message.ack - Ack the message. + * @param {function} message.skip - Increate the number of available messages to + * simultaneously receive. + */ +Subscription.prototype.decorateMessage_ = function(message) { + var self = this; + + this.inProgressAckIds[message.ackId] = true; + + message.ack = self.ack.bind(self, message.ackId); + + message.skip = function() { + delete self.inProgressAckIds[message.ackId]; + self.refreshPausedStatus_(); + }; + + return message; +}; + /** * Delete the subscription. Pull requests from the current subscription will be * errored once unsubscription is complete. @@ -609,33 +558,36 @@ Subscription.prototype.setAckDeadline = function(options, callback) { }; /** - * Add functionality on top of a message returned from the API, including the - * ability to `ack` and `skip` the message. + * Begin listening for events on the subscription. This method keeps track of + * how many message listeners are assigned, and then removed, making sure + * polling is handled automatically. * - * This also records the message as being "in progress". See - * {module:subscription#refreshPausedStatus_}. + * As long as there is one active message listener, the connection is open. As + * soon as there are no more message listeners, the connection is closed. * * @private * - * @param {object} message - A message object. - * @return {object} message - The original message after being decorated. - * @param {function} message.ack - Ack the message. - * @param {function} message.skip - Increate the number of available messages to - * simultaneously receive. + * @example + * subscription.listenForEvents_(); */ -Subscription.prototype.decorateMessage_ = function(message) { +Subscription.prototype.listenForEvents_ = function() { var self = this; - this.inProgressAckIds[message.ackId] = true; - - message.ack = self.ack.bind(self, message.ackId); - - message.skip = function() { - delete self.inProgressAckIds[message.ackId]; - self.refreshPausedStatus_(); - }; + this.on('newListener', function(event) { + if (event === 'message') { + self.messageListeners++; + if (self.closed) { + self.closed = false; + self.startPulling_(); + } + } + }); - return message; + this.on('removeListener', function(event) { + if (event === 'message' && --self.messageListeners === 0) { + self.closed = true; + } + }); }; /** @@ -660,4 +612,52 @@ Subscription.prototype.refreshPausedStatus_ = function() { } }; +/** + * Poll the backend for new messages. This runs a loop to ping the API at the + * provided interval from the subscription's instantiation. If one wasn't + * provided, the default value is 10 milliseconds. + * + * If messages are received, they are emitted on the `message` event. + * + * Note: This method is automatically called once a message event handler is + * assigned to the description. + * + * To stop pulling, see {@linkcode module:pubsub/subscription#close}. + * + * @private + * + * @example + * subscription.startPulling_(); + */ +Subscription.prototype.startPulling_ = function() { + var self = this; + + if (this.closed || this.paused) { + return; + } + + var maxResults; + + if (this.maxInProgress < Infinity) { + maxResults = this.maxInProgress - Object.keys(this.inProgressAckIds).length; + } + + this.pull({ + returnImmediately: false, + maxResults: maxResults + }, function(err, messages, apiResponse) { + if (err) { + self.emit('error', err, apiResponse); + } + + if (messages) { + messages.forEach(function(message) { + self.emit('message', message, apiResponse); + }); + } + + setTimeout(self.startPulling_.bind(self), self.interval); + }); +}; + module.exports = Subscription; diff --git a/test/pubsub/subscription.js b/test/pubsub/subscription.js index 9da29ab0113..54258fb88ea 100644 --- a/test/pubsub/subscription.js +++ b/test/pubsub/subscription.js @@ -17,21 +17,33 @@ 'use strict'; var assert = require('assert'); +var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); -var Subscription; function FakeIAM() { this.calledWith_ = [].slice.call(arguments); } +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('Subscription', function() { + var Subscription; + var subscription; + var PROJECT_ID = 'test-project'; var SUB_NAME = 'test-subscription'; var SUB_FULL_NAME = 'projects/' + PROJECT_ID + '/subscriptions/' + SUB_NAME; - var pubsubMock = { - projectId: PROJECT_ID, - makeReq_: util.noop + var PUBSUB = { + projectId: PROJECT_ID }; var message = 'howdy'; var messageBuffer = new Buffer(message).toString('base64'); @@ -49,19 +61,21 @@ describe('Subscription', function() { data: message, id: 7 }; - var subscription; before(function() { - mockery.registerMock('./iam', FakeIAM); + mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock('./iam.js', FakeIAM); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); + Subscription = require('../../lib/pubsub/subscription.js'); }); beforeEach(function() { - subscription = new Subscription(pubsubMock, { name: SUB_NAME }); + subscription = new Subscription(PUBSUB, { name: SUB_NAME }); }); describe('initialization', function() { @@ -71,7 +85,7 @@ describe('Subscription', function() { Subscription.formatName_ = formatName_; done(); }; - new Subscription(pubsubMock, { name: SUB_NAME }); + new Subscription(PUBSUB, { name: SUB_NAME }); }); it('should honor configuration settings', function() { @@ -81,7 +95,7 @@ describe('Subscription', function() { interval: 100, maxInProgress: 3 }; - var sub = new Subscription(pubsubMock, CONFIG); + var sub = new Subscription(PUBSUB, CONFIG); assert.strictEqual(sub.autoAck, CONFIG.autoAck); assert.strictEqual(sub.interval, CONFIG.interval); assert.strictEqual(sub.maxInProgress, 3); @@ -92,28 +106,23 @@ describe('Subscription', function() { }); it('should default autoAck to false if not specified', function() { - var sub = new Subscription(pubsubMock, { name: SUB_NAME }); - assert.strictEqual(sub.autoAck, false); + assert.strictEqual(subscription.autoAck, false); }); it('should set default interval if one is not specified', function() { - var sub = new Subscription(pubsubMock, { name: SUB_NAME }); - assert.equal(sub.interval, 10); + assert.equal(subscription.interval, 10); }); it('should start inProgressAckIds as an empty object', function() { - var sub = new Subscription(pubsubMock, { name: SUB_NAME }); - assert.equal(Object.keys(sub.inProgressAckIds).length, 0); + assert.deepEqual(subscription.inProgressAckIds, {}); }); it('should default maxInProgress to Infinity if not specified', function() { - var sub = new Subscription(pubsubMock, { name: SUB_NAME }); - assert.strictEqual(sub.maxInProgress, Infinity); + assert.strictEqual(subscription.maxInProgress, Infinity); }); it('should set messageListeners to 0', function() { - var sub = new Subscription(pubsubMock, { name: SUB_NAME }); - assert.strictEqual(sub.messageListeners, 0); + assert.strictEqual(subscription.messageListeners, 0); }); it('should not be paused', function() { @@ -122,73 +131,100 @@ describe('Subscription', function() { it('should create an iam object', function() { assert.deepEqual(subscription.iam.calledWith_, [ - pubsubMock, - SUB_FULL_NAME + PUBSUB, + { + baseUrl: '/subscriptions', + id: SUB_NAME + } ]); }); - }); - describe('formatName_', function() { - it('should format name', function() { - var formattedName = Subscription.formatName_(PROJECT_ID, SUB_NAME); - assert.equal(formattedName, SUB_FULL_NAME); - }); + it('should inherit from ServiceObject', function() { + assert(subscription instanceof ServiceObject); - it('should format name when given a complete name', function() { - var formattedName = Subscription.formatName_(PROJECT_ID, SUB_FULL_NAME); - assert.equal(formattedName, SUB_FULL_NAME); - }); - }); + var calledWith = subscription.calledWith_[0]; - describe('listenForEvents_', function() { - afterEach(function() { - subscription.removeAllListeners(); - }); - - it('should start pulling once a message listener is bound', function(done) { - subscription.startPulling_ = function() { - done(); - }; - subscription.on('message', util.noop); + assert.strictEqual(calledWith.parent, PUBSUB); + assert.strictEqual(calledWith.baseUrl, '/subscriptions'); + assert.strictEqual(calledWith.id, SUB_NAME); + assert.deepEqual(calledWith.methods, { + exists: true, + get: true, + getMetadata: true + }); }); - it('should track the number of listeners', function() { - subscription.startPulling_ = util.noop; - - assert.strictEqual(subscription.messageListeners, 0); + it('should allow creating if it is a Topic', function(done) { + var topicInstance = {}; - subscription.on('message', util.noop); - assert.strictEqual(subscription.messageListeners, 1); + var pubSubInstance = extend({}, PUBSUB, { + subscribe: { + bind: function(context, topic) { + assert.strictEqual(context, pubSubInstance); + assert.strictEqual(topic, topicInstance); + done(); + } + } + }); - subscription.removeListener('message', util.noop); - assert.strictEqual(subscription.messageListeners, 0); + var subscription = new Subscription(pubSubInstance, { + name: SUB_NAME, + topic: topicInstance + }); + assert(subscription instanceof ServiceObject); + + var calledWith = subscription.calledWith_[0]; + + assert.strictEqual(calledWith.parent, pubSubInstance); + assert.strictEqual(calledWith.baseUrl, '/subscriptions'); + assert.strictEqual(calledWith.id, SUB_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); }); + }); - it('should only run a single pulling loop', function() { - var startPullingCallCount = 0; + describe('formatMessage_', function() { + it('should decode stringified JSON to object', function() { + var obj = { hi: 'there' }; + var stringified = new Buffer(JSON.stringify(obj)).toString('base64'); + var attributes = {}; - subscription.startPulling_ = function() { - startPullingCallCount++; - }; + var msg = Subscription.formatMessage_({ + ackId: 3, + message: { + data: stringified, + messageId: 7, + attributes: attributes + } + }); - subscription.on('message', util.noop); - subscription.on('message', util.noop); + assert.deepEqual(msg, { + ackId: 3, + id: 7, + data: obj, + attributes: attributes + }); + }); - assert.strictEqual(startPullingCallCount, 1); + it('should decode buffer to string', function() { + var msg = Subscription.formatMessage_(messageObj.receivedMessages[0]); + assert.deepEqual(msg, expectedMessage); }); + }); - it('should close when no more message listeners are bound', function() { - subscription.startPulling_ = util.noop; - subscription.on('message', util.noop); - subscription.on('message', util.noop); - // 2 listeners: sub should be open. - assert.strictEqual(subscription.closed, false); - subscription.removeListener('message', util.noop); - // 1 listener: sub should be open. - assert.strictEqual(subscription.closed, false); - subscription.removeListener('message', util.noop); - // 0 listeners: sub should be closed. - assert.strictEqual(subscription.closed, true); + describe('formatName_', function() { + it('should format name', function() { + var formattedName = Subscription.formatName_(PROJECT_ID, SUB_NAME); + assert.equal(formattedName, SUB_FULL_NAME); + }); + + it('should format name when given a complete name', function() { + var formattedName = Subscription.formatName_(PROJECT_ID, SUB_FULL_NAME); + assert.equal(formattedName, SUB_FULL_NAME); }); }); @@ -204,20 +240,22 @@ describe('Subscription', function() { it('should accept a single id', function() { assert.doesNotThrow(function() { + subscription.request = util.noop; subscription.ack(1, util.noop); }); }); it('should accept an array of ids', function() { assert.doesNotThrow(function() { + subscription.request = util.noop; subscription.ack([1], util.noop); }); }); it('should make an array out of ids', function(done) { var ID = 1; - subscription.makeReq_ = function(method, path, qs, body) { - assert.deepEqual(body.ackIds, [ID]); + subscription.request = function(reqOpts) { + assert.deepEqual(reqOpts.json.ackIds, [ID]); done(); }; subscription.ack(ID, assert.ifError); @@ -225,16 +263,16 @@ describe('Subscription', function() { it('should make correct api request', function(done) { var IDS = [1, 2, 3]; - subscription.makeReq_ = function(method, path, qs, body) { - assert.equal(path, SUB_FULL_NAME + ':acknowledge'); - assert.deepEqual(body.ackIds, IDS); + subscription.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, ':acknowledge'); + assert.deepEqual(reqOpts.json.ackIds, IDS); done(); }; subscription.ack(IDS, assert.ifError); }); it('should unmark the ack ids as being in progress', function(done) { - subscription.makeReq_ = function(method, path, query, body, callback) { + subscription.request = function(reqOpts, callback) { callback(); }; @@ -253,7 +291,7 @@ describe('Subscription', function() { }); it('should not unmark if there was an error', function(done) { - subscription.makeReq_ = function(method, path, query, body, callback) { + subscription.request = function(reqOpts, callback) { callback(new Error('Error.')); }; @@ -270,7 +308,7 @@ describe('Subscription', function() { }); it('should refresh paused status', function(done) { - subscription.makeReq_ = function(method, path, query, body, callback) { + subscription.request = function(reqOpts, callback) { callback(); }; @@ -282,7 +320,7 @@ describe('Subscription', function() { it('should pass error to callback', function(done) { var error = new Error('Error.'); - subscription.makeReq_ = function(method, path, query, body, callback) { + subscription.request = function(reqOpts, callback) { callback(error); }; @@ -294,7 +332,7 @@ describe('Subscription', function() { it('should pass apiResponse to callback', function(done) { var resp = { success: true }; - subscription.makeReq_ = function(method, path, qs, body, callback) { + subscription.request = function(reqOpts, callback) { callback(null, resp); }; subscription.ack(1, function(err, apiResponse) { @@ -304,10 +342,68 @@ describe('Subscription', function() { }); }); + describe('delete', function() { + it('should delete a subscription', function(done) { + FakeServiceObject.prototype.delete = function() { + assert.strictEqual(this, subscription); + done(); + }; + subscription.delete(); + }); + + it('should close a subscription once deleted', function() { + FakeServiceObject.prototype.delete = function(callback) { + callback(); + }; + subscription.closed = false; + subscription.delete(); + assert.strictEqual(subscription.closed, true); + }); + + it('should remove all listeners', function(done) { + FakeServiceObject.prototype.delete = function(callback) { + callback(); + }; + subscription.removeAllListeners = function() { + done(); + }; + subscription.delete(); + }); + + it('should execute callback when deleted', function(done) { + FakeServiceObject.prototype.delete = function(callback) { + callback(); + }; + subscription.delete(done); + }); + + it('should execute callback with an api error', function(done) { + var error = new Error('Error.'); + FakeServiceObject.prototype.delete = function(callback) { + callback(error); + }; + subscription.delete(function(err) { + assert.equal(err, error); + done(); + }); + }); + + it('should execute callback with apiResponse', function(done) { + var resp = { success: true }; + FakeServiceObject.prototype.delete = function(callback) { + callback(null, resp); + }; + subscription.delete(function(err, apiResponse) { + assert.deepEqual(resp, apiResponse); + done(); + }); + }); + }); + describe('pull', function() { beforeEach(function() { subscription.ack = util.noop; - subscription.makeReq_ = function(method, path, qs, body, callback) { + subscription.request = function(reqOpts, callback) { callback(null, messageObj); }; }); @@ -317,27 +413,27 @@ describe('Subscription', function() { }); it('should default returnImmediately to false', function(done) { - subscription.makeReq_ = function(method, path, qs, body) { - assert.strictEqual(body.returnImmediately, false); + subscription.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.returnImmediately, false); done(); }; subscription.pull({}, assert.ifError); }); it('should honor options', function(done) { - subscription.makeReq_ = function(method, path, qs, body) { - assert.strictEqual(body.returnImmediately, true); + subscription.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.returnImmediately, true); done(); }; subscription.pull({ returnImmediately: true }, assert.ifError); }); it('should make correct api request', function(done) { - subscription.makeReq_ = function(method, path, qs, body) { - assert.equal(method, 'POST'); - assert.equal(path, SUB_FULL_NAME + ':pull'); - assert.strictEqual(body.returnImmediately, false); - assert.equal(body.maxMessages, 1); + subscription.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':pull'); + assert.strictEqual(reqOpts.json.returnImmediately, false); + assert.strictEqual(reqOpts.json.maxMessages, 1); done(); }; @@ -346,7 +442,7 @@ describe('Subscription', function() { it('should pass error to callback', function(done) { var error = new Error('Error.'); - subscription.makeReq_ = function(method, path, qs, body, callback) { + subscription.request = function(reqOpts, callback) { callback(error); }; subscription.pull(function(err) { @@ -417,7 +513,7 @@ describe('Subscription', function() { }); it('should not autoAck if no messages returned', function(done) { - subscription.makeReq_ = function(method, path, qs, body, callback) { + subscription.request = function(reqOpts, callback) { callback(null, { receivedMessages: [] }); }; subscription.ack = function() { @@ -470,7 +566,7 @@ describe('Subscription', function() { callback(null, { success: true }); }; - subscription.makeReq_ = function(method, path, qs, body, callback) { + subscription.request = function(reqOpts, callback) { callback(null, resp); }; @@ -482,6 +578,170 @@ describe('Subscription', function() { }); }); + describe('setAckDeadline', function() { + it('should set the ack deadline', function(done) { + subscription.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':modifyAckDeadline'); + assert.deepEqual(reqOpts.json, { + ackIds: [123], + ackDeadlineSeconds: 10 + }); + done(); + }; + subscription.setAckDeadline({ ackIds: [123], seconds: 10 }, done); + }); + + it('should execute the callback', function(done) { + subscription.request = function(reqOpts, callback) { + callback(); + }; + subscription.setAckDeadline({}, done); + }); + + it('should execute the callback with apiResponse', function(done) { + var resp = { success: true }; + subscription.request = function(reqOpts, callback) { + callback(null, resp); + }; + subscription.setAckDeadline({}, function(err, apiResponse) { + assert.deepEqual(resp, apiResponse); + done(); + }); + }); + }); + + describe('decorateMessage_', function() { + var message = { + ackId: 'b' + }; + + it('should return the message', function() { + var decoratedMessage = subscription.decorateMessage_(message); + assert.strictEqual(decoratedMessage.ackId, message.ackId); + }); + + it('should mark the message as being in progress', function() { + subscription.decorateMessage_(message); + assert.strictEqual(subscription.inProgressAckIds[message.ackId], true); + }); + + describe('ack', function() { + it('should add an ack function to ack', function() { + var decoratedMessage = subscription.decorateMessage_(message); + assert.equal(typeof decoratedMessage.ack, 'function'); + }); + + it('should pass the ackId to subscription.ack', function(done) { + subscription.ack = function(ackId, callback) { + assert.strictEqual(ackId, message.ackId); + callback(); + }; + + subscription.decorateMessage_(message).ack(done); + }); + }); + + describe('skip', function() { + it('should add a skip function', function() { + var decoratedMessage = subscription.decorateMessage_(message); + assert.equal(typeof decoratedMessage.skip, 'function'); + }); + + it('should unmark the message as being in progress', function() { + subscription.decorateMessage_(message).skip(); + + var inProgressAckIds = subscription.inProgressAckIds; + assert.strictEqual(inProgressAckIds[message.ackId], undefined); + }); + + it('should refresh the paused status', function(done) { + subscription.refreshPausedStatus_ = done; + subscription.decorateMessage_(message).skip(); + }); + }); + }); + + describe('refreshPausedStatus_', function() { + it('should pause if the ackIds in progress is too high', function() { + subscription.inProgressAckIds = { id1: true, id2: true, id3: true }; + + subscription.maxInProgress = 2; + subscription.refreshPausedStatus_(); + assert.strictEqual(subscription.paused, true); + + subscription.maxInProgress = 3; + subscription.refreshPausedStatus_(); + assert.strictEqual(subscription.paused, true); + + subscription.maxInProgress = Infinity; + subscription.refreshPausedStatus_(); + assert.strictEqual(subscription.paused, false); + }); + + it('should start pulling if paused and listeners exist', function(done) { + subscription.startPulling_ = done; + + subscription.inProgressAckIds = { id1: true, id2: true, id3: true }; + subscription.paused = true; + subscription.maxInProgress = Infinity; + subscription.messageListeners = 1; + subscription.refreshPausedStatus_(); + }); + }); + + describe('listenForEvents_', function() { + afterEach(function() { + subscription.removeAllListeners(); + }); + + it('should start pulling once a message listener is bound', function(done) { + subscription.startPulling_ = function() { + done(); + }; + subscription.on('message', util.noop); + }); + + it('should track the number of listeners', function() { + subscription.startPulling_ = util.noop; + + assert.strictEqual(subscription.messageListeners, 0); + + subscription.on('message', util.noop); + assert.strictEqual(subscription.messageListeners, 1); + + subscription.removeListener('message', util.noop); + assert.strictEqual(subscription.messageListeners, 0); + }); + + it('should only run a single pulling loop', function() { + var startPullingCallCount = 0; + + subscription.startPulling_ = function() { + startPullingCallCount++; + }; + + subscription.on('message', util.noop); + subscription.on('message', util.noop); + + assert.strictEqual(startPullingCallCount, 1); + }); + + it('should close when no more message listeners are bound', function() { + subscription.startPulling_ = util.noop; + subscription.on('message', util.noop); + subscription.on('message', util.noop); + // 2 listeners: sub should be open. + assert.strictEqual(subscription.closed, false); + subscription.removeListener('message', util.noop); + // 1 listener: sub should be open. + assert.strictEqual(subscription.closed, false); + subscription.removeListener('message', util.noop); + // 0 listeners: sub should be closed. + assert.strictEqual(subscription.closed, true); + }); + }); + describe('startPulling_', function() { beforeEach(function() { subscription.pull = util.noop; @@ -625,202 +885,4 @@ describe('Subscription', function() { subscription.startPulling_(); }); }); - - describe('delete', function() { - it('should delete a subscription', function(done) { - subscription.makeReq_ = function(method, path) { - assert.equal(method, 'DELETE'); - assert.equal(path, subscription.name); - done(); - }; - subscription.delete(); - }); - - it('should close a subscription once deleted', function() { - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(); - }; - subscription.closed = false; - subscription.delete(); - assert.strictEqual(subscription.closed, true); - }); - - it('should remove all listeners', function(done) { - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(); - }; - subscription.removeAllListeners = function() { - done(); - }; - subscription.delete(); - }); - - it('should execute callback when deleted', function(done) { - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(); - }; - subscription.delete(done); - }); - - it('should execute callback with an api error', function(done) { - var error = new Error('Error.'); - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(error); - }; - subscription.delete(function(err) { - assert.equal(err, error); - done(); - }); - }); - - it('should execute callback with apiResponse', function(done) { - var resp = { success: true }; - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(null, resp); - }; - subscription.delete(function(err, apiResponse) { - assert.deepEqual(resp, apiResponse); - done(); - }); - }); - }); - - describe('setAckDeadline', function() { - it('should set the ack deadline', function(done) { - subscription.makeReq_ = function(method, path, qs, body) { - assert.equal(method, 'POST'); - assert.equal(path, this.name + ':modifyAckDeadline'); - assert.equal(qs, null); - assert.deepEqual(body, { ackIds: [123], ackDeadlineSeconds: 10 }); - done(); - }; - subscription.setAckDeadline({ ackIds: [123], seconds: 10 }, done); - }); - - it('should execute the callback', function(done) { - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(); - }; - subscription.setAckDeadline({}, done); - }); - - it('should execute the callback with apiResponse', function(done) { - var resp = { success: true }; - subscription.makeReq_ = function(method, path, qs, body, callback) { - callback(null, resp); - }; - subscription.setAckDeadline({}, function(err, apiResponse) { - assert.deepEqual(resp, apiResponse); - done(); - }); - }); - }); - - describe('decorateMessage_', function() { - var message = { - ackId: 'b' - }; - - it('should return the message', function() { - var decoratedMessage = subscription.decorateMessage_(message); - assert.strictEqual(decoratedMessage.ackId, message.ackId); - }); - - it('should mark the message as being in progress', function() { - subscription.decorateMessage_(message); - assert.strictEqual(subscription.inProgressAckIds[message.ackId], true); - }); - - describe('ack', function() { - it('should add an ack function to ack', function() { - var decoratedMessage = subscription.decorateMessage_(message); - assert.equal(typeof decoratedMessage.ack, 'function'); - }); - - it('should pass the ackId to subscription.ack', function(done) { - subscription.ack = function(ackId, callback) { - assert.strictEqual(ackId, message.ackId); - callback(); - }; - - subscription.decorateMessage_(message).ack(done); - }); - }); - - describe('skip', function() { - it('should add a skip function', function() { - var decoratedMessage = subscription.decorateMessage_(message); - assert.equal(typeof decoratedMessage.skip, 'function'); - }); - - it('should unmark the message as being in progress', function() { - subscription.decorateMessage_(message).skip(); - - var inProgressAckIds = subscription.inProgressAckIds; - assert.strictEqual(inProgressAckIds[message.ackId], undefined); - }); - - it('should refresh the paused status', function(done) { - subscription.refreshPausedStatus_ = done; - subscription.decorateMessage_(message).skip(); - }); - }); - }); - - describe('refreshPausedStatus_', function() { - it('should pause if the ackIds in progress is too high', function() { - subscription.inProgressAckIds = { id1: true, id2: true, id3: true }; - - subscription.maxInProgress = 2; - subscription.refreshPausedStatus_(); - assert.strictEqual(subscription.paused, true); - - subscription.maxInProgress = 3; - subscription.refreshPausedStatus_(); - assert.strictEqual(subscription.paused, true); - - subscription.maxInProgress = Infinity; - subscription.refreshPausedStatus_(); - assert.strictEqual(subscription.paused, false); - }); - - it('should start pulling if paused and listeners exist', function(done) { - subscription.startPulling_ = done; - - subscription.inProgressAckIds = { id1: true, id2: true, id3: true }; - subscription.paused = true; - subscription.maxInProgress = Infinity; - subscription.messageListeners = 1; - subscription.refreshPausedStatus_(); - }); - }); - - describe('formatMessage_', function() { - it('should decode stringified JSON to object', function() { - var obj = { hi: 'there' }; - var stringified = new Buffer(JSON.stringify(obj)).toString('base64'); - var attributes = {}; - - var msg = Subscription.formatMessage_({ - ackId: 3, - message: { - data: stringified, - messageId: 7, - attributes: attributes - } - }); - - assert.deepEqual(msg, { - ackId: 3, - id: 7, - data: obj, - attributes: attributes - }); - }); - - it('should decode buffer to string', function() { - var msg = Subscription.formatMessage_(messageObj.receivedMessages[0]); - assert.deepEqual(msg, expectedMessage); - }); - }); }); From 40504ed71c0a386be056ff8dd52563666830c179 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 09:51:00 -0500 Subject: [PATCH 34/49] pubsub.topic --- lib/pubsub/topic.js | 124 +++++++++-------- test/pubsub/subscription.js | 5 + test/pubsub/topic.js | 260 +++++++++++++++++------------------- 3 files changed, 186 insertions(+), 203 deletions(-) diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js index 2b72ffea058..bf621758eca 100644 --- a/lib/pubsub/topic.js +++ b/lib/pubsub/topic.js @@ -177,8 +177,6 @@ function Topic(pubsub, name) { }); this.name = Topic.formatName_(pubsub.projectId, name); - - this.projectId = pubsub.projectId; this.pubsub = pubsub; this.unformattedName = name; } @@ -218,6 +216,67 @@ Topic.formatName_ = function(projectId, name) { return 'projects/' + projectId + '/topics/' + name; }; +/** + * Get a list of the subscriptions registered to this topic. You may optionally + * provide a query object as the first argument to customize the response. + * + * Your provided callback will be invoked with an error object if an API error + * occurred or an array of {@linkcode module:pubsub/subscription} objects. + * + * @resource [Subscriptions: list API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics.subscriptions/list} + * + * @param {object=} options - Configuration object. + * @param {number=} options.pageSize - Maximum number of results to return. + * @param {string=} options.pageToken - Page token. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, subscriptions, nextQuery, apiResponse) { + * // If `nextQuery` is non-null, there may be more results to fetch. To do + * // so, run `topic.getSubscriptions(nextQuery, callback);`. + * }; + * + * // Get all subscriptions for this topic. + * topic.getSubscriptions(callback); + * + * // Customize the query. + * topic.getSubscriptions({ + * pageSize: 3 + * }, callback); + * + * //- + * // Get the subscriptions for this topic as a readable object stream. + * //- + * topic.getSubscriptions() + * .on('error', console.error) + * .on('data', function(subscription) { + * // subscription is a Subscription object. + * }) + * .on('end', function() { + * // All subscriptions retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * topic.getSubscriptions() + * .on('data', function(subscription) { + * this.end(); + * }); + */ +Topic.prototype.getSubscriptions = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + options.topic = this; + + return this.pubsub.getSubscriptions(options, callback); +}; + /** * Publish the provided message or array of messages. On success, an array of * messageIds is returned in the response. @@ -300,67 +359,6 @@ Topic.prototype.publish = function(messages, callback) { }); }; -/** - * Get a list of the subscriptions registered to this topic. You may optionally - * provide a query object as the first argument to customize the response. - * - * Your provided callback will be invoked with an error object if an API error - * occurred or an array of {@linkcode module:pubsub/subscription} objects. - * - * @resource [Subscriptions: list API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.topics.subscriptions/list} - * - * @param {object=} options - Configuration object. - * @param {number=} options.pageSize - Maximum number of results to return. - * @param {string=} options.pageToken - Page token. - * @param {function} callback - The callback function. - * - * @example - * var callback = function(err, subscriptions, nextQuery, apiResponse) { - * // If `nextQuery` is non-null, there may be more results to fetch. To do - * // so, run `topic.getSubscriptions(nextQuery, callback);`. - * }; - * - * // Get all subscriptions for this topic. - * topic.getSubscriptions(callback); - * - * // Customize the query. - * topic.getSubscriptions({ - * pageSize: 3 - * }, callback); - * - * //- - * // Get the subscriptions for this topic as a readable object stream. - * //- - * topic.getSubscriptions() - * .on('error', console.error) - * .on('data', function(subscription) { - * // subscription is a Subscription object. - * }) - * .on('end', function() { - * // All subscriptions retrieved. - * }); - * - * //- - * // If you anticipate many results, you can end a stream early to prevent - * // unnecessary processing and API requests. - * //- - * topic.getSubscriptions() - * .on('data', function(subscription) { - * this.end(); - * }); - */ -Topic.prototype.getSubscriptions = function(options, callback) { - if (is.fn(options)) { - callback = options; - options = {}; - } - - options = options || {}; - options.topic = this; - - return this.pubsub.getSubscriptions(options, callback); -}; - /** * Create a subscription to this topic. You may optionally provide an object to * customize the subscription. diff --git a/test/pubsub/subscription.js b/test/pubsub/subscription.js index 54258fb88ea..25fbffb1da6 100644 --- a/test/pubsub/subscription.js +++ b/test/pubsub/subscription.js @@ -74,6 +74,11 @@ describe('Subscription', function() { Subscription = require('../../lib/pubsub/subscription.js'); }); + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { subscription = new Subscription(PUBSUB, { name: SUB_NAME }); }); diff --git a/test/pubsub/topic.js b/test/pubsub/topic.js index 5e97b402008..a83a5cf81c3 100644 --- a/test/pubsub/topic.js +++ b/test/pubsub/topic.js @@ -17,60 +17,105 @@ 'use strict'; var assert = require('assert'); +var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + var util = require('../../lib/common/util.js'); -var Topic; +var ServiceObject = require('../../lib/common/service-object.js'); function FakeIAM() { this.calledWith_ = [].slice.call(arguments); } +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('Topic', function() { + var Topic; + var topic; + var PROJECT_ID = 'test-project'; var TOPIC_NAME = 'test-topic'; - var TOPIC_FULL_NAME = 'projects/' + PROJECT_ID + '/topics/' + TOPIC_NAME; - var pubsubMock = { + var PUBSUB = { projectId: PROJECT_ID, - makeReq_: util.noop + createTopic: util.noop }; - var topic; before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('./iam', FakeIAM); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); + Topic = require('../../lib/pubsub/topic'); }); + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { - topic = new Topic(pubsubMock, TOPIC_NAME); + topic = new Topic(PUBSUB, TOPIC_NAME); }); describe('initialization', function() { + it('should inherit from ServiceObject', function(done) { + var pubsubInstance = extend({}, PUBSUB, { + createTopic: { + bind: function(context) { + assert.strictEqual(context, pubsubInstance); + done(); + } + } + }); + + var topic = new Topic(pubsubInstance, TOPIC_NAME); + assert(topic instanceof ServiceObject); + + var calledWith = topic.calledWith_[0]; + + assert.strictEqual(calledWith.parent, pubsubInstance); + assert.strictEqual(calledWith.baseUrl, '/topics'); + assert.strictEqual(calledWith.id, TOPIC_NAME); + assert.deepEqual(calledWith.methods, { + create: true, + delete: true, + exists: true, + get: true, + getMetadata: true + }); + }); + + it('should create an iam object', function() { + assert.deepEqual(topic.iam.calledWith_, [ + PUBSUB, + { + baseUrl: '/topics', + id: TOPIC_NAME + } + ]); + }); + it('should format name', function(done) { var formatName_ = Topic.formatName_; Topic.formatName_ = function() { Topic.formatName_ = formatName_; done(); }; - new Topic(pubsubMock, TOPIC_NAME); - }); - - it('should assign projectId to `this`', function() { - assert.equal(topic.projectId, PROJECT_ID); + new Topic(PUBSUB, TOPIC_NAME); }); it('should assign pubsub object to `this`', function() { - assert.deepEqual(topic.pubsub, pubsubMock); - }); - - it('should create an iam object', function() { - assert.deepEqual(topic.iam.calledWith_, [ - pubsubMock, - TOPIC_FULL_NAME - ]); + assert.deepEqual(topic.pubsub, PUBSUB); }); }); @@ -110,6 +155,31 @@ describe('Topic', function() { }); }); + describe('getSubscriptions', function() { + it('should accept just a callback', function(done) { + topic.pubsub.getSubscriptions = function(options, callback) { + assert.deepEqual(options, { topic: topic }); + callback(); + }; + + topic.getSubscriptions(done); + }); + + it('should pass correct args to pubsub#getSubscriptions', function(done) { + var opts = { a: 'b', c: 'd' }; + + topic.pubsub = { + getSubscriptions: function(options, callback) { + assert.deepEqual(options, opts); + assert.deepEqual(options.topic, topic); + callback(); + } + }; + + topic.getSubscriptions(opts, done); + }); + }); + describe('publish', function() { var message = 'howdy'; var messageObject = { data: message }; @@ -131,11 +201,10 @@ describe('Topic', function() { }); it('should send correct api request', function(done) { - topic.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, topic.name + ':publish'); - assert.strictEqual(query, null); - assert.deepEqual(body, { + topic.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':publish'); + assert.deepEqual(reqOpts.json, { messages: [ { data: new Buffer(JSON.stringify(message)).toString('base64') } ] @@ -147,142 +216,44 @@ describe('Topic', function() { }); it('should execute callback', function(done) { - topic.makeReq_ = function(method, path, query, body, callback) { - callback(); + topic.request = function(reqOpts, callback) { + callback(null, {}); }; topic.publish(messageObject, done); }); - it('should execute callback with apiResponse', function(done) { - var resp = { success: true }; - topic.makeReq_ = function(method, path, query, body, callback) { - callback(null, resp); - }; - - topic.publish(messageObject, function(err, ackIds, apiResponse) { - assert.deepEqual(resp, apiResponse); - done(); - }); - }); - }); + it('should execute callback with error', function(done) { + var error = new Error('Error.'); + var apiResponse = {}; - describe('delete', function() { - it('should delete a topic', function(done) { - topic.makeReq_ = function(method, path) { - assert.equal(method, 'DELETE'); - assert.equal(path, topic.name); - done(); + topic.request = function(reqOpts, callback) { + callback(error, apiResponse); }; - topic.delete(); - }); - it('should call the callback', function(done) { - topic.makeReq_ = function(method, path, q, body, callback) { - callback(); - }; - topic.delete(done); - }); + topic.publish(messageObject, function(err, ackIds, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(ackIds, null); + assert.strictEqual(apiResponse_, apiResponse); - it('should call the callback with apiResponse', function(done) { - var resp = { success: true }; - topic.makeReq_ = function(method, path, q, body, callback) { - callback(null, resp); - }; - topic.delete(function(err, apiResponse) { - assert.deepEqual(resp, apiResponse); done(); }); }); - }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - topic.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, topic.name); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + it('should execute callback with apiResponse', function(done) { + var resp = { success: true }; - done(); + topic.request = function(reqOpts, callback) { + callback(null, resp); }; - topic.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - topic.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error & API response', function(done) { - topic.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - topic.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should assign the response to the metadata property', function(done) { - topic.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(topic.metadata, apiResponse); - done(); - }); - }); - - it('should exec callback with metadata & API response', function(done) { - topic.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); + topic.publish(messageObject, function(err, ackIds, apiResponse) { + assert.deepEqual(resp, apiResponse); + done(); }); }); }); - describe('getSubscriptions', function() { - it('should accept just a callback', function(done) { - topic.pubsub.getSubscriptions = function(options, callback) { - assert.deepEqual(options, { topic: topic }); - callback(); - }; - - topic.getSubscriptions(done); - }); - - it('should pass correct args to pubsub#getSubscriptions', function(done) { - var opts = { a: 'b', c: 'd' }; - - topic.pubsub = { - getSubscriptions: function(options, callback) { - assert.deepEqual(options, opts); - assert.deepEqual(options.topic, topic); - callback(); - } - }; - - topic.getSubscriptions(opts, done); - }); - }); - describe('subscribe', function() { it('should pass correct arguments to pubsub#subscribe', function(done) { var subscriptionName = 'subName'; @@ -313,6 +284,15 @@ describe('Topic', function() { topic.subscription(subscriptionName, opts); }); + it('should attach the topic instance to the options', function(done) { + topic.pubsub.subscription = function(name, options) { + assert.strictEqual(options.topic, topic); + done(); + }; + + topic.subscription(); + }); + it('should return the result', function(done) { topic.pubsub.subscription = function() { return done; From 4f285ffac7eed94d5fd71b408d526dcc9cf75939 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 10:25:55 -0500 Subject: [PATCH 35/49] pubsub.index --- test/compute/index.js | 2 +- test/pubsub/index.js | 465 ++++++++++++++++++++++++------------------ 2 files changed, 268 insertions(+), 199 deletions(-) diff --git a/test/compute/index.js b/test/compute/index.js index f8c2dc96863..defcb623ba5 100644 --- a/test/compute/index.js +++ b/test/compute/index.js @@ -95,9 +95,9 @@ describe('Compute', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/util.js', fakeUtil); mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../common/util.js', fakeUtil); mockery.registerMock('./firewall.js', FakeFirewall); mockery.registerMock('./network.js', FakeNetwork); mockery.registerMock('./operation.js', FakeOperation); diff --git a/test/pubsub/index.js b/test/pubsub/index.js index 18e0dbf21c4..bd9bde925a8 100644 --- a/test/pubsub/index.js +++ b/test/pubsub/index.js @@ -18,11 +18,14 @@ var arrify = require('arrify'); var assert = require('assert'); +var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); var request = require('request'); -var extend = require('extend'); -var util = require('../../lib/common/util'); + +var Service = require('../../lib/common/service.js'); var Topic = require('../../lib/pubsub/topic.js'); +var util = require('../../lib/common/util.js'); var SubscriptionCached = require('../../lib/pubsub/subscription.js'); var SubscriptionOverride; @@ -45,6 +48,13 @@ fakeRequest.defaults = function() { var fakeUtil = extend({}, util); +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + var extended = false; var fakeStreamRouter = { extend: function(Class, methods) { @@ -65,15 +75,18 @@ describe('PubSub', function() { var pubsub; before(function() { + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('../common/util.js', fakeUtil); mockery.registerMock('./subscription.js', Subscription); mockery.registerMock('./topic.js', Topic); mockery.registerMock('request', fakeRequest); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); + PubSub = require('../../lib/pubsub'); }); @@ -86,7 +99,7 @@ describe('PubSub', function() { SubscriptionOverride = null; requestOverride = null; pubsub = new PubSub({ projectId: PROJECT_ID }); - pubsub.makeReq_ = function(method, path, q, body, callback) { + pubsub.request = function(method, path, q, body, callback) { callback(); }; }); @@ -114,130 +127,92 @@ describe('PubSub', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - }); - describe('getTopics', function() { - var topicName = 'fake-topic'; - var apiResponse = { topics: [{ name: topicName }]}; + it('should inherit from Service', function() { + assert(pubsub instanceof Service); - beforeEach(function() { - pubsub.makeReq_ = function(method, path, q, body, callback) { - callback(null, apiResponse); - }; - }); + var calledWith = pubsub.calledWith_[0]; - it('should accept a query and a callback', function(done) { - pubsub.getTopics({}, done); + var baseUrl = 'https://pubsub.googleapis.com/v1'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/cloud-platform' + ]); }); + }); - it('should accept just a callback', function(done) { - pubsub.getTopics(done); - }); + describe('createTopic', function() { + it('should make the correct API request', function(done) { + var topicName = 'new-topic-name'; - it('should build the right request', function() { - pubsub.makeReq_ = function(method, path) { - assert.equal(method, 'GET'); - assert.equal(path, 'projects/' + PROJECT_ID + '/topics'); + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'PUT'); + assert.strictEqual(reqOpts.uri, '/topics/' + topicName); + done(); }; - pubsub.getTopics(function() {}); - }); - it('should return Topic instances with metadata', function(done) { - var topic = {}; + pubsub.createTopic(topicName, function() {}); + }); - pubsub.topic = function(name) { - assert.strictEqual(name, topicName); - return topic; - }; + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = {}; - pubsub.getTopics(function(err, topics) { - assert.ifError(err); - assert.strictEqual(topics[0], topic); - assert.strictEqual(topics[0].metadata, apiResponse.topics[0]); - done(); + beforeEach(function() { + pubsub.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; }); - }); - it('should return a query if more results exist', function() { - var token = 'next-page-token'; - pubsub.makeReq_ = function(method, path, q, body, callback) { - callback(null, { nextPageToken: token }); - }; - var query = { pageSize: 1 }; - pubsub.getTopics(query, function(err, topics, nextQuery) { - assert.ifError(err); - assert.strictEqual(query.pageSize, nextQuery.pageSize); - assert.equal(query.pageToken, token); + it('should return an error & API response', function(done) { + pubsub.createTopic('new-topic', function(err, topic, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(topic, null); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); }); }); - it('should pass error if api returns an error', function() { - var error = new Error('Error'); - pubsub.makeReq_ = function(method, path, q, body, callback) { - callback(error); - }; - pubsub.getTopics(function(err) { - assert.equal(err, error); - }); - }); + describe('success', function() { + var apiResponse = {}; - it('should pass apiResponse to callback', function(done) { - var resp = { success: true }; - pubsub.makeReq_ = function(method, path, q, body, callback) { - callback(null, resp); - }; - pubsub.getTopics(function(err, topics, nextQuery, apiResponse) { - assert.equal(resp, apiResponse); - done(); + beforeEach(function() { + pubsub.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; }); - }); - }); - describe('createTopic', function() { - it('should create a topic', function() { - var topicName = 'new-topic-name'; - pubsub.makeReq_ = function(method, path, q, body) { - assert.equal(method, 'PUT'); - assert.equal(path, 'projects/' + PROJECT_ID + '/topics/' + topicName); - assert.equal(body, null); - }; - pubsub.createTopic(topicName, function() {}); - }); + it('should return a Topic object', function(done) { + var topicName = 'new-topic'; + var topicInstance = {}; - it('should return a Topic object', function() { - pubsub.createTopic('new-topic', function(err, topic) { - assert.ifError(err); - assert(topic instanceof Topic); - }); - }); + pubsub.topic = function(name) { + assert.strictEqual(name, topicName); + return topicInstance; + }; - it('should pass apiResponse to callback', function(done) { - var resp = { success: true }; - pubsub.makeReq_ = function(method, path, q, body, callback) { - callback(null, resp); - }; - pubsub.createTopic('new-topic', function(err, topic, apiResponse) { - assert.equal(resp, apiResponse); - done(); + pubsub.createTopic(topicName, function(err, topic) { + assert.ifError(err); + assert.strictEqual(topic, topicInstance); + done(); + }); }); - }); - }); - describe('topic', function() { - it('should throw if a name is not provided', function() { - assert.throws(function() { - pubsub.topic(); - }, /name must be specified/); - }); - - it('should return a Topic object', function() { - assert(pubsub.topic('new-topic') instanceof Topic); + it('should pass apiResponse to callback', function(done) { + pubsub.createTopic('new-topic', function(err, topic, apiResponse_) { + assert.ifError(err); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); }); }); describe('getSubscriptions', function() { beforeEach(function() { - pubsub.makeReq_ = function(method, path, q, body, callback) { + pubsub.request = function(reqOpts, callback) { callback(null, { subscriptions: [{ name: 'fake-subscription' }] }); }; }); @@ -250,11 +225,11 @@ describe('PubSub', function() { pubsub.getSubscriptions(done); }); - it('should pass the correct arguments to the API', function() { - pubsub.makeReq_ = function(method, path, query) { - assert.equal(method, 'GET'); - assert.equal(path, 'projects/' + PROJECT_ID + '/subscriptions'); - assert.equal(query.query, undefined); + it('should pass the correct arguments to the API', function(done) { + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/subscriptions'); + assert.deepEqual(reqOpts.qs, {}); + done(); }; pubsub.getSubscriptions(assert.ifError); @@ -263,16 +238,15 @@ describe('PubSub', function() { describe('topics', function() { var TOPIC; var TOPIC_NAME = 'topic'; - var TOPIC_SUBCRIPTION_NAME = - 'projects/' + PROJECT_ID + '/topics/' + TOPIC_NAME + '/subscriptions'; + var TOPIC_SUBCRIPTION_NAME = '/topics/' + TOPIC_NAME + '/subscriptions'; before(function() { TOPIC = new Topic(pubsub, TOPIC_NAME); }); it('should subscribe to a topic by string', function(done) { - pubsub.makeReq_ = function(method, path) { - assert.equal(path, TOPIC_SUBCRIPTION_NAME); + pubsub.request = function(reqOpts) { + assert.equal(reqOpts.uri, TOPIC_SUBCRIPTION_NAME); done(); }; @@ -280,8 +254,8 @@ describe('PubSub', function() { }); it('should subscribe to a topic by Topic instance', function(done) { - pubsub.makeReq_ = function(method, path) { - assert.equal(path, TOPIC_SUBCRIPTION_NAME); + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, TOPIC_SUBCRIPTION_NAME); done(); }; @@ -292,9 +266,9 @@ describe('PubSub', function() { it('should pass options to API request', function(done) { var opts = { pageSize: 10, pageToken: 'abc' }; - pubsub.makeReq_ = function(method, path, query) { - assert.equal(query.pageSize, opts.pageSize); - assert.equal(query.pageToken, opts.pageToken); + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.qs.pageSize, opts.pageSize); + assert.strictEqual(reqOpts.qs.pageToken, opts.pageToken); done(); }; @@ -305,7 +279,7 @@ describe('PubSub', function() { var error = new Error('Error'); var resp = { error: true }; - pubsub.makeReq_ = function(method, path, q, body, callback) { + pubsub.request = function(reqOpts, callback) { callback(error, resp); }; @@ -330,7 +304,7 @@ describe('PubSub', function() { var subFullName = 'projects/' + PROJECT_ID + '/subscriptions/' + subName; - pubsub.makeReq_ = function(method, path, query, body, callback) { + pubsub.request = function(reqOpts, callback) { callback(null, { subscriptions: [subName] }); }; @@ -346,7 +320,7 @@ describe('PubSub', function() { it('should return a query if more results exist', function() { var token = 'next-page-token'; - pubsub.makeReq_ = function(method, path, q, body, callback) { + pubsub.request = function(reqOpts, callback) { callback(null, { nextPageToken: token }); }; @@ -362,7 +336,7 @@ describe('PubSub', function() { it('should pass apiResponse to callback', function(done) { var resp = { success: true }; - pubsub.makeReq_ = function(method, path, q, body, callback) { + pubsub.request = function(reqOpts, callback) { callback(null, resp); }; @@ -373,15 +347,96 @@ describe('PubSub', function() { }); }); + describe('getTopics', function() { + var topicName = 'fake-topic'; + var apiResponse = { topics: [{ name: topicName }]}; + + beforeEach(function() { + pubsub.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); + + it('should accept a query and a callback', function(done) { + pubsub.getTopics({}, done); + }); + + it('should accept just a callback', function(done) { + pubsub.getTopics(done); + }); + + it('should build the right request', function(done) { + pubsub.request = function(reqOpts) { + assert.equal(reqOpts.uri, '/topics'); + done(); + }; + pubsub.getTopics(function() {}); + }); + + it('should return Topic instances with metadata', function(done) { + var topic = {}; + + pubsub.topic = function(name) { + assert.strictEqual(name, topicName); + return topic; + }; + + pubsub.getTopics(function(err, topics) { + assert.ifError(err); + assert.strictEqual(topics[0], topic); + assert.strictEqual(topics[0].metadata, apiResponse.topics[0]); + done(); + }); + }); + + it('should return a query if more results exist', function() { + var token = 'next-page-token'; + pubsub.request = function(reqOpts, callback) { + callback(null, { nextPageToken: token }); + }; + var query = { pageSize: 1 }; + pubsub.getTopics(query, function(err, topics, nextQuery) { + assert.ifError(err); + assert.strictEqual(query.pageSize, nextQuery.pageSize); + assert.equal(query.pageToken, token); + }); + }); + + it('should pass error if api returns an error', function() { + var error = new Error('Error'); + pubsub.request = function(reqOpts, callback) { + callback(error); + }; + pubsub.getTopics(function(err) { + assert.equal(err, error); + }); + }); + + it('should pass apiResponse to callback', function(done) { + var resp = { success: true }; + pubsub.request = function(reqOpts, callback) { + callback(null, resp); + }; + pubsub.getTopics(function(err, topics, nextQuery, apiResponse) { + assert.equal(resp, apiResponse); + done(); + }); + }); + }); + describe('subscribe', function() { var TOPIC_NAME = 'topic'; var TOPIC = { - name: 'projects/' + PROJECT_ID + '/topics/' + TOPIC_NAME + name: '/topics/' + TOPIC_NAME }; var SUB_NAME = 'subscription'; var SUBSCRIPTION = { - name: 'projects/' + PROJECT_ID + '/subscriptions/' + SUB_NAME + name: '/subscriptions/' + SUB_NAME + }; + + var apiResponse = { + name: 'subscription-name' }; it('should throw if no Topic is provided', function() { @@ -397,119 +452,133 @@ describe('PubSub', function() { }); it('should not require configuration options', function(done) { - pubsub.makeReq_ = function(method, path, qs, body, callback) { - callback(); + pubsub.request = function(reqOpts, callback) { + callback(null, apiResponse); }; pubsub.subscribe(TOPIC_NAME, SUB_NAME, done); }); - it('should cretae a topic object from a string', function(done) { + it('should create a topic object from a string', function(done) { + pubsub.request = util.noop; + pubsub.topic = function(topicName) { - assert.equal(topicName, TOPIC_NAME); - done(); + assert.strictEqual(topicName, TOPIC_NAME); + setImmediate(done); return TOPIC; }; pubsub.subscribe(TOPIC_NAME, SUB_NAME, assert.ifError); }); - it('should create a subscription object from a string', function(done) { - var opts = {}; + it('should send correct request', function(done) { + pubsub.topic = function(topicName) { + return { + name: topicName + }; + }; - pubsub.subscription = function(subName, options) { - assert.equal(subName, SUB_NAME); - assert.deepEqual(options, opts); + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'PUT'); + assert.strictEqual(reqOpts.uri, SUBSCRIPTION.name); + assert.strictEqual(reqOpts.json.topic, TOPIC_NAME); done(); - return SUBSCRIPTION; }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); + pubsub.subscribe(TOPIC_NAME, SUB_NAME, assert.ifError); }); - it('should pass options to a created subscription object', function(done) { - var opts = { a: 'b', c: 'd' }; + it('should pass options to the api request', function(done) { + var opts = { ackDeadlineSeconds: 90 }; - pubsub.subscription = function(subName, options) { - assert.equal(subName, SUB_NAME); - assert.deepEqual(options, opts); + pubsub.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.ackDeadlineSeconds, 90); done(); - return SUBSCRIPTION; }; pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); }); - it('should send correct request', function(done) { - pubsub.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'PUT'); - assert.equal(path, SUBSCRIPTION.name); - assert.equal(body.topic, TOPIC.name); - done(); - }; + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { name: SUB_NAME }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, assert.ifError); - }); + beforeEach(function() { + pubsub.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); - it('should re-use existing subscription if specified', function(done) { - pubsub.subscription = function() { - return SUBSCRIPTION; - }; + it('should re-use existing subscription if specified', function(done) { + pubsub.subscription = function() { + return SUBSCRIPTION; + }; - pubsub.makeReq_ = function(method, path, query, body, callback) { - callback({ code: 409 }); - }; + pubsub.request = function(reqOpts, callback) { + callback({ code: 409 }, apiResponse); + }; - // Don't re-use an existing subscription (error if one exists). - pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err) { - assert.equal(err.code, 409); + // Don't re-use an existing subscription (error if one exists). + pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err) { + assert.equal(err.code, 409); + }); + + // Re-use an existing subscription (ignore error if one exists). + var opts = { reuseExisting: true }; + pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, function(err, sub) { + assert.ifError(err); + assert.deepEqual(sub, SUBSCRIPTION); + + done(); + }); }); - // Re-use an existing subscription (ignore error if one exists). - var opts = { reuseExisting: true }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, function(err, sub) { - assert.ifError(err); - assert.deepEqual(sub, SUBSCRIPTION); + it('should return error & API response to the callback', function(done) { + pubsub.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; - done(); + pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err, sub, resp) { + assert.strictEqual(err, error); + assert.strictEqual(sub, null); + assert.strictEqual(resp, apiResponse); + done(); + }); }); }); - it('should return an api error to the callback', function(done) { - var error = new Error('Error.'); - - pubsub.makeReq_ = function(method, path, query, body, callback) { - callback(error); - }; + describe('success', function() { + var apiResponse = { name: SUB_NAME }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err) { - assert.equal(err, error); - done(); + beforeEach(function() { + pubsub.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; }); - }); - it('should return apiResponse to the callback', function(done) { - var resp = { success: true }; + it('should pass options to a new subscription object', function(done) { + var opts = { a: 'b', c: 'd' }; - pubsub.makeReq_ = function(method, path, query, body, callback) { - callback(null, resp); - }; + pubsub.subscription = function(subName, options) { + assert.strictEqual(subName, SUB_NAME); + assert.deepEqual(options, opts); + setImmediate(done); + return SUBSCRIPTION; + }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err, sub, apiResponse) { - assert.deepEqual(resp, apiResponse); - done(); + pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); }); - }); - - it('should pass options to the api request', function(done) { - var opts = { ackDeadlineSeconds: 90 }; - pubsub.makeReq_ = function(method, path, query, body) { - assert.strictEqual(body.ackDeadlineSeconds, opts.ackDeadlineSeconds); - done(); - }; + it('should return apiResponse to the callback', function(done) { + pubsub.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); + pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err, sub, resp) { + assert.strictEqual(resp, apiResponse); + done(); + }); + }); }); }); @@ -552,15 +621,15 @@ describe('PubSub', function() { }); }); - describe('makeReq_', function() { - it('should pass network requests to the connection object', function(done) { - var pubsub = new PubSub({ projectId: PROJECT_ID }); - - pubsub.makeAuthenticatedRequest_ = function() { - done(); - }; + describe('topic', function() { + it('should throw if a name is not provided', function() { + assert.throws(function() { + pubsub.topic(); + }, /name must be specified/); + }); - pubsub.makeReq_(null, null, null, null, assert.ifError); + it('should return a Topic object', function() { + assert(pubsub.topic('new-topic') instanceof Topic); }); }); }); From f05bfc417ec3a90695e84b54e0b8ca527f48ac8a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 10:37:20 -0500 Subject: [PATCH 36/49] resource.index --- test/resource/index.js | 110 +++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 70 deletions(-) diff --git a/test/resource/index.js b/test/resource/index.js index 36d778febec..f31f7d3690e 100644 --- a/test/resource/index.js +++ b/test/resource/index.js @@ -20,6 +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'); function FakeProject() { @@ -51,6 +54,13 @@ var fakeUtil = extend({}, util, { } }); +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + describe('Resource', function() { var PROJECT_ID = 'test-project-id'; @@ -58,9 +68,11 @@ describe('Resource', function() { var resource; before(function() { + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('../common/util.js', fakeUtil); mockery.registerMock('./project.js', FakeProject); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false @@ -106,32 +118,21 @@ describe('Resource', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - it('should create an authenticated request function', function(done) { - var options = { - projectId: 'projectId', - credentials: 'credentials', - email: 'email', - keyFilename: 'keyFile' - }; + it('should localize the projectId', function() { + assert.equal(resource.defaultProjectId_, PROJECT_ID); + }); - makeAuthenticatedRequestFactoryOverride = function(options_) { - assert.deepEqual(options_, { - credentials: options.credentials, - email: options.email, - keyFile: options.keyFilename, - scopes: [ - 'https://www.googleapis.com/auth/cloud-platform' - ] - }); - return done; - }; + it('should inherit from Service', function() { + assert(resource instanceof Service); - var resource = new Resource(options); - resource.makeAuthenticatedRequest_(); - }); + var calledWith = resource.calledWith_[0]; - it('should localize the projectId', function() { - assert.equal(resource.defaultProjectId_, PROJECT_ID); + var baseUrl = 'https://cloudresourcemanager.googleapis.com/v1beta1'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/cloud-platform' + ]); + assert.strictEqual(resource.projectIdRequired, false); }); }); @@ -143,8 +144,8 @@ describe('Resource', function() { it('should not require any options', function(done) { var expectedBody = { projectId: NEW_PROJECT_ID }; - resource.makeReq_ = function(method, path, query, body) { - assert.deepEqual(body, expectedBody); + resource.request = function(reqOpts) { + assert.deepEqual(reqOpts.json, expectedBody); done(); }; @@ -152,11 +153,10 @@ describe('Resource', function() { }); it('should make the correct API request', function(done) { - resource.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, '/'); - assert.strictEqual(query, null); - assert.deepEqual(body, EXPECTED_BODY); + resource.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/projects'); + assert.deepEqual(reqOpts.json, EXPECTED_BODY); done(); }; @@ -169,7 +169,7 @@ describe('Resource', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - resource.makeReq_ = function(method, path, query, body, callback) { + resource.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -188,7 +188,7 @@ describe('Resource', function() { var apiResponse = { projectId: NEW_PROJECT_ID }; beforeEach(function() { - resource.makeReq_ = function(method, path, query, body, callback) { + resource.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -215,8 +215,8 @@ describe('Resource', function() { describe('getProjects', function() { it('should accept only a callback', function(done) { - resource.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + resource.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -226,11 +226,9 @@ describe('Resource', function() { it('should make the correct API request', function(done) { var query = { a: 'b', c: 'd' }; - resource.makeReq_ = function(method, path, query_, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, '/'); - assert.strictEqual(query_, query); - assert.strictEqual(body, null); + resource.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/projects'); + assert.strictEqual(reqOpts.qs, query); done(); }; @@ -243,7 +241,7 @@ describe('Resource', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - resource.makeReq_ = function(method, path, query, body, callback) { + resource.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); @@ -267,7 +265,7 @@ describe('Resource', function() { }; beforeEach(function() { - resource.makeReq_ = function(method, path, query, body, callback) { + resource.request = function(reqOpts, callback) { callback(null, apiResponse); }; }); @@ -281,7 +279,7 @@ describe('Resource', function() { pageToken: nextPageToken }; - resource.makeReq_ = function(method, path, query, body, callback) { + resource.request = function(reqOpts, callback) { callback(null, apiResponseWithNextPageToken); }; @@ -338,32 +336,4 @@ describe('Resource', function() { }, /A project ID is required/); }); }); - - describe('makeReq_', function() { - it('should make the correct request', function(done) { - var base = 'https://cloudresourcemanager.googleapis.com/v1beta1/projects'; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - resource.makeAuthenticatedRequest_ = function(reqOpts, callback) { - assert.strictEqual(reqOpts.method, method); - - assert.strictEqual(reqOpts.uri, base + path); - assert.strictEqual(reqOpts.qs, query); - assert.strictEqual(reqOpts.json, body); - callback(); - }; - - resource.makeReq_(method, path, query, body, done); - }); - }); }); From 856248b2b21499b6a4d03b0c6f2fd428b5582512 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 10:43:51 -0500 Subject: [PATCH 37/49] resource.project --- lib/resource/project.js | 26 ++-- test/resource/project.js | 300 +++++++-------------------------------- 2 files changed, 68 insertions(+), 258 deletions(-) diff --git a/lib/resource/project.js b/lib/resource/project.js index 729ac850f67..ed090c14b59 100644 --- a/lib/resource/project.js +++ b/lib/resource/project.js @@ -103,6 +103,19 @@ function Project(resource, id) { */ delete: true, + /** + * Check if the project exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the project exists or not. + * + * @example + * project.exists(function(err, exists) {}); + */ + exists: true, + /** * Get a project if it exists. * @@ -139,19 +152,6 @@ function Project(resource, id) { */ getMetadata: true, - /** - * Check if the project exists. - * - * @param {function} callback - The callback function. - * @param {?error} callback.err - An error returned while making this - * request. - * @param {boolean} callback.exists - Whether the project exists or not. - * - * @example - * project.exists(function(err, exists) {}); - */ - exists: true, - /** * Set the project's metadata. * diff --git a/test/resource/project.js b/test/resource/project.js index 10a65c28548..e28e9955e41 100644 --- a/test/resource/project.js +++ b/test/resource/project.js @@ -17,168 +17,75 @@ 'use strict'; var assert = require('assert'); +var extend = require('extend'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Project = require('../../lib/resource/project.js'); +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); -describe('Project', function() { - var RESOURCE = {}; - var ID = 'project-id'; +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); +describe('Project', function() { + var Project; var project; - beforeEach(function() { - project = new Project(RESOURCE, ID); - }); + var RESOURCE = { + createProject: util.noop + }; + var ID = 'project-id'; - describe('instantiation', function() { - it('should localize the resource', function() { - assert.strictEqual(project.resource, RESOURCE); - }); + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); - it('should localize the ID', function() { - assert.strictEqual(project.id, ID); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false }); - it('should default metadata to an empty object', function() { - assert.deepEqual(project.metadata, {}); - }); + Project = require('../../lib/resource/project.js'); }); - describe('delete', function() { - it('should make the correct API request', function(done) { - project.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'DELETE'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - done(); - }; - - project.delete(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should return an error if the request fails', function(done) { - project.delete(function(err, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.delete(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { - projectId: ID - }; - - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - project.delete(function(err, apiResponse_) { - assert.ifError(err); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.delete(); - }); - }); - }); + after(function() { + mockery.deregisterAll(); + mockery.disable(); }); - describe('getMetadata', function() { - it('should make the correct API request', function(done) { - project.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'GET'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - - done(); - }; - - project.getMetadata(assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should execute callback with error and API response', function(done) { - project.getMetadata(function(err, metadata, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(metadata, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.getMetadata(); - }); - }); - }); - - describe('success', function() { - var apiResponse = { a: 'b', c: 'd' }; + beforeEach(function() { + project = new Project(RESOURCE, ID); + }); - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; + describe('instantiation', function() { + it('should inherit from ServiceObject', function(done) { + var resourceInstance = extend({}, RESOURCE, { + createProject: { + bind: function(context) { + assert.strictEqual(context, resourceInstance); + done(); + } + } }); - it('should update the metadata to the API response', function(done) { - project.getMetadata(function(err) { - assert.ifError(err); - assert.strictEqual(project.metadata, apiResponse); - done(); - }); - }); + var project = new Project(resourceInstance, ID); + assert(project instanceof ServiceObject); - it('should exec callback with metadata and API response', function(done) { - project.getMetadata(function(err, metadata, apiResponse_) { - assert.ifError(err); - assert.strictEqual(metadata, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); + var calledWith = project.calledWith_[0]; - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.getMetadata(); - }); + assert.strictEqual(calledWith.parent, resourceInstance); + assert.strictEqual(calledWith.baseUrl, '/projects'); + assert.strictEqual(calledWith.id, ID); + assert.deepEqual(calledWith.methods, { + create: true, + delete: true, + exists: true, + get: true, + getMetadata: true, + setMetadata: true }); }); }); @@ -188,17 +95,15 @@ describe('Project', function() { var apiResponse = { a: 'b', c: 'd' }; beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { + project.request = function(reqOpts, callback) { callback(error, apiResponse); }; }); it('should make the correct API request', function(done) { - project.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'POST'); - assert.strictEqual(path, ':undelete'); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + project.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, ':undelete'); done(); }; @@ -220,99 +125,4 @@ describe('Project', function() { }); }); }); - - describe('setMetadata', function() { - var METADATA = { a: 'b', c: 'd' }; - - it('should make the correct API request', function(done) { - project.makeReq_ = function(method, path, query, body) { - assert.strictEqual(method, 'PUT'); - assert.strictEqual(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, METADATA); - - done(); - }; - - project.setMetadata(METADATA, assert.ifError); - }); - - describe('error', function() { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - }); - - it('should return an error if the request fails', function(done) { - project.setMetadata(METADATA, function(err, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.setMetadata(METADATA); - }); - }); - }); - - describe('success', function() { - var apiResponse = { - projectId: ID - }; - - beforeEach(function() { - project.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); - }; - }); - - it('should execute callback with API response', function(done) { - project.setMetadata(METADATA, function(err, apiResponse_) { - assert.ifError(err); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should not require a callback', function() { - assert.doesNotThrow(function() { - project.setMetadata(METADATA); - }); - }); - }); - }); - - describe('makeReq_', function() { - it('should make the correct request to Resource', function(done) { - var expectedPathPrefix = '/' + project.id; - - var method = 'POST'; - var path = '/test'; - var query = { - a: 'b', - c: 'd' - }; - var body = { - a: 'b', - c: 'd' - }; - - project.resource.makeReq_ = function(method_, path_, query_, body_, cb) { - assert.strictEqual(method_, method); - assert.strictEqual(path_, expectedPathPrefix + path); - assert.strictEqual(query_, query); - assert.strictEqual(body_, body); - cb(); - }; - - project.makeReq_(method, path, query, body, done); - }); - }); }); From 72d6c3955f1196cbde79d155963f3cde829bd2a5 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 14:48:34 -0500 Subject: [PATCH 38/49] search.document --- test/search/document.js | 148 +++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 87 deletions(-) diff --git a/test/search/document.js b/test/search/document.js index c5c937e24c8..7ef166dc79f 100644 --- a/test/search/document.js +++ b/test/search/document.js @@ -17,40 +17,70 @@ 'use strict'; var assert = require('assert'); +var mockery = require('mockery'); +var nodeutil = require('util'); -var Document = require('../../lib/search/document.js'); -var Field = require('../../lib/search/field.js'); +var ServiceObject = require('../../lib/common/service-object.js'); var DOCUMENT_JSON = require('../testdata/search-document.json'); +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + describe('Document', function() { + var Document; var document; - var SEARCH_INSTANCE = { - projectId: 'project-id' - }; - - var INDEX_INSTANCE = { - search_: SEARCH_INSTANCE - }; + var INDEX_INSTANCE = {}; var ID = 'document-id'; + before(function() { + mockery.registerMock('../common/service-object.js', FakeServiceObject); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Document = require('../../lib/search/document.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + beforeEach(function() { document = new Document(INDEX_INSTANCE, ID); }); describe('instantiation', function() { - it('should localize the Search instance', function() { - assert.deepEqual(document.search_, SEARCH_INSTANCE); + it('should localize the id', function() { + assert.equal(document.id, ID); }); - it('should localize the Index instance', function() { - assert.deepEqual(document.index_, INDEX_INSTANCE); + it('should create an empty fields object', function() { + assert.deepEqual(document.fields, {}); }); - it('should localize the id', function() { - assert.equal(document.id, ID); + it('should inherit from ServiceObject', function() { + assert(document instanceof ServiceObject); + + var calledWith = document.calledWith_[0]; + + assert.strictEqual(calledWith.parent, INDEX_INSTANCE); + assert.strictEqual(calledWith.baseUrl, '/documents'); + assert.strictEqual(calledWith.id, ID); + assert.deepEqual(calledWith.methods, { + delete: true, + exists: true, + get: true + }); }); }); @@ -66,7 +96,7 @@ describe('Document', function() { it('should return a Field instance', function() { var field = document.addField(FIELD_NAME); - assert(field instanceof Field); + assert.strictEqual(field.constructor.name, 'Field'); }); it('should localize the Field instance', function() { @@ -76,56 +106,21 @@ describe('Document', function() { }); }); - describe('delete', function() { - it('should delete the document', function(done) { - document.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'DELETE'); - assert.equal(path, ''); - assert.strictEqual(query, null); - assert.strictEqual(body, null); - done(); - }; - - document.delete(assert.ifError); - }); - - it('should pass an error if one occurred', function(done) { - var error = new Error('Error.'); - var apiResponse = { a: 'b', c: 'd' }; - - document.makeReq_ = function(method, path, query, body, callback) { - callback(error, apiResponse); - }; - - document.delete(function(err, apiResponse_) { - assert.deepEqual(err, error); - assert.deepEqual(apiResponse_, apiResponse); - done(); - }); - }); - - it('should pass the API response to the callback', function(done) { - var apiResponse = { a: 'b', c: 'd' }; - - document.makeReq_ = function(method, path, query, body, callback) { - callback(null, apiResponse); + describe('create', function() { + it('should call Index.createDocument', function(done) { + document.parent.createDocument = function(document_, callback) { + assert.strictEqual(document_, document); + callback(); }; - document.delete(function(err, apiResponse_) { - assert.ifError(err); - assert.deepEqual(apiResponse_, apiResponse); - done(); - }); + document.create(done); }); }); describe('getMetadata', function() { - it('should get the document from the API', function(done) { - document.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/'); - assert.strictEqual(query, null); - assert.strictEqual(body, null); + it('should call ServiceObject.delete', function(done) { + FakeServiceObject.prototype.getMetadata = function() { + assert.strictEqual(this, document); done(); }; @@ -136,7 +131,7 @@ describe('Document', function() { var error = new Error('Error.'); var apiResponse = { a: 'b', c: 'd' }; - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(error, apiResponse); }; @@ -148,7 +143,7 @@ describe('Document', function() { }); it('should reset the localized fields', function(done) { - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, {}); }; @@ -164,7 +159,7 @@ describe('Document', function() { }); it('should create and localize Field instances', function(done) { - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, DOCUMENT_JSON); }; @@ -194,7 +189,7 @@ describe('Document', function() { }); it('should reset the localized rank', function(done) { - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, {}); }; @@ -208,7 +203,7 @@ describe('Document', function() { }); it('should localize a new rank', function(done) { - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, DOCUMENT_JSON); }; @@ -220,7 +215,7 @@ describe('Document', function() { }); it('should execute the callback with Document & api resp', function(done) { - document.makeReq_ = function(method, path, query, body, callback) { + FakeServiceObject.prototype.getMetadata = function(callback) { callback(null, DOCUMENT_JSON); }; @@ -280,25 +275,4 @@ describe('Document', function() { assert.equal(documentJson.rank, 8); }); }); - - describe('makeReq_', function() { - it('should call index instance makeReq_', function(done) { - var method = 'POST'; - var path = '/'; - var query = 'query'; - var body = 'body'; - var callback = 'callback'; - - document.index_.makeReq_ = function(m, p, q, b, c) { - assert.equal(m, method); - assert.equal(p, '/documents/' + ID + path); - assert.equal(q, query); - assert.equal(b, body); - assert.equal(c, callback); - done(); - }; - - document.makeReq_(method, path, query, body, callback); - }); - }); }); From f82171794b6617aec0c160d323d8d29662a3193c Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 15:01:27 -0500 Subject: [PATCH 39/49] search.index-class --- lib/search/index-class.js | 1 - test/search/index-class.js | 103 ++++++++++++++++--------------------- test/search/index.js | 6 +++ 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/lib/search/index-class.js b/lib/search/index-class.js index 6fb9d73cd32..c05af9a2516 100644 --- a/lib/search/index-class.js +++ b/lib/search/index-class.js @@ -70,7 +70,6 @@ function Index(search, id) { } }); - this.search_ = search; this.id = id; } diff --git a/test/search/index-class.js b/test/search/index-class.js index 0273e1d599f..aad0a7aa4d7 100644 --- a/test/search/index-class.js +++ b/test/search/index-class.js @@ -20,6 +20,9 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); var util = require('../../lib/common/util.js'); function FakeDocument() { @@ -27,6 +30,13 @@ function FakeDocument() { } FakeDocument.prototype.toJSON = util.noop; +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + var extended = false; var fakeStreamRouter = { extend: function(Class, methods) { @@ -48,8 +58,10 @@ describe('Index', function() { var ID = 'index-id'; before(function() { - mockery.registerMock('./document.js', FakeDocument); + mockery.registerMock('../common/service-object.js', FakeServiceObject); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); + mockery.registerMock('./document.js', FakeDocument); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false @@ -72,24 +84,21 @@ describe('Index', function() { assert(extended); // See `fakeStreamRouter.extend` }); - it('should localize the Search instance', function() { - assert.deepEqual(index.search_, SEARCH_INSTANCE); - }); + it('should inherit from ServiceObject', function() { + assert(index instanceof ServiceObject); - it('should localize the id', function() { - assert.equal(index.id, ID); - }); + var calledWith = index.calledWith_[0]; - it('should throw if an ID is not provided', function() { - assert.throws(function() { - new Index(SEARCH_INSTANCE); - }, /An ID is needed/); + assert.strictEqual(calledWith.parent, SEARCH_INSTANCE); + assert.strictEqual(calledWith.baseUrl, '/indexes'); + assert.strictEqual(calledWith.id, ID); + assert.deepEqual(calledWith.methods, {}); }); }); describe('createDocument', function() { beforeEach(function() { - index.makeReq_ = util.noop; + index.request = util.noop; }); it('should accept a Document object', function(done) { @@ -116,11 +125,10 @@ describe('Index', function() { var document = new FakeDocument(); document.toJSON = function() { return expectedDocumentJson; }; - index.makeReq_ = function(method, path, query, body) { - assert.equal(method, 'POST'); - assert.equal(path, '/documents'); - assert.strictEqual(query, null); - assert.deepEqual(body, expectedDocumentJson); + index.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/documents'); + assert.deepEqual(reqOpts.json, expectedDocumentJson); done(); }; @@ -131,7 +139,7 @@ describe('Index', function() { var error = new Error('Error.'); var apiResponse = { a: 'b', c: 'd' }; - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -148,7 +156,7 @@ describe('Index', function() { it('should execute callback with Document object', function(done) { var apiResponse = { a: 'b', c: 'd' }; - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -176,11 +184,9 @@ describe('Index', function() { it('should get document from the API', function(done) { var query = { a: 'b', c: 'd' }; - index.makeReq_ = function(method, path, q, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/documents'); - assert.deepEqual(q, query); - assert.strictEqual(body, null); + index.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/documents'); + assert.deepEqual(reqOpts.qs, query); done(); }; @@ -188,8 +194,8 @@ describe('Index', function() { }); it('should send empty query if only a callback is given', function(done) { - index.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + index.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -200,7 +206,7 @@ describe('Index', function() { var error = new Error('Error.'); var apiResponse = { a: 'b', c: 'd' }; - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -225,7 +231,7 @@ describe('Index', function() { return true; }; - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -251,7 +257,7 @@ describe('Index', function() { pageToken: apiResponse.nextPageToken }); - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -280,11 +286,9 @@ describe('Index', function() { query: 'completed=true' }; - index.makeReq_ = function(method, path, q, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/search'); - assert.deepEqual(q, query); - assert.strictEqual(body, null); + index.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/search'); + assert.deepEqual(reqOpts.qs, query); done(); }; @@ -294,8 +298,8 @@ describe('Index', function() { it('should build a query object from a string', function(done) { var query = 'completed=true'; - index.makeReq_ = function(method, path, q) { - assert.deepEqual(q, { query: query }); + index.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, { query: query }); done(); }; @@ -306,7 +310,7 @@ describe('Index', function() { var apiResponse = { a: 'b', c: 'd' }; var error = new Error('Error.'); - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -330,7 +334,7 @@ describe('Index', function() { return true; }; - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -356,7 +360,7 @@ describe('Index', function() { pageToken: apiResponse.nextPageToken }); - index.makeReq_ = function(method, path, query, body, callback) { + index.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -407,25 +411,4 @@ describe('Index', function() { assert.equal(document.rank, documentObject.rank); }); }); - - describe('makeReq_', function() { - it('should call search instance makeReq_', function(done) { - var method = 'POST'; - var path = '/'; - var query = 'query'; - var body = 'body'; - var callback = 'callback'; - - index.search_.makeReq_ = function(m, p, q, b, c) { - assert.equal(m, method); - assert.equal(p, '/indexes/' + ID + path); - assert.equal(q, query); - assert.equal(b, body); - assert.equal(c, callback); - done(); - }; - - index.makeReq_(method, path, query, body, callback); - }); - }); }); diff --git a/test/search/index.js b/test/search/index.js index 1e0cc9f7d1b..238d337634d 100644 --- a/test/search/index.js +++ b/test/search/index.js @@ -270,6 +270,12 @@ describe('Search', function() { }); describe('index', function() { + it('should throw if an ID is not provided', function() { + assert.throws(function() { + search.index(); + }, /An ID is needed/); + }); + it('should return a new Index object', function() { var indexId = 'index-id'; var index = search.index(indexId); From 92c7c8fc8ab28180031bdbb36d550805b2f4ff2e Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 15:06:27 -0500 Subject: [PATCH 40/49] search.index --- test/search/index.js | 101 +++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 66 deletions(-) diff --git a/test/search/index.js b/test/search/index.js index 238d337634d..cd0819bbc33 100644 --- a/test/search/index.js +++ b/test/search/index.js @@ -20,7 +20,10 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); var mockery = require('mockery'); +var nodeutil = require('util'); var prop = require('propprop'); + +var Service = require('../../lib/common/service.js'); var util = require('../../lib/common/util.js'); function FakeIndex() { @@ -52,6 +55,13 @@ var fakeUtil = extend({}, util, { } }); +function FakeService() { + this.calledWith_ = arguments; + Service.apply(this, arguments); +} + +nodeutil.inherits(FakeService, Service); + describe('Search', function() { var Search; var search; @@ -59,9 +69,11 @@ describe('Search', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('./index-class.js', FakeIndex); + mockery.registerMock('../common/service.js', FakeService); mockery.registerMock('../common/stream-router.js', fakeStreamRouter); mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock('./index-class.js', FakeIndex); + mockery.enable({ useCleanCache: true, warnOnUnregistered: false @@ -76,8 +88,6 @@ describe('Search', function() { }); beforeEach(function() { - makeAuthenticatedRequestFactoryOverride = null; - search = new Search({ projectId: PROJECT_ID }); @@ -107,34 +117,18 @@ describe('Search', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - it('should create an authenticated request function', function(done) { - var options = { - projectId: 'projectId', - credentials: 'credentials', - email: 'email', - keyFilename: 'keyFile' - }; + it('should inherit from Service', function() { + assert(search instanceof Service); - makeAuthenticatedRequestFactoryOverride = function(options_) { - assert.deepEqual(options_, { - credentials: options.credentials, - email: options.email, - keyFile: options.keyFilename, - scopes: [ - 'https://www.googleapis.com/auth/cloud-platform', - 'https://www.googleapis.com/auth/cloudsearch', - 'https://www.googleapis.com/auth/userinfo.email' - ] - }); - return done; - }; + var calledWith = search.calledWith_[0]; - var search = new Search(options); - search.makeAuthenticatedRequest_(); - }); - - it('should localize the projectId', function() { - assert.equal(search.projectId_, PROJECT_ID); + var baseUrl = 'https://cloudsearch.googleapis.com/v1'; + assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.deepEqual(calledWith.scopes, [ + 'https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/cloudsearch', + 'https://www.googleapis.com/auth/userinfo.email' + ]); }); }); @@ -142,11 +136,9 @@ describe('Search', function() { it('should get indexes from the API', function(done) { var query = { a: 'b', c: 'd' }; - search.makeReq_ = function(method, path, q, body) { - assert.equal(method, 'GET'); - assert.equal(path, '/indexes'); - assert.deepEqual(q, query); - assert.strictEqual(body, null); + search.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/indexes'); + assert.deepEqual(reqOpts.qs, query); done(); }; @@ -156,9 +148,9 @@ describe('Search', function() { it('should rename query.prefix to indexNamePrefix', function(done) { var query = { prefix: 'prefix' }; - search.makeReq_ = function(method, path, q) { - assert.equal(typeof q.prefix, 'undefined'); - assert.equal(q.indexNamePrefix, query.prefix); + search.request = function(reqOpts) { + assert.strictEqual(typeof reqOpts.qs.prefix, 'undefined'); + assert.strictEqual(reqOpts.qs.indexNamePrefix, query.prefix); done(); }; @@ -166,8 +158,8 @@ describe('Search', function() { }); it('should send empty query if only a callback is given', function(done) { - search.makeReq_ = function(method, path, query) { - assert.deepEqual(query, {}); + search.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); done(); }; @@ -178,7 +170,7 @@ describe('Search', function() { var error = new Error('Error.'); var apiResponse = { a: 'b', c: 'd' }; - search.makeReq_ = function(method, path, query, body, callback) { + search.request = function(reqOpts, callback) { callback(error, apiResponse); }; @@ -205,7 +197,7 @@ describe('Search', function() { return true; }; - search.makeReq_ = function(method, path, query, body, callback) { + search.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -236,7 +228,7 @@ describe('Search', function() { return {}; }; - search.makeReq_ = function(method, path, query, body, callback) { + search.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -256,7 +248,7 @@ describe('Search', function() { pageToken: apiResponse.nextPageToken }); - search.makeReq_ = function(method, path, query, body, callback) { + search.request = function(reqOpts, callback) { callback(null, apiResponse); }; @@ -283,27 +275,4 @@ describe('Search', function() { assert.deepEqual(index.calledWith_, [search, indexId]); }); }); - - describe('makeReq_', function() { - it('should make correct authenticated request', function(done) { - var method = 'POST'; - var path = '/'; - var query = 'query'; - var body = 'body'; - - search.makeAuthenticatedRequest_ = function(reqOpts, callback) { - assert.equal(reqOpts.method, method); - assert.equal(reqOpts.qs, query); - - var baseUri = 'https://cloudsearch.googleapis.com/v1/'; - assert.equal(reqOpts.uri, baseUri + 'projects/' + PROJECT_ID + path); - - assert.equal(reqOpts.json, body); - - callback(); - }; - - search.makeReq_(method, path, query, body, done); - }); - }); }); From f96590a8ec0a2ed492bde417a6c49c5559ac5a20 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 16:29:09 -0500 Subject: [PATCH 41/49] storage.acl --- test/storage/acl.js | 105 ++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 67 deletions(-) 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() { From 3d8bdb681d9eba398eb8fe594f8760668caaf6a8 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Nov 2015 17:08:16 -0500 Subject: [PATCH 42/49] storage.bucket --- test/storage/bucket.js | 482 +++++++++++++++-------------------------- test/storage/index.js | 6 + 2 files changed, 185 insertions(+), 303 deletions(-) diff --git a/test/storage/bucket.js b/test/storage/bucket.js index bf72eb23b74..b9116dac2b6 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; + } - it('should accept string or file input for destination', function(done) { - var destinations = [ - 'destination.txt', - bucket.file('destination.txt') - ]; + assert.strictEqual(name, destinationFileName); - async.each(destinations, function(destination, next) { - bucket.storage.makeAuthenticatedRequest_ = function(reqOpts) { - assert(reqOpts.uri.indexOf(bucket.name + '/o/destination.txt') > -1); - next(); + 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); + + 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(); }); }); @@ -492,20 +523,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 +543,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 +553,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 +562,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 +576,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 +592,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 +605,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 +627,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 +638,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 +697,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 +770,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 +1101,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/index.js b/test/storage/index.js index 315472ad0e2..f42c0c0a38c 100644 --- a/test/storage/index.js +++ b/test/storage/index.js @@ -96,6 +96,12 @@ describe('Storage', function() { }); 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); From 1b87f65abd04c7319c1d4e304b6260ee654953b7 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 3 Nov 2015 09:28:22 -0500 Subject: [PATCH 43/49] storage.file --- lib/storage/file.js | 332 +++++++++++------------ test/storage/bucket.js | 6 + test/storage/file.js | 598 ++++++++++++++++++++--------------------- 3 files changed, 468 insertions(+), 468 deletions(-) diff --git a/lib/storage/file.js b/lib/storage/file.js index bcbd0f9b4ae..5b196a09362 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -31,9 +31,9 @@ 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'); /** @@ -299,105 +299,6 @@ File.prototype.copy = function(destination, 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); - }); - }); -}; - /** * Create a readable stream to read the contents of the remote file. It can be * piped to a writable stream or listened to for 'data' events to read a file's @@ -664,7 +565,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, @@ -1310,71 +1211,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) { - 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); - }); -}; - /** * 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. @@ -1459,6 +1295,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. * diff --git a/test/storage/bucket.js b/test/storage/bucket.js index b9116dac2b6..0f3132b2161 100644 --- a/test/storage/bucket.js +++ b/test/storage/bucket.js @@ -504,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); }); 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); From f12965f486d2233dc9548be4ca2bb9e6494b79e6 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 3 Nov 2015 09:36:42 -0500 Subject: [PATCH 44/49] storage.index --- test/storage/index.js | 77 +++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/test/storage/index.js b/test/storage/index.js index f42c0c0a38c..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,8 +101,17 @@ 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' + ]); }); }); @@ -116,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(); }; @@ -128,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) { @@ -143,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); @@ -159,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) { @@ -172,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) { @@ -183,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) { @@ -193,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); @@ -224,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 @@ -237,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) { @@ -247,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) { @@ -256,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) { @@ -268,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) { @@ -285,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) { From 85331d24ced771d7ac3cc7fcbfd743619e8c0a9c Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 3 Nov 2015 17:06:30 -0500 Subject: [PATCH 45/49] common.serviceobject --- lib/common/service-object.js | 20 +- test/common/service-object.js | 534 ++++++++++++++++++++++++++++++++++ 2 files changed, 547 insertions(+), 7 deletions(-) create mode 100644 test/common/service-object.js diff --git a/lib/common/service-object.js b/lib/common/service-object.js index d48d67faccb..74793c43aae 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -66,9 +66,16 @@ function ServiceObject(config) { var allMethodNames = Object.keys(ServiceObject.prototype); allMethodNames .filter(function(methodName) { - return methodName !== 'request' && // All ServiceObjects need `request`. - !self[methodName] && // The ServiceObject didn't redefine the method. - !config.methods[methodName]; // This method isn't wanted. + return ( + // All ServiceObjects need `request`. + methodName !== 'request' && + + // The ServiceObject didn't redefine the method. + self[methodName] === ServiceObject.prototype[methodName] && + + // This method isn't wanted. + !config.methods[methodName] + ); }) .forEach(function(methodName) { self[methodName] = undefined; @@ -154,7 +161,7 @@ ServiceObject.prototype.exists = function(callback) { return; } - callback(true); + callback(null, false); }); }; @@ -181,12 +188,12 @@ ServiceObject.prototype.get = function(config, callback) { config = config || {}; - var autoCreate = config.autoCreate; + var autoCreate = config.autoCreate && is.fn(this.create); delete config.autoCreate; this.getMetadata(function(err, metadata) { if (err) { - if (err.code === 404 && autoCreate && self.create) { + if (err.code === 404 && autoCreate) { if (!is.empty(config)) { self.create(config, callback); } else { @@ -199,7 +206,6 @@ ServiceObject.prototype.get = function(config, callback) { return; } - self.metadata = metadata; callback(null, self, metadata); }); }; diff --git a/test/common/service-object.js b/test/common/service-object.js new file mode 100644 index 00000000000..3e9f5b0caa6 --- /dev/null +++ b/test/common/service-object.js @@ -0,0 +1,534 @@ +/** + * Copyright 2015 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. + */ + +'use strict'; + +var assert = require('assert'); +var extend = require('extend'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +describe('ServiceObject', function() { + var serviceObject; + var originalRequest = ServiceObject.prototype.request; + + var CONFIG = { + baseUrl: 'base-url', + parent: {}, + id: 'id', + createMethod: util.noop + }; + + beforeEach(function() { + ServiceObject.prototype.request = originalRequest; + serviceObject = new ServiceObject(CONFIG); + }); + + describe('instantiation', function() { + it('should create an empty metadata object', function() { + assert.deepEqual(serviceObject.metadata, {}); + }); + + it('should localize the baseUrl', function() { + assert.strictEqual(serviceObject.baseUrl, CONFIG.baseUrl); + }); + + it('should localize the parent instance', function() { + assert.strictEqual(serviceObject.parent, CONFIG.parent); + }); + + it('should localize the ID', function() { + assert.strictEqual(serviceObject.id, CONFIG.id); + }); + + it('should localize the createMethod', function() { + assert.strictEqual(serviceObject.createMethod, CONFIG.createMethod); + }); + + it('should clear out methods that are not asked for', function() { + var config = extend({}, CONFIG, { + methods: { + create: true + } + }); + + var serviceObject = new ServiceObject(config); + + assert.strictEqual(typeof serviceObject.create, 'function'); + assert.strictEqual(serviceObject.delete, undefined); + }); + }); + + describe('create', function() { + it('should call createMethod', function(done) { + var config = extend({}, CONFIG, { + createMethod: createMethod + }); + var options = {}; + + function createMethod(id, options_, callback) { + assert.strictEqual(id, config.id); + assert.strictEqual(options_, options); + callback(null, {}, {}); // calls done() + } + + var serviceObject = new ServiceObject(config); + serviceObject.create(options, done); + }); + + it('should not require options', function(done) { + var config = extend({}, CONFIG, { + createMethod: createMethod + }); + + function createMethod(id, options, callback) { + assert.strictEqual(id, config.id); + assert.strictEqual(typeof options, 'function'); + assert.strictEqual(callback, undefined); + options(null, {}, {}); // calls done() + } + + var serviceObject = new ServiceObject(config); + serviceObject.create(done); + }); + + it('should pass error to callback', function(done) { + var config = extend({}, CONFIG, { + createMethod: createMethod + }); + var options = {}; + + var error = new Error('Error.'); + var apiResponse = {}; + + function createMethod(id, options_, callback) { + callback(error, {}, apiResponse); + } + + var serviceObject = new ServiceObject(config); + serviceObject.create(options, function(err, instance, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(instance, null); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should return instance and apiResponse to callback', function(done) { + var config = extend({}, CONFIG, { + createMethod: createMethod + }); + var options = {}; + + var apiResponse = {}; + + function createMethod(id, options_, callback) { + callback(null, {}, apiResponse); + } + + var serviceObject = new ServiceObject(config); + serviceObject.create(options, function(err, instance_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(instance_, serviceObject); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should assign metadata', function(done) { + var config = extend({}, CONFIG, { + createMethod: createMethod + }); + var options = {}; + + var instance = { + metadata: {} + }; + + function createMethod(id, options_, callback) { + callback(null, instance, {}); + } + + var serviceObject = new ServiceObject(config); + serviceObject.create(options, function(err, instance_) { + assert.ifError(err); + assert.strictEqual(instance_.metadata, instance.metadata); + done(); + }); + }); + }); + + describe('delete', function() { + it('should make the correct request', function(done) { + var serviceObject; + + ServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(this, serviceObject); + assert.strictEqual(reqOpts.method, 'DELETE'); + assert.strictEqual(reqOpts.uri, ''); + + done(); + }; + + serviceObject = new ServiceObject(CONFIG); + serviceObject.delete(assert.ifError); + }); + + it('should execute callback with correct arguments', function(done) { + var error = new Error('Error.'); + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + + var serviceObject = new ServiceObject(CONFIG); + serviceObject.delete(function(err, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe('exists', function() { + it('should call get', function(done) { + serviceObject.get = function() { + done(); + }; + + serviceObject.exists(); + }); + + it('should execute callback with false if 404', function(done) { + serviceObject.get = function(callback) { + callback({ code: 404 }); + }; + + serviceObject.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + + it('should execute callback with error if not 404', function(done) { + var error = { code: 500 }; + + serviceObject.get = function(callback) { + callback(error); + }; + + serviceObject.exists(function(err, exists) { + assert.strictEqual(err, error); + assert.strictEqual(exists, undefined); + done(); + }); + }); + + it('should execute callback with false if no error', function(done) { + serviceObject.get = function(callback) { + callback(); + }; + + serviceObject.exists(function(err, exists) { + assert.ifError(err); + assert.strictEqual(exists, false); + done(); + }); + }); + }); + + describe('get', function() { + it('should get the metadata', function(done) { + serviceObject.getMetadata = function() { + done(); + }; + + serviceObject.get(assert.ifError); + }); + + it('should execute callback with error & metadata', function(done) { + var error = new Error('Error.'); + var metadata = {}; + + serviceObject.getMetadata = function(callback) { + callback(error, metadata); + }; + + serviceObject.get(function(err, instance, metadata_) { + assert.strictEqual(err, error); + assert.strictEqual(instance, null); + assert.strictEqual(metadata_, metadata); + + done(); + }); + }); + + it('should execute callback with instance & metadata', function(done) { + var metadata = {}; + + serviceObject.getMetadata = function(callback) { + callback(null, metadata); + }; + + serviceObject.get(function(err, instance, metadata_) { + assert.ifError(err); + + assert.strictEqual(instance, serviceObject); + assert.strictEqual(metadata_, metadata); + + done(); + }); + }); + + describe('autoCreate', function() { + var AUTO_CREATE_CONFIG; + + var ERROR = { code: 404 }; + var METADATA = {}; + + beforeEach(function() { + AUTO_CREATE_CONFIG = { + autoCreate: true + }; + + serviceObject.getMetadata = function(callback) { + callback(ERROR, METADATA); + }; + }); + + it('should not auto create if there is no create method', function(done) { + serviceObject.create = undefined; + + serviceObject.get(AUTO_CREATE_CONFIG, function(err) { + assert.strictEqual(err, ERROR); + done(); + }); + }); + + it('should pass config to create if it was provided', function(done) { + var config = extend({}, AUTO_CREATE_CONFIG, { + maxResults: 5 + }); + + serviceObject.create = function(config_) { + assert.strictEqual(config_, config); + done(); + }; + + serviceObject.get(config, assert.ifError); + }); + + it('should pass only a callback to create if no config', function(done) { + serviceObject.create = function(callback) { + callback(); // done() + }; + + serviceObject.get(AUTO_CREATE_CONFIG, done); + }); + }); + }); + + describe('getMetadata', function() { + it('should make the correct request', function(done) { + ServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(this, serviceObject); + assert.strictEqual(reqOpts.uri, ''); + done(); + }; + + serviceObject.getMetadata(); + }); + + it('should execute callback with error & apiResponse', function(done) { + var error = new Error('Error.'); + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + + serviceObject.getMetadata(function(err, metadata, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(metadata, null); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should update metadata', function(done) { + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + + serviceObject.getMetadata(function(err) { + assert.ifError(err); + assert.strictEqual(serviceObject.metadata, apiResponse); + done(); + }); + }); + + it('should execute callback with metadata & API response', function(done) { + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + + serviceObject.getMetadata(function(err, metadata, apiResponse_) { + assert.ifError(err); + assert.strictEqual(metadata, apiResponse); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe('setMetadata', function() { + it('should make the correct request', function(done) { + var metadata = {}; + + ServiceObject.prototype.request = function(reqOpts) { + assert.strictEqual(this, serviceObject); + assert.strictEqual(reqOpts.method, 'PATCH'); + assert.strictEqual(reqOpts.uri, ''); + assert.strictEqual(reqOpts.json, metadata); + done(); + }; + + serviceObject.setMetadata(metadata); + }); + + it('should execute callback with error & apiResponse', function(done) { + var error = new Error('Error.'); + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + + serviceObject.setMetadata({}, function(err, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should update metadata', function(done) { + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + + serviceObject.setMetadata({}, function(err) { + assert.ifError(err); + assert.strictEqual(serviceObject.metadata, apiResponse); + done(); + }); + }); + + it('should execute callback with metadata & API response', function(done) { + var apiResponse = {}; + + ServiceObject.prototype.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + + serviceObject.setMetadata({}, function(err, apiResponse_) { + assert.ifError(err); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + }); + + describe.only('request', function() { + var reqOpts; + + beforeEach(function() { + reqOpts = { + uri: 'uri' + }; + }); + + it('should send the right request to the parent', function(done) { + serviceObject.parent.request = function(reqOpts_, callback) { + assert.strictEqual(reqOpts_, reqOpts); + callback(); // done() + }; + + serviceObject.request(reqOpts, done); + }); + + it('should compose the correct uri', function(done) { + var expectedUri = [ + serviceObject.baseUrl, + serviceObject.id, + reqOpts.uri + ].join('/'); + + serviceObject.parent.request = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + serviceObject.request(reqOpts, assert.ifError); + }); + + it('should remove empty components', function(done) { + var reqOpts = { + uri: '' + }; + + var expectedUri = [ + serviceObject.baseUrl, + serviceObject.id + // reqOpts.uri (reqOpts.uri is an empty string, so it should be removed) + ].join('/'); + + serviceObject.parent.request = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + serviceObject.request(reqOpts, assert.ifError); + }); + + it('should trim slashes', function(done) { + var reqOpts = { + uri: '//1/2//' + }; + + var expectedUri = [ + serviceObject.baseUrl, + serviceObject.id, + '1/2' + ].join('/'); + + serviceObject.parent.request = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + serviceObject.request(reqOpts, assert.ifError); + }); + }); +}); From 4f0d3b1e9ce3f53452074695691fbc23307ce08c Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 5 Nov 2015 13:44:28 -0500 Subject: [PATCH 46/49] common.service --- lib/common/service.js | 4 +- test/common/service-object.js | 2 +- test/common/service.js | 244 ++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 test/common/service.js diff --git a/lib/common/service.js b/lib/common/service.js index 5a54160562f..68dccf8b9c8 100644 --- a/lib/common/service.js +++ b/lib/common/service.js @@ -39,9 +39,9 @@ var util = require('./util.js'); */ function Service(config, options) { this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({ + scopes: config.scopes, credentials: options.credentials, keyFile: options.keyFilename, - scopes: config.scopes, email: options.email }); @@ -84,8 +84,6 @@ Service.prototype.request = function(reqOpts, callback) { // Good: https://.../projects:list .replace(/\/:/g, ':'); - // console.log(reqOpts) - this.makeAuthenticatedRequest(reqOpts, callback); }; diff --git a/test/common/service-object.js b/test/common/service-object.js index 3e9f5b0caa6..d33ce81525d 100644 --- a/test/common/service-object.js +++ b/test/common/service-object.js @@ -460,7 +460,7 @@ describe('ServiceObject', function() { }); }); - describe.only('request', function() { + describe('request', function() { var reqOpts; beforeEach(function() { diff --git a/test/common/service.js b/test/common/service.js new file mode 100644 index 00000000000..de6e8443666 --- /dev/null +++ b/test/common/service.js @@ -0,0 +1,244 @@ +/** + * Copyright 2015 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. + */ + +'use strict'; + +var assert = require('assert'); +var extend = require('extend'); +var mockery = require('mockery'); + +var util = require('../../lib/common/util.js'); + +var makeAuthenticatedRequestFactoryCache = util.makeAuthenticatedRequestFactory; +var makeAuthenticatedRequestFactoryOverride; +util.makeAuthenticatedRequestFactory = function() { + if (makeAuthenticatedRequestFactoryOverride) { + return makeAuthenticatedRequestFactoryOverride.apply(this, arguments); + } else { + return makeAuthenticatedRequestFactoryCache.apply(this, arguments); + } +}; + +describe('ServiceObject', function() { + var Service; + var service; + + var CONFIG = { + scopes: [], + baseUrl: 'base-url', + projectIdRequired: false + }; + + var OPTIONS = { + credentials: {}, + keyFile: {}, + email: 'email', + projectId: 'project-id', + }; + + before(function() { + mockery.registerMock('./util.js', util); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Service = require('../../lib/common/service.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + + beforeEach(function() { + makeAuthenticatedRequestFactoryOverride = null; + service = new Service(CONFIG, OPTIONS); + }); + + describe('instantiation', function() { + it('should create an authenticated request factory', function() { + var authenticatedRequest = {}; + + makeAuthenticatedRequestFactoryOverride = function(config) { + assert.strictEqual(config.scopes, CONFIG.scopes); + assert.strictEqual(config.credentials, OPTIONS.credentials); + assert.strictEqual(config.keyFile, OPTIONS.keyFilename); + assert.strictEqual(config.email, OPTIONS.email); + + return authenticatedRequest; + }; + + var svc = new Service(CONFIG, OPTIONS); + assert.strictEqual(svc.makeAuthenticatedRequest, authenticatedRequest); + }); + + it('should localize the authClient', function() { + var authClient = {}; + + makeAuthenticatedRequestFactoryOverride = function() { + return { + authClient: authClient + }; + }; + + var service = new Service(CONFIG, OPTIONS); + assert.strictEqual(service.authClient, authClient); + }); + + it('should localize the baseUrl', function() { + assert.strictEqual(service.baseUrl, CONFIG.baseUrl); + }); + + it('should localize the getCredentials method', function() { + function getCredentials() {} + + makeAuthenticatedRequestFactoryOverride = function() { + return { + authClient: {}, + getCredentials: getCredentials + }; + }; + + var service = new Service(CONFIG, OPTIONS); + assert.strictEqual(service.getCredentials, getCredentials); + }); + + it('should localize the projectId', function() { + assert.strictEqual(service.projectId, OPTIONS.projectId); + }); + + it('should localize the projectIdRequired', function() { + assert.strictEqual(service.projectIdRequired, CONFIG.projectIdRequired); + }); + + it('should default projectIdRequired to true', function() { + var service = new Service({}, OPTIONS); + assert.strictEqual(service.projectIdRequired, true); + }); + }); + + describe('request', function() { + var reqOpts; + + beforeEach(function() { + reqOpts = { + uri: 'uri' + }; + }); + + it('should send the right request to the parent', function(done) { + service.makeAuthenticatedRequest = function(reqOpts_, callback) { + assert.strictEqual(reqOpts_, reqOpts); + callback(); // done() + }; + + service.request(reqOpts, done); + }); + + it('should compose the correct uri', function(done) { + var expectedUri = [ + service.baseUrl, + reqOpts.uri + ].join('/'); + + service.makeAuthenticatedRequest = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + service.request(reqOpts, assert.ifError); + }); + + it('should trim slashes', function(done) { + var reqOpts = { + uri: '//1/2//' + }; + + var expectedUri = [ + service.baseUrl, + '1/2' + ].join('/'); + + service.makeAuthenticatedRequest = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + service.request(reqOpts, assert.ifError); + }); + + it('should replace path/:subpath with path:subpath', function(done) { + var reqOpts = { + uri: ':test' + }; + + var expectedUri = service.baseUrl + reqOpts.uri; + + service.makeAuthenticatedRequest = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + done(); + }; + + service.request(reqOpts, assert.ifError); + }); + + describe('projectIdRequired', function() { + describe('false', function() { + it('should include the projectId', function(done) { + var config = extend({}, CONFIG, { projectIdRequired: false }); + var service = new Service(config, OPTIONS); + + var expectedUri = [ + service.baseUrl, + reqOpts.uri + ].join('/'); + + service.makeAuthenticatedRequest = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + + done(); + }; + + service.request(reqOpts, assert.ifError); + }); + }); + + describe('true', function() { + it('should not include the projectId', function(done) { + var config = extend({}, CONFIG, { projectIdRequired: true }); + var service = new Service(config, OPTIONS); + + var expectedUri = [ + service.baseUrl, + 'projects', + service.projectId, + reqOpts.uri + ].join('/'); + + service.makeAuthenticatedRequest = function(reqOpts_) { + assert.strictEqual(reqOpts_.uri, expectedUri); + + done(); + }; + + service.request(reqOpts, assert.ifError); + }); + }); + }); + }); +}); From 70151fb9b87452719fba8b28feb5f2487b1609ca Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 5 Nov 2015 14:00:52 -0500 Subject: [PATCH 47/49] fix examples --- lib/bigquery/dataset.js | 14 +++++++++----- lib/dns/zone.js | 1 + lib/storage/bucket.js | 3 ++- lib/storage/file.js | 43 ++++++++++++++++++----------------------- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 452abfdf2cb..4dc6df138ad 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -53,6 +53,15 @@ var Table = require('./table.js'); * * @alias module:bigquery/dataset * @constructor + * + * @example + * var gcloud = require('gcloud'); + * + * var bigquery = gcloud.bigquery({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * var dataset = bigquery.dataset('institutions'); */ function Dataset(bigQuery, id) { var methods = { @@ -178,11 +187,6 @@ nodeutil.inherits(Dataset, ServiceObject); * schema: 'UNITID,INSTNM,ADDR,CITY,STABBR,ZIP,FIPS,OBEREG,CHFNM,...' * }; * - * var bigquery = gcloud.bigquery({ - * projectId: 'grape-spaceship-123' - * }); - * var dataset = bigquery.dataset('institutions'); - * * dataset.createTable(tableConfig, function(err, table, apiResponse) {}); */ Dataset.prototype.createTable = function(id, options, callback) { diff --git a/lib/dns/zone.js b/lib/dns/zone.js index cf0813fda8d..8bfd091579b 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -79,6 +79,7 @@ function Zone(dns, name) { * * @example * var config = { + * dnsName: 'example.com.', * // ... * }; * diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js index d3cb28a1e33..ea7c1e32fca 100644 --- a/lib/storage/bucket.js +++ b/lib/storage/bucket.js @@ -88,10 +88,11 @@ 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) { diff --git a/lib/storage/file.js b/lib/storage/file.js index 5b196a09362..d58c94ec2cb 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -90,6 +90,18 @@ 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) { var methods = { @@ -157,13 +169,7 @@ 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) {}); @@ -338,8 +344,7 @@ File.prototype.copy = 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() @@ -549,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. @@ -603,6 +605,8 @@ File.prototype.createResumableUpload = function(metadata, callback) { * completely, however this is **not recommended**. * * @example + * var fs = require('fs'); + * * //- * //

Uploading a File

* // @@ -610,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. @@ -623,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. @@ -646,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: { From fe2a9a56d3b39662116c99cacb7e53b1259ad105 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 5 Nov 2015 15:39:19 -0500 Subject: [PATCH 48/49] docs + change.create --- lib/bigquery/dataset.js | 5 +++ lib/bigquery/index.js | 4 +- lib/bigquery/table.js | 8 +--- lib/compute/disk.js | 4 +- lib/compute/index.js | 6 +-- lib/compute/zone.js | 4 +- lib/dns/change.js | 44 ++++++++++++++++++++- lib/dns/index.js | 5 ++- lib/dns/zone.js | 6 --- system-test/dns.js | 4 +- test/dns/change.js | 85 ++++++++++++++++++++++++++++++++++------- test/dns/zone.js | 6 --- 12 files changed, 137 insertions(+), 44 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index 4dc6df138ad..b21339be16a 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -192,6 +192,11 @@ nodeutil.inherits(Dataset, ServiceObject); Dataset.prototype.createTable = function(id, options, callback) { var self = this; + if (is.fn(options)) { + callback = options; + options = {}; + } + extend(true, options, { tableReference: { datasetId: this.id, diff --git a/lib/bigquery/index.js b/lib/bigquery/index.js index c071e22db36..6a8cc5d5aba 100644 --- a/lib/bigquery/index.js +++ b/lib/bigquery/index.js @@ -144,7 +144,7 @@ BigQuery.prototype.createDataset = function(id, callback) { }; /** - * Create a reference to an existing dataset. + * Create a reference to a dataset. * * @param {string} id - ID of the dataset. * @return {module:bigquery/dataset} @@ -363,7 +363,7 @@ BigQuery.prototype.getJobs = function(options, callback) { }; /** - * Create a reference to an existing Job. + * Create a reference to an existing job. * * @param {string} id - ID of the job. * @return {module:bigquery/job} diff --git a/lib/bigquery/table.js b/lib/bigquery/table.js index 43994c107d6..be0f1181c12 100644 --- a/lib/bigquery/table.js +++ b/lib/bigquery/table.js @@ -83,14 +83,10 @@ function Table(dataset, id) { /** * Create a table. * - * @param {object} config - See {module:bigquery/dataset#createTable}. + * @param {object=} options - See {module:bigquery/dataset#createTable}. * * @example - * var config = { - * // ... - * }; - * - * table.create(config, function(err, table, apiResponse) { + * table.create(function(err, table, apiResponse) { * if (!err) { * // The table was created successfully. * } diff --git a/lib/compute/disk.js b/lib/compute/disk.js index 58729bae5c4..8647479471e 100644 --- a/lib/compute/disk.js +++ b/lib/compute/disk.js @@ -74,7 +74,7 @@ function Disk(zone, name) { /** * Create a persistent disk. * - * @param {object} config - See {module:compute#createDisk}. + * @param {object} config - See {module:compute/zone#createDisk}. * * @example * var config = { @@ -271,7 +271,7 @@ Disk.prototype.delete = function(callback) { * * @resource [Snapshots Overview]{@link https://cloud.google.com/compute/docs/disks/persistent-disks#snapshots} * - * @param {string} name - Name of the existing snapshot. + * @param {string} name - Name of the snapshot. * @return {module:compute/snapshot} * * @example diff --git a/lib/compute/index.js b/lib/compute/index.js index ff14a46f474..daa7003d427 100644 --- a/lib/compute/index.js +++ b/lib/compute/index.js @@ -305,11 +305,11 @@ Compute.prototype.createNetwork = function(name, config, callback) { * * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} * - * @param {string} name - Name of the existing firewall. + * @param {string} name - Name of the firewall. * @return {module:compute/firewall} * * @example - * var firewall = gce.firewall('existing-firewall'); + * var firewall = gce.firewall('firewall-name'); */ Compute.prototype.firewall = function(name) { return new Firewall(this, name); @@ -1287,7 +1287,7 @@ Compute.prototype.getZones = function(options, callback) { * * @resource [Networks Overview]{@link https://cloud.google.com/compute/docs/networking#networks} * - * @param {string} name - Name of the existing network. + * @param {string} name - Name of the network. * @return {module:compute/network} * * @example diff --git a/lib/compute/zone.js b/lib/compute/zone.js index d8f25178a25..9fe9c169da9 100644 --- a/lib/compute/zone.js +++ b/lib/compute/zone.js @@ -425,7 +425,7 @@ Zone.prototype.createVM = function(name, config, callback) { * * @resource [Disks Overview]{@link https://cloud.google.com/compute/docs/disks} * - * @param {string} name - Name of the existing disk. + * @param {string} name - Name of the disk. * @return {module:compute/disk} * * @example @@ -767,7 +767,7 @@ Zone.prototype.operation = function(name) { * * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} * - * @param {string} name - Name of the existing virtual machine. + * @param {string} name - Name of the virtual machine. * @return {module:compute/vm} * * @example diff --git a/lib/dns/change.js b/lib/dns/change.js index 85062b5b76f..6872290e6a6 100644 --- a/lib/dns/change.js +++ b/lib/dns/change.js @@ -64,6 +64,15 @@ function Change(zone, id) { /** * Get a change 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 * change.get(function(err, change, apiResponse) { * // `change.metadata` has been populated. @@ -102,11 +111,44 @@ function Change(zone, id) { parent: zone, baseUrl: '/changes', id: id, - createMethod: zone.createChange.bind(zone), methods: methods }); } nodeutil.inherits(Change, ServiceObject); +/** + * Create a change. + * + * @param {object} config - See {module:dns/zone#createChange}. + * + * @example + * var config = { + * add: { + * // ... + * } + * }; + * + * change.create(config, function(err, change, apiResponse) { + * if (!err) { + * // The change was created successfully. + * } + * }); + */ +Change.prototype.create = function(config, callback) { + var self = this; + + this.parent.createChange(config, function(err, change, apiResponse) { + if (err) { + callback(err, null, apiResponse); + return; + } + + self.id = change.id; + self.metadata = change.metadata; + + callback(null, self, apiResponse); + }); +}; + module.exports = Change; diff --git a/lib/dns/index.js b/lib/dns/index.js index 908c4fb6b0c..df66b42b32a 100644 --- a/lib/dns/index.js +++ b/lib/dns/index.js @@ -20,6 +20,7 @@ 'use strict'; +var arrify = require('arrify'); var extend = require('extend'); var is = require('is'); var nodeutil = require('util'); @@ -181,7 +182,7 @@ DNS.prototype.getZones = function(query, callback) { return; } - var zones = (resp.managedZones || []).map(function(zone) { + var zones = arrify(resp.managedZones).map(function(zone) { var zoneInstance = self.zone(zone.name); zoneInstance.metadata = zone; return zoneInstance; @@ -200,7 +201,7 @@ DNS.prototype.getZones = function(query, callback) { }; /** - * Create a zone object representing an existing managed zone. + * Create a zone object representing a managed zone. * * @throws {error} If a zone name is not provided. * diff --git a/lib/dns/zone.js b/lib/dns/zone.js index 8bfd091579b..c131e42a7ae 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -173,8 +173,6 @@ Zone.prototype.addRecords = function(records, callback) { /** * Create a reference to an existing change object in this zone. * - * @throws {error} If an id is not provided. - * * @param {string} id - The change id. * @return {module:dns/change} * @@ -182,10 +180,6 @@ Zone.prototype.addRecords = function(records, callback) { * var change = zone.change('change-id'); */ Zone.prototype.change = function(id) { - if (!id) { - throw new Error('A change id is required.'); - } - return new Change(this, id); }; diff --git a/system-test/dns.js b/system-test/dns.js index 66e5f3268c8..0550f8eca81 100644 --- a/system-test/dns.js +++ b/system-test/dns.js @@ -251,7 +251,9 @@ var DNS_DOMAIN = process.env.GCLOUD_TESTS_DNS_DOMAIN; data: '10 0 5222 127.0.0.1.' }); - ZONE.createChange({ add: record }, function(err, change) { + var change = ZONE.change(); + + change.create({ add: record }, function(err) { assert.ifError(err); var addition = change.metadata.additions[0]; diff --git a/test/dns/change.js b/test/dns/change.js index a26aba89965..b8e7a57cdac 100644 --- a/test/dns/change.js +++ b/test/dns/change.js @@ -17,7 +17,6 @@ 'use strict'; var assert = require('assert'); -var extend = require('extend'); var mockery = require('mockery'); var nodeutil = require('util'); @@ -63,22 +62,12 @@ describe('Change', function() { }); describe('instantiation', function() { - it('should inherit from ServiceObject', function(done) { - var zoneInstance = extend({}, ZONE, { - createChange: { - bind: function(context) { - assert.strictEqual(context, zoneInstance); - done(); - } - } - }); - - var change = new Change(zoneInstance, CHANGE_ID); + it('should inherit from ServiceObject', function() { assert(change instanceof ServiceObject); var calledWith = change.calledWith_[0]; - assert.strictEqual(calledWith.parent, zoneInstance); + assert.strictEqual(calledWith.parent, ZONE); assert.strictEqual(calledWith.baseUrl, '/changes'); assert.strictEqual(calledWith.id, CHANGE_ID); assert.deepEqual(calledWith.methods, { @@ -88,4 +77,74 @@ describe('Change', function() { }); }); }); + + describe('change', function() { + it('should call the parent change method', function(done) { + var config = {}; + + change.parent.createChange = function(config_) { + assert.strictEqual(config, config_); + done(); + }; + + change.create(config, assert.ifError); + }); + + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = {}; + + beforeEach(function() { + change.parent.createChange = function(config, callback) { + callback(error, null, apiResponse); + }; + }); + + it('should execute callback with error & apiResponse', function(done) { + change.create({}, function(err, change, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(change, null); + assert.strictEqual(apiResponse_, apiResponse); + + done(); + }); + }); + }); + + describe('success', function() { + var changeInstance = { + id: 'id', + metadata: {} + }; + var apiResponse = {}; + + beforeEach(function() { + change.parent.createChange = function(config, callback) { + callback(null, changeInstance, apiResponse); + }; + }); + + it('should execute callback with self & API response', function(done) { + change.create({}, function(err, change_, apiResponse_) { + assert.ifError(err); + + assert.strictEqual(change_, change); + assert.strictEqual(apiResponse_, apiResponse); + + done(); + }); + }); + + it('should assign the ID and metadata from the change', function(done) { + change.create({}, function(err, change_) { + assert.ifError(err); + + assert.strictEqual(change_.id, changeInstance.id); + assert.strictEqual(change_.metadata, changeInstance.metadata); + + done(); + }); + }); + }); + }); }); diff --git a/test/dns/zone.js b/test/dns/zone.js index 9af1672a136..134435b40df 100644 --- a/test/dns/zone.js +++ b/test/dns/zone.js @@ -163,12 +163,6 @@ describe('Zone', function() { }); describe('change', function() { - it('should throw if an ID is not provided', function() { - assert.throws(function() { - zone.change(); - }, /A change id is required/); - }); - it('should return a Change object', function() { var changeId = 'change-id'; From 01ce6101ae0304b5a5426f4cfa9a2ae6099d6c5f Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Thu, 5 Nov 2015 15:56:40 -0500 Subject: [PATCH 49/49] doc clean up --- lib/bigquery/dataset.js | 2 +- lib/compute/network.js | 2 +- lib/compute/region.js | 2 +- lib/dns/zone.js | 2 +- lib/pubsub/index.js | 14 ++++++-------- lib/pubsub/subscription.js | 5 ++++- lib/pubsub/topic.js | 11 +++++------ lib/resource/index.js | 4 ++-- lib/resource/project.js | 2 +- lib/search/document.js | 11 ----------- lib/storage/index.js | 2 +- 11 files changed, 23 insertions(+), 34 deletions(-) diff --git a/lib/bigquery/dataset.js b/lib/bigquery/dataset.js index b21339be16a..2f33adbdf98 100644 --- a/lib/bigquery/dataset.js +++ b/lib/bigquery/dataset.js @@ -366,7 +366,7 @@ Dataset.prototype.query = function(options, callback) { }; /** - * Return a new instance of reference to an existing Table object. + * Create a Table object. * * @param {string} id - The ID of the table. * @return {module:bigquery/table} diff --git a/lib/compute/network.js b/lib/compute/network.js index db7a922ddad..541a26c406d 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -254,7 +254,7 @@ Network.prototype.delete = function(callback) { * * @resource [Firewalls Overview]{@link https://cloud.google.com/compute/docs/networking#firewalls} * - * @param {string} name - Name of the existing firewall. + * @param {string} name - Name of the firewall. * * @example * var firewall = network.firewall('firewall-name'); diff --git a/lib/compute/region.js b/lib/compute/region.js index 80ba730f7f0..fb6b90d8319 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -132,7 +132,7 @@ nodeutil.inherits(Region, ServiceObject); * * @resource [Instances and Networks]{@link https://cloud.google.com/compute/docs/instances-and-network} * - * @param {string} name - Name of the existing address. + * @param {string} name - Name of the address. * @return {module:compute/address} * * @example diff --git a/lib/dns/zone.js b/lib/dns/zone.js index c131e42a7ae..5b2060b7aa3 100644 --- a/lib/dns/zone.js +++ b/lib/dns/zone.js @@ -171,7 +171,7 @@ Zone.prototype.addRecords = function(records, callback) { }; /** - * Create a reference to an existing change object in this zone. + * Create a reference to a change object in this zone. * * @param {string} id - The change id. * @return {module:dns/change} diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js index 0e0303bc76c..672f2d1e7c8 100644 --- a/lib/pubsub/index.js +++ b/lib/pubsub/index.js @@ -471,10 +471,9 @@ PubSub.prototype.subscribe = function(topic, subName, options, callback) { }; /** - * Create a Subscription object in reference to an existing subscription. This - * command by itself will not run any API requests. You will receive a - * {@linkcode module:pubsub/subscription} object, which will allow you to - * interact with your subscription. + * Create a Subscription object. This command by itself will not run any API + * requests. You will receive a {@linkcode module:pubsub/subscription} object, + * which will allow you to interact with a subscription. * * @throws {Error} If a name is not provided. * @@ -487,7 +486,7 @@ PubSub.prototype.subscribe = function(topic, subName, options, callback) { * @return {module:pubsub/subscription} * * @example - * var subscription = pubsub.subscription('my-existing-subscription'); + * var subscription = pubsub.subscription('my-subscription'); * * // Register a listener for `message` events. * subscription.on('message', function(message) { @@ -508,8 +507,7 @@ PubSub.prototype.subscription = function(name, options) { }; /** - * Create a Topic object to reference an existing topic. See - * {module:pubsub/createTopic} to create a topic. + * Create a Topic object. See {module:pubsub/createTopic} to create a topic. * * @throws {Error} If a name is not provided. * @@ -517,7 +515,7 @@ PubSub.prototype.subscription = function(name, options) { * @return {module:pubsub/topic} * * @example - * var topic = pubsub.topic('my-existing-topic'); + * var topic = pubsub.topic('my-topic'); * * topic.publish({ * data: 'New message!' diff --git a/lib/pubsub/subscription.js b/lib/pubsub/subscription.js index a15d3b79bab..ec0a146a794 100644 --- a/lib/pubsub/subscription.js +++ b/lib/pubsub/subscription.js @@ -110,7 +110,7 @@ var util = require('../common/util.js'); * // From {@linkcode module:pubsub/topic#subscription}: * //- * var topic = pubsub.topic('my-topic'); - * var subscription = topic.subscription('my-existing-subscription'); + * var subscription = topic.subscription('my-subscription'); * // `subscription` is a Subscription object. * * //- @@ -168,6 +168,9 @@ function Subscription(pubsub, options) { * normally required for the `create` method must be contained within this * object as well. * + * **`autoCreate` is only available if you accessed this object + * through {module:pubsub/topic#subscription}.** + * * @param {options=} options - Configuration object. * @param {boolean} options.autoCreate - Automatically create the object if * it does not exist. Default: `false` diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js index bf621758eca..81508fa272c 100644 --- a/lib/pubsub/topic.js +++ b/lib/pubsub/topic.js @@ -68,7 +68,7 @@ function Topic(pubsub, name) { /** * Create a topic. * - * @param {object} config - See {module:pubsub#createTopic}. + * @param {object=} config - See {module:pubsub#createTopic}. * * @example * topic.create(function(err, topic, apiResponse) { @@ -398,10 +398,9 @@ Topic.prototype.subscribe = function(subName, options, callback) { }; /** - * Create a Subscription object in reference to an existing subscription. This - * command by itself will not run any API requests. You will receive a - * {@linkcode module:pubsub/subscription} object, which will allow you to - * interact with your subscription. + * Create a Subscription object. This command by itself will not run any API + * requests. You will receive a {@linkcode module:pubsub/subscription} object, + * which will allow you to interact with a subscription. * * @param {string} name - Name of the subscription. * @param {object=} options - Configuration object. @@ -412,7 +411,7 @@ Topic.prototype.subscribe = function(subName, options, callback) { * @return {module:pubsub/subscription} * * @example - * var subscription = topic.subscription('my-existing-subscription'); + * var subscription = topic.subscription('my-subscription'); * * // Register a listener for `message` events. * subscription.on('message', function(message) { diff --git a/lib/resource/index.js b/lib/resource/index.js index 28d6cd3737f..a9e7e56b9a4 100644 --- a/lib/resource/index.js +++ b/lib/resource/index.js @@ -244,8 +244,8 @@ Resource.prototype.getProjects = function(options, callback) { }; /** - * Create a Project object to reference an existing project. See - * {module:resoucemanager/createProject} to create a project. + * Create a Project object. See {module:resoucemanager/createProject} to create + * a project. * * @throws {Error} If an ID is not provided. * diff --git a/lib/resource/project.js b/lib/resource/project.js index ed090c14b59..6be7c454121 100644 --- a/lib/resource/project.js +++ b/lib/resource/project.js @@ -71,7 +71,7 @@ function Project(resource, id) { /** * Create a project. * - * @param {object} config - See {module:resource#createProject}. + * @param {object=} config - See {module:resource#createProject}. * * @example * project.create(function(err, zone, apiResponse) { diff --git a/lib/search/document.js b/lib/search/document.js index 72500a4b8e2..9abdc731d64 100644 --- a/lib/search/document.js +++ b/lib/search/document.js @@ -91,15 +91,6 @@ function Document(index, id) { /** * Get a document 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 * document.get(function(err, document, apiResponse) { * // `document.fields` has been populated. @@ -146,8 +137,6 @@ Document.prototype.addField = function(name) { /** * Create a document. * - * @param {object} config - See {module:search/index#createDocument}. - * * @example * document.create(function(err, document, apiResponse) { * if (!err) { diff --git a/lib/storage/index.js b/lib/storage/index.js index 49b79c886a7..62247da20b3 100644 --- a/lib/storage/index.js +++ b/lib/storage/index.js @@ -155,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