From b8a00b21ad35151ca38dc21fa93dcdc8992540e7 Mon Sep 17 00:00:00 2001 From: Alexander Fenster Date: Mon, 20 Nov 2017 13:00:18 -0800 Subject: [PATCH] Excise common. (#2751) --- packages/common/package.json | 73 - packages/common/src/index.js | 51 - packages/common/src/logger.js | 78 - packages/common/src/operation.js | 192 --- packages/common/src/paginator.js | 255 ---- packages/common/src/service-object.js | 388 ----- packages/common/src/service.js | 192 --- packages/common/src/util.js | 807 ----------- packages/common/test/index.js | 53 - packages/common/test/logger.js | 93 -- packages/common/test/operation.js | 395 ----- packages/common/test/paginator.js | 605 -------- packages/common/test/service-object.js | 882 ----------- packages/common/test/service.js | 530 ------- packages/common/test/util.js | 1854 ------------------------ 15 files changed, 6448 deletions(-) delete mode 100644 packages/common/package.json delete mode 100644 packages/common/src/index.js delete mode 100644 packages/common/src/logger.js delete mode 100644 packages/common/src/operation.js delete mode 100644 packages/common/src/paginator.js delete mode 100644 packages/common/src/service-object.js delete mode 100644 packages/common/src/service.js delete mode 100644 packages/common/src/util.js delete mode 100644 packages/common/test/index.js delete mode 100644 packages/common/test/logger.js delete mode 100644 packages/common/test/operation.js delete mode 100644 packages/common/test/paginator.js delete mode 100644 packages/common/test/service-object.js delete mode 100644 packages/common/test/service.js delete mode 100644 packages/common/test/util.js diff --git a/packages/common/package.json b/packages/common/package.json deleted file mode 100644 index fdf992afc3b..00000000000 --- a/packages/common/package.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "@google-cloud/common", - "version": "0.14.0", - "author": "Google Inc.", - "description": "Common components for Cloud APIs Node.js Client Libraries", - "contributors": [ - { - "name": "Burcu Dogan", - "email": "jbd@google.com" - }, - { - "name": "Johan Euphrosine", - "email": "proppy@google.com" - }, - { - "name": "Patrick Costello", - "email": "pcostell@google.com" - }, - { - "name": "Ryan Seys", - "email": "ryan@ryanseys.com" - }, - { - "name": "Silvano Luciani", - "email": "silvano@google.com" - }, - { - "name": "Stephen Sawchuk", - "email": "sawchuk@gmail.com" - } - ], - "main": "./src/index.js", - "files": [ - "src", - "AUTHORS", - "CONTRIBUTORS", - "LICENSE" - ], - "repository": "googlecloudplatform/google-cloud-node", - "dependencies": { - "array-uniq": "^1.0.3", - "arrify": "^1.0.1", - "concat-stream": "^1.6.0", - "create-error-class": "^3.0.2", - "duplexify": "^3.5.0", - "ent": "^2.2.0", - "extend": "^3.0.0", - "google-auto-auth": "^0.7.1", - "is": "^3.2.0", - "log-driver": "^1.2.5", - "methmeth": "^1.1.0", - "modelo": "^4.2.0", - "request": "^2.79.0", - "retry-request": "^3.0.0", - "split-array-stream": "^1.0.0", - "stream-events": "^1.0.1", - "string-format-obj": "^1.1.0", - "through2": "^2.0.3" - }, - "devDependencies": { - "mocha": "^4.0.0", - "proxyquire": "^1.7.10", - "uuid": "^3.0.1" - }, - "scripts": { - "publish-module": "node ../../scripts/publish.js common", - "test": "mocha test/*.js" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=4.0.0" - } -} diff --git a/packages/common/src/index.js b/packages/common/src/index.js deleted file mode 100644 index f27cd73b7b1..00000000000 --- a/packages/common/src/index.js +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Copyright 2016 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. - */ - -/** - * @type {module:common/logger} - * @private - */ -exports.logger = require('./logger.js'); - -/** - * @type {module:common/operation} - * @private - */ -exports.Operation = require('./operation.js'); - -/** - * @type {module:common/paginator} - * @private - */ -exports.paginator = require('./paginator.js'); - -/** - * @type {module:common/service} - * @private - */ -exports.Service = require('./service.js'); - -/** - * @type {module:common/serviceObject} - * @private - */ -exports.ServiceObject = require('./service-object.js'); - -/** - * @type {module:common/util} - * @private - */ -exports.util = require('./util.js'); diff --git a/packages/common/src/logger.js b/packages/common/src/logger.js deleted file mode 100644 index 14b9959c585..00000000000 --- a/packages/common/src/logger.js +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright 2016 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'; - -/*! - * @module common/logger - */ - -var format = require('string-format-obj'); -var is = require('is'); -var logDriver = require('log-driver'); - -/** - * The default list of log levels. - * @type {string[]} - */ -var LEVELS = [ - 'silent', - 'error', - 'warn', - 'info', - 'debug', - 'silly' -]; - -/** - * Create a logger to print output to the console. - * - * @param {string=|object=} options - Configuration object. If a string, it is - * treated as `options.level`. - * @param {string=} options.level - The minimum log level that will print to the - * console. (Default: `error`) - * @param {string[]=} options.levels - The list of levels to use. (Default: - * logger.LEVELS) - * @param {string=} options.tag - A tag to use in log messages. - */ -function logger(options) { - if (is.string(options)) { - options = { - level: options - }; - } - - options = options || {}; - - return logDriver({ - levels: options.levels || LEVELS, - - level: options.level || 'error', - - format: function() { - var args = [].slice.call(arguments); - - return format('{level}{tag} {message}', { - level: args.shift().toUpperCase(), - tag: options.tag ? ':' + options.tag + ':' : '', - message: args.join(' ') - }); - } - }); -} - -module.exports = logger; -module.exports.LEVELS = LEVELS; diff --git a/packages/common/src/operation.js b/packages/common/src/operation.js deleted file mode 100644 index ec36a4e06d9..00000000000 --- a/packages/common/src/operation.js +++ /dev/null @@ -1,192 +0,0 @@ -/*! - * Copyright 2016 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/operation - */ - -'use strict'; - -var events = require('events'); -var extend = require('extend'); -var modelo = require('modelo'); - -/** - * @type {module:common/serviceObject} - * @private - */ -var ServiceObject = require('./service-object.js'); - -// jscs:disable maximumLineLength -/** - * An Operation object allows you to interact with APIs that take longer to - * process things. - * - * @constructor - * @alias module:common/operation - * - * @param {object} config - Configuration object. - * @param {module:common/service|module:common/serviceObject|module:common/grpcService|module:common/grpcServiceObject} config.parent - The - * parent object. - * @param {string} id - The operation ID. - */ -// jscs:enable maximumLineLength -function Operation(config) { - var methods = { - /** - * Checks to see if an operation exists. - */ - exists: true, - - /** - * Retrieves the operation. - */ - get: true, - - /** - * Retrieves metadata for the operation. - */ - getMetadata: { - reqOpts: { - name: config.id - } - } - }; - - config = extend({ - baseUrl: '' - }, config); - - config.methods = config.methods || methods; - - ServiceObject.call(this, config); - events.EventEmitter.call(this); - - this.completeListeners = 0; - this.hasActiveListeners = false; - - this.listenForEvents_(); -} - -modelo.inherits(Operation, ServiceObject, events.EventEmitter); - -/** - * Wraps the `complete` and `error` events in a Promise. - * - * @return {promise} - */ -Operation.prototype.promise = function() { - var self = this; - - return new self.Promise(function(resolve, reject) { - self - .on('error', reject) - .on('complete', function(metadata) { - resolve([metadata]); - }); - }); -}; - -/** - * Begin listening for events on the operation. This method keeps track of how - * many "complete" listeners are registered and removed, making sure polling is - * handled automatically. - * - * As long as there is one active "complete" listener, the connection is open. - * When there are no more listeners, the polling stops. - * - * @private - */ -Operation.prototype.listenForEvents_ = function() { - var self = this; - - this.on('newListener', function(event) { - if (event === 'complete') { - self.completeListeners++; - - if (!self.hasActiveListeners) { - self.hasActiveListeners = true; - self.startPolling_(); - } - } - }); - - this.on('removeListener', function(event) { - if (event === 'complete' && --self.completeListeners === 0) { - self.hasActiveListeners = false; - } - }); -}; - -/** - * Poll for a status update. Execute the callback: - * - * - callback(err): Operation failed - * - callback(): Operation incomplete - * - callback(null, metadata): Operation complete - * - * @private - * - * @param {function} callback - */ -Operation.prototype.poll_ = function(callback) { - this.getMetadata(function(err, resp) { - if (err || resp.error) { - callback(err || resp.error); - return; - } - - if (!resp.done) { - callback(); - return; - } - - callback(null, resp); - }); -}; - -/** - * Poll `getMetadata` to check the operation's status. This runs a loop to ping - * the API on an interval. - * - * Note: This method is automatically called once a "complete" event handler is - * registered on the operation. - * - * @private - */ -Operation.prototype.startPolling_ = function() { - var self = this; - - if (!this.hasActiveListeners) { - return; - } - - this.poll_(function(err, metadata) { - if (err) { - self.emit('error', err); - return; - } - - if (!metadata) { - setTimeout(self.startPolling_.bind(self), 500); - return; - } - - self.emit('complete', metadata); - }); -}; - -module.exports = Operation; diff --git a/packages/common/src/paginator.js b/packages/common/src/paginator.js deleted file mode 100644 index 178c226e3a5..00000000000 --- a/packages/common/src/paginator.js +++ /dev/null @@ -1,255 +0,0 @@ -/*! - * 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/paginator - */ - -'use strict'; - -var arrify = require('arrify'); -var concat = require('concat-stream'); -var extend = require('extend'); -var is = require('is'); -var split = require('split-array-stream'); - -/** - * @type {module:common/util} - * @private - */ -var util = require('./util.js'); - -/*! Developer Documentation - * - * paginator is used to auto-paginate `nextQuery` methods as well as - * streamifying them. - * - * Before: - * - * search.query('done=true', function(err, results, nextQuery) { - * search.query(nextQuery, function(err, results, nextQuery) {}); - * }); - * - * After: - * - * search.query('done=true', function(err, results) {}); - * - * Methods to extend should be written to accept callbacks and return a - * `nextQuery`. - */ -var paginator = {}; - -/** - * Cache the original method, then overwrite it on the Class's prototype. - * - * @param {function} Class - The parent class of the methods to extend. - * @param {string|string[]} methodNames - Name(s) of the methods to extend. - */ -paginator.extend = function(Class, methodNames) { - methodNames = arrify(methodNames); - - methodNames.forEach(function(methodName) { - var originalMethod = Class.prototype[methodName]; - - // map the original method to a private member - Class.prototype[methodName + '_'] = originalMethod; - - // overwrite the original to auto-paginate - Class.prototype[methodName] = function() { - var parsedArguments = paginator.parseArguments_(arguments); - return paginator.run_(parsedArguments, originalMethod.bind(this)); - }; - }); -}; - -/** - * Wraps paginated API calls in a readable object stream. - * - * This method simply calls the nextQuery recursively, emitting results to a - * stream. The stream ends when `nextQuery` is null. - * - * `maxResults` will act as a cap for how many results are fetched and emitted - * to the stream. - * - * @param {string} methodName - Name of the method to streamify. - * @return {function} - Wrapped function. - */ -paginator.streamify = function(methodName) { - return function() { - var parsedArguments = paginator.parseArguments_(arguments); - var originalMethod = this[methodName + '_'] || this[methodName]; - - return paginator.runAsStream_(parsedArguments, originalMethod.bind(this)); - }; -}; - -/** - * Parse a pseudo-array `arguments` for a query and callback. - * - * @param {array} args - The original `arguments` pseduo-array that the original - * method received. - */ -paginator.parseArguments_ = function(args) { - var query; - var autoPaginate = true; - var maxApiCalls = -1; - var maxResults = -1; - var callback; - - var firstArgument = args[0]; - var lastArgument = args[args.length - 1]; - - if (is.fn(firstArgument)) { - callback = firstArgument; - } else { - query = firstArgument; - } - - if (is.fn(lastArgument)) { - callback = lastArgument; - } - - if (is.object(query)) { - query = extend(true, {}, query); - - // Check if the user only asked for a certain amount of results. - if (is.number(query.maxResults)) { - // `maxResults` is used API-wide. - maxResults = query.maxResults; - } else if (is.number(query.pageSize)) { - // `pageSize` is Pub/Sub's `maxResults`. - maxResults = query.pageSize; - } - - if (is.number(query.maxApiCalls)) { - maxApiCalls = query.maxApiCalls; - delete query.maxApiCalls; - } - - if (callback && - (maxResults !== -1 || // The user specified a limit. - query.autoPaginate === false)) { - autoPaginate = false; - } - } - - return { - query: query || {}, - autoPaginate: autoPaginate, - maxApiCalls: maxApiCalls, - maxResults: maxResults, - callback: callback - }; -}; - -/** - * This simply checks to see if `autoPaginate` is set or not, if it's true - * then we buffer all results, otherwise simply call the original method. - * - * @param {array} parsedArguments - Parsed arguments from the original method - * call. - * @param {object=|string=} parsedArguments.query - Query object. This is most - * commonly an object, but to make the API more simple, it can also be a - * string in some places. - * @param {function=} parsedArguments.callback - Callback function. - * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled. - * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make. - * @param {number} parsedArguments.maxResults - Maximum results to return. - * @param {function} originalMethod - The cached method that accepts a callback - * and returns `nextQuery` to receive more results. - */ -paginator.run_ = function(parsedArguments, originalMethod) { - var query = parsedArguments.query; - var callback = parsedArguments.callback; - var autoPaginate = parsedArguments.autoPaginate; - - if (autoPaginate) { - this.runAsStream_(parsedArguments, originalMethod) - .on('error', callback) - .pipe(concat(function(results) { - callback(null, results); - })); - } else { - originalMethod(query, callback); - } -}; - -/** - * This method simply calls the nextQuery recursively, emitting results to a - * stream. The stream ends when `nextQuery` is null. - * - * `maxResults` will act as a cap for how many results are fetched and emitted - * to the stream. - * - * @param {object=|string=} parsedArguments.query - Query object. This is most - * commonly an object, but to make the API more simple, it can also be a - * string in some places. - * @param {function=} parsedArguments.callback - Callback function. - * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled. - * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make. - * @param {number} parsedArguments.maxResults - Maximum results to return. - * @param {function} originalMethod - The cached method that accepts a callback - * and returns `nextQuery` to receive more results. - * @return {stream} - Readable object stream. - */ -paginator.runAsStream_ = function(parsedArguments, originalMethod) { - var query = parsedArguments.query; - var resultsToSend = parsedArguments.maxResults; - - var limiter = util.createLimiter(makeRequest, { - maxApiCalls: parsedArguments.maxApiCalls - }); - - var stream = limiter.stream; - - stream.once('reading', function() { - makeRequest(query); - }); - - function makeRequest(query) { - originalMethod(query, onResultSet); - } - - function onResultSet(err, results, nextQuery) { - if (err) { - stream.destroy(err); - return; - } - - if (resultsToSend >= 0 && results.length > resultsToSend) { - results = results.splice(0, resultsToSend); - } - - resultsToSend -= results.length; - - split(results, stream, function(streamEnded) { - if (streamEnded) { - return; - } - - if (nextQuery && resultsToSend !== 0) { - limiter.makeRequest(nextQuery); - return; - } - - stream.push(null); - }); - } - - return limiter.stream; -}; - -module.exports = paginator; diff --git a/packages/common/src/service-object.js b/packages/common/src/service-object.js deleted file mode 100644 index d9b6191eade..00000000000 --- a/packages/common/src/service-object.js +++ /dev/null @@ -1,388 +0,0 @@ -/*! - * 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-object - */ - -'use strict'; - -var arrify = require('arrify'); -var exec = require('methmeth'); -var extend = require('extend'); -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. - * - * @constructor - * @alias module:common/service-object - * - * @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 {object=} config.methods - A map of each method name that should be - * inherited. - * @param {object} config.methods[].reqOpts - Default request options for this - * particular method. A common use case is when `setMetadata` requires a - * `PUT` method to override the default `PATCH`. - * @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; - this.methods = config.methods || {}; - this.interceptors = []; - this.Promise = this.parent.Promise; - - if (config.methods) { - var allMethodNames = Object.keys(ServiceObject.prototype); - allMethodNames - .filter(function(methodName) { - return ( - // All ServiceObjects need `request`. - !/^request/.test(methodName) && - - // 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; - }); - } -} - -/** - * 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) { - var args = [].slice.call(arguments); - - if (!err) { - self.metadata = instance.metadata; - args[1] = self; // replace the created `instance` with this one. - } - - callback.apply(null, args); - } - - 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) { - var methodConfig = this.methods.delete || {}; - callback = callback || util.noop; - - var reqOpts = extend({ - method: 'DELETE', - uri: '' - }, methodConfig.reqOpts); - - // 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); - }); -}; - -/** - * 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(null, 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. - */ -ServiceObject.prototype.get = function(config, callback) { - var self = this; - - if (is.fn(config)) { - callback = config; - config = {}; - } - - config = config || {}; - - var autoCreate = config.autoCreate && is.fn(this.create); - delete config.autoCreate; - - function onCreate(err, instance, apiResponse) { - if (err) { - if (err.code === 409) { - self.get(config, callback); - return; - } - - callback(err, null, apiResponse); - return; - } - - callback(null, instance, apiResponse); - } - - this.getMetadata(function(err, metadata) { - if (err) { - if (err.code === 404 && autoCreate) { - var args = []; - - if (!is.empty(config)) { - args.push(config); - } - - args.push(onCreate); - - self.create.apply(self, args); - return; - } - - callback(err, null, metadata); - return; - } - - 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; - - var methodConfig = this.methods.getMetadata || {}; - - var reqOpts = extend({ - uri: '' - }, methodConfig.reqOpts); - - // 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); - return; - } - - self.metadata = resp; - - callback(null, self.metadata, resp); - }); -}; - -/** - * 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; - - var methodConfig = this.methods.setMetadata || {}; - - var reqOpts = extend(true, { - method: 'PATCH', - uri: '', - json: metadata - }, methodConfig.reqOpts); - - // 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); - 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) { - reqOpts = extend(true, {}, reqOpts); - - var isAbsoluteUrl = reqOpts.uri.indexOf('http') === 0; - - var uriComponents = [ - this.baseUrl, - this.id || '', - reqOpts.uri - ]; - - if (isAbsoluteUrl) { - uriComponents.splice(0, uriComponents.indexOf(reqOpts.uri)); - } - - reqOpts.uri = uriComponents - .filter(exec('trim')) // Limit to non-empty strings. - .map(function(uriComponent) { - var trimSlashesRegex = /^\/*|\/*$/g; - return uriComponent.replace(trimSlashesRegex, ''); - }) - .join('/'); - - var childInterceptors = arrify(reqOpts.interceptors_); - var localInterceptors = [].slice.call(this.interceptors); - - reqOpts.interceptors_ = childInterceptors.concat(localInterceptors); - - if (!callback) { - return this.parent.requestStream(reqOpts); - } - - this.parent.request(reqOpts, callback); -}; - -/** - * 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) { - ServiceObject.prototype.request_.call(this, reqOpts, callback); -}; - -/** - * 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. - */ -ServiceObject.prototype.requestStream = function(reqOpts) { - return ServiceObject.prototype.request_.call(this, reqOpts); -}; - -/*! Developer Documentation - * - * All async methods (except for streams) will return a Promise in the event - * that a callback is omitted. - */ -util.promisifyAll(ServiceObject); - -module.exports = ServiceObject; diff --git a/packages/common/src/service.js b/packages/common/src/service.js deleted file mode 100644 index 2cbfeb118ba..00000000000 --- a/packages/common/src/service.js +++ /dev/null @@ -1,192 +0,0 @@ -/*! - * 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'; - -var arrify = require('arrify'); -var extend = require('extend'); - -/** - * @type {module:common/util} - * @private - */ -var util = require('./util.js'); - -var PROJECT_ID_TOKEN = '{{projectId}}'; - -/** - * 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. - * - * @constructor - * @alias module:common/service - * - * @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). - */ -function Service(config, options) { - options = options || {}; - - this.baseUrl = config.baseUrl; - this.globalInterceptors = arrify(options.interceptors_); - this.interceptors = []; - this.packageJson = config.packageJson; - this.projectId = options.projectId || PROJECT_ID_TOKEN; - this.projectIdRequired = config.projectIdRequired !== false; - this.Promise = options.promise || Promise; - - var reqCfg = extend({}, config, { - projectIdRequired: this.projectIdRequired, - projectId: this.projectId, - credentials: options.credentials, - keyFile: options.keyFilename, - email: options.email - }); - - this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(reqCfg); - this.authClient = this.makeAuthenticatedRequest.authClient; - this.getCredentials = this.makeAuthenticatedRequest.getCredentials; - - var isCloudFunctionEnv = !!process.env.FUNCTION_NAME; - - if (isCloudFunctionEnv) { - this.interceptors.push({ - request: function(reqOpts) { - reqOpts.forever = false; - return reqOpts; - } - }); - } -} - -/** - * Get and update the Service's project ID. - * - * @param {function} callback - The callback function. - */ -Service.prototype.getProjectId = function(callback) { - var self = this; - - this.authClient.getProjectId(function(err, projectId) { - if (err) { - callback(err); - return; - } - - if (self.projectId === PROJECT_ID_TOKEN && projectId) { - self.projectId = projectId; - } - - callback(null, self.projectId); - }); -}; - -/** - * 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) { - reqOpts = extend(true, {}, reqOpts); - - var isAbsoluteUrl = reqOpts.uri.indexOf('http') === 0; - - var uriComponents = [ - this.baseUrl - ]; - - if (this.projectIdRequired) { - uriComponents.push('projects'); - uriComponents.push(this.projectId); - } - - uriComponents.push(reqOpts.uri); - - if (isAbsoluteUrl) { - uriComponents.splice(0, uriComponents.indexOf(reqOpts.uri)); - } - - reqOpts.uri = uriComponents - .map(function(uriComponent) { - var trimSlashesRegex = /^\/*|\/*$/g; - return uriComponent.replace(trimSlashesRegex, ''); - }) - .join('/') - // Some URIs have colon separators. - // Bad: https://.../projects/:list - // Good: https://.../projects:list - .replace(/\/:/g, ':'); - - // Interceptors should be called in the order they were assigned. - var combinedInterceptors = [].slice.call(this.globalInterceptors) - .concat(this.interceptors) - .concat(arrify(reqOpts.interceptors_)); - - var interceptor; - - while ((interceptor = combinedInterceptors.shift()) && interceptor.request) { - reqOpts = interceptor.request(reqOpts); - } - - delete reqOpts.interceptors_; - - var pkg = this.packageJson; - reqOpts.headers = extend({}, reqOpts.headers, { - 'User-Agent': util.getUserAgentFromPackageJson(pkg), - 'x-goog-api-client': `gl-node/${process.versions.node} gccl/${pkg.version}` - }); - - return this.makeAuthenticatedRequest(reqOpts, callback); -}; - -/** - * 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) { - Service.prototype.request_.call(this, reqOpts, callback); -}; - -/** - * 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. - */ -Service.prototype.requestStream = function(reqOpts) { - return Service.prototype.request_.call(this, reqOpts); -}; - -module.exports = Service; diff --git a/packages/common/src/util.js b/packages/common/src/util.js deleted file mode 100644 index 286847aa084..00000000000 --- a/packages/common/src/util.js +++ /dev/null @@ -1,807 +0,0 @@ -/** - * Copyright 2014 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/*! - * @module common/util - */ - -'use strict'; - -var createErrorClass = require('create-error-class'); -var duplexify = require('duplexify'); -var ent = require('ent'); -var extend = require('extend'); -var format = require('string-format-obj'); -var googleAuth = require('google-auto-auth'); -var is = require('is'); -var request = require('request').defaults({ - timeout: 60000, - gzip: true, - forever: true, - pool: { - maxSockets: Infinity - } -}); -var retryRequest = require('retry-request'); -var streamEvents = require('stream-events'); -var through = require('through2'); -var uniq = require('array-uniq'); - -var util = module.exports; - -var errorMessage = format([ - 'Sorry, we cannot connect to Cloud Services without a project ID.', - 'You may specify one with an environment variable named "GCLOUD_PROJECT".', - 'See {baseUrl}/{path} for a detailed guide on creating an authenticated', - 'connection.' -].join(' '), { - baseUrl: 'https://googlecloudplatform.github.io/google-cloud-node/#', - path: 'docs/guides/authentication' -}); - -var missingProjectIdError = new Error(errorMessage); - -util.missingProjectIdError = missingProjectIdError; - -/** - * No op. - * - * @example - * function doSomething(callback) { - * callback = callback || noop; - * } - */ -function noop() {} - -util.noop = noop; - -/** - * Custom error type for API errors. - * - * @param {object} errorBody - Error object. - */ -util.ApiError = createErrorClass('ApiError', function(errorBody) { - this.code = errorBody.code; - this.errors = errorBody.errors; - this.response = errorBody.response; - - try { - this.errors = JSON.parse(this.response.body).error.errors; - } catch (e) {} - - var messages = []; - - if (errorBody.message) { - messages.push(errorBody.message); - } - - if (this.errors && this.errors.length === 1) { - messages.push(this.errors[0].message); - } else if (this.response && this.response.body) { - messages.push(ent.decode(errorBody.response.body.toString())); - } else if (!errorBody.message) { - messages.push('Error during request.'); - } - - this.message = uniq(messages).join(' - '); -}); - -/** - * Custom error type for partial errors returned from the API. - * - * @param {object} b - Error object. - */ -util.PartialFailureError = createErrorClass('PartialFailureError', function(b) { - var errorObject = b; - - this.errors = errorObject.errors; - this.response = errorObject.response; - - var defaultErrorMessage = 'A failure occurred during this request.'; - this.message = errorObject.message || defaultErrorMessage; -}); - -/** - * Uniformly process an API response. - * - * @param {*} err - Error value. - * @param {*} resp - Response value. - * @param {*} body - Body value. - * @param {function} callback - The callback function. - */ -function handleResp(err, resp, body, callback) { - callback = callback || util.noop; - - var parsedResp = extend( - true, - { err: err || null }, - resp && util.parseHttpRespMessage(resp), - body && util.parseHttpRespBody(body) - ); - - callback(parsedResp.err, parsedResp.body, parsedResp.resp); -} - -util.handleResp = handleResp; - -/** - * Sniff an incoming HTTP response message for errors. - * - * @param {object} httpRespMessage - An incoming HTTP response message from - * `request`. - * @return {object} parsedHttpRespMessage - The parsed response. - * @param {?error} parsedHttpRespMessage.err - An error detected. - * @param {object} parsedHttpRespMessage.resp - The original response object. - */ -function parseHttpRespMessage(httpRespMessage) { - var parsedHttpRespMessage = { - resp: httpRespMessage - }; - - if (httpRespMessage.statusCode < 200 || httpRespMessage.statusCode > 299) { - // Unknown error. Format according to ApiError standard. - parsedHttpRespMessage.err = new util.ApiError({ - errors: [], - code: httpRespMessage.statusCode, - message: httpRespMessage.statusMessage, - response: httpRespMessage - }); - } - - return parsedHttpRespMessage; -} - -util.parseHttpRespMessage = parseHttpRespMessage; - -/** - * Parse the response body from an HTTP request. - * - * @param {object} body - The response body. - * @return {object} parsedHttpRespMessage - The parsed response. - * @param {?error} parsedHttpRespMessage.err - An error detected. - * @param {object} parsedHttpRespMessage.body - The original body value provided - * will try to be JSON.parse'd. If it's successful, the parsed value will be - * returned here, otherwise the original value. - */ -function parseHttpRespBody(body) { - var parsedHttpRespBody = { - body: body - }; - - if (is.string(body)) { - try { - parsedHttpRespBody.body = JSON.parse(body); - } catch(err) {} - } - - if (parsedHttpRespBody.body && parsedHttpRespBody.body.error) { - // Error from JSON API. - parsedHttpRespBody.err = new util.ApiError(parsedHttpRespBody.body.error); - } - - return parsedHttpRespBody; -} - -util.parseHttpRespBody = parseHttpRespBody; - -/** - * Take a Duplexify stream, fetch an authenticated connection header, and create - * an outgoing writable stream. - * - * @param {Duplexify} dup - Duplexify stream. - * @param {object} options - Configuration object. - * @param {module:common/connection} options.connection - A connection instance, - * used to get a token with and send the request through. - * @param {object} options.metadata - Metadata to send at the head of the - * request. - * @param {object} options.request - Request object, in the format of a standard - * Node.js http.request() object. - * @param {string=} options.request.method - Default: "POST". - * @param {string=} options.request.qs.uploadType - Default: "multipart". - * @param {string=} options.streamContentType - Default: - * "application/octet-stream". - * @param {function} onComplete - Callback, executed after the writable Request - * stream has completed. - */ -function makeWritableStream(dup, options, onComplete) { - onComplete = onComplete || util.noop; - - var writeStream = through(); - dup.setWritable(writeStream); - - var defaultReqOpts = { - method: 'POST', - qs: { - uploadType: 'multipart' - } - }; - - var metadata = options.metadata || {}; - - var reqOpts = extend(true, defaultReqOpts, options.request, { - multipart: [ - { - 'Content-Type': 'application/json', - body: JSON.stringify(metadata) - }, - { - 'Content-Type': metadata.contentType || 'application/octet-stream', - body: writeStream - } - ] - }); - - options.makeAuthenticatedRequest(reqOpts, { - onAuthenticated: function(err, authenticatedReqOpts) { - if (err) { - dup.destroy(err); - return; - } - - request(authenticatedReqOpts, function(err, resp, body) { - util.handleResp(err, resp, body, function(err, data) { - if (err) { - dup.destroy(err); - return; - } - - dup.emit('response', resp); - onComplete(data); - }); - }); - } - }); -} - -util.makeWritableStream = makeWritableStream; - -/** - * Returns true if the API request should be retried, given the error that was - * given the first time the request was attempted. This is used for rate limit - * related errors as well as intermittent server errors. - * - * @param {error} err - The API error to check if it is appropriate to retry. - * @return {boolean} True if the API request should be retried, false otherwise. - */ -function shouldRetryRequest(err) { - if (err) { - if ([429, 500, 502, 503].indexOf(err.code) !== -1) { - return true; - } - - if (err.errors) { - for (var i in err.errors) { - var reason = err.errors[i].reason; - if (reason === 'rateLimitExceeded') { - return true; - } - if (reason === 'userRateLimitExceeded') { - return true; - } - } - } - } - - return false; -} - -util.shouldRetryRequest = shouldRetryRequest; - -/** - * Get a function for making authenticated requests. - * - * @throws {Error} If a projectId is requested, but not able to be detected. - * - * @param {object} config - Configuration object. - * @param {boolean=} config.autoRetry - Automatically retry requests if the - * response is related to rate limits or certain intermittent server errors. - * We will exponentially backoff subsequent requests by default. (default: - * true) - * @param {object=} config.credentials - Credentials object. - * @param {boolean=} config.customEndpoint - If true, just return the provided - * request options. Default: false. - * @param {string=} config.email - Account email address, required for PEM/P12 - * usage. - * @param {number=} config.maxRetries - Maximum number of automatic retries - * attempted before returning the error. (default: 3) - * @param {string=} config.keyFile - Path to a .json, .pem, or .p12 keyfile. - * @param {array} config.scopes - Array of scopes required for the API. - */ -function makeAuthenticatedRequestFactory(config) { - config = config || {}; - - var authClient = googleAuth(config); - - /** - * The returned function that will make an authenticated request. - * - * @param {type} reqOpts - Request options in the format `request` expects. - * @param {object|function} options - Configuration object or callback - * function. - * @param {function=} options.onAuthenticated - If provided, a request will - * not be made. Instead, this function is passed the error & authenticated - * request options. - */ - function makeAuthenticatedRequest(reqOpts, options) { - var stream; - var reqConfig = extend({}, config); - var activeRequest_; - - if (!options) { - stream = duplexify(); - reqConfig.stream = stream; - } - - function onAuthenticated(err, authenticatedReqOpts) { - var autoAuthFailed = - err && - err.message.indexOf('Could not load the default credentials') > -1; - - if (autoAuthFailed) { - // Even though authentication failed, the API might not actually care. - authenticatedReqOpts = reqOpts; - } - - if (!err || autoAuthFailed) { - var projectId = authClient.projectId; - - if (config.projectId && config.projectId !== '{{projectId}}') { - projectId = config.projectId; - } - - try { - authenticatedReqOpts = util.decorateRequest( - authenticatedReqOpts, - projectId - ); - err = null; - } catch(e) { - // A projectId was required, but we don't have one. - // Re-use the "Could not load the default credentials error" if auto - // auth failed. - err = err || e; - } - } - - if (err) { - if (stream) { - stream.destroy(err); - } else { - (options.onAuthenticated || options)(err); - } - - return; - } - - if (options && options.onAuthenticated) { - options.onAuthenticated(null, authenticatedReqOpts); - } else { - activeRequest_ = - util.makeRequest(authenticatedReqOpts, reqConfig, options); - } - } - - if (reqConfig.customEndpoint) { - // Using a custom API override. Do not use `google-auto-auth` for - // authentication. (ex: connecting to a local Datastore server) - onAuthenticated(null, reqOpts); - } else { - authClient.authorizeRequest(reqOpts, onAuthenticated); - } - - if (stream) { - return stream; - } - - return { - abort: function() { - if (activeRequest_) { - activeRequest_.abort(); - activeRequest_ = null; - } - } - }; - } - - makeAuthenticatedRequest.getCredentials = - authClient.getCredentials.bind(authClient); - - makeAuthenticatedRequest.authClient = authClient; - - return makeAuthenticatedRequest; -} - -util.makeAuthenticatedRequestFactory = makeAuthenticatedRequestFactory; - -/** - * Make a request through the `retryRequest` module with built-in error handling - * and exponential back off. - * - * @param {object} reqOpts - Request options in the format `request` expects. - * @param {object=} config - Configuration object. - * @param {boolean=} config.autoRetry - Automatically retry requests if the - * response is related to rate limits or certain intermittent server errors. - * We will exponentially backoff subsequent requests by default. (default: - * true) - * @param {number=} config.maxRetries - Maximum number of automatic retries - * attempted before returning the error. (default: 3) - * @param {function} callback - The callback function. - */ -function makeRequest(reqOpts, config, callback) { - if (is.fn(config)) { - callback = config; - config = {}; - } - - config = config || {}; - - var options = { - request: request, - - retries: config.autoRetry !== false ? config.maxRetries || 3 : 0, - - shouldRetryFn: function(httpRespMessage) { - var err = util.parseHttpRespMessage(httpRespMessage).err; - return err && util.shouldRetryRequest(err); - } - }; - - if (config.stream) { - var dup = config.stream; - var requestStream; - var isGetRequest = (reqOpts.method || 'GET').toUpperCase() === 'GET'; - - if (isGetRequest) { - requestStream = retryRequest(reqOpts, options); - dup.setReadable(requestStream); - } else { - // Streaming writable HTTP requests cannot be retried. - requestStream = request(reqOpts); - dup.setWritable(requestStream); - } - - // Replay the Request events back to the stream. - requestStream.on('error', dup.destroy.bind(dup)) - .on('response', dup.emit.bind(dup, 'response')) - .on('complete', dup.emit.bind(dup, 'complete')); - - dup.abort = requestStream.abort; - } else { - return retryRequest(reqOpts, options, function(err, response, body) { - util.handleResp(err, response, body, callback); - }); - } -} - -util.makeRequest = makeRequest; - -/** - * Decorate the options about to be made in a request. - * - * @param {object} reqOpts - The options to be passed to `request`. - * @param {string} projectId - The project ID. - * @return {object} reqOpts - The decorated reqOpts. - */ -function decorateRequest(reqOpts, projectId) { - delete reqOpts.autoPaginate; - delete reqOpts.autoPaginateVal; - delete reqOpts.objectMode; - - if (is.object(reqOpts.qs)) { - delete reqOpts.qs.autoPaginate; - delete reqOpts.qs.autoPaginateVal; - reqOpts.qs = util.replaceProjectIdToken(reqOpts.qs, projectId); - } - - if (is.object(reqOpts.json)) { - delete reqOpts.json.autoPaginate; - delete reqOpts.json.autoPaginateVal; - reqOpts.json = util.replaceProjectIdToken(reqOpts.json, projectId); - } - - reqOpts.uri = util.replaceProjectIdToken(reqOpts.uri, projectId); - - return reqOpts; -} - -util.decorateRequest = decorateRequest; - -/** - * Populate the `{{projectId}}` placeholder. - * - * @throws {Error} If a projectId is required, but one is not provided. - * - * @param {*} - Any input value that may contain a placeholder. Arrays and - * objects will be looped. - * @param {string} projectId - A projectId. If not provided - * @return {*} - The original argument with all placeholders populated. - */ -function replaceProjectIdToken(value, projectId) { - if (is.array(value)) { - value = value.map(function(val) { - return replaceProjectIdToken(val, projectId); - }); - } - - if (is.object(value) && is.fn(value.hasOwnProperty)) { - for (var opt in value) { - if (value.hasOwnProperty(opt)) { - value[opt] = replaceProjectIdToken(value[opt], projectId); - } - } - } - - if (is.string(value) && value.indexOf('{{projectId}}') > -1) { - if (!projectId || projectId === '{{projectId}}') { - throw util.missingProjectIdError; - } - value = value.replace(/{{projectId}}/g, projectId); - } - - return value; -} - -util.replaceProjectIdToken = replaceProjectIdToken; - -/** - * Extend a global configuration object with user options provided at the time - * of sub-module instantiation. - * - * Connection details currently come in two ways: `credentials` or - * `keyFilename`. Because of this, we have a special exception when overriding a - * global configuration object. If a user provides either to the global - * configuration, then provides another at submodule instantiation-time, the - * latter is preferred. - * - * @param {object} globalConfig - The global configuration object. - * @param {object=} overrides - The instantiation-time configuration object. - * @return {object} - */ -function extendGlobalConfig(globalConfig, overrides) { - globalConfig = globalConfig || {}; - overrides = overrides || {}; - - var defaultConfig = {}; - - if (process.env.GCLOUD_PROJECT) { - defaultConfig.projectId = process.env.GCLOUD_PROJECT; - } - - var options = extend({}, globalConfig); - - var hasGlobalConnection = options.credentials || options.keyFilename; - var isOverridingConnection = overrides.credentials || overrides.keyFilename; - - if (hasGlobalConnection && isOverridingConnection) { - delete options.credentials; - delete options.keyFilename; - } - - var extendedConfig = extend(true, defaultConfig, options, overrides); - - // Preserve the original (not cloned) interceptors. - extendedConfig.interceptors_ = globalConfig.interceptors_; - - return extendedConfig; -} - -util.extendGlobalConfig = extendGlobalConfig; - -/** - * Merge and validate API configurations. - * - * @param {object} globalContext - gcloud-level context. - * @param {object} globalContext.config_ - gcloud-level configuration. - * @param {object} localConfig - Service-level configurations. - * @param {object=} options - Configuration object. - * @param {boolean} options.projectIdRequired - Whether to throw if a project ID - * is required, but not provided by the user. (Default: true) - * @return {object} config - Merged and validated configuration. - */ -function normalizeArguments(globalContext, localConfig, options) { - options = options || {}; - - var globalConfig = globalContext && globalContext.config_; - - return util.extendGlobalConfig(globalConfig, localConfig); -} - -util.normalizeArguments = normalizeArguments; - -/** - * Limit requests according to a `maxApiCalls` limit. - * - * @param {function} makeRequestFn - The function that will be called. - * @param {object=} options - Configuration object. - * @param {number} options.maxApiCalls - The maximum number of API calls to - * make. - */ -function createLimiter(makeRequestFn, options) { - var stream = streamEvents(through.obj()); - - var requestsMade = 0; - var requestsToMake = -1; - - options = options || {}; - - if (is.number(options.maxApiCalls)) { - requestsToMake = options.maxApiCalls; - } - - return { - makeRequest: function() { - requestsMade++; - - if (requestsToMake >= 0 && requestsMade > requestsToMake) { - stream.push(null); - return; - } - - makeRequestFn.apply(null, arguments); - - return stream; - }, - - stream: stream - }; -} - -util.createLimiter = createLimiter; - -function isCustomType(unknown, module) { - function getConstructorName(obj) { - return obj.constructor && obj.constructor.name.toLowerCase(); - } - - var moduleNameParts = module.split('/'); - - var parentModuleName = moduleNameParts[0] && moduleNameParts[0].toLowerCase(); - var subModuleName = moduleNameParts[1] && moduleNameParts[1].toLowerCase(); - - if (subModuleName && getConstructorName(unknown) !== subModuleName) { - return false; - } - - var walkingModule = unknown; - do { - if (getConstructorName(walkingModule) === parentModuleName) { - return true; - } - } while ((walkingModule = walkingModule.parent)); - - return false; -} - -util.isCustomType = isCustomType; - -/** - * Create a properly-formatted User-Agent string from a package.json file. - * - * @param {object} packageJson - A module's package.json file. - * @return {string} userAgent - The formatted User-Agent string. - */ -function getUserAgentFromPackageJson(packageJson) { - var hyphenatedPackageName = packageJson.name - .replace('@google-cloud', 'gcloud-node') // For legacy purposes. - .replace('/', '-'); // For UA spec-compliance purposes. - - return hyphenatedPackageName + '/' + packageJson.version; -} - -util.getUserAgentFromPackageJson = getUserAgentFromPackageJson; - -/** - * Wraps a callback style function to conditionally return a promise. - * - * @param {function} originalMethod - The method to promisify. - * @param {object=} options - Promise options. - * @param {boolean} options.singular - Resolve the promise with single arg - * instead of an array. - * @return {function} wrapped - */ -function promisify(originalMethod, options) { - if (originalMethod.promisified_) { - return originalMethod; - } - - options = options || {}; - - var slice = Array.prototype.slice; - - var wrapper = function() { - var context = this; - var last; - - for (last = arguments.length - 1; last >= 0; last--) { - var arg = arguments[last]; - - if (is.undefined(arg)) { - continue; // skip trailing undefined. - } - - if (!is.fn(arg)) { - break; // non-callback last argument found. - } - - return originalMethod.apply(context, arguments); - } - - // peel trailing undefined. - var args = slice.call(arguments, 0, last + 1); - - var PromiseCtor = Promise; - - // Because dedupe will likely create a single install of - // @google-cloud/common to be shared amongst all modules, we need to - // localize it at the Service level. - if (context && context.Promise) { - PromiseCtor = context.Promise; - } - - return new PromiseCtor(function(resolve, reject) { - args.push(function() { - var callbackArgs = slice.call(arguments); - var err = callbackArgs.shift(); - - if (err) { - return reject(err); - } - - if (options.singular && callbackArgs.length === 1) { - resolve(callbackArgs[0]); - } else { - resolve(callbackArgs); - } - }); - - originalMethod.apply(context, args); - }); - }; - - wrapper.promisified_ = true; - return wrapper; -} - -util.promisify = promisify; - -/** - * Promisifies certain Class methods. This will not promisify private or - * streaming methods. - * - * @param {module:common/service} Class - Service class. - * @param {object=} options - Configuration object. - */ -function promisifyAll(Class, options) { - var exclude = options && options.exclude || []; - - var methods = Object - .keys(Class.prototype) - .filter(function(methodName) { - return is.fn(Class.prototype[methodName]) && // is it a function? - !/(^\_|(Stream|\_)|promise$)/.test(methodName) && // is it promisable? - exclude.indexOf(methodName) === -1; // is it blacklisted? - }); - - methods.forEach(function(methodName) { - var originalMethod = Class.prototype[methodName]; - - if (!originalMethod.promisified_) { - Class.prototype[methodName] = util.promisify(originalMethod, options); - } - }); -} - -util.promisifyAll = promisifyAll; diff --git a/packages/common/test/index.js b/packages/common/test/index.js deleted file mode 100644 index 8e9dc933499..00000000000 --- a/packages/common/test/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2016 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 proxyquire = require('proxyquire'); - -var fakeLogger = {}; -var fakeOperation = {}; -var fakePaginator = {}; -var fakeService = {}; -var fakeServiceObject = {}; -var fakeUtil = {}; - -describe('common', function() { - var common; - - before(function() { - common = proxyquire('../src/index.js', { - './logger.js': fakeLogger, - './operation.js': fakeOperation, - './paginator.js': fakePaginator, - './service.js': fakeService, - './service-object.js': fakeServiceObject, - './util.js': fakeUtil - }); - }); - - it('should correctly export the common modules', function() { - assert.deepEqual(common, { - logger: fakeLogger, - Operation: fakeOperation, - paginator: fakePaginator, - Service: fakeService, - ServiceObject: fakeServiceObject, - util: fakeUtil - }); - }); -}); diff --git a/packages/common/test/logger.js b/packages/common/test/logger.js deleted file mode 100644 index 51a38c04ca3..00000000000 --- a/packages/common/test/logger.js +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright 2016 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 proxyquire = require('proxyquire'); - -var LEVELS = [ - 'silent', - 'error', - 'warn', - 'info', - 'debug', - 'silly' -]; - -function fakeLogDriver(config) { - return config; -} - -describe('logger base-functionality', function() { - var logger; - - before(function() { - logger = proxyquire('../src/logger.js', { - 'log-driver': fakeLogDriver - }); - }); - - it('should expose the default list of levels', function() { - assert.deepEqual(logger.LEVELS, LEVELS); - }); - - it('should create a logger with the correct levels', function() { - assert.deepEqual(logger().levels, LEVELS); - }); - - it('should create a logger with custom levels', function() { - var customLevels = [ 'level-1', 'level-2', 'level-3' ]; - assert.deepEqual(logger({ levels: customLevels }).levels, customLevels); - }); - - it('should use a specified level', function() { - var level = 'level'; - assert.strictEqual(logger({ level: level }).level, level); - }); - - it('should treat a single arguments as the level', function() { - var level = 'level'; - assert.strictEqual(logger(level).level, level); - }); - - it('should default level to error', function() { - assert.strictEqual(logger().level, 'error'); - }); - - describe('formatting', function() { - var LEVEL = 'level-name'; - var TAG = 'tag-name'; - var MESSAGES = [ - 'message-1', - 'message-2' - ]; - - it('should correctly format without a tag', function() { - var formatted = logger().format(LEVEL, MESSAGES[0], MESSAGES[1]); - - assert.strictEqual(formatted, 'LEVEL-NAME message-1 message-2'); - }); - - it('should correctly format with a tag', function() { - var formatted = logger({ - tag: TAG - }).format(LEVEL, MESSAGES[0], MESSAGES[1]); - - assert.strictEqual(formatted, 'LEVEL-NAME:tag-name: message-1 message-2'); - }); - }); -}); diff --git a/packages/common/test/operation.js b/packages/common/test/operation.js deleted file mode 100644 index e413a5f662c..00000000000 --- a/packages/common/test/operation.js +++ /dev/null @@ -1,395 +0,0 @@ -/*! - * Copyright 2016 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 EventEmitter = require('events').EventEmitter; -var proxyquire = require('proxyquire'); - -var util = require('../src/util.js'); - -var fakeModelo = { - inherits: function() { - this.calledWith_ = arguments; - return require('modelo').inherits.apply(this, arguments); - } -}; - -function FakeServiceObject() { - this.serviceObjectArguments_ = arguments; -} - -describe('Operation', function() { - var FAKE_SERVICE = {}; - var OPERATION_ID = '/a/b/c/d'; - - var Operation; - var operation; - - before(function() { - Operation = proxyquire('../src/operation.js', { - modelo: fakeModelo, - './service-object.js': FakeServiceObject - }); - }); - - beforeEach(function() { - operation = new Operation({ - parent: FAKE_SERVICE, - id: OPERATION_ID - }); - operation.Promise = Promise; - }); - - describe('instantiation', function() { - it('should extend ServiceObject and EventEmitter', function() { - var args = fakeModelo.calledWith_; - - assert.strictEqual(args[0], Operation); - assert.strictEqual(args[1], FakeServiceObject); - assert.strictEqual(args[2], EventEmitter); - }); - - it('should pass ServiceObject the correct config', function() { - var config = operation.serviceObjectArguments_[0]; - - assert.strictEqual(config.baseUrl, ''); - assert.strictEqual(config.parent, FAKE_SERVICE); - assert.strictEqual(config.id, OPERATION_ID); - - assert.deepEqual(config.methods, { - exists: true, - get: true, - getMetadata: { - reqOpts: { - name: OPERATION_ID - } - } - }); - }); - - it('should allow overriding baseUrl', function() { - var baseUrl = 'baseUrl'; - - var operation = new Operation({ - baseUrl: baseUrl - }); - - assert.strictEqual(operation.serviceObjectArguments_[0].baseUrl, baseUrl); - }); - - it('should localize listener variables', function() { - assert.strictEqual(operation.completeListeners, 0); - assert.strictEqual(operation.hasActiveListeners, false); - }); - - it('should call listenForEvents_', function() { - var listenForEvents = Operation.prototype.listenForEvents_; - var called = false; - - Operation.prototype.listenForEvents_ = function() { - called = true; - }; - - new Operation(FAKE_SERVICE, OPERATION_ID); - assert.strictEqual(called, true); - Operation.prototype.listenForEvents_ = listenForEvents; - }); - }); - - describe('promise', function() { - beforeEach(function() { - operation.startPolling_ = util.noop; - }); - - it('should return an instance of the localized Promise', function() { - var FakePromise = operation.Promise = function() {}; - var promise = operation.promise(); - - assert(promise instanceof FakePromise); - }); - - it('should reject the promise if an error occurs', function() { - var error = new Error('err'); - - setImmediate(function() { - operation.emit('error', error); - }); - - return operation.promise().then(function() { - throw new Error('Promise should have been rejected.'); - }, function(err) { - assert.strictEqual(err, error); - }); - }); - - it('should resolve the promise on complete', function() { - var metadata = {}; - - setImmediate(function() { - operation.emit('complete', metadata); - }); - - return operation.promise().then(function(data) { - assert.deepEqual(data, [metadata]); - }); - }); - }); - - describe('listenForEvents_', function() { - beforeEach(function() { - operation.startPolling_ = util.noop; - }); - - it('should start polling when complete listener is bound', function(done) { - operation.startPolling_ = function() { - done(); - }; - - operation.on('complete', util.noop); - }); - - it('should track the number of listeners', function() { - assert.strictEqual(operation.completeListeners, 0); - - operation.on('complete', util.noop); - assert.strictEqual(operation.completeListeners, 1); - - operation.removeListener('complete', util.noop); - assert.strictEqual(operation.completeListeners, 0); - }); - - it('should only run a single pulling loop', function() { - var startPollingCallCount = 0; - - operation.startPolling_ = function() { - startPollingCallCount++; - }; - - operation.on('complete', util.noop); - operation.on('complete', util.noop); - - assert.strictEqual(startPollingCallCount, 1); - }); - - it('should close when no more message listeners are bound', function() { - operation.on('complete', util.noop); - operation.on('complete', util.noop); - assert.strictEqual(operation.hasActiveListeners, true); - - operation.removeListener('complete', util.noop); - assert.strictEqual(operation.hasActiveListeners, true); - - operation.removeListener('complete', util.noop); - assert.strictEqual(operation.hasActiveListeners, false); - }); - }); - - describe('poll_', function() { - it('should call getMetdata', function(done) { - operation.getMetadata = function() { - done(); - }; - - operation.poll_(assert.ifError); - }); - - describe('could not get metadata', function() { - it('should callback with an error', function(done) { - var error = new Error('Error.'); - - operation.getMetadata = function(callback) { - callback(error); - }; - - operation.poll_(function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should callback with the operation error', function(done) { - var apiResponse = { - error: {} - }; - - operation.getMetadata = function(callback) { - callback(null, apiResponse, apiResponse); - }; - - operation.poll_(function(err) { - assert.strictEqual(err, apiResponse.error); - done(); - }); - }); - }); - - describe('operation incomplete', function() { - var apiResponse = { done: false }; - - beforeEach(function() { - operation.getMetadata = function(callback) { - callback(null, apiResponse); - }; - }); - - it('should callback with no arguments', function(done) { - operation.poll_(function(err, resp) { - assert.strictEqual(err, undefined); - assert.strictEqual(resp, undefined); - done(); - }); - }); - }); - - describe('operation complete', function() { - var apiResponse = { done: true }; - - beforeEach(function() { - operation.getMetadata = function(callback) { - callback(null, apiResponse); - }; - }); - - it('should emit complete with metadata', function(done) { - operation.poll_(function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp, apiResponse); - done(); - }); - }); - }); - }); - - describe('startPolling_', function() { - var listenForEvents_; - - before(function() { - listenForEvents_ = Operation.prototype.listenForEvents_; - }); - - after(function() { - Operation.prototype.listenForEvents_ = listenForEvents_; - }); - - beforeEach(function() { - Operation.prototype.listenForEvents_ = util.noop; - operation.hasActiveListeners = true; - }); - - afterEach(function() { - operation.hasActiveListeners = false; - }); - - it('should not call getMetadata if no listeners', function(done) { - operation.hasActiveListeners = false; - - operation.getMetadata = done; // if called, test will fail. - - operation.startPolling_(); - done(); - }); - - it('should call getMetadata if listeners are registered', function(done) { - operation.hasActiveListeners = true; - - operation.getMetadata = function() { - done(); - }; - - operation.startPolling_(); - }); - - describe('API error', function() { - var error = new Error('Error.'); - - beforeEach(function() { - operation.getMetadata = function(callback) { - callback(error); - }; - }); - - it('should emit the error', function(done) { - operation.on('error', function(err) { - assert.strictEqual(err, error); - done(); - }); - - operation.startPolling_(); - }); - }); - - describe('operation pending', function() { - var apiResponse = { done: false }; - var setTimeoutCached = global.setTimeout; - - beforeEach(function() { - operation.getMetadata = function(callback) { - callback(null, apiResponse, apiResponse); - }; - }); - - after(function() { - global.setTimeout = setTimeoutCached; - }); - - it('should call startPolling_ after 500 ms', function(done) { - var startPolling_ = operation.startPolling_; - var startPollingCalled = false; - - global.setTimeout = function(fn, timeoutMs) { - fn(); // should call startPolling_ - assert.strictEqual(timeoutMs, 500); - }; - - operation.startPolling_ = function() { - if (!startPollingCalled) { - // Call #1. - startPollingCalled = true; - startPolling_.apply(this, arguments); - return; - } - - // This is from the setTimeout call. - assert.strictEqual(this, operation); - done(); - }; - - operation.startPolling_(); - }); - }); - - describe('operation complete', function() { - var apiResponse = { done: true }; - - beforeEach(function() { - operation.getMetadata = function(callback) { - callback(null, apiResponse, apiResponse); - }; - }); - - it('should emit complete with metadata', function(done) { - operation.on('complete', function(metadata) { - assert.strictEqual(metadata, apiResponse); - done(); - }); - - operation.startPolling_(); - }); - }); - }); -}); diff --git a/packages/common/test/paginator.js b/packages/common/test/paginator.js deleted file mode 100644 index 7432322e565..00000000000 --- a/packages/common/test/paginator.js +++ /dev/null @@ -1,605 +0,0 @@ -/** - * 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 proxyquire = require('proxyquire'); -var stream = require('stream'); -var through = require('through2'); -var uuid = require('uuid'); - -var paginator = require('../src/paginator.js'); -var util = extend({}, require('../src/util.js')); - -var overrides = {}; - -function override(name, object) { - var cachedObject = extend({}, object); - overrides[name] = {}; - - Object.keys(object).forEach(function(methodName) { - if (typeof object[methodName] !== 'function') { - return; - } - - object[methodName] = function() { - var args = arguments; - - if (overrides[name][methodName]) { - return overrides[name][methodName].apply(this, args); - } - - return cachedObject[methodName].apply(this, args); - }; - }); -} - -function resetOverrides() { - overrides = Object.keys(overrides).reduce(function(acc, name) { - acc[name] = {}; - return acc; - }, {}); -} - -describe('paginator', function() { - var UUID = uuid.v1(); - - function FakeClass() {} - - before(function() { - override('util', util); - paginator = proxyquire('../src/paginator.js', { - './util.js': util - }); - override('paginator', paginator); - }); - - beforeEach(function() { - FakeClass.prototype = { methodToExtend: function() { return UUID; } }; - resetOverrides(); - }); - - after(function() { - resetOverrides(); - }); - - describe('extend', function() { - it('should overwrite a method on a class', function() { - var originalMethod = FakeClass.prototype.methodToExtend; - paginator.extend(FakeClass, 'methodToExtend'); - var overwrittenMethod = FakeClass.prototype.methodToExtend; - - assert.notEqual(originalMethod, overwrittenMethod); - }); - - it('should store the original method as a private member', function() { - var originalMethod = FakeClass.prototype.methodToExtend; - paginator.extend(FakeClass, 'methodToExtend'); - - assert.strictEqual(originalMethod, FakeClass.prototype.methodToExtend_); - }); - - it('should accept an array or string method names', function() { - var originalMethod = FakeClass.prototype.methodToExtend; - - FakeClass.prototype.anotherMethodToExtend = function() {}; - var anotherMethod = FakeClass.prototype.anotherMethodToExtend; - - var methodsToExtend = ['methodToExtend', 'anotherMethodToExtend']; - paginator.extend(FakeClass, methodsToExtend); - - assert.notEqual(originalMethod, FakeClass.prototype.methodToExtend); - assert.notEqual(anotherMethod, FakeClass.prototype.anotherMethodToExtend); - }); - - it('should parse the arguments', function(done) { - overrides.paginator.parseArguments_ = function(args) { - assert.deepEqual([].slice.call(args), [1, 2, 3]); - done(); - }; - - overrides.paginator.run_ = util.noop; - - paginator.extend(FakeClass, 'methodToExtend'); - FakeClass.prototype.methodToExtend(1, 2, 3); - }); - - it('should call router when the original method is called', function(done) { - var expectedReturnValue = FakeClass.prototype.methodToExtend(); - var parsedArguments = { a: 'b', c: 'd' }; - - overrides.paginator.parseArguments_ = function() { - return parsedArguments; - }; - - overrides.paginator.run_ = function(args, originalMethod) { - assert.strictEqual(args, parsedArguments); - assert.equal(originalMethod(), expectedReturnValue); - done(); - }; - - paginator.extend(FakeClass, 'methodToExtend'); - FakeClass.prototype.methodToExtend(); - }); - - it('should maintain `this` context', function(done) { - FakeClass.prototype.methodToExtend = function() { return this.uuid; }; - - var cls = new FakeClass(); - cls.uuid = uuid.v1(); - - overrides.paginator.run_ = function(args, originalMethod) { - assert.equal(originalMethod(), cls.uuid); - done(); - }; - - paginator.extend(FakeClass, 'methodToExtend'); - cls.methodToExtend(); - }); - - it('should return what the router returns', function() { - var uniqueValue = 234; - overrides.paginator.run_ = function() { - return uniqueValue; - }; - - paginator.extend(FakeClass, 'methodToExtend'); - assert.equal(FakeClass.prototype.methodToExtend(), uniqueValue); - }); - }); - - describe('streamify', function() { - beforeEach(function() { - FakeClass.prototype.streamMethod = paginator.streamify('methodToExtend'); - }); - - it('should return a function', function() { - var fakeStreamMethod = FakeClass.prototype.streamMethod; - assert.strictEqual(typeof fakeStreamMethod, 'function'); - }); - - it('should parse the arguments', function(done) { - var fakeArgs = [1, 2, 3]; - - overrides.paginator.parseArguments_ = function(args) { - assert.deepEqual(fakeArgs, [].slice.call(args)); - done(); - }; - - overrides.paginator.runAsStream_ = util.noop; - FakeClass.prototype.streamMethod.apply(FakeClass.prototype, fakeArgs); - }); - - it('should run the method as a stream', function(done) { - var parsedArguments = { a: 'b', c: 'd' }; - - overrides.paginator.parseArguments_ = function() { - return parsedArguments; - }; - - overrides.paginator.runAsStream_ = function(args, callback) { - assert.strictEqual(args, parsedArguments); - assert.strictEqual(callback(), UUID); - done(); - }; - - FakeClass.prototype.streamMethod(); - }); - - it('should apply the proper context', function(done) { - var parsedArguments = { a: 'b', c: 'd' }; - - FakeClass.prototype.methodToExtend = function() { return this; }; - - overrides.paginator.parseArguments_ = function() { - return parsedArguments; - }; - - overrides.paginator.runAsStream_ = function(args, callback) { - assert.strictEqual(callback(), FakeClass.prototype); - done(); - }; - - FakeClass.prototype.streamMethod(); - }); - - it('should check for a private member', function(done) { - var parsedArguments = { a: 'b', c: 'd' }; - var fakeValue = 123; - - FakeClass.prototype.methodToExtend_ = function() { return fakeValue; }; - - overrides.paginator.parseArguments_ = function() { - return parsedArguments; - }; - - overrides.paginator.runAsStream_ = function(args, callback) { - assert.strictEqual(callback(), fakeValue); - done(); - }; - - FakeClass.prototype.streamMethod(); - }); - - it('should return a stream', function() { - var fakeStream = through.obj(); - - overrides.paginator.parseArguments_ = util.noop; - - overrides.paginator.runAsStream_ = function() { - return fakeStream; - }; - - var stream = FakeClass.prototype.streamMethod(); - - assert.strictEqual(fakeStream, stream); - }); - }); - - describe('parseArguments_', function() { - it('should set defaults', function() { - var parsedArguments = paginator.parseArguments_([]); - - assert.strictEqual(Object.keys(parsedArguments.query).length, 0); - assert.strictEqual(parsedArguments.autoPaginate, true); - assert.strictEqual(parsedArguments.maxApiCalls, -1); - assert.strictEqual(parsedArguments.maxResults, -1); - assert.strictEqual(parsedArguments.callback, undefined); - }); - - it('should detect a callback if first argument is a function', function() { - var args = [ util.noop ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.callback, args[0]); - }); - - it('should use any other first argument as query', function() { - var args = [ 'string' ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.query, args[0]); - }); - - it('should not make an undefined value the query', function() { - var args = [ undefined, util.noop ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.deepEqual(parsedArguments.query, {}); - }); - - it('should detect a callback if last argument is a function', function() { - var args = [ 'string', util.noop ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.callback, args[1]); - }); - - it('should not assign a callback if a fn is not provided', function() { - var args = [ 'string' ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.callback, undefined); - }); - - it('should set maxApiCalls from query.maxApiCalls', function() { - var args = [ { maxApiCalls: 10 } ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.maxApiCalls, args[0].maxApiCalls); - assert.strictEqual(parsedArguments.query.maxApiCalls, undefined); - }); - - it('should set maxResults from query.maxResults', function() { - var args = [ { maxResults: 10 } ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.maxResults, args[0].maxResults); - }); - - it('should set maxResults from query.pageSize', function() { - var args = [ { pageSize: 10 } ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.maxResults, args[0].pageSize); - }); - - it('should set autoPaginate: false if there is a maxResults', function() { - var args = [ { maxResults: 10 }, util.noop ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.autoPaginate, false); - }); - - it('should set autoPaginate: false query.autoPaginate', function() { - var args = [ { autoPaginate: false }, util.noop ]; - var parsedArguments = paginator.parseArguments_(args); - - assert.strictEqual(parsedArguments.autoPaginate, false); - }); - }); - - describe('run_', function() { - beforeEach(function() { - overrides.paginator.runAsStream_ = util.noop; - }); - - describe('autoPaginate', function() { - it('should call runAsStream_ when autoPaginate:true', function(done) { - var parsedArguments = { - autoPaginate: true, - callback: util.noop - }; - - overrides.paginator.runAsStream_ = function(args, originalMethod) { - assert.strictEqual(args, parsedArguments); - originalMethod(); - return through(); - }; - - paginator.run_(parsedArguments, done); - }); - - it('should execute callback on error', function(done) { - var error = new Error('Error.'); - - var parsedArguments = { - autoPaginate: true, - callback: function(err) { - assert.strictEqual(err, error); - done(); - } - }; - - overrides.paginator.runAsStream_ = function() { - var stream = through(); - setImmediate(function() { - stream.emit('error', error); - }); - return stream; - }; - - paginator.run_(parsedArguments, util.noop); - }); - - it('should return all results on end', function(done) { - var results = ['a', 'b', 'c']; - - var parsedArguments = { - autoPaginate: true, - callback: function(err, results_) { - assert.deepEqual(results_.toString().split(''), results); - done(); - } - }; - - overrides.paginator.runAsStream_ = function() { - var stream = through(); - - setImmediate(function() { - results.forEach(function(result) { - stream.push(result); - }); - - stream.push(null); - }); - - return stream; - }; - - paginator.run_(parsedArguments, util.noop); - }); - }); - - describe('manual pagination', function() { - it('should recoginze autoPaginate: false', function(done) { - var parsedArguments = { - autoPaginate: false, - query: { - a: 'b', - c: 'd' - }, - callback: done - }; - - paginator.run_(parsedArguments, function(query, callback) { - assert.deepEqual(query, parsedArguments.query); - - callback(); - }); - }); - }); - }); - - describe('runAsStream_', function() { - var PARSED_ARGUMENTS = { - query: { - a: 'b', c: 'd' - } - }; - - beforeEach(function() { - overrides.util.createLimiter = function(makeRequest) { - var transformStream = new stream.Transform({ objectMode: true }); - transformStream.destroy = through.obj().destroy.bind(transformStream); - - setImmediate(function() { - transformStream.emit('reading'); - }); - - return { - makeRequest: makeRequest, - stream: transformStream - }; - }; - }); - - it('should call original method when stream opens', function(done) { - function originalMethod(query) { - assert.strictEqual(query, PARSED_ARGUMENTS.query); - done(); - } - - paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - }); - - it('should emit an error if one occurs', function(done) { - var error = new Error('Error.'); - - function originalMethod(query, callback) { - setImmediate(function() { - callback(error); - }); - } - - var rs = paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - rs.on('error', function(err) { - assert.deepEqual(err, error); - done(); - }); - }); - - it('should push results onto the stream', function(done) { - var results = ['a', 'b', 'c']; - var resultsReceived = []; - - function originalMethod(query, callback) { - setImmediate(function() { - callback(null, results); - }); - } - - var rs = paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - rs.on('data', function(result) { - resultsReceived.push(result); - }); - rs.on('end', function() { - assert.deepEqual(resultsReceived, ['a', 'b', 'c']); - done(); - }); - }); - - describe('maxApiCalls', function() { - var maxApiCalls = 10; - - it('should create a limiter', function(done) { - overrides.util.createLimiter = function(makeRequest, options) { - assert.strictEqual(options.maxApiCalls, maxApiCalls); - - setImmediate(done); - - return { - stream: through.obj() - }; - }; - - paginator.runAsStream_({ maxApiCalls: maxApiCalls }, util.noop); - }); - }); - - describe('limits', function() { - var limit = 1; - - function originalMethod(query, callback) { - setImmediate(function() { - callback(null, [1, 2, 3]); - }); - } - - it('should respect maxResults', function(done) { - var numResultsReceived = 0; - - paginator.runAsStream_({ maxResults: limit }, originalMethod) - .on('data', function() { numResultsReceived++; }) - .on('end', function() { - assert.strictEqual(numResultsReceived, limit); - done(); - }); - }); - }); - - it('should get more results if nextQuery exists', function(done) { - var nextQuery = { a: 'b', c: 'd' }; - var nextQuerySent = false; - - function originalMethod(query, callback) { - if (nextQuerySent) { - assert.deepEqual(query, nextQuery); - done(); - return; - } - - setImmediate(function() { - nextQuerySent = true; - callback(null, [], nextQuery); - }); - } - - paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - }); - - it('should not push more results if stream ends early', function(done) { - var results = ['a', 'b', 'c']; - - function originalMethod(query, callback) { - setImmediate(function() { - callback(null, results); - }); - } - - var rs = paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - rs.on('data', function(result) { - if (result === 'b') { - // Pre-maturely end the stream. - this.end(); - } - - assert.notEqual(result, 'c'); - }); - rs.on('end', function() { - done(); - }); - }); - - it('should not get more results if stream ends early', function(done) { - var results = ['a', 'b', 'c']; - - var originalMethodCalledCount = 0; - - function originalMethod(query, callback) { - originalMethodCalledCount++; - - setImmediate(function() { - callback(null, results, {}); - }); - } - - var rs = paginator.runAsStream_(PARSED_ARGUMENTS, originalMethod); - rs.on('data', function(result) { - if (result === 'b') { - // Pre-maturely end the stream. - this.end(); - } - }); - rs.on('end', function() { - assert.equal(originalMethodCalledCount, 1); - done(); - }); - }); - }); -}); diff --git a/packages/common/test/service-object.js b/packages/common/test/service-object.js deleted file mode 100644 index e0fa46188fe..00000000000 --- a/packages/common/test/service-object.js +++ /dev/null @@ -1,882 +0,0 @@ -/** - * 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 proxyquire = require('proxyquire'); - -var util = require('../src/util.js'); - -var promisified = false; -var fakeUtil = extend({}, util, { - promisifyAll: function(Class) { - if (Class.name === 'ServiceObject') { - promisified = true; - } - } -}); - -describe('ServiceObject', function() { - var ServiceObject; - var serviceObject; - var originalRequest; - - var CONFIG = { - baseUrl: 'base-url', - parent: {}, - id: 'id', - createMethod: util.noop - }; - - before(function() { - ServiceObject = proxyquire('../src/service-object.js', { - './util.js': fakeUtil - }); - - originalRequest = ServiceObject.prototype.request; - }); - - beforeEach(function() { - ServiceObject.prototype.request = originalRequest; - serviceObject = new ServiceObject(CONFIG); - }); - - describe('instantiation', function() { - it('should promisify all the things', function() { - assert(promisified); - }); - - 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 localize the methods', function() { - var methods = {}; - - var config = extend({}, CONFIG, { - methods: methods - }); - - var serviceObject = new ServiceObject(config); - - assert.strictEqual(serviceObject.methods, methods); - }); - - it('should default methods to an empty object', function() { - assert.deepEqual(serviceObject.methods, {}); - }); - - 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); - }); - - it('should localize the Promise object', function() { - var FakePromise = function() {}; - var config = extend({}, CONFIG, { - parent: { - Promise: FakePromise - } - }); - - var serviceObject = new ServiceObject(config); - - assert.strictEqual(serviceObject.Promise, FakePromise); - }); - }); - - 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, null, 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(); - }); - }); - - it('should execute callback with any amount of arguments', function(done) { - var config = extend({}, CONFIG, { - createMethod: createMethod - }); - var options = {}; - - var args = ['a', 'b', 'c', 'd', 'e', 'f']; - - function createMethod(id, options_, callback) { - callback.apply(null, args); - } - - var serviceObject = new ServiceObject(config); - serviceObject.create(options, function() { - assert.deepEqual([].slice.call(arguments), args); - 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 extend the request options with defaults', function(done) { - var method = { - reqOpts: { - method: 'override', - qs: { - custom: true - } - } - }; - - ServiceObject.prototype.request = function(reqOpts_) { - assert.strictEqual(reqOpts_.method, method.reqOpts.method); - assert.deepEqual(reqOpts_.qs, method.reqOpts.qs); - done(); - }; - - var serviceObject = new ServiceObject(CONFIG); - serviceObject.methods.delete = method; - serviceObject.delete(); - }); - - it('should not require a callback', function() { - ServiceObject.prototype.request = function(reqOpts, callback) { - callback(); - }; - - assert.doesNotThrow(function() { - serviceObject.delete(); - }); - }); - - 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 true if no error', function(done) { - serviceObject.get = function(callback) { - callback(); - }; - - serviceObject.exists(function(err, exists) { - assert.ifError(err); - assert.strictEqual(exists, true); - done(); - }); - }); - }); - - describe('get', function() { - it('should get the metadata', function(done) { - serviceObject.getMetadata = function() { - done(); - }; - - serviceObject.get(assert.ifError); - }); - - it('handles not getting a config', function(done) { - serviceObject.getMetadata = function() { - done(); - }; - serviceObject.get(undefined, 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('error', function() { - it('should execute callback with error & API response', function(done) { - var error = new Error('Error.'); - var apiResponse = {}; - - serviceObject.create = function(callback) { - serviceObject.get = function(config, callback) { - assert.deepEqual(config, {}); - callback(); // done() - }; - - callback(error, null, apiResponse); - }; - - serviceObject.get(AUTO_CREATE_CONFIG, function(err, instance, resp) { - assert.strictEqual(err, error); - assert.strictEqual(instance, null); - assert.strictEqual(resp, apiResponse); - done(); - }); - }); - - it('should refresh the metadata after a 409', function(done) { - var error = { - code: 409 - }; - - serviceObject.create = function(callback) { - serviceObject.get = function(config, callback) { - assert.deepEqual(config, {}); - callback(); // done() - }; - - callback(error); - }; - - 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 extend the request options with defaults', function(done) { - var method = { - reqOpts: { - method: 'override', - qs: { - custom: true - } - } - }; - - ServiceObject.prototype.request = function(reqOpts_) { - assert.strictEqual(reqOpts_.method, method.reqOpts.method); - assert.deepEqual(reqOpts_.qs, method.reqOpts.qs); - done(); - }; - - var serviceObject = new ServiceObject(CONFIG); - serviceObject.methods.getMetadata = method; - 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 extend the request options with defaults', function(done) { - var metadataDefault = { - a: 'b' - }; - - var metadata = { - c: 'd' - }; - - var method = { - reqOpts: { - method: 'override', - qs: { - custom: true - }, - json: metadataDefault - } - }; - - var expectedJson = extend(true, {}, metadataDefault, metadata); - - ServiceObject.prototype.request = function(reqOpts_) { - assert.strictEqual(reqOpts_.method, method.reqOpts.method); - assert.deepEqual(reqOpts_.qs, method.reqOpts.qs); - assert.deepEqual(reqOpts_.json, expectedJson); - done(); - }; - - var serviceObject = new ServiceObject(CONFIG); - serviceObject.methods.setMetadata = method; - 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('request_', function() { - var reqOpts; - - beforeEach(function() { - reqOpts = { - uri: 'uri' - }; - }); - - it('should compose the correct request', function(done) { - var expectedUri = [ - serviceObject.baseUrl, - serviceObject.id, - reqOpts.uri - ].join('/'); - - serviceObject.parent.request = function(reqOpts_, callback) { - assert.notStrictEqual(reqOpts_, reqOpts); - assert.strictEqual(reqOpts_.uri, expectedUri); - assert.deepEqual(reqOpts_.interceptors_, []); - callback(); // done() - }; - - serviceObject.request_(reqOpts, done); - }); - - it('should not require a service object ID', function(done) { - var expectedUri = [ - serviceObject.baseUrl, - reqOpts.uri - ].join('/'); - - serviceObject.parent.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, expectedUri); - done(); - }; - - delete serviceObject.id; - - serviceObject.request_(reqOpts, assert.ifError); - }); - - it('should support absolute uris', function(done) { - var expectedUri = 'http://www.google.com'; - - serviceObject.parent.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, expectedUri); - done(); - }; - - serviceObject.request_({ uri: expectedUri }, 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); - }); - - it('should extend interceptors from child ServiceObjects', function(done) { - var parent = new ServiceObject(CONFIG); - parent.interceptors.push({ - request: function(reqOpts) { - reqOpts.parent = true; - return reqOpts; - } - }); - - var child = new ServiceObject(extend({}, CONFIG, { parent: parent })); - child.interceptors.push({ - request: function(reqOpts) { - reqOpts.child = true; - return reqOpts; - } - }); - - parent.parent.request = function(reqOpts) { - assert.deepEqual(reqOpts.interceptors_[0].request({}), { - child: true - }); - - assert.deepEqual(reqOpts.interceptors_[1].request({}), { - parent: true - }); - - done(); - }; - - child.request_({ uri: '' }, assert.ifError); - }); - - it('should pass a clone of the interceptors', function(done) { - serviceObject.interceptors.push({ - request: function(reqOpts) { - reqOpts.one = true; - return reqOpts; - } - }); - - serviceObject.parent.request = function(reqOpts) { - var serviceObjectInterceptors = serviceObject.interceptors; - assert.deepEqual(reqOpts.interceptors_, serviceObjectInterceptors); - assert.notStrictEqual(reqOpts.interceptors_, serviceObjectInterceptors); - done(); - }; - - serviceObject.request_({ uri: '' }, assert.ifError); - }); - - it('should call the parent requestStream method', function() { - var fakeObj = {}; - - var expectedUri = [ - serviceObject.baseUrl, - serviceObject.id, - reqOpts.uri - ].join('/'); - - serviceObject.parent.requestStream = function(reqOpts_) { - assert.notStrictEqual(reqOpts_, reqOpts); - assert.strictEqual(reqOpts_.uri, expectedUri); - assert.deepEqual(reqOpts_.interceptors_, []); - return fakeObj; - }; - - var returnVal = serviceObject.request_(reqOpts); - assert.strictEqual(returnVal, fakeObj); - }); - }); - - describe('request', function() { - var request_; - - before(function() { - request_ = ServiceObject.prototype.request_; - }); - - after(function() { - ServiceObject.prototype.request_ = request_; - }); - - it('should call through to request_', function(done) { - var fakeOptions = {}; - - ServiceObject.prototype.request_ = function(reqOpts, callback) { - assert.strictEqual(reqOpts, fakeOptions); - callback(); - }; - - serviceObject.request_ = function() { - done(new Error('Should call to the prototype directly.')); - }; - - serviceObject.request(fakeOptions, done); - }); - }); - - describe('requestStream', function() { - var request_; - - before(function() { - request_ = ServiceObject.prototype.request_; - }); - - after(function() { - ServiceObject.prototype.request_ = request_; - }); - - it('should call through to request_', function() { - var fakeOptions = {}; - var fakeStream = {}; - - ServiceObject.prototype.request_ = function(reqOpts) { - assert.strictEqual(reqOpts, fakeOptions); - return fakeStream; - }; - - serviceObject.request_ = function() { - throw new Error('Should call to the prototype directly.'); - }; - - var stream = serviceObject.requestStream(fakeOptions); - assert.strictEqual(stream, fakeStream); - }); - }); -}); diff --git a/packages/common/test/service.js b/packages/common/test/service.js deleted file mode 100644 index 9822817d4af..00000000000 --- a/packages/common/test/service.js +++ /dev/null @@ -1,530 +0,0 @@ -/** - * 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 proxyquire = require('proxyquire').noPreserveCache(); - -var util = require('../src/util.js'); - -var makeAuthenticatedRequestFactoryCache = util.makeAuthenticatedRequestFactory; -var makeAuthenticatedRequestFactoryOverride; -util.makeAuthenticatedRequestFactory = function() { - if (makeAuthenticatedRequestFactoryOverride) { - return makeAuthenticatedRequestFactoryOverride.apply(this, arguments); - } - - return makeAuthenticatedRequestFactoryCache.apply(this, arguments); -}; - -describe('Service', function() { - var Service; - var service; - - var CONFIG = { - scopes: [], - baseUrl: 'base-url', - projectIdRequired: false, - packageJson: { - name: '@google-cloud/service', - version: '0.2.0' - } - }; - - var OPTIONS = { - credentials: {}, - keyFile: {}, - email: 'email', - projectId: 'project-id', - }; - - before(function() { - Service = proxyquire('../src/service.js', { - './util.js': util - }); - }); - - beforeEach(function() { - makeAuthenticatedRequestFactoryOverride = null; - service = new Service(CONFIG, OPTIONS); - }); - - describe('instantiation', function() { - it('should not require options', function() { - assert.doesNotThrow(function() { - new Service(CONFIG); - }); - }); - - it('should create an authenticated request factory', function() { - var authenticatedRequest = {}; - - makeAuthenticatedRequestFactoryOverride = function(config) { - var expectedConfig = extend({}, CONFIG, { - credentials: OPTIONS.credentials, - keyFile: OPTIONS.keyFilename, - email: OPTIONS.email, - projectIdRequired: CONFIG.projectIdRequired, - projectId: OPTIONS.projectId - }); - - assert.deepEqual(config, expectedConfig); - - 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 default globalInterceptors to an empty array', function() { - assert.deepEqual(service.globalInterceptors, []); - }); - - it('should preserve the original global interceptors', function() { - var globalInterceptors = []; - - var options = extend({}, OPTIONS); - options.interceptors_ = globalInterceptors; - - var service = new Service({}, options); - assert.strictEqual(service.globalInterceptors, globalInterceptors); - }); - - it('should default interceptors to an empty array', function() { - assert.deepEqual(service.interceptors, []); - }); - - it('should localize package.json', function() { - assert.strictEqual(service.packageJson, CONFIG.packageJson); - }); - - it('should localize the projectId', function() { - assert.strictEqual(service.projectId, OPTIONS.projectId); - }); - - it('should default projectId with placeholder', function() { - var service = new Service({}, {}); - assert.strictEqual(service.projectId, '{{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); - }); - - it('should localize the Promise object', function() { - var FakePromise = function() {}; - var service = new Service({}, { promise: FakePromise }); - - assert.strictEqual(service.Promise, FakePromise); - }); - - it('should localize the native Promise object by default', function() { - assert.strictEqual(service.Promise, global.Promise); - }); - - it('should disable forever agent for Cloud Function envs', function() { - process.env.FUNCTION_NAME = 'cloud-function-name'; - var service = new Service(CONFIG, OPTIONS); - delete process.env.FUNCTION_NAME; - - var interceptor = service.interceptors[0]; - - var modifiedReqOpts = interceptor.request({ forever: true }); - assert.strictEqual(modifiedReqOpts.forever, false); - }); - }); - - describe('getProjectId', function() { - it('should get the project ID from the auth client', function(done) { - service.authClient = { - getProjectId: function() { - done(); - } - }; - - service.getProjectId(assert.ifError); - }); - - it('should return error from auth client', function(done) { - var error = new Error('Error.'); - - service.authClient = { - getProjectId: function(callback) { - callback(error); - } - }; - - service.getProjectId(function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should update and return the project ID if found', function(done) { - var service = new Service({}, {}); - var projectId = 'detected-project-id'; - - service.authClient = { - getProjectId: function(callback) { - callback(null, projectId); - } - }; - - service.getProjectId(function(err, projectId_) { - assert.ifError(err); - assert.strictEqual(service.projectId, projectId); - assert.strictEqual(projectId_, projectId); - done(); - }); - }); - }); - - describe('request_', function() { - var reqOpts; - - beforeEach(function() { - reqOpts = { - uri: 'uri' - }; - }); - - it('should compose the correct request', function(done) { - var expectedUri = [ - service.baseUrl, - reqOpts.uri - ].join('/'); - - service.makeAuthenticatedRequest = function(reqOpts_, callback) { - assert.notStrictEqual(reqOpts_, reqOpts); - assert.strictEqual(reqOpts_.uri, expectedUri); - assert.strictEqual(reqOpts.interceptors_, undefined); - callback(); // done() - }; - - service.request_(reqOpts, done); - }); - - it('should support absolute uris', function(done) { - var expectedUri = 'http://www.google.com'; - - service.makeAuthenticatedRequest = function(reqOpts) { - assert.strictEqual(reqOpts.uri, expectedUri); - done(); - }; - - service.request_({ uri: expectedUri }, 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); - }); - - it('should add the User Agent', function(done) { - var userAgent = 'user-agent/0.0.0'; - - var getUserAgentFn = util.getUserAgentFromPackageJson; - util.getUserAgentFromPackageJson = function(packageJson) { - util.getUserAgentFromPackageJson = getUserAgentFn; - assert.strictEqual(packageJson, service.packageJson); - return userAgent; - }; - - service.makeAuthenticatedRequest = function(reqOpts) { - assert.strictEqual(reqOpts.headers['User-Agent'], userAgent); - done(); - }; - - service.request_(reqOpts, assert.ifError); - }); - - it('should add the api-client header', function(done) { - service.makeAuthenticatedRequest = function(reqOpts) { - var pkg = service.packageJson; - assert.strictEqual( - reqOpts.headers['x-goog-api-client'], - `gl-node/${process.versions.node} gccl/${pkg.version}` - ); - 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); - }); - }); - }); - - describe('request interceptors', function() { - it('should call the request interceptors in order', function(done) { - var reqOpts = { - uri: '', - interceptors_: [] - }; - - // Called first. - service.globalInterceptors.push({ - request: function(reqOpts) { - reqOpts.order = '1'; - return reqOpts; - } - }); - - // Called third. - service.interceptors.push({ - request: function(reqOpts) { - reqOpts.order += '3'; - return reqOpts; - } - }); - - // Called second. - service.globalInterceptors.push({ - request: function(reqOpts) { - reqOpts.order += '2'; - return reqOpts; - } - }); - - // Called fifth. - reqOpts.interceptors_.push({ - request: function(reqOpts) { - reqOpts.order += '5'; - return reqOpts; - } - }); - - // Called fourth. - service.interceptors.push({ - request: function(reqOpts) { - reqOpts.order += '4'; - return reqOpts; - } - }); - - // Called sixth. - reqOpts.interceptors_.push({ - request: function(reqOpts) { - reqOpts.order += '6'; - return reqOpts; - } - }); - - service.makeAuthenticatedRequest = function(reqOpts) { - assert.strictEqual(reqOpts.order, '123456'); - done(); - }; - - service.request_(reqOpts, assert.ifError); - }); - - it('should not affect original interceptor arrays', function(done) { - function request(reqOpts) { return reqOpts; } - - var globalInterceptors = [{ request: request }]; - var localInterceptors = [{ request: request }]; - var requestInterceptors = [{ request: request }]; - - var originalGlobalInterceptors = [].slice.call(globalInterceptors); - var originalLocalInterceptors = [].slice.call(localInterceptors); - var originalRequestInterceptors = [].slice.call(requestInterceptors); - - service.makeAuthenticatedRequest = function() { - assert.deepEqual(globalInterceptors, originalGlobalInterceptors); - assert.deepEqual(localInterceptors, originalLocalInterceptors); - assert.deepEqual(requestInterceptors, originalRequestInterceptors); - done(); - }; - - service.request_({ - uri: '', - interceptors_: requestInterceptors - }, assert.ifError); - }); - - it('should not call unrelated interceptors', function(done) { - service.interceptors.push({ - anotherInterceptor: function() { - done(); // Will throw. - }, - request: function() { - setImmediate(done); - return {}; - } - }); - - service.makeAuthenticatedRequest = util.noop; - - service.request_({ uri: '' }, assert.ifError); - }); - }); - }); - - describe('request', function() { - var request_; - - before(function() { - request_ = Service.prototype.request_; - }); - - after(function() { - Service.prototype.request_ = request_; - }); - - it('should call through to _request', function(done) { - var fakeOpts = {}; - - Service.prototype.request_ = function(reqOpts, callback) { - assert.strictEqual(reqOpts, fakeOpts); - callback(); - }; - - service.request(fakeOpts, done); - }); - }); - - describe('requestStream', function() { - var request_; - - before(function() { - request_ = Service.prototype.request_; - }); - - after(function() { - Service.prototype.request_ = request_; - }); - - it('should return whatever _request returns', function() { - var fakeOpts = {}; - var fakeStream = {}; - - Service.prototype.request_ = function(reqOpts) { - assert.strictEqual(reqOpts, fakeOpts); - return fakeStream; - }; - - var stream = service.requestStream(fakeOpts); - assert.strictEqual(stream, fakeStream); - }); - }); -}); diff --git a/packages/common/test/util.js b/packages/common/test/util.js deleted file mode 100644 index af3c23bd015..00000000000 --- a/packages/common/test/util.js +++ /dev/null @@ -1,1854 +0,0 @@ -/** - * Copyright 2014 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http:// www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict'; - -var assert = require('assert'); -var duplexify; -var extend = require('extend'); -var format = require('string-format-obj'); -var googleAuth = require('google-auto-auth'); -var is = require('is'); -var proxyquire = require('proxyquire'); -var request = require('request'); -var retryRequest = require('retry-request'); -var stream = require('stream'); -var streamEvents = require('stream-events'); - -var googleAutoAuthOverride; -function fakeGoogleAutoAuth() { - return (googleAutoAuthOverride || googleAuth).apply(null, arguments); -} - -var REQUEST_DEFAULT_CONF; -var requestOverride; -function fakeRequest() { - return (requestOverride || request).apply(null, arguments); -} -fakeRequest.defaults = function(defaultConfiguration) { - // Ignore the default values, so we don't have to test for them in every API - // call. - REQUEST_DEFAULT_CONF = defaultConfiguration; - return fakeRequest; -}; - -var retryRequestOverride; -function fakeRetryRequest() { - return (retryRequestOverride || retryRequest).apply(null, arguments); -} - -var streamEventsOverride; -function fakeStreamEvents() { - return (streamEventsOverride || streamEvents).apply(null, arguments); -} - -describe('common/util', function() { - var util; - var utilOverrides = {}; - - before(function() { - util = proxyquire('../src/util', { - 'google-auto-auth': fakeGoogleAutoAuth, - request: fakeRequest, - 'retry-request': fakeRetryRequest, - 'stream-events': fakeStreamEvents - }); - var utilCached = extend(true, {}, util); - - // Override all util methods, allowing them to be mocked. Overrides are - // removed before each test. - Object.keys(util).forEach(function(utilMethod) { - if (typeof util[utilMethod] !== 'function') { - return; - } - - util[utilMethod] = function() { - return (utilOverrides[utilMethod] || utilCached[utilMethod]) - .apply(this, arguments); - }; - }); - - duplexify = require('duplexify'); - }); - - beforeEach(function() { - googleAutoAuthOverride = null; - requestOverride = null; - retryRequestOverride = null; - streamEventsOverride = null; - utilOverrides = {}; - }); - - it('should have set correct defaults on Request', function() { - assert.deepEqual(REQUEST_DEFAULT_CONF, { - timeout: 60000, - gzip: true, - forever: true, - pool: { - maxSockets: Infinity - } - }); - }); - - it('should export an error for module instantiation errors', function() { - var errorMessage = format([ - 'Sorry, we cannot connect to Cloud Services without a project ID.', - 'You may specify one with an environment variable named', - '"GCLOUD_PROJECT". See {baseUrl}/{path} for a detailed guide on creating', - 'an authenticated connection.' - ].join(' '), { - baseUrl: 'https://googlecloudplatform.github.io/google-cloud-node/#', - path: 'docs/guides/authentication' - }); - - var missingProjectIdError = new Error(errorMessage); - assert.deepEqual(util.missingProjectIdError, missingProjectIdError); - }); - - describe('ApiError', function() { - it('should build correct ApiError', function() { - var error = { - errors: [ new Error(), new Error() ], - code: 100, - message: 'Uh oh', - response: { a: 'b', c: 'd' } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.errors, error.errors); - assert.strictEqual(apiError.code, error.code); - assert.strictEqual(apiError.message, error.message); - assert.strictEqual(apiError.response, error.response); - }); - - it('should detect ApiError message from response body', function() { - var errorMessage = 'API error message'; - - var error = { - errors: [ new Error(errorMessage) ], - code: 100, - response: { a: 'b', c: 'd' } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, errorMessage); - }); - - it('should parse the response body for errors', function() { - var error = new Error('Error.'); - var errors = [error, error]; - - var errorBody = { - response: { - body: JSON.stringify({ - error: { - errors: errors - } - }) - } - }; - - var apiError = new util.ApiError(errorBody); - - assert.deepEqual(apiError.errors, errors); - }); - - it('should append the custom error message', function() { - var errorMessage = 'API error message'; - var customErrorMessage = 'Custom error message'; - var expectedErrorMessage = [customErrorMessage, errorMessage].join(' - '); - - var error = { - errors: [ new Error(errorMessage) ], - code: 100, - response: { a: 'b', c: 'd' }, - message: customErrorMessage - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, expectedErrorMessage); - }); - - it('should parse and append the decoded response body', function() { - var errorMessage = 'API error message'; - var responseBodyMsg = 'Response body message <'; - var expectedErrorMessage = [ - errorMessage, - 'Response body message <' - ].join(' - '); - - var error = { - message: errorMessage, - code: 100, - response: { - body: new Buffer(responseBodyMsg) - } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, expectedErrorMessage); - }); - - it('should use default message if there are no errors', function() { - var expectedErrorMessage = 'Error during request.'; - - var error = { - code: 100, - response: { a: 'b', c: 'd' } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, expectedErrorMessage); - }); - - it('should use default message if too many errors', function() { - var expectedErrorMessage = 'Error during request.'; - - var error = { - errors: [ new Error(), new Error() ], - code: 100, - response: { a: 'b', c: 'd' } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, expectedErrorMessage); - }); - - it('should filter out duplicate errors', function() { - var expectedErrorMessage = 'Error during request.'; - - var error = { - code: 100, - message: expectedErrorMessage, - response: { - body: expectedErrorMessage - } - }; - - var apiError = new util.ApiError(error); - - assert.strictEqual(apiError.message, expectedErrorMessage); - }); - }); - - describe('PartialFailureError', function() { - it('should build correct PartialFailureError', function() { - var error = { - errors: [ new Error(), new Error() ], - response: { a: 'b', c: 'd' }, - message: 'Partial failure occurred' - }; - - var partialFailureError = new util.PartialFailureError(error); - - assert.strictEqual(partialFailureError.errors, error.errors); - assert.strictEqual(partialFailureError.response, error.response); - assert.strictEqual(partialFailureError.message, error.message); - }); - - it('should use default message', function() { - var expectedErrorMessage = 'A failure occurred during this request.'; - - var error = { - errors: [], - response: { a: 'b', c: 'd' } - }; - - var partialFailureError = new util.PartialFailureError(error); - - assert.strictEqual(partialFailureError.message, expectedErrorMessage); - }); - }); - - describe('extendGlobalConfig', function() { - it('should favor `keyFilename` when `credentials` is global', function() { - var globalConfig = { credentials: {} }; - var options = util.extendGlobalConfig(globalConfig, { - keyFilename: 'key.json' - }); - assert.strictEqual(options.credentials, undefined); - }); - - it('should favor `credentials` when `keyFilename` is global', function() { - var globalConfig = { keyFilename: 'key.json' }; - var options = util.extendGlobalConfig(globalConfig, { credentials: {} }); - assert.strictEqual(options.keyFilename, undefined); - }); - - it('should honor the GCLOUD_PROJECT environment variable', function() { - var newProjectId = 'envvar-project-id'; - var cachedProjectId = process.env.GCLOUD_PROJECT; - process.env.GCLOUD_PROJECT = newProjectId; - - // No projectId specified: - var globalConfig = { keyFilename: 'key.json' }; - var overrides = {}; - - var options = util.extendGlobalConfig(globalConfig, overrides); - - if (cachedProjectId) { - process.env.GCLOUD_PROJECT = cachedProjectId; - } else { - delete process.env.GCLOUD_PROJECT; - } - - assert.strictEqual(options.projectId, newProjectId); - }); - - it('should not modify original object', function() { - var globalConfig = { keyFilename: 'key.json' }; - util.extendGlobalConfig(globalConfig, { credentials: {} }); - assert.deepEqual(globalConfig, { keyFilename: 'key.json' }); - }); - - it('should link the original interceptors_', function() { - var interceptors = []; - var globalConfig = { interceptors_: interceptors }; - util.extendGlobalConfig(globalConfig, {}); - assert.strictEqual(globalConfig.interceptors_, interceptors); - }); - }); - - describe('handleResp', function() { - it('should handle errors', function(done) { - var error = new Error('Error.'); - - util.handleResp(error, {}, null, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('uses a no-op callback if none is sent', function() { - util.handleResp(undefined, {}, ''); - }); - - it('should parse response', function(done) { - var err = { a: 'b', c: 'd' }; - var resp = { a: 'b', c: 'd' }; - var body = { a: 'b', c: 'd' }; - - var returnedErr = { a: 'b', c: 'd' }; - var returnedBody = { a: 'b', c: 'd' }; - var returnedResp = { a: 'b', c: 'd' }; - - utilOverrides.parseHttpRespMessage = function(resp_) { - assert.strictEqual(resp_, resp); - - return { - resp: returnedResp - }; - }; - - utilOverrides.parseHttpRespBody = function(body_) { - assert.strictEqual(body_, body); - - return { - body: returnedBody - }; - }; - - util.handleResp(err, resp, body, function(err, body, resp) { - assert.deepEqual(err, returnedErr); - assert.deepEqual(body, returnedBody); - assert.deepEqual(resp, returnedResp); - done(); - }); - }); - - it('should parse response for error', function(done) { - var error = new Error('Error.'); - - utilOverrides.parseHttpRespMessage = function() { - return { err: error }; - }; - - util.handleResp(null, {}, {}, function(err) { - assert.deepEqual(err, error); - done(); - }); - }); - - it('should parse body for error', function(done) { - var error = new Error('Error.'); - - utilOverrides.parseHttpRespBody = function() { - return { err: error }; - }; - - util.handleResp(null, {}, {}, function(err) { - assert.deepEqual(err, error); - done(); - }); - }); - - it('should not parse undefined response', function(done) { - utilOverrides.parseHttpRespMessage = function() { - done(); // Will throw. - }; - - util.handleResp(null, null, null, done); - }); - - it('should not parse undefined body', function(done) { - utilOverrides.parseHttpRespBody = function() { - done(); // Will throw. - }; - - util.handleResp(null, null, null, done); - }); - }); - - describe('parseHttpRespMessage', function() { - it('should build ApiError with non-200 status and message', function(done) { - var httpRespMessage = { statusCode: 400, statusMessage: 'Not Good' }; - - utilOverrides.ApiError = function(error_) { - assert.strictEqual(error_.code, httpRespMessage.statusCode); - assert.strictEqual(error_.message, httpRespMessage.statusMessage); - assert.strictEqual(error_.response, httpRespMessage); - - done(); - }; - - util.parseHttpRespMessage(httpRespMessage); - }); - - it('should return the original response message', function() { - var httpRespMessage = {}; - var parsedHttpRespMessage = util.parseHttpRespMessage(httpRespMessage); - assert.strictEqual(parsedHttpRespMessage.resp, httpRespMessage); - }); - }); - - describe('parseHttpRespBody', function() { - it('should detect body errors', function() { - var apiErr = { - errors: [{ message: 'bar' }], - code: 400, - message: 'an error occurred' - }; - - var parsedHttpRespBody = util.parseHttpRespBody({ error: apiErr }); - var expectedErrorMessage = [ - apiErr.message, - apiErr.errors[0].message - ].join(' - '); - - assert.deepEqual(parsedHttpRespBody.err.errors, apiErr.errors); - assert.strictEqual(parsedHttpRespBody.err.code, apiErr.code); - assert.deepEqual(parsedHttpRespBody.err.message, expectedErrorMessage); - }); - - it('should try to parse JSON if body is string', function() { - var httpRespBody = '{ "foo": "bar" }'; - var parsedHttpRespBody = util.parseHttpRespBody(httpRespBody); - - assert.strictEqual(parsedHttpRespBody.body.foo, 'bar'); - }); - - it('should return the original body', function() { - var httpRespBody = {}; - var parsedHttpRespBody = util.parseHttpRespBody(httpRespBody); - - assert.strictEqual(parsedHttpRespBody.body, httpRespBody); - }); - }); - - describe('makeWritableStream', function() { - it('should use defaults', function(done) { - var dup = duplexify(); - var metadata = { a: 'b', c: 'd' }; - - util.makeWritableStream(dup, { - metadata: metadata, - makeAuthenticatedRequest: function(request) { - assert.equal(request.method, 'POST'); - assert.equal(request.qs.uploadType, 'multipart'); - - assert.strictEqual(Array.isArray(request.multipart), true); - - var mp = request.multipart; - - assert.strictEqual(mp[0]['Content-Type'], 'application/json'); - assert.strictEqual(mp[0].body, JSON.stringify(metadata)); - - assert.strictEqual(mp[1]['Content-Type'], 'application/octet-stream'); - // (is a writable stream:) - assert.strictEqual(typeof mp[1].body._writableState, 'object'); - - done(); - } - }); - }); - - it('should allow overriding defaults', function(done) { - var dup = duplexify(); - - var req = { - method: 'PUT', - qs: { - uploadType: 'media' - }, - something: 'else' - }; - - util.makeWritableStream(dup, { - metadata: { - contentType: 'application/json' - }, - makeAuthenticatedRequest: function(request) { - assert.equal(request.method, req.method); - assert.deepEqual(request.qs, req.qs); - assert.equal(request.something, req.something); - - var mp = request.multipart; - assert.strictEqual(mp[1]['Content-Type'], 'application/json'); - - done(); - }, - - request: req - }); - }); - - it('should emit an error', function(done) { - var error = new Error('Error.'); - - var ws = duplexify(); - ws.on('error', function(err) { - assert.equal(err, error); - done(); - }); - - util.makeWritableStream(ws, { - makeAuthenticatedRequest: function(request, opts) { - opts.onAuthenticated(error); - } - }); - }); - - it('should set the writable stream', function(done) { - var dup = duplexify(); - - dup.setWritable = function() { - done(); - }; - - util.makeWritableStream(dup, { - makeAuthenticatedRequest: function() {} - }); - }); - - it('should emit an error if the request fails', function(done) { - var dup = duplexify(); - var fakeStream = new stream.Writable(); - var error = new Error('Error.'); - - fakeStream.write = function() {}; - dup.end = function() {}; - - utilOverrides.handleResp = function(err, res, body, callback) { - callback(error); - }; - - requestOverride = function(reqOpts, callback) { - callback(error); - }; - - dup.on('error', function(err) { - assert.strictEqual(err, error); - done(); - }); - - util.makeWritableStream(dup, { - makeAuthenticatedRequest: function(request, opts) { - opts.onAuthenticated(); - } - }); - - setImmediate(function() { - fakeStream.emit('complete', {}); - }); - }); - - it('should emit the response', function(done) { - var dup = duplexify(); - var fakeStream = new stream.Writable(); - var fakeResponse = {}; - - fakeStream.write = function() {}; - - utilOverrides.handleResp = function(err, res, body, callback) { - callback(); - }; - - requestOverride = function(reqOpts, callback) { - callback(null, fakeResponse); - }; - - var options = { - makeAuthenticatedRequest: function(request, opts) { - opts.onAuthenticated(); - } - }; - - dup.on('response', function(resp) { - assert.strictEqual(resp, fakeResponse); - done(); - }); - - util.makeWritableStream(dup, options, util.noop); - }); - - it('should pass back the response data to the callback', function(done) { - var dup = duplexify(); - var fakeStream = new stream.Writable(); - var fakeResponse = {}; - - fakeStream.write = function() {}; - - utilOverrides.handleResp = function(err, res, body, callback) { - callback(null, fakeResponse); - }; - - requestOverride = function(reqOpts, callback) { - callback(); - }; - - var options = { - makeAuthenticatedRequest: function(request, opts) { - opts.onAuthenticated(); - } - }; - - util.makeWritableStream(dup, options, function(data) { - assert.strictEqual(data, fakeResponse); - done(); - }); - - setImmediate(function() { - fakeStream.emit('complete', {}); - }); - }); - }); - - describe('makeAuthenticatedRequestFactory', function() { - var authClient = { - getCredentials: function() {}, - projectId: 'project-id' - }; - - beforeEach(function() { - googleAutoAuthOverride = function() { - return authClient; - }; - }); - - it('should create an authClient', function(done) { - var config = {}; - - googleAutoAuthOverride = function(config_) { - assert.strictEqual(config_, config); - setImmediate(done); - return authClient; - }; - - util.makeAuthenticatedRequestFactory(config); - }); - - it('should return a function', function() { - assert.equal(typeof util.makeAuthenticatedRequestFactory(), 'function'); - }); - - it('should return a getCredentials method', function(done) { - function getCredentials() { - done(); - } - - googleAutoAuthOverride = function() { - return { getCredentials: getCredentials }; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest.getCredentials(); - }); - - it('should return the authClient', function() { - var authClient = { getCredentials: function() {} }; - - googleAutoAuthOverride = function() { - return authClient; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - assert.strictEqual(makeAuthenticatedRequest.authClient, authClient); - }); - - describe('customEndpoint (no authentication attempted)', function() { - var makeAuthenticatedRequest; - var config = { - customEndpoint: true - }; - var expectedProjectId = authClient.projectId; - - beforeEach(function() { - makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(config); - }); - - it('should decorate the request', function(done) { - var reqOpts = { a: 'b', c: 'd' }; - var decoratedRequest = {}; - - utilOverrides.decorateRequest = function(reqOpts_, projectId) { - assert.strictEqual(reqOpts_, reqOpts); - assert.deepEqual(projectId, expectedProjectId); - return decoratedRequest; - }; - - makeAuthenticatedRequest(reqOpts, { - onAuthenticated: function(err, authenticatedReqOpts) { - assert.ifError(err); - assert.strictEqual(authenticatedReqOpts, decoratedRequest); - done(); - } - }); - }); - - it('should return missing projectId error', function(done) { - var reqOpts = { a: 'b', c: 'd' }; - - utilOverrides.decorateRequest = function() { - throw util.missingProjectIdError; - }; - - makeAuthenticatedRequest(reqOpts, { - onAuthenticated: function(err) { - assert.strictEqual(err, util.missingProjectIdError); - done(); - } - }); - }); - - it('should pass options back to callback', function(done) { - var reqOpts = { a: 'b', c: 'd' }; - - makeAuthenticatedRequest(reqOpts, { - onAuthenticated: function(err, authenticatedReqOpts) { - assert.ifError(err); - assert.deepEqual(reqOpts, authenticatedReqOpts); - done(); - } - }); - }); - - it('should not authenticate requests with a custom API', function(done) { - var reqOpts = { a: 'b', c: 'd' }; - - utilOverrides.makeRequest = function(rOpts) { - assert.deepEqual(rOpts, reqOpts); - done(); - }; - - makeAuthenticatedRequest(reqOpts, assert.ifError); - }); - }); - - describe('needs authentication', function() { - it('should pass correct args to authorizeRequest', function(done) { - var reqOpts = { e: 'f', g: 'h' }; - - authClient.authorizeRequest = function(rOpts) { - assert.deepEqual(rOpts, reqOpts); - done(); - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest(reqOpts, {}); - }); - - it('should return a stream if callback is missing', function() { - authClient.authorizeRequest = function() {}; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({}); - assert(makeAuthenticatedRequest({}) instanceof stream.Stream); - }); - - describe('projectId', function() { - it('should default to authClient projectId', function(done) { - authClient.projectId = 'authclient-project-id'; - - utilOverrides.decorateRequest = function(reqOpts, projectId) { - assert.strictEqual(projectId, authClient.projectId); - setImmediate(done); - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory({ - customEndpoint: true - }); - - makeAuthenticatedRequest({}, { - onAuthenticated: assert.ifError - }); - }); - - it('should use user-provided projectId', function(done) { - authClient.projectId = 'authclient-project-id'; - - var config = { - customEndpoint: true, - projectId: 'project-id' - }; - - utilOverrides.decorateRequest = function(reqOpts, projectId) { - assert.strictEqual(projectId, config.projectId); - setImmediate(done); - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory( - config - ); - - makeAuthenticatedRequest({}, { - onAuthenticated: assert.ifError - }); - }); - }); - - describe('authentication errors', function() { - var error = new Error('Error.'); - - beforeEach(function() { - authClient.authorizeRequest = function(rOpts, callback) { - setImmediate(function() { - callback(error); - }); - }; - }); - - it('should attempt request anyway', function(done) { - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - - var correctReqOpts = {}; - var incorrectReqOpts = {}; - - authClient.authorizeRequest = function(rOpts, callback) { - var error = new Error('Could not load the default credentials'); - callback(error, incorrectReqOpts); - }; - - makeAuthenticatedRequest(correctReqOpts, { - onAuthenticated: function(err, reqOpts) { - assert.ifError(err); - - assert.strictEqual(reqOpts, correctReqOpts); - assert.notStrictEqual(reqOpts, incorrectReqOpts); - - done(); - } - }); - }); - - it('should block decorateRequest error', function(done) { - var decorateRequestError = new Error('Error.'); - utilOverrides.decorateRequest = function() { - throw decorateRequestError; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest({}, { - onAuthenticated: function(err) { - assert.notStrictEqual(err, decorateRequestError); - assert.strictEqual(err, error); - done(); - } - }); - }); - - it('should invoke the callback with error', function(done) { - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest({}, function(err) { - assert.strictEqual(err, error); - done(); - }); - }); - - it('should exec onAuthenticated callback with error', function(done) { - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest({}, { - onAuthenticated: function(err) { - assert.strictEqual(err, error); - done(); - } - }); - }); - - it('should emit an error and end the stream', function(done) { - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest({}).on('error', function(err) { - assert.strictEqual(err, error); - - var stream = this; - setImmediate(function() { - assert.strictEqual(stream.destroyed, true); - done(); - }); - }); - }); - }); - - describe('authentication success', function() { - var reqOpts = { a: 'b', c: 'd' }; - - beforeEach(function() { - authClient.authorizeRequest = function(rOpts, callback) { - callback(null, rOpts); - }; - }); - - it('should return authenticated request to callback', function(done) { - utilOverrides.decorateRequest = function(reqOpts_) { - assert.strictEqual(reqOpts_, reqOpts); - return reqOpts; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest(reqOpts, { - onAuthenticated: function(err, authenticatedReqOpts) { - assert.strictEqual(authenticatedReqOpts, reqOpts); - done(); - } - }); - }); - - it('should make request with correct options', function(done) { - var config = { a: 'b', c: 'd' }; - - utilOverrides.decorateRequest = function(reqOpts_) { - assert.strictEqual(reqOpts_, reqOpts); - return reqOpts; - }; - - utilOverrides.makeRequest = function(authenticatedReqOpts, cfg, cb) { - assert.strictEqual(authenticatedReqOpts, reqOpts); - assert.deepEqual(cfg, config); - cb(); - }; - - var makeAuthenticatedRequest = - util.makeAuthenticatedRequestFactory(config); - makeAuthenticatedRequest(reqOpts, done); - }); - - it('should return abort() from the active request', function(done) { - var retryRequest = { - abort: done - }; - - utilOverrides.makeRequest = function() { - return retryRequest; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - makeAuthenticatedRequest(reqOpts, assert.ifError).abort(); - }); - - it('should only abort() once', function(done) { - var retryRequest = { - abort: done // Will throw if called more than once. - }; - - utilOverrides.makeRequest = function() { - return retryRequest; - }; - - var makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(); - var request = makeAuthenticatedRequest(reqOpts, assert.ifError); - - request.abort(); // done() - request.abort(); // done() - }); - - it('should provide stream to makeRequest', function(done) { - var stream; - - utilOverrides.makeRequest = function(authenticatedReqOpts, cfg) { - setImmediate(function() { - assert.strictEqual(cfg.stream, stream); - done(); - }); - }; - - var makeAuthenticatedRequest = - util.makeAuthenticatedRequestFactory({}); - stream = makeAuthenticatedRequest(reqOpts); - }); - }); - }); - }); - - describe('shouldRetryRequest', function() { - it('should return false if there is no error', function() { - assert.strictEqual(util.shouldRetryRequest(), false); - }); - - it('should return false from generic error', function() { - var error = new Error('Generic error with no code'); - - assert.strictEqual(util.shouldRetryRequest(error), false); - }); - - it('should return true with error code 429', function() { - var error = new Error('429'); - error.code = 429; - - assert.strictEqual(util.shouldRetryRequest(error), true); - }); - - it('should return true with error code 500', function() { - var error = new Error('500'); - error.code = 500; - - assert.strictEqual(util.shouldRetryRequest(error), true); - }); - - it('should return true with error code 502', function() { - var error = new Error('502'); - error.code = 502; - - assert.strictEqual(util.shouldRetryRequest(error), true); - }); - - it('should return true with error code 503', function() { - var error = new Error('503'); - error.code = 503; - - assert.strictEqual(util.shouldRetryRequest(error), true); - }); - - it('should detect rateLimitExceeded reason', function() { - var rateLimitError = new Error('Rate limit error without code.'); - rateLimitError.errors = [{ reason: 'rateLimitExceeded' }]; - - assert.strictEqual(util.shouldRetryRequest(rateLimitError), true); - }); - - it('should detect userRateLimitExceeded reason', function() { - var rateLimitError = new Error('Rate limit error without code.'); - rateLimitError.errors = [{ reason: 'userRateLimitExceeded' }]; - - assert.strictEqual(util.shouldRetryRequest(rateLimitError), true); - }); - }); - - describe('makeRequest', function() { - var reqOpts = { - method: 'GET' - }; - - function testDefaultRetryRequestConfig(done) { - return function(reqOpts_, config) { - assert.strictEqual(reqOpts_, reqOpts); - assert.equal(config.retries, 3); - assert.strictEqual(config.request, fakeRequest); - - var error = new Error('Error.'); - utilOverrides.parseHttpRespMessage = function() { - return { err: error }; - }; - utilOverrides.shouldRetryRequest = function(err) { - assert.strictEqual(err, error); - done(); - }; - - config.shouldRetryFn(); - }; - } - - var noRetryRequestConfig = { autoRetry: false }; - function testNoRetryRequestConfig(done) { - return function(reqOpts, config) { - assert.strictEqual(config.retries, 0); - done(); - }; - } - - var customRetryRequestConfig = { maxRetries: 10 }; - function testCustomRetryRequestConfig(done) { - return function(reqOpts, config) { - assert.strictEqual(config.retries, customRetryRequestConfig.maxRetries); - done(); - }; - } - - describe('callback mode', function() { - it('should pass the default options to retryRequest', function(done) { - retryRequestOverride = testDefaultRetryRequestConfig(done); - util.makeRequest(reqOpts, {}); - }); - - it('should allow turning off retries to retryRequest', function(done) { - retryRequestOverride = testNoRetryRequestConfig(done); - util.makeRequest(reqOpts, noRetryRequestConfig); - }); - - it('should override number of retries to retryRequest', function(done) { - retryRequestOverride = testCustomRetryRequestConfig(done); - util.makeRequest(reqOpts, customRetryRequestConfig); - }); - - it('should return the instance of retryRequest', function() { - var requestInstance = {}; - - retryRequestOverride = function() { - return requestInstance; - }; - - var request = util.makeRequest(reqOpts, assert.ifError); - assert.strictEqual(request, requestInstance); - }); - }); - - describe('stream mode', function() { - it('should forward the specified events to the stream', function(done) { - var requestStream = duplexify(); - var userStream = duplexify(); - - var error = new Error('Error.'); - var response = {}; - var complete = {}; - - userStream - .on('error', function(error_) { - assert.strictEqual(error_, error); - requestStream.emit('response', response); - }) - .on('response', function(response_) { - assert.strictEqual(response_, response); - requestStream.emit('complete', complete); - }) - .on('complete', function(complete_) { - assert.strictEqual(complete_, complete); - done(); - }); - - retryRequestOverride = function() { - setImmediate(function() { - requestStream.emit('error', error); - }); - - return requestStream; - }; - - util.makeRequest(reqOpts, { stream: userStream }); - }); - - describe('GET requests', function() { - it('should use retryRequest', function(done) { - var userStream = duplexify(); - - retryRequestOverride = function(reqOpts_) { - assert.strictEqual(reqOpts_, reqOpts); - setImmediate(done); - return new stream.Stream(); - }; - - util.makeRequest(reqOpts, { stream: userStream }); - }); - - it('should set the readable stream', function(done) { - var userStream = duplexify(); - var retryRequestStream = new stream.Stream(); - - retryRequestOverride = function() { - return retryRequestStream; - }; - - userStream.setReadable = function(stream) { - assert.strictEqual(stream, retryRequestStream); - done(); - }; - - util.makeRequest(reqOpts, { stream: userStream }); - }); - - it('should expose the abort method from retryRequest', function(done) { - var userStream = duplexify(); - - retryRequestOverride = function() { - var requestStream = new stream.Stream(); - requestStream.abort = done; - return requestStream; - }; - - util.makeRequest(reqOpts, { stream: userStream }); - userStream.abort(); - }); - }); - - describe('non-GET requests', function() { - it('should not use retryRequest', function(done) { - var userStream = duplexify(); - var reqOpts = { - method: 'POST' - }; - - retryRequestOverride = done; // will throw. - requestOverride = function(reqOpts_) { - assert.strictEqual(reqOpts_, reqOpts); - setImmediate(done); - return userStream; - }; - - util.makeRequest(reqOpts, { stream: userStream }); - }); - - it('should set the writable stream', function(done) { - var userStream = duplexify(); - var requestStream = new stream.Stream(); - - requestOverride = function() { - return requestStream; - }; - - userStream.setWritable = function(stream) { - assert.strictEqual(stream, requestStream); - done(); - }; - - util.makeRequest({ method: 'POST' }, { stream: userStream }); - }); - - it('should expose the abort method from request', function(done) { - var userStream = duplexify(); - - requestOverride = function() { - var requestStream = duplexify(); - requestStream.abort = done; - return requestStream; - }; - - util.makeRequest(reqOpts, { stream: userStream }); - userStream.abort(); - }); - }); - }); - - describe('callback mode', function() { - it('should optionally accept config', function(done) { - retryRequestOverride = testDefaultRetryRequestConfig(done); - util.makeRequest(reqOpts, assert.ifError); - }); - - it('should pass the default options to retryRequest', function(done) { - retryRequestOverride = testDefaultRetryRequestConfig(done); - util.makeRequest(reqOpts, {}, assert.ifError); - }); - - it('should allow turning off retries to retryRequest', function(done) { - retryRequestOverride = testNoRetryRequestConfig(done); - util.makeRequest(reqOpts, noRetryRequestConfig, assert.ifError); - }); - - it('should override number of retries to retryRequest', function(done) { - retryRequestOverride = testCustomRetryRequestConfig(done); - util.makeRequest(reqOpts, customRetryRequestConfig, assert.ifError); - }); - - it('should let handleResp handle the response', function(done) { - var error = new Error('Error.'); - var response = { a: 'b', c: 'd' }; - var body = response.a; - - retryRequestOverride = function(rOpts, opts, callback) { - callback(error, response, body); - }; - - utilOverrides.handleResp = function(err, resp, body_) { - assert.strictEqual(err, error); - assert.strictEqual(resp, response); - assert.strictEqual(body_, body); - done(); - }; - - util.makeRequest({}, {}, assert.ifError); - }); - }); - }); - - describe('decorateRequest', function() { - it('should delete qs.autoPaginate', function() { - var decoratedReqOpts = util.decorateRequest({ - autoPaginate: true - }); - - assert.strictEqual(decoratedReqOpts.autoPaginate, undefined); - }); - - it('should delete qs.autoPaginateVal', function() { - var decoratedReqOpts = util.decorateRequest({ - autoPaginateVal: true - }); - - assert.strictEqual(decoratedReqOpts.autoPaginateVal, undefined); - }); - - it('should delete objectMode', function() { - var decoratedReqOpts = util.decorateRequest({ - objectMode: true - }); - - assert.strictEqual(decoratedReqOpts.objectMode, undefined); - }); - - it('should delete qs.autoPaginate', function() { - var decoratedReqOpts = util.decorateRequest({ - qs: { - autoPaginate: true - } - }); - - assert.strictEqual(decoratedReqOpts.qs.autoPaginate, undefined); - }); - - it('should delete qs.autoPaginateVal', function() { - var decoratedReqOpts = util.decorateRequest({ - qs: { - autoPaginateVal: true - } - }); - - assert.strictEqual(decoratedReqOpts.qs.autoPaginateVal, undefined); - }); - - it('should delete json.autoPaginate', function() { - var decoratedReqOpts = util.decorateRequest({ - json: { - autoPaginate: true - } - }); - - assert.strictEqual(decoratedReqOpts.json.autoPaginate, undefined); - }); - - it('should delete json.autoPaginateVal', function() { - var decoratedReqOpts = util.decorateRequest({ - json: { - autoPaginateVal: true - } - }); - - assert.strictEqual(decoratedReqOpts.json.autoPaginateVal, undefined); - }); - - it('should replace project ID tokens for qs object', function() { - var projectId = 'project-id'; - var reqOpts = { - uri: 'http://', - qs: {} - }; - var decoratedQs = {}; - - utilOverrides.replaceProjectIdToken = function(qs, projectId_) { - utilOverrides = {}; - assert.strictEqual(qs, reqOpts.qs); - assert.strictEqual(projectId_, projectId); - return decoratedQs; - }; - - var decoratedRequest = util.decorateRequest(reqOpts, projectId); - assert.strictEqual(decoratedRequest.qs, decoratedQs); - }); - - it('should replace project ID tokens for json object', function() { - var projectId = 'project-id'; - var reqOpts = { - uri: 'http://', - json: {} - }; - var decoratedJson = {}; - - utilOverrides.replaceProjectIdToken = function(json, projectId_) { - utilOverrides = {}; - assert.strictEqual(reqOpts.json, json); - assert.strictEqual(projectId_, projectId); - return decoratedJson; - }; - - var decoratedRequest = util.decorateRequest(reqOpts, projectId); - assert.strictEqual(decoratedRequest.json, decoratedJson); - }); - - it('should decorate the request', function() { - var projectId = 'project-id'; - var reqOpts = { - uri: 'http://' - }; - var decoratedUri = 'http://decorated'; - - utilOverrides.replaceProjectIdToken = function(uri, projectId_) { - assert.strictEqual(uri, reqOpts.uri); - assert.strictEqual(projectId_, projectId); - return decoratedUri; - }; - - assert.deepEqual( - util.decorateRequest(reqOpts, projectId), - { uri: decoratedUri } - ); - }); - }); - - describe('projectId placeholder', function() { - var PROJECT_ID = 'project-id'; - - it('should replace any {{projectId}} it finds', function() { - assert.deepEqual(util.replaceProjectIdToken({ - here: 'A {{projectId}} Z', - nested: { - here: 'A {{projectId}} Z', - nested: { - here: 'A {{projectId}} Z' - } - }, - array: [ - { - here: 'A {{projectId}} Z', - nested: { - here: 'A {{projectId}} Z' - }, - nestedArray: [ - { - here: 'A {{projectId}} Z', - nested: { - here: 'A {{projectId}} Z' - } - } - ] - } - ] - }, PROJECT_ID), - { - here: 'A ' + PROJECT_ID + ' Z', - nested: { - here: 'A ' + PROJECT_ID + ' Z', - nested: { - here: 'A ' + PROJECT_ID + ' Z' - } - }, - array: [ - { - here: 'A ' + PROJECT_ID + ' Z', - nested: { - here: 'A ' + PROJECT_ID + ' Z' - }, - nestedArray: [ - { - here: 'A ' + PROJECT_ID + ' Z', - nested: { - here: 'A ' + PROJECT_ID + ' Z' - } - } - ] - } - ] - }); - }); - - it('should replace more than one {{projectId}}', function() { - assert.deepEqual(util.replaceProjectIdToken({ - here: 'A {{projectId}} M {{projectId}} Z', - }, PROJECT_ID), - { - here: 'A ' + PROJECT_ID + ' M ' + PROJECT_ID + ' Z' - }); - }); - - it('should throw if it needs a projectId and cannot find it', function() { - assert.throws(function() { - util.replaceProjectIdToken({ - here: '{{projectId}}' - }); - }, new RegExp(util.missingProjectIdError)); - }); - }); - - describe('normalizeArguments', function() { - var fakeContext = { - config_: { - projectId: 'grapespaceship911' - } - }; - - it('should return an extended object', function() { - var local = { a: 'b' }; - var config; - - utilOverrides.extendGlobalConfig = function(globalConfig, localConfig) { - assert.strictEqual(globalConfig, fakeContext.config_); - assert.strictEqual(localConfig, local); - return fakeContext.config_; - }; - - config = util.normalizeArguments(fakeContext, local); - assert.strictEqual(config, fakeContext.config_); - }); - }); - - describe('createLimiter', function() { - function REQUEST_FN() {} - var OPTIONS = {}; - - it('should create an object stream with stream-events', function(done) { - streamEventsOverride = function(stream) { - assert.strictEqual(stream._readableState.objectMode, true); - setImmediate(done); - return stream; - }; - - util.createLimiter(REQUEST_FN, OPTIONS); - }); - - it('should return a makeRequest function', function() { - var limiter = util.createLimiter(REQUEST_FN, OPTIONS); - assert(is.fn(limiter.makeRequest)); - }); - - it('should return the created stream', function() { - var streamEventsStream = {}; - - streamEventsOverride = function() { - return streamEventsStream; - }; - - var limiter = util.createLimiter(REQUEST_FN, OPTIONS); - assert.strictEqual(limiter.stream, streamEventsStream); - }); - - describe('makeRequest', function() { - it('should pass arguments to request method', function(done) { - var args = [{}, {}]; - - var limiter = util.createLimiter(function(obj1, obj2) { - assert.strictEqual(obj1, args[0]); - assert.strictEqual(obj2, args[1]); - done(); - }); - - limiter.makeRequest.apply(null, args); - }); - - it('should not make more requests than the limit', function(done) { - var callsMade = 0; - var maxApiCalls = 10; - - var limiter = util.createLimiter(function() { - callsMade++; - limiter.makeRequest(); - }, { - maxApiCalls: maxApiCalls - }); - - limiter.makeRequest(); - - limiter.stream - .on('data', util.noop) - .on('end', function() { - assert.strictEqual(callsMade, maxApiCalls); - done(); - }); - }); - }); - }); - - describe('isCustomType', function() { - function PubSub() {} - - function MiddleLayer() { - this.parent = new PubSub(); - } - - function Subscription() { - this.parent = new MiddleLayer(); - } - - var pubsub = new PubSub(); - var subscription = new Subscription(); - - describe('Service objects', function() { - it('should match by constructor name', function() { - assert(util.isCustomType(pubsub, 'pubsub')); - }); - - it('should support any casing', function() { - assert(util.isCustomType(pubsub, 'PubSub')); - }); - - it('should not match if the wrong Service', function() { - assert(!util.isCustomType(subscription, 'BigQuery')); - }); - }); - - describe('ServiceObject objects', function() { - it('should match by constructor names', function() { - assert(util.isCustomType(subscription, 'pubsub')); - assert(util.isCustomType(subscription, 'pubsub/subscription')); - - assert(util.isCustomType(subscription, 'middlelayer')); - assert(util.isCustomType(subscription, 'middlelayer/subscription')); - }); - - it('should support any casing', function() { - assert(util.isCustomType(subscription, 'PubSub/Subscription')); - }); - - it('should not match if the wrong ServiceObject', function() { - assert(!util.isCustomType(subscription, 'pubsub/topic')); - }); - }); - }); - - describe('getUserAgentFromPackageJson', function() { - it('should format a User Agent string from a package.json', function() { - var userAgent = util.getUserAgentFromPackageJson({ - name: '@google-cloud/storage', - version: '0.1.0' - }); - - assert.strictEqual(userAgent, 'gcloud-node-storage/0.1.0'); - }); - }); - - describe('promisifyAll', function() { - var fakeArgs = [null, 1, 2, 3]; - var fakeError = new Error('err.'); - - var FakeClass; - var instance; - var context; - - beforeEach(function() { - context = null; - - FakeClass = function() {}; - - FakeClass.prototype.methodName = function(callback) { - context = this; - callback.apply(null, fakeArgs); - }; - - FakeClass.prototype.methodSingle = function(callback) { - context = this; - callback(null, fakeArgs[1]); - }; - - FakeClass.prototype.methodError = function(callback) { - context = this; - callback(fakeError); - }; - - FakeClass.prototype.method_ = util.noop; - FakeClass.prototype._method = util.noop; - FakeClass.prototype.methodStream = util.noop; - FakeClass.prototype.promise = util.noop; - - util.promisifyAll(FakeClass); - instance = new FakeClass(); - }); - - it('should promisify the correct method', function() { - assert(FakeClass.prototype.methodName.promisified_); - assert(FakeClass.prototype.methodSingle.promisified_); - assert(FakeClass.prototype.methodError.promisified_); - - assert.strictEqual(FakeClass.prototype.method_, util.noop); - assert.strictEqual(FakeClass.prototype._method, util.noop); - assert.strictEqual(FakeClass.prototype.methodStream, util.noop); - assert.strictEqual(FakeClass.prototype.promise, util.noop); - }); - - it('should optionally except an exclude list', function() { - function FakeClass2() {} - - FakeClass2.prototype.methodSync = util.noop; - FakeClass2.prototype.method = function() {}; - - util.promisifyAll(FakeClass2, { - exclude: ['methodSync'] - }); - - assert.strictEqual(FakeClass2.prototype.methodSync, util.noop); - assert(FakeClass2.prototype.method.promisified_); - }); - - it('should pass the options object to promisify', function(done) { - var promisify = util.promisify; - var fakeOptions = { - a: 'a' - }; - - util.promisify = function(method, options) { - assert.strictEqual(method, FakeClass2.prototype.method); - assert.strictEqual(options, fakeOptions); - util.promisify = promisify; - done(); - }; - - function FakeClass2() {} - FakeClass2.prototype.method = function() {}; - - util.promisifyAll(FakeClass2, fakeOptions); - }); - - it('should not re-promisify methods', function() { - var method = FakeClass.prototype.methodName; - - util.promisifyAll(FakeClass); - - assert.strictEqual(FakeClass.prototype.methodName, method); - }); - }); - - describe('promisify', function() { - var fakeContext = {}; - var func; - var fakeArgs; - - beforeEach(function() { - fakeArgs = [null, 1, 2, 3]; - - func = util.promisify(function(callback) { - callback.apply(this, fakeArgs); - }); - }); - - it('should not re-promisify the function', function() { - var original = func; - - func = util.promisify(func); - - assert.strictEqual(original, func); - }); - - it('should not return a promise in callback mode', function(done) { - var returnVal = func.call(fakeContext, function() { - var args = [].slice.call(arguments); - - assert.deepEqual(args, fakeArgs); - assert.strictEqual(this, fakeContext); - assert(!returnVal); - done(); - }); - }); - - it('should return a promise when the callback is omitted', function() { - return func().then(function(args) { - assert.deepEqual(args, fakeArgs.slice(1)); - }); - }); - - it('should reject the promise on a failed request', function() { - var error = new Error('err'); - - fakeArgs = [error]; - - return func().then(function() { - throw new Error('Should have gone to failure block'); - }, function(err) { - assert.strictEqual(err, error); - }); - }); - - it('should allow the Promise object to be overridden', function() { - var FakePromise = function() {}; - var promise = func.call({ Promise: FakePromise }); - - assert(promise instanceof FakePromise); - }); - - it('should resolve singular arguments', function() { - var fakeArg = 'hi'; - - func = util.promisify(function(callback) { - callback.apply(this, [null, fakeArg]); - }, { - singular: true - }); - - return func().then(function(arg) { - assert.strictEqual(arg, fakeArg); - }); - }); - - it('should ignore singular when multiple args are present', function() { - var fakeArgs = ['a', 'b']; - - func = util.promisify(function(callback) { - callback.apply(this, [null].concat(fakeArgs)); - }, { - singular: true - }); - - return func().then(function(args) { - assert.deepEqual(args, fakeArgs); - }); - }); - - describe('trailing undefined arguments', function() { - it('should not return a promise in callback mode', function(done) { - var func = util.promisify(function(optional) { - assert(is.fn(optional)); - optional(null); - }); - - var returnVal = func(function() { - assert(!returnVal); - done(); - }); - }); - - it('should return a promise when callback omitted', function(done) { - var func = util.promisify(function(optional) { - assert.strictEqual(arguments.length, 1); - assert(is.fn(optional)); - optional(null); - }); - - func(undefined, undefined).then(function() { - done(); - }); - }); - - it('should not mistake non-function args for callbacks', function(done) { - var func = util.promisify(function(foo, optional) { - assert.strictEqual(arguments.length, 2); - assert(is.fn(optional)); - optional(null); - }); - - func('foo').then(function() { - done(); - }); - }); - }); - }); -});