From 1bcc94d83691111c5c98c919071c254a4c070450 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Tue, 16 Feb 2016 10:00:35 -0500 Subject: [PATCH] implement gRPC support pubsub: convert to grpc --- lib/common/grpc-service-object.js | 102 +++++ lib/common/grpc-service.js | 354 +++++++++++++++++ lib/common/service-object.js | 3 +- lib/datastore/index.js | 4 +- lib/pubsub/iam.js | 88 +++-- lib/pubsub/index.js | 136 ++++--- lib/pubsub/subscription.js | 176 +++++---- lib/pubsub/topic.js | 59 +-- package.json | 7 +- system-test/pubsub.js | 42 +- test/bigquery/dataset.js | 9 +- test/bigquery/index.js | 10 +- test/bigquery/job.js | 7 +- test/bigquery/table.js | 13 +- test/common/grpc-service-object.js | 159 ++++++++ test/common/grpc-service.js | 595 +++++++++++++++++++++++++++++ test/common/service.js | 4 +- test/common/util.js | 2 +- test/compute/address.js | 7 +- test/compute/disk.js | 9 +- test/compute/firewall.js | 7 +- test/compute/index.js | 20 +- test/compute/network.js | 7 +- test/compute/operation.js | 9 +- test/compute/region.js | 13 +- test/compute/snapshot.js | 7 +- test/compute/vm.js | 7 +- test/compute/zone.js | 15 +- test/datastore/dataset.js | 6 +- test/datastore/index.js | 4 +- test/datastore/query.js | 26 -- test/datastore/request.js | 10 +- test/datastore/transaction.js | 7 +- test/dns/change.js | 7 +- test/dns/index.js | 10 +- test/dns/zone.js | 13 +- test/docs.js | 9 +- test/index.js | 20 +- test/logging/index.js | 20 +- test/logging/log.js | 9 +- test/logging/sink.js | 7 +- test/prediction/index.js | 10 +- test/prediction/model.js | 9 +- test/pubsub/iam.js | 147 +++---- test/pubsub/index.js | 277 ++++++++------ test/pubsub/subscription.js | 181 +++++---- test/pubsub/topic.js | 82 ++-- test/resource/index.js | 10 +- test/resource/project.js | 7 +- test/search/document.js | 7 +- test/search/index-class.js | 11 +- test/search/index.js | 10 +- test/storage/bucket.js | 13 +- test/storage/channel.js | 7 +- test/storage/file.js | 9 +- test/storage/index.js | 8 +- test/translate/index.js | 4 +- 57 files changed, 2110 insertions(+), 701 deletions(-) create mode 100644 lib/common/grpc-service-object.js create mode 100644 lib/common/grpc-service.js create mode 100644 test/common/grpc-service-object.js create mode 100644 test/common/grpc-service.js diff --git a/lib/common/grpc-service-object.js b/lib/common/grpc-service-object.js new file mode 100644 index 00000000000..7123cc90be8 --- /dev/null +++ b/lib/common/grpc-service-object.js @@ -0,0 +1,102 @@ +/*! + * 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/grpcServiceObject + */ + +'use strict'; + +var extend = require('extend'); +var nodeutil = require('util'); + +/** + * @type {module:common/serviceObject} + * @private + */ +var ServiceObject = require('./service-object.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('./util.js'); + +/** + * GrpcServiceObject is a base class, meant to be inherited from by a service + * object that uses the gRPC protobuf API. + * + * @private + * + * @param {object} config - Configuration object. + */ +function GrpcServiceObject(config) { + ServiceObject.call(this, config); +} + +nodeutil.inherits(GrpcServiceObject, ServiceObject); + +/** + * Delete the object. + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + */ +GrpcServiceObject.prototype.delete = function(callback) { + var protoOpts = this.methods.delete.protoOpts; + var reqOpts = this.methods.delete.reqOpts; + + this.request(protoOpts, reqOpts, callback || util.noop); +}; + +/** + * 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. + */ +GrpcServiceObject.prototype.getMetadata = function(callback) { + var protoOpts = this.methods.getMetadata.protoOpts; + var reqOpts = this.methods.getMetadata.reqOpts; + + this.request(protoOpts, reqOpts, callback); +}; + +/** + * 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. + */ +GrpcServiceObject.prototype.setMetadata = function(metadata, callback) { + var protoOpts = this.methods.setMetadata.protoOpts; + var reqOpts = extend(true, {}, this.methods.setMetadata.reqOpts, metadata); + + this.request(protoOpts, reqOpts, callback || util.noop); +}; + +/** + * Patch a request to the GrpcService object. + * + * @private + */ +GrpcServiceObject.prototype.request = function(protoOpts, reqOpts, callback) { + this.parent.request(protoOpts, reqOpts, callback); +}; + +module.exports = GrpcServiceObject; diff --git a/lib/common/grpc-service.js b/lib/common/grpc-service.js new file mode 100644 index 00000000000..575660a8f2f --- /dev/null +++ b/lib/common/grpc-service.js @@ -0,0 +1,354 @@ +/*! + * 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/grpcService + */ + +'use strict'; + +var camelize = require('camelize'); +var googleProtoFiles = require('google-proto-files'); +var grpc = require('grpc'); +var is = require('is'); +var nodeutil = require('util'); +var path = require('path'); +var snakeize = require('snakeize'); + +/** + * @type {module:common/service} + * @private + */ +var Service = require('./service.js'); + +/** + * @const {object} - A map of protobuf codes to HTTP status codes. + * @private + */ +var HTTP_ERROR_CODE_MAP = { + 0: { + code: 200, + message: 'OK' + }, + + 1: { + code: 499, + message: 'Client Closed Request' + }, + + 2: { + code: 500, + message: 'Internal Server Error' + }, + + 3: { + code: 400, + message: 'Bad Request' + }, + + 4: { + code: 504, + message: 'Gateway Timeout' + }, + + 5: { + code: 404, + message: 'Not Found' + }, + + 6: { + code: 409, + message: 'Conflict' + }, + + 7: { + code: 403, + message: 'Forbidden' + }, + + 8: { + code: 429, + message: 'Too Many Requests' + }, + + 9: { + code: 412, + message: 'Precondition Failed' + }, + + 10: { + code: 409, + message: 'Conflict' + }, + + 11: { + code: 400, + message: 'Bad Request' + }, + + 12: { + code: 501, + message: 'Not Implemented' + }, + + 13: { + code: 500, + message: 'Internal Server Error' + }, + + 14: { + code: 503, + message: 'Service Unavailable' + }, + + 15: { + code: 500, + message: 'Internal Server Error' + }, + + 16: { + code: 401, + message: 'Unauthorized' + } +}; + +/** + * Service is a base class, meant to be inherited from by a "service," like + * BigQuery or Storage. + * + * This handles making authenticated requests by exposing a `makeReq_` function. + * + * @param {object} config - Configuration object. + * @param {string} config.baseUrl - The base URL to make API requests to. + * @param {string[]} config.scopes - The scopes required for the request. + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + */ +function GrpcService(config, options) { + Service.call(this, config, options); + + var service = config.service; + var apiVersion = config.apiVersion; + var rootDir = googleProtoFiles('..'); + + if (config.customEndpoint) { + this.grpcCredentials = grpc.credentials.createInsecure(); + } + + this.protoOpts = config.proto; + this.proto = grpc.load({ + root: rootDir, + file: path.relative(rootDir, googleProtoFiles[service][apiVersion]) + }).google[service][apiVersion]; +} + +nodeutil.inherits(GrpcService, Service); + +/** + * Make an authenticated request with gRPC. + * + * @param {object} protoOpts - The proto options. + * @param {string} protoOpts.service - The service name. + * @param {string} protoOpts.method - The method name. + * @param {number=} protoOpts.timeout - After how many milliseconds should the + * request cancel. + * @param {object} reqOpts - The request options. + * @param {function=} callback - The callback function. + */ +GrpcService.prototype.request = function(protoOpts, reqOpts, callback) { + if (global.GCLOUD_SANDBOX_ENV) { + return global.GCLOUD_SANDBOX_ENV; + } + + var self = this; + var proto = this.proto; + + if (!this.grpcCredentials) { + // We must establish an authClient to give to grpc. + var continueRequest = this.request.bind(this, protoOpts, reqOpts, callback); + this.getGrpcCredentials_(continueRequest); + return; + } + + var grpcOpts = {}; + + if (is.number(protoOpts.timeout)) { + grpcOpts.deadline = new Date(Date.now() + protoOpts.timeout); + } + + // Clean up gcloud-specific options. + delete reqOpts.autoPaginate; + delete reqOpts.autoPaginateVal; + + var service = new proto[protoOpts.service]( + this.baseUrl, + this.grpcCredentials + ); + + // snakeize and camelize are used to transform camelCase request options to + // snake_case. This is what ProtoBuf.js (via gRPC) expects. Similarly, the + // response is in snake_case, which is why we use camelize to return it to + // camelCase. + // + // An option will be added to gRPC to allow us to skip this step: + // https://github.com/grpc/grpc/issues/5005 + service[protoOpts.method](snakeize(reqOpts), function(err, resp) { + if (err) { + if (HTTP_ERROR_CODE_MAP[err.code]) { + var httpError = HTTP_ERROR_CODE_MAP[err.code]; + err.code = httpError.code; + } + + callback(err); + return; + } + + callback(null, self.convertBuffers_(camelize(resp))); + }, null, grpcOpts); +}; + +/** + * Iterate over an object, finding anything that resembles a Buffer, then + * convert it to a base64 string representation. + * + * @todo Replace this function: https://github.com/grpc/grpc/issues/5006 + * + * @private + * + * @param {*} data - An object or array to iterate over. + * @return {*} - The converted object. + */ +GrpcService.prototype.convertBuffers_ = function(data) { + if (is.array(data)) { + return data.map(this.convertBuffers_.bind(this)); + } + + if (is.object(data)) { + for (var prop in data) { + if (data.hasOwnProperty(prop)) { + var value = data[prop]; + + if (Buffer.isBuffer(value)) { + data[prop] = value.toString('base64'); + } else if (this.isBufferLike_(value)) { + data[prop] = new Buffer(this.objToArr_(value)).toString('base64'); + } else { + data[prop] = this.convertBuffers_(value); + } + } + } + } + + return data; +}; + +/** + * To authorize requests through gRPC, we must get the raw google-auth-library + * auth client object. + * + * @private + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error getting an auth client. + */ +GrpcService.prototype.getGrpcCredentials_ = function(callback) { + var self = this; + + this.authClient.getCredentials(function(err) { + if (err) { + callback(err); + return; + } + + var authClient = self.authClient.authClient; + + self.grpcCredentials = grpc.credentials.combineChannelCredentials( + grpc.credentials.createSsl(), + grpc.credentials.createFromGoogleCredential(authClient) + ); + + callback(); + }); +}; + +/** + * protobufjs via gRPC returns an object-ified array, like: + * { + * 0: 0, + * 1: 22 + * } + * + * @todo Replace this function: https://github.com/grpc/grpc/issues/5006 + * + * @private + * + * @param {*} value - Any value. + * @return {boolean} - Is the object a buffer. + */ +GrpcService.prototype.isBufferLike_ = function(value) { + if (!is.object(value) || is.empty(value)) { + return false; + } + + delete value.length; + delete value.parent; + + var lastNumber; + + // Is every property name a number? + return Object.keys(value).every(function(key) { + var numericKey = parseInt(key, 10); + + if (is.number(lastNumber) && numericKey - lastNumber !== 1) { + return false; + } + + lastNumber = numericKey; + + return !isNaN(numericKey) && String(numericKey).length === key.length; + }); +}; + +/** + * Convert an array-like object to an array. + * + * @todo Replace this function: https://github.com/grpc/grpc/issues/5006 + * + * @private + * + * @param {object} obj - An array-like object. + * @return {array} - The converted array. + * + * @example + * grpcService.objToArr_({ + * 0: 'a', + * 1: 'b', + * 2: 'c' + * }); + * // ['a', 'b', 'c'] + */ +GrpcService.prototype.objToArr_ = function(obj) { + var arr = []; + + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + arr.push(obj[prop]); + } + } + + return arr; +}; + +module.exports = GrpcService; diff --git a/lib/common/service-object.js b/lib/common/service-object.js index d7353079aae..dd8013db9b6 100644 --- a/lib/common/service-object.js +++ b/lib/common/service-object.js @@ -137,14 +137,13 @@ ServiceObject.prototype.create = function(options, callback) { */ ServiceObject.prototype.delete = function(callback) { var methodConfig = this.methods.delete || {}; + callback = callback || util.noop; var reqOpts = extend({ method: 'DELETE', uri: '' }, methodConfig.reqOpts); - callback = callback || util.noop; - // 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) { diff --git a/lib/datastore/index.js b/lib/datastore/index.js index ae4693797b9..71d279c24a7 100644 --- a/lib/datastore/index.js +++ b/lib/datastore/index.js @@ -24,7 +24,7 @@ * @type {module:datastore/entity} * @private */ -var entity = require('./entity'); +var entity = require('./entity.js'); /** * @type {module:common/util} @@ -36,7 +36,7 @@ var util = require('../common/util.js'); * @type {module:datastore/dataset} * @private */ -var Dataset = require('./dataset'); +var Dataset = require('./dataset.js'); /*! Developer Documentation * diff --git a/lib/pubsub/iam.js b/lib/pubsub/iam.js index 66199de8f0e..c21add1a715 100644 --- a/lib/pubsub/iam.js +++ b/lib/pubsub/iam.js @@ -25,10 +25,10 @@ var is = require('is'); var nodeutil = require('util'); /** - * @type {module:common/serviceObject} + * @type {module:common/grpcService} * @private */ -var ServiceObject = require('../common/service-object.js'); +var GrpcService = require('../common/grpc-service.js'); /*! Developer Documentation * @@ -75,18 +75,23 @@ var ServiceObject = require('../common/service-object.js'); * var subscription = pubsub.subscription('my-subscription'); * // subscription.iam */ -function IAM(pubsub, scope) { - ServiceObject.call(this, { - parent: pubsub, - baseUrl: scope.baseUrl, - id: scope.id, - methods: { - // Nothing needed other than the `request` method. - } - }); +function IAM(pubsub, id) { + var config = { + baseUrl: pubsub.defaultBaseUrl_, + service: 'iam', + apiVersion: 'v1', + scopes: [ + 'https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/cloud-platform' + ] + }; + + this.id = id; + + GrpcService.call(this, config, pubsub.options); } -nodeutil.inherits(IAM, ServiceObject); +nodeutil.inherits(IAM, GrpcService); /** * Get the IAM policy @@ -105,16 +110,16 @@ nodeutil.inherits(IAM, ServiceObject); * subscription.iam.getPolicy(function(err, policy, apiResponse) {}); */ IAM.prototype.getPolicy = function(callback) { - this.request({ - uri: ':getIamPolicy' - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + var protoOpts = { + service: 'IAMPolicy', + method: 'getIamPolicy' + }; - callback(null, resp, resp); - }); + var reqOpts = { + resource: this.id + }; + + this.request(protoOpts, reqOpts, callback); }; /** @@ -154,20 +159,17 @@ IAM.prototype.setPolicy = function(policy, callback) { throw new Error('A policy object is required.'); } - this.request({ - method: 'POST', - uri: ':setIamPolicy', - json: { - policy: policy - } - }, function(err, resp) { - if (err) { - callback(err, null, resp); - return; - } + var protoOpts = { + service: 'IAMPolicy', + method: 'setIamPolicy' + }; - callback(null, resp, resp); - }); + var reqOpts = { + resource: this.id, + policy: policy + }; + + this.request(protoOpts, reqOpts, callback); }; /** @@ -224,13 +226,17 @@ IAM.prototype.testPermissions = function(permissions, callback) { permissions = arrify(permissions); - this.request({ - method: 'POST', - uri: ':testIamPermissions', - json: { - permissions: permissions - } - }, function(err, resp) { + var protoOpts = { + service: 'IAMPolicy', + method: 'testIamPermissions' + }; + + var reqOpts = { + resource: this.id, + permissions: permissions + }; + + this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js index 07cc6931bee..14c1d9a32de 100644 --- a/lib/pubsub/index.js +++ b/lib/pubsub/index.js @@ -26,10 +26,10 @@ var is = require('is'); var nodeutil = require('util'); /** - * @type {module:common/service} + * @type {module:common/grpcService} * @private */ -var Service = require('../common/service.js'); +var GrpcService = require('../common/grpc-service.js'); /** * @type {module:pubsub/subscription} @@ -88,21 +88,26 @@ function PubSub(options) { return new PubSub(options); } + this.defaultBaseUrl_ = 'pubsub.googleapis.com'; this.determineBaseUrl_(); var config = { - baseUrl: this.baseUrl, - customEndpoint: this.customEndpoint, + baseUrl: this.baseUrl_, + customEndpoint: this.customEndpoint_, + service: 'pubsub', + apiVersion: 'v1', scopes: [ 'https://www.googleapis.com/auth/pubsub', 'https://www.googleapis.com/auth/cloud-platform' ] }; - Service.call(this, config, options); + this.options = options; + + GrpcService.call(this, config, options); } -nodeutil.inherits(PubSub, Service); +nodeutil.inherits(PubSub, GrpcService); /** * Create a topic with the given name. @@ -128,10 +133,16 @@ PubSub.prototype.createTopic = function(name, callback) { callback = callback || util.noop; - this.request({ - method: 'PUT', - uri: '/topics/' + name, - }, function(err, resp) { + var protoOpts = { + service: 'Publisher', + method: 'createTopic' + }; + + var reqOpts = { + name: Topic.formatName_(this.projectId, name) + }; + + this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, null, resp); return; @@ -223,30 +234,30 @@ PubSub.prototype.getSubscriptions = function(options, callback) { options = {}; } - options = options || {}; - - var topicName; - - if (is.string(options.topic)) { - topicName = options.topic; - } else if (options.topic instanceof Topic) { - topicName = options.topic.unformattedName; - } + var protoOpts = {}; + var reqOpts = extend({}, options); - var query = {}; + if (options.topic) { + protoOpts = { + service: 'Publisher', + method: 'listTopicSubscriptions' + }; - if (options.pageSize) { - query.pageSize = options.pageSize; - } + if (options.topic instanceof Topic) { + reqOpts.topic = options.topic.name; + } else { + reqOpts.topic = options.topic; + } + } else { + protoOpts = { + service: 'Subscriber', + method: 'listSubscriptions' + }; - if (options.pageToken) { - query.pageToken = options.pageToken; + reqOpts.project = 'projects/' + this.projectId; } - this.request({ - uri: (topicName ? '/topics/' + topicName : '') + '/subscriptions', - qs: query - }, function(err, resp) { + this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, null, null, resp); return; @@ -353,12 +364,18 @@ PubSub.prototype.getTopics = function(query, callback) { query = {}; } - this.request({ - uri: '/topics', - qs: query - }, function(err, result) { + var protoOpts = { + service: 'Publisher', + method: 'listTopics' + }; + + var reqOpts = extend({ + project: 'projects/' + this.projectId + }, query); + + this.request(protoOpts, reqOpts, function(err, result) { if (err) { - callback(err, null, null, result); + callback(err, null, result); return; } @@ -447,8 +464,6 @@ PubSub.prototype.getTopics = function(query, callback) { * pubsub.subscribe(topic, name, function(err, subscription, apiResponse) {}); */ PubSub.prototype.subscribe = function(topic, subName, options, callback) { - var self = this; - if (!is.string(topic) && !(topic instanceof Topic)) { throw new Error('A Topic is required for a new subscription.'); } @@ -466,28 +481,32 @@ PubSub.prototype.subscribe = function(topic, subName, options, callback) { topic = this.topic(topic); } - var body = extend(true, {}, options, { - topic: topic.name + var subscription = this.subscription(subName, options); + + var protoOpts = { + service: 'Subscriber', + method: 'createSubscription', + timeout: options.timeout + }; + + var reqOpts = extend(true, {}, options, { + topic: topic.name, + name: subscription.name }); - delete body.autoAck; - delete body.encoding; - delete body.interval; - delete body.maxInProgress; - delete body.reuseExisting; - delete body.timeout; - - this.request({ - method: 'PUT', - uri: '/subscriptions/' + subName, - json: body - }, function(err, resp) { + delete reqOpts.autoAck; + delete reqOpts.encoding; + delete reqOpts.interval; + delete reqOpts.maxInProgress; + delete reqOpts.reuseExisting; + delete reqOpts.timeout; + + this.request(protoOpts, reqOpts, function(err, resp) { if (err && !(err.code === 409 && options.reuseExisting)) { callback(err, null, resp); return; } - var subscription = self.subscription(subName, options); callback(null, subscription, resp); }); }; @@ -566,21 +585,18 @@ PubSub.prototype.topic = function(name) { * @private */ PubSub.prototype.determineBaseUrl_ = function() { - var baseUrl; + var baseUrl = this.defaultBaseUrl_; + var leadingProtocol = new RegExp('^https*://'); var trailingSlashes = new RegExp('/*$'); if (process.env.PUBSUB_EMULATOR_HOST) { + this.customEndpoint_ = true; baseUrl = process.env.PUBSUB_EMULATOR_HOST; - this.customEndpoint = true; - } else { - baseUrl = 'https://pubsub.googleapis.com/v1'; - } - - if (baseUrl.indexOf('http') !== 0) { - baseUrl = 'http://' + baseUrl; } - this.baseUrl = baseUrl.replace(trailingSlashes, ''); + this.baseUrl_ = baseUrl + .replace(leadingProtocol, '') + .replace(trailingSlashes, ''); }; /*! Developer Documentation diff --git a/lib/pubsub/subscription.js b/lib/pubsub/subscription.js index bed8ead10f6..823ca796341 100644 --- a/lib/pubsub/subscription.js +++ b/lib/pubsub/subscription.js @@ -27,16 +27,16 @@ var modelo = require('modelo'); var prop = require('propprop'); /** - * @type {module:pubsub/iam} + * @type {module:common/grpcServiceObject} * @private */ -var IAM = require('./iam.js'); +var GrpcServiceObject = require('../common/grpc-service-object.js'); /** - * @type {module:common/serviceObject} + * @type {module:pubsub/iam} * @private */ -var ServiceObject = require('../common/service-object.js'); +var IAM = require('./iam.js'); /** * @type {module:common/util} @@ -44,6 +44,13 @@ var ServiceObject = require('../common/service-object.js'); */ var util = require('../common/util.js'); +/** + * @const {number} - The amount of time a subscription pull HTTP connection to + * Pub/Sub stays open. + * @private + */ +var PUBSUB_API_TIMEOUT = 90000; + /*! Developer Documentation * * @param {module:pubsub} pubsub - PubSub object. @@ -148,8 +155,7 @@ var util = require('../common/util.js'); * subscription.removeListener('message', onMessage); */ function Subscription(pubsub, options) { - var baseUrl = '/subscriptions'; - var unformattedName = options.name.split('/').pop(); + this.name = Subscription.formatName_(pubsub.projectId, options.name); var methods = { /** @@ -194,7 +200,8 @@ function Subscription(pubsub, options) { * @resource [Subscriptions: get API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get} * * @param {function} callback - The callback function. - * @param {?error} callback.err - An API error. + * @param {?error} callback.err - An error returned while making this + * request. * @param {?object} callback.metadata - Metadata of the subscription from * the API. * @param {object} callback.apiResponse - Raw API response. @@ -202,13 +209,20 @@ function Subscription(pubsub, options) { * @example * subscription.getMetadata(function(err, metadata, apiResponse) {}); */ - getMetadata: true + getMetadata: { + protoOpts: { + service: 'Subscriber', + method: 'getSubscription' + }, + reqOpts: { + subscription: this.name + } + } }; var config = { parent: pubsub, - baseUrl: baseUrl, - id: unformattedName, + id: this.name, methods: methods }; @@ -235,9 +249,28 @@ function Subscription(pubsub, options) { config.methods.create = true; } - ServiceObject.call(this, config); + GrpcServiceObject.call(this, config); events.EventEmitter.call(this); + this.autoAck = is.boolean(options.autoAck) ? options.autoAck : false; + this.closed = true; + this.encoding = options.encoding || 'utf-8'; + this.inProgressAckIds = {}; + this.interval = is.number(options.interval) ? options.interval : 10; + this.maxInProgress = + is.number(options.maxInProgress) ? options.maxInProgress : Infinity; + this.messageListeners = 0; + this.paused = false; + + if (is.number(options.timeout)) { + this.timeout = options.timeout; + } else { + // The default timeout used in gcloud-node is 60s, but a pull request times + // out around 90 seconds. Allow an extra couple of seconds to give the API a + // chance to respond on its own before terminating the connection. + this.timeout = PUBSUB_API_TIMEOUT + 2000; + } + /** * [IAM (Identity and Access Management)](https://cloud.google.com/pubsub/access_control) * allows you to set permissions on invidual resources and offers a wider @@ -264,37 +297,12 @@ function Subscription(pubsub, options) { * console.log(policy); * }); */ - this.iam = new IAM(pubsub, { - baseUrl: baseUrl, - id: unformattedName - }); - - this.name = Subscription.formatName_(pubsub.projectId, options.name); - - this.autoAck = is.boolean(options.autoAck) ? options.autoAck : false; - this.closed = true; - this.encoding = options.encoding || 'utf-8'; - this.inProgressAckIds = {}; - this.interval = is.number(options.interval) ? options.interval : 10; - this.maxInProgress = - is.number(options.maxInProgress) ? options.maxInProgress : Infinity; - this.messageListeners = 0; - this.paused = false; - - if (is.number(options.timeout)) { - this.timeout = options.timeout; - } else { - // The default timeout used in gcloud-node is 60s, but a pull request times - // out around 90 seconds. Allow an extra couple of seconds to give the API a - // chance to respond on its own before terminating the connection. - var PUBSUB_API_TIMEOUT = 90000; - this.timeout = PUBSUB_API_TIMEOUT + 2000; - } + this.iam = new IAM(pubsub, this.name); this.listenForEvents_(); } -modelo.inherits(Subscription, ServiceObject, events.EventEmitter); +modelo.inherits(Subscription, GrpcServiceObject, events.EventEmitter); /** * Simplify a message from an API response to have three properties, `id`, @@ -369,13 +377,17 @@ Subscription.prototype.ack = function(ackIds, callback) { callback = callback || util.noop; - this.request({ - method: 'POST', - uri: ':acknowledge', - json: { - ackIds: ackIds - } - }, function(err, resp) { + var protoOpts = { + service: 'Subscriber', + method: 'acknowledge' + }; + + var reqOpts = { + subscription: this.name, + ackIds: ackIds + }; + + this.request(protoOpts, reqOpts, function(err, resp) { if (!err) { ackIds.forEach(function(ackId) { delete self.inProgressAckIds[ackId]; @@ -425,6 +437,9 @@ Subscription.prototype.decorateMessage_ = function(message) { * @resource [Subscriptions: delete API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/delete} * * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.apiResponse - Raw API response. * * @example * subscription.delete(function(err, apiResponse) {}); @@ -434,7 +449,16 @@ Subscription.prototype.delete = function(callback) { callback = callback || util.noop; - ServiceObject.prototype.delete.call(this, function(err, resp) { + var protoOpts = { + service: 'Subscriber', + method: 'deleteSubscription' + }; + + var reqOpts = { + subscription: this.name + }; + + this.request(protoOpts, reqOpts, function(err, resp) { if (err) { callback(err, resp); return; @@ -509,30 +533,34 @@ Subscription.prototype.pull = function(options, callback) { options.maxResults = MAX_EVENTS_LIMIT; } - this.activeRequest_ = this.request({ - timeout: this.timeout, - method: 'POST', - uri: ':pull', - json: { - returnImmediately: !!options.returnImmediately, - maxMessages: options.maxResults - } - }, function(err, response) { + var protoOpts = { + service: 'Subscriber', + method: 'pull', + timeout: this.timeout + }; + + var reqOpts = { + subscription: this.name, + returnImmediately: !!options.returnImmediately, + maxMessages: options.maxResults + }; + + this.activeRequest_ = this.request(protoOpts, reqOpts, function(err, resp) { self.activeRequest_ = null; if (err) { - if (err.code === 'ETIMEDOUT' && !err.connect) { + if (err.code === 504) { // Simulate a server timeout where no messages were received. - response = { + resp = { receivedMessages: [] }; } else { - callback(err, null, response); + callback(err, null, resp); return; } } - var messages = arrify(response.receivedMessages) + var messages = arrify(resp.receivedMessages) .map(function(msg) { return Subscription.formatMessage_(msg, self.encoding); }) @@ -544,10 +572,10 @@ Subscription.prototype.pull = function(options, callback) { var ackIds = messages.map(prop('ackId')); self.ack(ackIds, function(err) { - callback(err, messages, response); + callback(err, messages, resp); }); } else { - callback(null, messages, response); + callback(null, messages, resp); } }); }; @@ -561,14 +589,14 @@ Subscription.prototype.pull = function(options, callback) { * @resource [Subscriptions: modifyAckDeadline API Documentation]{@link https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyAckDeadline} * * @param {object} options - The configuration object. - * @param {number|number[]} options.ackIds - The ack id(s) to change. + * @param {string|string[]} options.ackIds - The ack id(s) to change. * @param {number} options.seconds - Number of seconds after call is made to * set the deadline of the ack. * @param {Function=} callback - The callback function. * * @example * var options = { - * ackIds: [123], + * ackIds: ['abc'], * seconds: 10 // Expire in 10 seconds from call. * }; * @@ -577,14 +605,18 @@ Subscription.prototype.pull = function(options, callback) { Subscription.prototype.setAckDeadline = function(options, callback) { callback = callback || util.noop; - this.request({ - method: 'POST', - uri: ':modifyAckDeadline', - json: { - ackIds: arrify(options.ackIds), - ackDeadlineSeconds: options.seconds - } - }, function(err, resp) { + var protoOpts = { + service: 'Subscriber', + method: 'modifyAckDeadline' + }; + + var reqOpts = { + subscription: this.name, + ackIds: arrify(options.ackIds), + ackDeadlineSeconds: options.seconds + }; + + this.request(protoOpts, reqOpts, function(err, resp) { callback(err, resp); }); }; @@ -620,7 +652,7 @@ Subscription.prototype.listenForEvents_ = function() { self.closed = true; if (self.activeRequest_) { - self.activeRequest_.abort(); + self.activeRequest_.cancel(); } } }); diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js index abc80a9b53b..d23a97dd6c7 100644 --- a/lib/pubsub/topic.js +++ b/lib/pubsub/topic.js @@ -35,13 +35,13 @@ var util = require('../common/util.js'); * @type {module:pubsub/iam} * @private */ -var IAM = require('./iam'); +var IAM = require('./iam.js'); /** - * @type {module:common/serviceObject} + * @type {module:common/grpcServiceObject} * @private */ -var ServiceObject = require('../common/service-object.js'); +var GrpcServiceObject = require('../common/grpc-service-object.js'); /*! Developer Documentation * @@ -62,11 +62,8 @@ var ServiceObject = require('../common/service-object.js'); * var topic = pubsub.topic('my-topic'); */ function Topic(pubsub, name) { - var baseUrl = '/topics'; - this.name = Topic.formatName_(pubsub.projectId, name); this.pubsub = pubsub; - this.unformattedName = name.split('/').pop(); var methods = { /** @@ -93,7 +90,15 @@ function Topic(pubsub, name) { * @example * topic.delete(function(err, apiResponse) {}); */ - delete: true, + delete: { + protoOpts: { + service: 'Publisher', + method: 'deleteTopic' + }, + reqOpts: { + topic: this.name + } + }, /** * Check if the topic exists. @@ -138,13 +143,20 @@ function Topic(pubsub, name) { * @param {object} callback.metadata - The metadata of the Topic. * @param {object} callback.apiResponse - The full API response. */ - getMetadata: true + getMetadata: { + protoOpts: { + service: 'Publisher', + method: 'getTopic' + }, + reqOpts: { + topic: this.name + } + } }; - ServiceObject.call(this, { + GrpcServiceObject.call(this, { parent: pubsub, - baseUrl: baseUrl, - id: this.unformattedName, + id: this.name, createMethod: pubsub.createTopic.bind(pubsub), methods: methods }); @@ -175,13 +187,10 @@ function Topic(pubsub, name) { * console.log(policy); * }); */ - this.iam = new IAM(pubsub, { - baseUrl: baseUrl, - id: this.unformattedName - }); + this.iam = new IAM(pubsub, this.name); } -nodeutil.inherits(Topic, ServiceObject); +nodeutil.inherits(Topic, GrpcServiceObject); /** * Format a message object as the upstream API expects it. @@ -343,13 +352,17 @@ Topic.prototype.publish = function(messages, callback) { callback = callback || util.noop; - this.request({ - method: 'POST', - uri: ':publish', - json: { - messages: messages.map(Topic.formatMessage_) - } - }, function(err, result) { + var protoOpts = { + service: 'Publisher', + method: 'publish', + }; + + var reqOpts = { + topic: this.name, + messages: messages.map(Topic.formatMessage_) + }; + + this.request(protoOpts, reqOpts, function(err, result) { if (err) { callback(err, null, result); return; diff --git a/package.json b/package.json index 3658603e3ac..5112483a29c 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "array-uniq": "^1.0.2", "arrify": "^1.0.0", "async": "^1.4.2", + "camelize": "^1.0.0", "concat-stream": "^1.5.0", "create-error-class": "^2.0.1", "dns-zonefile": "0.1.10", @@ -95,6 +96,8 @@ "gce-images": "^0.2.0", "gcs-resumable-upload": "^0.4.0", "google-auto-auth": "^0.2.0", + "google-proto-files": "^0.1.1", + "grpc": "^0.13.0", "hash-stream-validation": "^0.1.0", "is": "^3.0.1", "methmeth": "^1.0.0", @@ -107,6 +110,8 @@ "pumpify": "^1.3.3", "request": "^2.53.0", "retry-request": "^1.2.3", + "snake": "0.0.1", + "snakeize": "^0.1.0", "split-array-stream": "^1.0.0", "stream-events": "^1.0.1", "string-format-obj": "^1.0.0", @@ -123,7 +128,7 @@ "jshint": "^2.9.1", "mitm": "^1.1.0", "mocha": "^2.1.0", - "mockery": "^1.4.0", + "mockery-next": "^2.0.1-3", "node-uuid": "^1.4.3", "tmp": "0.0.27" }, diff --git a/system-test/pubsub.js b/system-test/pubsub.js index e216e8ecf47..81a9b9d2bc1 100644 --- a/system-test/pubsub.js +++ b/system-test/pubsub.js @@ -317,6 +317,41 @@ describe('pubsub', function() { subscription.ack(ackIds, done); }); }); + + it('should allow a custom timeout', function(done) { + var timeout = 5000; + this.timeout(timeout * 2); + + // We need to use a topic without any pending messages to allow the + // connection to stay open. + var topic = pubsub.topic(generateTopicName()); + var subscription = topic.subscription(generateSubName(), { + timeout: timeout + }); + + async.series([ + topic.create.bind(topic), + subscription.create.bind(subscription), + ], function(err) { + assert.ifError(err); + + var times = [Date.now()]; + + subscription.pull({ + returnImmediately: false + }, function(err) { + assert.ifError(err); + + times.push(Date.now()); + var runTime = times.pop() - times.pop(); + + assert(runTime >= timeout - 1000); + assert(runTime <= timeout + 1000); + + done(); + }); + }); + }); }); describe('IAM', function() { @@ -325,7 +360,12 @@ describe('pubsub', function() { topic.iam.getPolicy(function(err, policy) { assert.ifError(err); - assert.deepEqual(policy, { etag: 'ACAB' }); + + assert.deepEqual(policy, { + bindings: [], + etag: 'ACAB', + version: 0 + }); done(); }); }); diff --git a/test/bigquery/dataset.js b/test/bigquery/dataset.js index a09b1d719de..36b13ba3084 100644 --- a/test/bigquery/dataset.js +++ b/test/bigquery/dataset.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -57,8 +57,11 @@ describe('BigQuery/Dataset', function() { var ds; before(function() { - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/bigquery/index.js b/test/bigquery/index.js index e90fc47098f..8ca870d452d 100644 --- a/test/bigquery/index.js +++ b/test/bigquery/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -68,10 +68,10 @@ describe('BigQuery', function() { var bq; before(function() { - mockery.registerMock('./table.js', FakeTable); - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock('../../lib/bigquery/table.js', FakeTable); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/bigquery/job.js b/test/bigquery/job.js index 0475b44b04a..34ba7b52830 100644 --- a/test/bigquery/job.js +++ b/test/bigquery/job.js @@ -17,7 +17,7 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var is = require('is'); var nodeutil = require('util'); @@ -40,7 +40,10 @@ describe('BigQuery/Job', function() { var job; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/bigquery/table.js b/test/bigquery/table.js index 0110d9f585a..4d2794713f8 100644 --- a/test/bigquery/table.js +++ b/test/bigquery/table.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var prop = require('propprop'); var stream = require('stream'); @@ -93,10 +93,13 @@ describe('BigQuery/Table', function() { var tableOverrides = {}; before(function() { - mockery.registerMock('../storage/file.js', FakeFile); - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock('../../lib/storage/file.js', FakeFile); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/common/grpc-service-object.js b/test/common/grpc-service-object.js new file mode 100644 index 00000000000..c40dbb73f68 --- /dev/null +++ b/test/common/grpc-service-object.js @@ -0,0 +1,159 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var assert = require('assert'); +var extend = require('extend'); +var mockery = require('mockery-next'); + +function FakeServiceObject() { + this.calledWith_ = arguments; +} + +describe('GrpcServiceObject', function() { + var GrpcServiceObject; + var grpcServiceObject; + + var CONFIG = {}; + var PROTO_OPTS = {}; + var REQ_OPTS = {}; + + before(function() { + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + GrpcServiceObject = require('../../lib/common/grpc-service-object.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + + beforeEach(function() { + grpcServiceObject = new GrpcServiceObject(CONFIG); + + grpcServiceObject.methods = { + delete: { + protoOpts: PROTO_OPTS, + reqOpts: REQ_OPTS + }, + getMetadata: { + protoOpts: PROTO_OPTS, + reqOpts: REQ_OPTS + }, + setMetadata: { + protoOpts: PROTO_OPTS, + reqOpts: REQ_OPTS + } + }; + }); + + describe('instantiation', function() { + it('should inherit from ServiceObject', function() { + assert(grpcServiceObject instanceof FakeServiceObject); + + var calledWith = grpcServiceObject.calledWith_; + assert.strictEqual(calledWith[0], CONFIG); + }); + }); + + describe('delete', function() { + it('should make the correct request', function(done) { + grpcServiceObject.request = function(protoOpts, reqOpts, callback) { + var deleteMethod = grpcServiceObject.methods.delete; + assert.strictEqual(protoOpts, deleteMethod.protoOpts); + assert.strictEqual(reqOpts, deleteMethod.reqOpts); + callback(); // done() + }; + + grpcServiceObject.delete(done); + }); + + it('should not require a callback', function(done) { + grpcServiceObject.request = function(protoOpts, reqOpts, callback) { + assert.doesNotThrow(callback); + done(); + }; + + grpcServiceObject.delete(); + }); + }); + + describe('getMetadata', function() { + it('should make the correct request', function(done) { + grpcServiceObject.request = function(protoOpts, reqOpts, callback) { + var getMetadataMethod = grpcServiceObject.methods.getMetadata; + assert.strictEqual(protoOpts, getMetadataMethod.protoOpts); + assert.strictEqual(reqOpts, getMetadataMethod.reqOpts); + callback(); // done() + }; + + grpcServiceObject.getMetadata(done); + }); + }); + + describe('setMetadata', function() { + var DEFAULT_REQ_OPTS = { a: 'b' }; + var METADATA = { a: 'c' }; + + it('should make the correct request', function(done) { + var setMetadataMethod = grpcServiceObject.methods.setMetadata; + var expectedReqOpts = extend(true, {}, DEFAULT_REQ_OPTS, METADATA); + + grpcServiceObject.methods.setMetadata.reqOpts = DEFAULT_REQ_OPTS; + + grpcServiceObject.request = function(protoOpts, reqOpts, callback) { + assert.strictEqual(protoOpts, setMetadataMethod.protoOpts); + assert.deepEqual(reqOpts, expectedReqOpts); + callback(); // done() + }; + + grpcServiceObject.setMetadata(METADATA, done); + }); + + it('should not require a callback', function(done) { + grpcServiceObject.request = function(protoOpts, reqOpts, callback) { + assert.doesNotThrow(callback); + done(); + }; + + grpcServiceObject.setMetadata(METADATA); + }); + }); + + describe('request', function() { + it('should call the parent instance request method', function(done) { + grpcServiceObject.parent = { + request: function(protoOpts, reqOpts, callback) { + assert.strictEqual(protoOpts, PROTO_OPTS); + assert.strictEqual(reqOpts, REQ_OPTS); + callback(); // done() + } + }; + + grpcServiceObject.request(PROTO_OPTS, REQ_OPTS, done); + }); + }); +}); diff --git a/test/common/grpc-service.js b/test/common/grpc-service.js new file mode 100644 index 00000000000..37dbedec973 --- /dev/null +++ b/test/common/grpc-service.js @@ -0,0 +1,595 @@ +/** + * 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 googleProtoFiles = require('google-proto-files'); +var grpc = require('grpc'); +var is = require('is'); +var mockery = require('mockery-next'); +var path = require('path'); + +function FakeService() { + this.calledWith_ = arguments; +} + +var googleProtoFilesOverride; +function fakeGoogleProtoFiles() { + return (googleProtoFilesOverride || googleProtoFiles).apply(null, arguments); +} + +var grpcLoadOverride; +var fakeGrpc = { + load: function() { + return (grpcLoadOverride || grpc.load).apply(null, arguments); + }, + credentials: { + combineChannelCredentials: function() { + return { + name: 'combineChannelCredentials', + args: arguments + }; + }, + createSsl: function() { + return { + name: 'createSsl', + args: arguments + }; + }, + createFromGoogleCredential: function() { + return { + name: 'createFromGoogleCredential', + args: arguments + }; + }, + createInsecure: function() { + return { + name: 'createInsecure', + args: arguments + }; + } + } +}; + +describe('GrpcService', function() { + var GrpcService; + var grpcService; + + var CONFIG = { + proto: {}, + service: 'Service', + apiVersion: 'v1' + }; + + var OPTIONS = {}; + var ROOT_DIR = '/root/dir'; + var PROTO_FILE_PATH = 'filepath.proto'; + + var MOCK_GRPC_API = { google: {} }; + MOCK_GRPC_API.google[CONFIG.service] = {}; + MOCK_GRPC_API.google[CONFIG.service][CONFIG.apiVersion] = {}; + + extend(true, fakeGoogleProtoFiles, MOCK_GRPC_API.google); + fakeGoogleProtoFiles[CONFIG.service][CONFIG.apiVersion] = PROTO_FILE_PATH; + + before(function() { + mockery.registerMock('google-proto-files', fakeGoogleProtoFiles); + mockery.registerMock('grpc', fakeGrpc); + mockery.registerMock('../../lib/common/service.js', FakeService); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + GrpcService = require('../../lib/common/grpc-service.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + + beforeEach(function() { + googleProtoFilesOverride = function() { + return ROOT_DIR; + }; + + grpcLoadOverride = function() { + return MOCK_GRPC_API; + }; + + grpcService = new GrpcService(CONFIG, OPTIONS); + }); + + afterEach(function() { + googleProtoFilesOverride = null; + grpcLoadOverride = null; + }); + + describe('instantiation', function() { + it('should inherit from Service', function() { + assert(grpcService instanceof FakeService); + + var calledWith = grpcService.calledWith_; + assert.strictEqual(calledWith[0], CONFIG); + assert.strictEqual(calledWith[1], OPTIONS); + }); + + it('should localize the proto opts', function() { + assert.strictEqual(grpcService.protoOpts, CONFIG.proto); + }); + + it('should get the root directory for the proto files', function(done) { + googleProtoFilesOverride = function(path) { + assert.strictEqual(path, '..'); + setImmediate(done); + return ROOT_DIR; + }; + + new GrpcService(CONFIG, OPTIONS); + }); + + it('should set insecure credentials if using customEndpoint', function() { + var config = extend({}, CONFIG, { customEndpoint: true }); + var grpcService = new GrpcService(config, OPTIONS); + assert.strictEqual(grpcService.grpcCredentials.name, 'createInsecure'); + }); + + it('should call grpc.load correctly', function(done) { + grpcLoadOverride = function(opts) { + assert.strictEqual(opts.root, ROOT_DIR); + + var expectedFilePath = path.relative(ROOT_DIR, PROTO_FILE_PATH); + assert.strictEqual(opts.file, expectedFilePath); + + setImmediate(done); + + return MOCK_GRPC_API; + }; + + var grpcService = new GrpcService(CONFIG, OPTIONS); + assert.strictEqual( + grpcService.proto, + MOCK_GRPC_API.google[CONFIG.service][CONFIG.apiVersion] + ); + }); + }); + + describe('request', function() { + var PROTO_OPTS = { service: 'service', method: 'method', timeout: 3000 }; + var REQ_OPTS = { camelOption: true }; + var GRPC_CREDENTIALS = {}; + + function ProtoService() {} + ProtoService.prototype.method = function() {}; + + beforeEach(function() { + grpcService.grpcCredentials = GRPC_CREDENTIALS; + + grpcService.baseUrl = 'http://base-url'; + grpcService.proto = {}; + grpcService.proto.service = ProtoService; + }); + + it('should not run in the gcloud sandbox environment', function() { + global.GCLOUD_SANDBOX_ENV = true; + assert.strictEqual(grpcService.request(), global.GCLOUD_SANDBOX_ENV); + delete global.GCLOUD_SANDBOX_ENV; + }); + + it('should get an auth client', function(done) { + delete grpcService.grpcCredentials; + + var timesCalled = 0; + + grpcService.getGrpcCredentials_ = function(continueFn) { + // It should call once to get the credentials, then again after. + // To test this, we simply don't set `grpcCredentials` like + // `getGrpcCredentials_` normally would. + timesCalled++; + + if (timesCalled === 1) { + continueFn(); + return; + } + + done(); + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, assert.ifError); + }); + + it('should create an instance of the proto service', function(done) { + grpcService.proto = {}; + grpcService.proto.service = function(baseUrl, credentials) { + assert.strictEqual(baseUrl, grpcService.baseUrl); + assert.strictEqual(credentials, GRPC_CREDENTIALS); + + setImmediate(done); + + return new ProtoService(); + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, assert.ifError); + }); + + it('should make the correct request on the proto service', function(done) { + grpcService.proto = {}; + grpcService.proto.service = function() { + return { + method: function(reqOpts) { + assert.strictEqual(reqOpts.camelOption, undefined); + assert.strictEqual(reqOpts.camel_option, REQ_OPTS.camelOption); + done(); + } + }; + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, assert.ifError); + }); + + it('should set a deadline if a timeout is provided', function(done) { + var expectedDeadlineRange = [ + Date.now() + PROTO_OPTS.timeout - 250, + Date.now() + PROTO_OPTS.timeout + 250 + ]; + + grpcService.proto = {}; + grpcService.proto.service = function() { + return { + method: function(reqOpts, callback, _, grpcOpts) { + assert(is.date(grpcOpts.deadline)); + + assert(grpcOpts.deadline.getTime() > expectedDeadlineRange[0]); + assert(grpcOpts.deadline.getTime() < expectedDeadlineRange[1]); + + done(); + } + }; + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, assert.ifError); + }); + + it('should remove gcloud-specific options', function(done) { + grpcService.proto = {}; + grpcService.proto.service = function() { + return { + method: function(reqOpts) { + assert.strictEqual(reqOpts.autoPaginate, undefined); + assert.strictEqual(reqOpts.autoPaginateVal, undefined); + done(); + } + }; + }; + + grpcService.request(PROTO_OPTS, { + autoPaginate: true, + autoPaginateVal: true + }, assert.ifError); + }); + + describe('error', function() { + var HTTP_ERROR_CODE_MAP = { + 0: { + code: 200, + message: 'OK' + }, + + 1: { + code: 499, + message: 'Client Closed Request' + }, + + 2: { + code: 500, + message: 'Internal Server Error' + }, + + 3: { + code: 400, + message: 'Bad Request' + }, + + 4: { + code: 504, + message: 'Gateway Timeout' + }, + + 5: { + code: 404, + message: 'Not Found' + }, + + 6: { + code: 409, + message: 'Conflict' + }, + + 7: { + code: 403, + message: 'Forbidden' + }, + + 8: { + code: 429, + message: 'Too Many Requests' + }, + + 9: { + code: 412, + message: 'Precondition Failed' + }, + + 10: { + code: 409, + message: 'Conflict' + }, + + 11: { + code: 400, + message: 'Bad Request' + }, + + 12: { + code: 501, + message: 'Not Implemented' + }, + + 13: { + code: 500, + message: 'Internal Server Error' + }, + + 14: { + code: 503, + message: 'Service Unavailable' + }, + + 15: { + code: 500, + message: 'Internal Server Error' + }, + + 16: { + code: 401, + message: 'Unauthorized' + } + }; + + it('should look up the http status from the code', function() { + /*jshint loopfunc:true */ + for (var grpcErrorCode in HTTP_ERROR_CODE_MAP) { + var grpcError = { code: grpcErrorCode }; + var httpError = HTTP_ERROR_CODE_MAP[grpcErrorCode]; + + grpcService.proto = {}; + grpcService.proto.service = function() { + return { + method: function(reqOpts, callback) { + callback(grpcError); + } + }; + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, function(err) { + assert.strictEqual(err, grpcError); + assert.strictEqual(err.code, httpError.code); + }); + } + /*jshint loopfunc:false */ + }); + }); + + describe('success', function() { + var RESPONSE = { + snake_property: true + }; + + beforeEach(function() { + grpcService.proto = {}; + grpcService.proto.service = function() { + return { + method: function(reqOpts, callback) { + callback(null, RESPONSE); + } + }; + }; + }); + + it('should execute callback with response', function(done) { + var expectedResponse = {}; + + grpcService.convertBuffers_ = function(response) { + assert.strictEqual(response.snake_property, undefined); + assert.strictEqual(response.snakeProperty, RESPONSE.snake_property); + return expectedResponse; + }; + + grpcService.request(PROTO_OPTS, REQ_OPTS, function(err, resp) { + assert.ifError(err); + assert.strictEqual(resp, expectedResponse); + done(); + }); + }); + }); + }); + + describe('convertBuffers_', function() { + var DATA_OBJECT = { prop: {} }; + var DATA = [DATA_OBJECT]; + + it('should check if data is buffer-like', function(done) { + grpcService.isBufferLike_ = function(data) { + assert.strictEqual(data, DATA_OBJECT.prop); + done(); + }; + + grpcService.convertBuffers_(DATA); + }); + + it('should convert buffer-like data into base64 strings', function() { + var buffer = new Buffer([1, 2, 3]); + var expectedString = buffer.toString('base64'); + + grpcService.isBufferLike_ = function() { + return true; + }; + + grpcService.objToArr_ = function(data) { + assert.strictEqual(data, DATA_OBJECT.prop); + return buffer; + }; + + var convertedData = grpcService.convertBuffers_(DATA); + assert.strictEqual(convertedData[0].prop, expectedString); + }); + + it('should convert buffers into base64 strings', function() { + var buffer = new Buffer([1, 2, 3]); + var expectedString = buffer.toString('base64'); + + var convertedData = grpcService.convertBuffers_([{ prop: buffer }]); + assert.strictEqual(convertedData[0].prop, expectedString); + }); + }); + + describe('getGrpcCredentials_', function() { + it('should get credentials from the auth client', function(done) { + grpcService.authClient = { + getCredentials: function() { + done(); + } + }; + + grpcService.getGrpcCredentials_(assert.ifError); + }); + + describe('error', function() { + var error = new Error('Error.'); + + beforeEach(function() { + grpcService.authClient = { + getCredentials: function(callback) { + callback(error); + } + }; + }); + + it('should execute callback with error', function(done) { + grpcService.getGrpcCredentials_(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + }); + + describe('success', function() { + var AUTH_CLIENT = {}; + + beforeEach(function() { + grpcService.authClient = { + getCredentials: function(callback) { + grpcService.authClient = { + authClient: AUTH_CLIENT + }; + + callback(); + } + }; + }); + + it('should set grpcCredentials', function(done) { + grpcService.getGrpcCredentials_(function(err) { + assert.ifError(err); + + var grpcCredentials = grpcService.grpcCredentials; + + assert.strictEqual( + grpcCredentials.name, + 'combineChannelCredentials' + ); + + var createSslArg = grpcCredentials.args[0]; + assert.strictEqual(createSslArg.name, 'createSsl'); + assert.deepEqual(createSslArg.args.length, 0); + + var createFromGoogleCredentialArg = grpcCredentials.args[1]; + assert.strictEqual( + createFromGoogleCredentialArg.name, + 'createFromGoogleCredential' + ); + assert.strictEqual( + createFromGoogleCredentialArg.args[0], + AUTH_CLIENT + ); + + done(); + }); + }); + }); + }); + + describe('isBufferLike_', function() { + it('should return false if not an object', function() { + assert.strictEqual(grpcService.isBufferLike_(0), false); + assert.strictEqual(grpcService.isBufferLike_(true), false); + assert.strictEqual(grpcService.isBufferLike_('not-buffer'), false); + }); + + it('should return false if empty', function() { + assert.strictEqual(grpcService.isBufferLike_({}), false); + }); + + it('should filter out `length` and `parent` properties', function() { + var obj = { + 1: 1, + 2: 2, + 3: 3, + length: 3, + parent: 'parent' + }; + + assert.strictEqual(grpcService.isBufferLike_(obj), true); + }); + + it('require every property name to be a number', function() { + var isBufferLike = { 1: 1, 2: 2, 3: 3 }; + var isNotBufferLike = { 1: 1, 2: 2, 3: 3, a: 'a' }; + var isNotBufferLike2 = { 1: 1, 2: 2, 3: 3, '4a': '4a' }; + var isNotBufferLike3 = { 1: 1, 2: 2, 3: 3, a4: 'a4' }; + var isNotBufferLike4 = { 1: 1, 3: 3, 5: 5 }; + + assert.strictEqual(grpcService.isBufferLike_(isBufferLike), true); + assert.strictEqual(grpcService.isBufferLike_(isNotBufferLike), false); + assert.strictEqual(grpcService.isBufferLike_(isNotBufferLike2), false); + assert.strictEqual(grpcService.isBufferLike_(isNotBufferLike3), false); + assert.strictEqual(grpcService.isBufferLike_(isNotBufferLike4), false); + }); + }); + + describe('objToArr_', function() { + it('should convert an object into an array', function() { + assert.deepEqual( + grpcService.objToArr_({ a: 'a', b: 'b', c: 'c' }), + ['a', 'b', 'c'] + ); + }); + }); +}); diff --git a/test/common/service.js b/test/common/service.js index a15c93c7ff0..b2b62a3bf87 100644 --- a/test/common/service.js +++ b/test/common/service.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var util = require('../../lib/common/util.js'); @@ -50,7 +50,7 @@ describe('Service', function() { }; before(function() { - mockery.registerMock('./util.js', util); + mockery.registerMock('../../lib/common/util.js', util); mockery.enable({ useCleanCache: true, diff --git a/test/common/util.js b/test/common/util.js index bb84fd56eb8..b663651e03b 100644 --- a/test/common/util.js +++ b/test/common/util.js @@ -20,7 +20,7 @@ var assert = require('assert'); var duplexify; var extend = require('extend'); var googleAuth = require('google-auto-auth'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var request = require('request'); var retryRequest = require('retry-request'); var stream = require('stream'); diff --git a/test/compute/address.js b/test/compute/address.js index 3775b314661..71c0080b84f 100644 --- a/test/compute/address.js +++ b/test/compute/address.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -41,7 +41,10 @@ describe('Address', function() { }; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/disk.js b/test/compute/disk.js index 49a21ddc217..f3bc2ecadaf 100644 --- a/test/compute/disk.js +++ b/test/compute/disk.js @@ -19,7 +19,7 @@ var assert = require('assert'); var extend = require('extend'); var format = require('string-format-obj'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -58,8 +58,11 @@ describe('Disk', function() { }); before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('./snapshot.js', FakeSnapshot); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/compute/snapshot.js', FakeSnapshot); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/firewall.js b/test/compute/firewall.js index af5e98bcd9a..c8895ec7759 100644 --- a/test/compute/firewall.js +++ b/test/compute/firewall.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -43,7 +43,10 @@ describe('Firewall', function() { var FIREWALL_NETWORK = 'global/networks/default'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/index.js b/test/compute/index.js index defcb623ba5..1368b7bec20 100644 --- a/test/compute/index.js +++ b/test/compute/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -95,15 +95,15 @@ describe('Compute', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./firewall.js', FakeFirewall); - mockery.registerMock('./network.js', FakeNetwork); - mockery.registerMock('./operation.js', FakeOperation); - mockery.registerMock('./region.js', FakeRegion); - mockery.registerMock('./snapshot.js', FakeSnapshot); - mockery.registerMock('./zone.js', FakeZone); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/compute/firewall.js', FakeFirewall); + mockery.registerMock('../../lib/compute/network.js', FakeNetwork); + mockery.registerMock('../../lib/compute/operation.js', FakeOperation); + mockery.registerMock('../../lib/compute/region.js', FakeRegion); + mockery.registerMock('../../lib/compute/snapshot.js', FakeSnapshot); + mockery.registerMock('../../lib/compute/zone.js', FakeZone); mockery.enable({ useCleanCache: true, diff --git a/test/compute/network.js b/test/compute/network.js index f4d3334001c..fcc40e55411 100644 --- a/test/compute/network.js +++ b/test/compute/network.js @@ -19,7 +19,7 @@ var assert = require('assert'); var extend = require('extend'); var format = require('string-format-obj'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -47,7 +47,10 @@ describe('Network', function() { }); before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/operation.js b/test/compute/operation.js index 6745ae3134b..b01ac3acf74 100644 --- a/test/compute/operation.js +++ b/test/compute/operation.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -50,8 +50,11 @@ describe('Operation', function() { var OPERATION_NAME = 'operation-name'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/region.js b/test/compute/region.js index 4af69e41f42..c1484b0feab 100644 --- a/test/compute/region.js +++ b/test/compute/region.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -63,10 +63,13 @@ describe('Region', function() { var REGION_NAME = 'us-central1'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('./address.js', FakeAddress); - mockery.registerMock('./operation.js', FakeOperation); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/compute/address.js', FakeAddress); + mockery.registerMock('../../lib/compute/operation.js', FakeOperation); mockery.enable({ useCleanCache: true, diff --git a/test/compute/snapshot.js b/test/compute/snapshot.js index 55016042b8f..dab500e5c55 100644 --- a/test/compute/snapshot.js +++ b/test/compute/snapshot.js @@ -17,7 +17,7 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -37,7 +37,10 @@ describe('Snapshot', function() { var SNAPSHOT_NAME = 'snapshot-name'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/vm.js b/test/compute/vm.js index 72c77c48edd..6892f51c63e 100644 --- a/test/compute/vm.js +++ b/test/compute/vm.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var util = require('../../lib/common/util.js'); @@ -48,7 +48,10 @@ describe('VM', function() { var VM_NAME = 'vm-name'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/compute/zone.js b/test/compute/zone.js index 9bf9896d6b9..c7ea51b0d6d 100644 --- a/test/compute/zone.js +++ b/test/compute/zone.js @@ -20,7 +20,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); var gceImages = require('gce-images'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -75,11 +75,14 @@ describe('Zone', function() { before(function() { mockery.registerMock('gce-images', fakeGceImages); - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('./disk.js', FakeDisk); - mockery.registerMock('./operation.js', FakeOperation); - mockery.registerMock('./vm.js', FakeVM); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/compute/disk.js', FakeDisk); + mockery.registerMock('../../lib/compute/operation.js', FakeOperation); + mockery.registerMock('../../lib/compute/vm.js', FakeVM); mockery.enable({ useCleanCache: true, diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index d16c2c7e649..e2594dde52e 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var util = require('../../lib/common/util.js'); var normalizeArgumentsCache = util.normalizeArguments; @@ -56,8 +56,8 @@ describe('Dataset', function() { }; before(function() { - mockery.registerMock('../common/util.js', util); - mockery.registerMock('./transaction.js', FakeTransaction); + mockery.registerMock('../../lib/common/util.js', util); + mockery.registerMock('../../lib/datastore/transaction.js', FakeTransaction); mockery.enable({ useCleanCache: true, diff --git a/test/datastore/index.js b/test/datastore/index.js index be9a0541a00..aa4ca08d4d4 100644 --- a/test/datastore/index.js +++ b/test/datastore/index.js @@ -28,13 +28,13 @@ var entity = { }; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); describe('Datastore', function() { var datastore; before(function() { - mockery.registerMock('./entity', entity); + mockery.registerMock('../../lib/datastore/entity.js', entity); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/datastore/query.js b/test/datastore/query.js index b61cb810c00..5f19c0227c5 100644 --- a/test/datastore/query.js +++ b/test/datastore/query.js @@ -23,9 +23,7 @@ var Query = require('../../lib/datastore/query.js'); var queryProto = require('../testdata/proto_query.json'); describe('Query', function() { - describe('instantiation', function() { - it('should use null for all falsy namespace values', function() { [ new Query('', 'Kind'), @@ -48,11 +46,9 @@ describe('Query', function() { var query = new Query(['kind1']); assert.strictEqual(query.autoPaginateVal, true); }); - }); describe('autoPaginate', function() { - it('should enable auto pagination', function() { var query = new Query(['kind1']).autoPaginate(); @@ -77,11 +73,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('filter', function() { - it('should support filtering', function() { var now = new Date(); var query = new Query(['kind1']).filter('date', '<=', now); @@ -149,11 +143,9 @@ describe('Query', function() { assert.equal(filter.op, '='); assert.equal(filter.val, 'Stephen'); }); - }); describe('hasAncestor', function() { - it('should support ancestor filtering', function() { var query = new Query(['kind1']).hasAncestor(['kind2', 123]); @@ -168,11 +160,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('order', function() { - it('should default ordering to ascending', function() { var query = new Query(['kind1']).order('name'); @@ -211,11 +201,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('groupBy', function() { - it('should store an array of properties to group by', function() { var query = new Query(['kind1']).groupBy(['name', 'size']); @@ -234,11 +222,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('select', function() { - it('should store an array of properties to select', function() { var query = new Query(['kind1']).select(['name', 'size']); @@ -257,11 +243,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('start', function() { - it('should capture the starting cursor value', function() { var query = new Query(['kind1']).start('X'); @@ -274,11 +258,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('end', function() { - it('should capture the ending cursor value', function() { var query = new Query(['kind1']).end('Z'); @@ -291,11 +273,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('limit', function() { - it('should capture the number of results to limit to', function() { var query = new Query(['kind1']).limit(20); @@ -308,11 +288,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('offset', function() { - it('should capture the number of results to offset by', function() { var query = new Query(['kind1']).offset(100); @@ -325,11 +303,9 @@ describe('Query', function() { assert.strictEqual(query, nextQuery); }); - }); describe('proto conversion', function() { - it('should be converted to a query proto successfully', function() { var query = new Query(['Kind']) .select(['name', 'count']) @@ -342,7 +318,5 @@ describe('Query', function() { assert.deepEqual(entity.queryToQueryProto(query), queryProto); }); - }); - }); diff --git a/test/datastore/request.js b/test/datastore/request.js index 9a5ba374858..3a245c08214 100644 --- a/test/datastore/request.js +++ b/test/datastore/request.js @@ -23,7 +23,7 @@ var entity = require('../../lib/datastore/entity.js'); var extend = require('extend'); var format = require('string-format-obj'); var is = require('is'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var mockRespGet = require('../testdata/response_get.json'); var pb = require('../../lib/datastore/pb.js'); var Query = require('../../lib/datastore/query.js'); @@ -99,10 +99,10 @@ describe('Request', function() { var CUSTOM_ENDPOINT = 'http://localhost:8080'; before(function() { - mockery.registerMock('./entity.js', fakeEntity); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./pb.js', pb); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/datastore/entity.js', fakeEntity); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/datastore/pb.js', pb); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); mockery.registerMock('request', fakeRequest); mockery.enable({ useCleanCache: true, diff --git a/test/datastore/transaction.js b/test/datastore/transaction.js index 30184ce702f..5a13f075747 100644 --- a/test/datastore/transaction.js +++ b/test/datastore/transaction.js @@ -20,7 +20,7 @@ var arrify = require('arrify'); var assert = require('assert'); var entity = require('../../lib/datastore/entity.js'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var util = require('../../lib/common/util.js'); var DatastoreRequestOverride = { @@ -56,7 +56,10 @@ describe('Transaction', function() { } before(function() { - mockery.registerMock('./request.js', FakeDatastoreRequest); + mockery.registerMock( + '../../lib/datastore/request.js', + FakeDatastoreRequest + ); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/dns/change.js b/test/dns/change.js index b8e7a57cdac..202e33d44de 100644 --- a/test/dns/change.js +++ b/test/dns/change.js @@ -17,7 +17,7 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -42,7 +42,10 @@ describe('Change', function() { var CHANGE_ID = 'change-id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, diff --git a/test/dns/index.js b/test/dns/index.js index 8b96f479bfc..bcb5298eef7 100644 --- a/test/dns/index.js +++ b/test/dns/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -61,10 +61,10 @@ describe('DNS', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./zone.js', FakeZone); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/dns/zone.js', FakeZone); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/dns/zone.js b/test/dns/zone.js index 134435b40df..980cb32702d 100644 --- a/test/dns/zone.js +++ b/test/dns/zone.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -89,10 +89,13 @@ describe('Zone', function() { before(function() { mockery.registerMock('dns-zonefile', fakeDnsZonefile); mockery.registerMock('fs', fakeFs); - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('./change.js', FakeChange); - mockery.registerMock('./record.js', FakeRecord); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/dns/change.js', FakeChange); + mockery.registerMock('../../lib/dns/record.js', FakeRecord); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/docs.js b/test/docs.js index 93276300f4b..25f0dbc3ccd 100644 --- a/test/docs.js +++ b/test/docs.js @@ -81,6 +81,10 @@ describe('documentation', function() { return console; }, {}); + // Set a global to indicate to any interested function inside of gcloud + // that this is a sandbox environment. + global.GCLOUD_SANDBOX_ENV = true; + var sandbox = { gcloud: gcloud, require: require, @@ -88,7 +92,8 @@ describe('documentation', function() { console: mockConsole, Buffer: Buffer, Date: Date, - Array: Array + Array: Array, + global: global }; fileDocBlocks.methods.forEach(function(method) { @@ -103,6 +108,8 @@ describe('documentation', function() { assert.doesNotThrow(runCodeInSandbox.bind(null, code, sandbox)); }); + + delete global.GCLOUD_SANDBOX_ENV; }); }); }); diff --git a/test/index.js b/test/index.js index 86c69ef8974..f3f1a635716 100644 --- a/test/index.js +++ b/test/index.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); function createFakeApi() { return function FakeApi() { @@ -46,15 +46,15 @@ describe('gcloud', function() { var gcloud; before(function() { - mockery.registerMock('./bigquery', FakeBigQuery); - mockery.registerMock('./compute', FakeCompute); - mockery.registerMock('./datastore', FakeDatastore); - mockery.registerMock('./dns', FakeDNS); - mockery.registerMock('./prediction', FakePrediction); - mockery.registerMock('./pubsub', FakePubSub); - mockery.registerMock('./resource', FakeResource); - mockery.registerMock('./search', FakeSearch); - mockery.registerMock('./storage', FakeStorage); + mockery.registerMock('../lib/bigquery', FakeBigQuery); + mockery.registerMock('../lib/compute', FakeCompute); + mockery.registerMock('../lib/datastore', FakeDatastore); + mockery.registerMock('../lib/dns', FakeDNS); + mockery.registerMock('../lib/prediction', FakePrediction); + mockery.registerMock('../lib/pubsub', FakePubSub); + mockery.registerMock('../lib/resource', FakeResource); + mockery.registerMock('../lib/search', FakeSearch); + mockery.registerMock('../lib/storage', FakeStorage); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/logging/index.js b/test/logging/index.js index d5bee6b2bb5..64594349f0b 100644 --- a/test/logging/index.js +++ b/test/logging/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -87,15 +87,15 @@ describe('Logging', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('../bigquery/dataset.js', FakeDataset); - mockery.registerMock('../pubsub/topic.js', FakeTopic); - mockery.registerMock('../storage/bucket.js', FakeBucket); - mockery.registerMock('./log.js', FakeLog); - mockery.registerMock('./entry.js', FakeEntry); - mockery.registerMock('./sink.js', FakeSink); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/bigquery/dataset.js', FakeDataset); + mockery.registerMock('../../lib/pubsub/topic.js', FakeTopic); + mockery.registerMock('../../lib/storage/bucket.js', FakeBucket); + mockery.registerMock('../../lib/logging/log.js', FakeLog); + mockery.registerMock('../../lib/logging/entry.js', FakeEntry); + mockery.registerMock('../../lib/logging/sink.js', FakeSink); mockery.enable({ useCleanCache: true, diff --git a/test/logging/log.js b/test/logging/log.js index a76c8c55a15..0e275b6ea49 100644 --- a/test/logging/log.js +++ b/test/logging/log.js @@ -19,7 +19,7 @@ var assert = require('assert'); var concat = require('concat-stream'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var through = require('through2'); @@ -57,8 +57,11 @@ describe('Log', function() { var assignSeverityToEntriesOverride = null; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('./entry.js', Entry); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/logging/entry.js', Entry); mockery.enable({ useCleanCache: true, diff --git a/test/logging/sink.js b/test/logging/sink.js index 55700072c63..3465d13cf90 100644 --- a/test/logging/sink.js +++ b/test/logging/sink.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -41,7 +41,10 @@ describe('Sink', function() { var SINK_NAME = 'sink-name'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, diff --git a/test/prediction/index.js b/test/prediction/index.js index 6e142b1dfab..ba94d91c50f 100644 --- a/test/prediction/index.js +++ b/test/prediction/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -61,10 +61,10 @@ describe('Prediction', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./model.js', FakeModel); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/prediction/model.js', FakeModel); mockery.enable({ useCleanCache: true, warnOnUnregistered: false diff --git a/test/prediction/model.js b/test/prediction/model.js index 3af23c335b4..6e65b324171 100644 --- a/test/prediction/model.js +++ b/test/prediction/model.js @@ -19,7 +19,7 @@ var assert = require('assert'); var concat = require('concat-stream'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var through = require('through2'); @@ -53,8 +53,11 @@ describe('Index', function() { var ID = 'model-id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, diff --git a/test/pubsub/iam.js b/test/pubsub/iam.js index 90a2b874b32..0190e54193f 100644 --- a/test/pubsub/iam.js +++ b/test/pubsub/iam.js @@ -17,31 +17,31 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); -var ServiceObject = require('../../lib/common/service-object.js'); +var GrpcService = require('../../lib/common/grpc-service.js'); var util = require('../../lib/common/util.js'); -function FakeServiceObject() { +function FakeGrpcService() { this.calledWith_ = arguments; - ServiceObject.apply(this, arguments); + GrpcService.apply(this, arguments); } -nodeutil.inherits(FakeServiceObject, ServiceObject); +nodeutil.inherits(FakeGrpcService, GrpcService); describe('IAM', function() { var IAM; var iam; - var PUBSUB = {}; - var CONFIG = { - baseUrl: '/baseurl', - id: 'id' + var PUBSUB = { + defaultBaseUrl_: 'base-url', + options: {} }; + var ID = 'id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock('../../lib/common/grpc-service.js', FakeGrpcService); mockery.enable({ useCleanCache: true, @@ -57,64 +57,44 @@ describe('IAM', function() { }); beforeEach(function() { - iam = new IAM(PUBSUB, CONFIG); + iam = new IAM(PUBSUB, ID); }); describe('initialization', function() { - it('should inherit from ServiceObject', function() { - assert(iam instanceof ServiceObject); + it('should inherit from GrpcService', function() { + assert(iam instanceof GrpcService); - var calledWith = iam.calledWith_[0]; + var config = iam.calledWith_[0]; + var options = iam.calledWith_[1]; - assert.strictEqual(calledWith.parent, PUBSUB); - assert.strictEqual(calledWith.baseUrl, CONFIG.baseUrl); - assert.strictEqual(calledWith.id, CONFIG.id); - assert.deepEqual(calledWith.methods, {}); + assert.strictEqual(config.baseUrl, PUBSUB.defaultBaseUrl_); + assert.strictEqual(config.service, 'iam'); + assert.strictEqual(config.apiVersion, 'v1'); + assert.deepEqual(config.scopes, [ + 'https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/cloud-platform' + ]); + + assert.strictEqual(options, PUBSUB.options); + }); + + it('should localize the ID', function() { + assert.strictEqual(iam.id, ID); }); }); describe('getPolicy', function() { it('should make the correct API request', function(done) { - iam.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, ':getIamPolicy'); - - done(); - }; - - iam.getPolicy(assert.ifError); - }); - - it('should handle errors properly', function(done) { - var apiResponse = {}; - var error = new Error('Error.'); - - iam.request = function(reqOpts, callback) { - callback(error, apiResponse); - }; - - iam.getPolicy(function(err, policy, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(policy, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); + iam.request = function(protoOpts, reqOpts, callback) { + assert.strictEqual(protoOpts.service, 'IAMPolicy'); + assert.strictEqual(protoOpts.method, 'getIamPolicy'); - it('should pass the callback the expected params', function(done) { - var apiResponse = { - bindings: [{ yo: 'yo' }] - }; + assert.strictEqual(reqOpts.resource, iam.id); - iam.request = function(reqOpts, callback) { - callback(null, apiResponse); + callback(); // done() }; - iam.getPolicy(function(err, policy, apiResponse_) { - assert.ifError(err); - assert.strictEqual(policy, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); + iam.getPolicy(done); }); }); @@ -128,48 +108,17 @@ describe('IAM', function() { it('should make the correct API request', function(done) { var policy = { etag: 'ACAB' }; - iam.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'POST'); - assert.strictEqual(reqOpts.uri, ':setIamPolicy'); - assert.deepEqual(reqOpts.json, { policy: policy }); - - done(); - }; - - iam.setPolicy(policy, assert.ifError); - }); - - it('should handle errors properly', function(done) { - var apiResponse = {}; - var error = new Error('Error.'); - - iam.request = function(reqOpts, callback) { - callback(error, apiResponse); - }; - - iam.setPolicy({}, function(err, policy, apiResponse_) { - assert.strictEqual(err, error); - assert.strictEqual(policy, null); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); - }); + iam.request = function(protoOpts, reqOpts, callback) { + assert.strictEqual(protoOpts.service, 'IAMPolicy'); + assert.strictEqual(protoOpts.method, 'setIamPolicy'); - it('should pass the callback the expected params', function(done) { - var apiResponse = { - bindings: [{ yo: 'yo' }] - }; + assert.strictEqual(reqOpts.resource, iam.id); + assert.strictEqual(reqOpts.policy, policy); - iam.request = function(reqOpts, callback) { - callback(null, apiResponse); + callback(); // done() }; - iam.setPolicy({}, function(err, policy, apiResponse_) { - assert.ifError(err); - assert.strictEqual(policy, apiResponse); - assert.strictEqual(apiResponse_, apiResponse); - done(); - }); + iam.setPolicy(policy, done); }); }); @@ -183,10 +132,12 @@ describe('IAM', function() { it('should make the correct API request', function(done) { var permissions = 'storage.bucket.list'; - iam.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'POST'); - assert.strictEqual(reqOpts.uri, ':testIamPermissions'); - assert.deepEqual(reqOpts.json, { permissions: [permissions] }); + iam.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'IAMPolicy'); + assert.strictEqual(protoOpts.method, 'testIamPermissions'); + + assert.strictEqual(reqOpts.resource, iam.id); + assert.deepEqual(reqOpts.permissions, [permissions]); done(); }; @@ -199,7 +150,7 @@ describe('IAM', function() { var error = new Error('Error.'); var apiResponse = {}; - iam.request = function(reqOpts, callback) { + iam.request = function(protoOpts, reqOpts, callback) { callback(error, apiResponse); }; @@ -220,7 +171,7 @@ describe('IAM', function() { permissions: ['storage.bucket.consume'] }; - iam.request = function(reqOpts, callback) { + iam.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; diff --git a/test/pubsub/index.js b/test/pubsub/index.js index ed0bc27ca67..2a0ee67dec5 100644 --- a/test/pubsub/index.js +++ b/test/pubsub/index.js @@ -19,11 +19,10 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); -var request = require('request'); -var Service = require('../../lib/common/service.js'); +var GrpcService = require('../../lib/common/grpc-service.js'); var Topic = require('../../lib/pubsub/topic.js'); var util = require('../../lib/common/util.js'); @@ -35,25 +34,14 @@ function Subscription(a, b) { return new OverrideFn(a, b); } -var requestCached = request; -var requestOverride; -function fakeRequest() { - return (requestOverride || requestCached).apply(null, arguments); -} -fakeRequest.defaults = function() { - // Ignore the default values, so we don't have to test for them in every API - // call. - return fakeRequest; -}; - var fakeUtil = extend({}, util); -function FakeService() { +function FakeGrpcService() { this.calledWith_ = arguments; - Service.apply(this, arguments); + GrpcService.apply(this, arguments); } -nodeutil.inherits(FakeService, Service); +nodeutil.inherits(FakeGrpcService, GrpcService); var extended = false; var fakeStreamRouter = { @@ -73,35 +61,38 @@ describe('PubSub', function() { var PubSub; var PROJECT_ID = 'test-project'; var pubsub; + var OPTIONS = { projectId: PROJECT_ID }; + + var PUBSUB_EMULATOR_HOST = process.env.PUBSUB_EMULATOR_HOST; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./subscription.js', Subscription); - mockery.registerMock('./topic.js', Topic); - mockery.registerMock('request', fakeRequest); + mockery.registerMock('../../lib/common/grpc-service.js', FakeGrpcService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/pubsub/subscription.js', Subscription); + mockery.registerMock('../../lib/pubsub/topic.js', Topic); mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); + delete process.env.PUBSUB_EMULATOR_HOST; PubSub = require('../../lib/pubsub'); }); after(function() { + if (PUBSUB_EMULATOR_HOST) { + process.env.PUBSUB_EMULATOR_HOST = PUBSUB_EMULATOR_HOST; + } + mockery.deregisterAll(); mockery.disable(); }); beforeEach(function() { SubscriptionOverride = null; - requestOverride = null; - pubsub = new PubSub({ projectId: PROJECT_ID }); - pubsub.request = function(method, path, q, body, callback) { - callback(); - }; + pubsub = new PubSub(OPTIONS); }); describe('instantiation', function() { @@ -128,27 +119,58 @@ describe('PubSub', function() { fakeUtil.normalizeArguments = normalizeArguments; }); - it('should inherit from Service', function() { - assert(pubsub instanceof Service); + it('should inherit from GrpcService', function() { + assert(pubsub instanceof GrpcService); var calledWith = pubsub.calledWith_[0]; - var baseUrl = 'https://pubsub.googleapis.com/v1'; + var baseUrl = 'pubsub.googleapis.com'; assert.strictEqual(calledWith.baseUrl, baseUrl); + assert.strictEqual(calledWith.service, 'pubsub'); + assert.strictEqual(calledWith.apiVersion, 'v1'); assert.deepEqual(calledWith.scopes, [ 'https://www.googleapis.com/auth/pubsub', 'https://www.googleapis.com/auth/cloud-platform' ]); }); + + it('should set the defaultBaseUrl_', function() { + assert.strictEqual(pubsub.defaultBaseUrl_, 'pubsub.googleapis.com'); + }); + + it('should use the PUBSUB_EMULATOR_HOST env var', function() { + var pubSubHost = 'pubsub-host'; + process.env.PUBSUB_EMULATOR_HOST = pubSubHost; + + var pubsub = new PubSub({ projectId: 'project-id' }); + delete process.env.PUBSUB_EMULATOR_HOST; + + var calledWith = pubsub.calledWith_[0]; + assert.strictEqual(calledWith.baseUrl, pubSubHost); + }); + + it('should localize the options provided', function() { + assert.strictEqual(pubsub.options, OPTIONS); + }); }); describe('createTopic', function() { it('should make the correct API request', function(done) { var topicName = 'new-topic-name'; + var formattedName = 'formatted-name'; + + var formatName_ = Topic.formatName_; + Topic.formatName_ = function(projectId, name) { + Topic.formatName_ = formatName_; + assert.strictEqual(projectId, pubsub.projectId); + assert.strictEqual(name, topicName); + return formattedName; + }; - pubsub.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'PUT'); - assert.strictEqual(reqOpts.uri, '/topics/' + topicName); + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Publisher'); + assert.strictEqual(protoOpts.method, 'createTopic'); + assert.strictEqual(reqOpts.name, formattedName); done(); }; @@ -160,7 +182,7 @@ describe('PubSub', function() { var apiResponse = {}; beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(error, apiResponse); }; }); @@ -179,7 +201,7 @@ describe('PubSub', function() { var apiResponse = {}; beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; }); @@ -212,7 +234,7 @@ describe('PubSub', function() { describe('getSubscriptions', function() { beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, { subscriptions: [{ name: 'fake-subscription' }] }); }; }); @@ -226,9 +248,10 @@ describe('PubSub', function() { }); it('should pass the correct arguments to the API', function(done) { - pubsub.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, '/subscriptions'); - assert.deepEqual(reqOpts.qs, {}); + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'listSubscriptions'); + assert.strictEqual(reqOpts.project, 'projects/' + pubsub.projectId); done(); }; @@ -238,15 +261,16 @@ describe('PubSub', function() { describe('topics', function() { var TOPIC; var TOPIC_NAME = 'topic'; - var TOPIC_SUBCRIPTION_NAME = '/topics/' + TOPIC_NAME + '/subscriptions'; - before(function() { + beforeEach(function() { TOPIC = new Topic(pubsub, TOPIC_NAME); }); it('should subscribe to a topic by string', function(done) { - pubsub.request = function(reqOpts) { - assert.equal(reqOpts.uri, TOPIC_SUBCRIPTION_NAME); + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Publisher'); + assert.strictEqual(protoOpts.method, 'listTopicSubscriptions'); + assert.strictEqual(reqOpts.topic, TOPIC_NAME); done(); }; @@ -254,8 +278,8 @@ describe('PubSub', function() { }); it('should subscribe to a topic by Topic instance', function(done) { - pubsub.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, TOPIC_SUBCRIPTION_NAME); + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(reqOpts.topic, TOPIC.name); done(); }; @@ -266,9 +290,9 @@ describe('PubSub', function() { it('should pass options to API request', function(done) { var opts = { pageSize: 10, pageToken: 'abc' }; - pubsub.request = function(reqOpts) { - assert.strictEqual(reqOpts.qs.pageSize, opts.pageSize); - assert.strictEqual(reqOpts.qs.pageToken, opts.pageToken); + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(reqOpts.pageSize, opts.pageSize); + assert.strictEqual(reqOpts.pageToken, opts.pageToken); done(); }; @@ -279,7 +303,7 @@ describe('PubSub', function() { var error = new Error('Error'); var resp = { error: true }; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(error, resp); }; @@ -304,7 +328,7 @@ describe('PubSub', function() { var subFullName = 'projects/' + PROJECT_ID + '/subscriptions/' + subName; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, { subscriptions: [subName] }); }; @@ -320,7 +344,7 @@ describe('PubSub', function() { it('should return a query if more results exist', function() { var token = 'next-page-token'; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, { nextPageToken: token }); }; @@ -336,7 +360,7 @@ describe('PubSub', function() { it('should pass apiResponse to callback', function(done) { var resp = { success: true }; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; @@ -352,7 +376,7 @@ describe('PubSub', function() { var apiResponse = { topics: [{ name: topicName }]}; beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; }); @@ -366,11 +390,20 @@ describe('PubSub', function() { }); it('should build the right request', function(done) { - pubsub.request = function(reqOpts) { - assert.equal(reqOpts.uri, '/topics'); + var options = { a: 'b', c: 'd' }; + var originalOptions = extend({}, options); + var expectedOptions = extend({}, options, { + project: 'projects/' + pubsub.projectId + }); + + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Publisher'); + assert.strictEqual(protoOpts.method, 'listTopics'); + assert.deepEqual(reqOpts, expectedOptions); + assert.deepEqual(options, originalOptions); done(); }; - pubsub.getTopics(function() {}); + pubsub.getTopics(options, function() {}); }); it('should return Topic instances with metadata', function(done) { @@ -391,7 +424,7 @@ describe('PubSub', function() { it('should return a query if more results exist', function() { var token = 'next-page-token'; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, { nextPageToken: token }); }; var query = { pageSize: 1 }; @@ -404,7 +437,7 @@ describe('PubSub', function() { it('should pass error if api returns an error', function() { var error = new Error('Error'); - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(error); }; pubsub.getTopics(function(err) { @@ -414,7 +447,7 @@ describe('PubSub', function() { it('should pass apiResponse to callback', function(done) { var resp = { success: true }; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; pubsub.getTopics(function(err, topics, nextQuery, apiResponse) { @@ -427,12 +460,12 @@ describe('PubSub', function() { describe('subscribe', function() { var TOPIC_NAME = 'topic'; var TOPIC = { - name: '/topics/' + TOPIC_NAME + name: 'projects/' + PROJECT_ID + '/topics/' + TOPIC_NAME }; var SUB_NAME = 'subscription'; var SUBSCRIPTION = { - name: '/subscriptions/' + SUB_NAME + name: 'projects/' + PROJECT_ID + '/subscriptions/' + SUB_NAME }; var apiResponse = { @@ -452,14 +485,27 @@ describe('PubSub', function() { }); it('should not require configuration options', function(done) { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; pubsub.subscribe(TOPIC_NAME, SUB_NAME, done); }); - it('should create a topic object from a string', function(done) { + it('should create a Subscription', function(done) { + var opts = { a: 'b', c: 'd' }; + + pubsub.subscription = function(subName, options) { + assert.strictEqual(subName, SUB_NAME); + assert.deepEqual(options, opts); + setImmediate(done); + return SUBSCRIPTION; + }; + + pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); + }); + + it('should create a Topic object from a string', function(done) { pubsub.request = util.noop; pubsub.topic = function(topicName) { @@ -478,10 +524,18 @@ describe('PubSub', function() { }; }; - pubsub.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'PUT'); - assert.strictEqual(reqOpts.uri, SUBSCRIPTION.name); - assert.strictEqual(reqOpts.json.topic, TOPIC_NAME); + pubsub.subscription = function(subName) { + return { + name: subName + }; + }; + + pubsub.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'createSubscription'); + assert.strictEqual(protoOpts.timeout, pubsub.timeout); + assert.strictEqual(reqOpts.topic, TOPIC_NAME); + assert.strictEqual(reqOpts.name, SUB_NAME); done(); }; @@ -502,9 +556,10 @@ describe('PubSub', function() { timeout: 30000 }; - var expectedBody = extend({}, options, { - topic: TOPIC_NAME - }); + var expectedBody = extend({ + topic: TOPIC_NAME, + name: SUB_NAME + }, options); delete expectedBody.autoAck; delete expectedBody.encoding; @@ -519,9 +574,15 @@ describe('PubSub', function() { }; }; - pubsub.request = function(reqOpts) { - assert.notStrictEqual(reqOpts.json, options); - assert.deepEqual(reqOpts.json, expectedBody); + pubsub.subscription = function() { + return { + name: SUB_NAME + }; + }; + + pubsub.request = function(protoOpts, reqOpts) { + assert.notStrictEqual(reqOpts, options); + assert.deepEqual(reqOpts, expectedBody); done(); }; @@ -533,7 +594,7 @@ describe('PubSub', function() { var apiResponse = { name: SUB_NAME }; beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(error, apiResponse); }; }); @@ -545,7 +606,7 @@ describe('PubSub', function() { return SUBSCRIPTION; }; - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback({ code: 409 }, apiResponse); }; @@ -566,7 +627,7 @@ describe('PubSub', function() { }); it('should return error & API response to the callback', function(done) { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(error, apiResponse); }; @@ -583,30 +644,25 @@ describe('PubSub', function() { var apiResponse = { name: SUB_NAME }; beforeEach(function() { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; }); - it('should pass options to a new subscription object', function(done) { - var opts = { a: 'b', c: 'd' }; + it('should return Subscription & resp to the callback', function(done) { + var subscription = {}; - pubsub.subscription = function(subName, options) { - assert.strictEqual(subName, SUB_NAME); - assert.deepEqual(options, opts); - setImmediate(done); - return SUBSCRIPTION; + pubsub.subscription = function() { + return subscription; }; - pubsub.subscribe(TOPIC_NAME, SUB_NAME, opts, assert.ifError); - }); - - it('should return apiResponse to the callback', function(done) { - pubsub.request = function(reqOpts, callback) { + pubsub.request = function(protoOpts, reqOpts, callback) { callback(null, apiResponse); }; pubsub.subscribe(TOPIC_NAME, SUB_NAME, function(err, sub, resp) { + assert.ifError(err); + assert.strictEqual(sub, subscription); assert.strictEqual(resp, apiResponse); done(); }); @@ -674,39 +730,44 @@ describe('PubSub', function() { delete process.env.PUBSUB_EMULATOR_HOST; }); - it('should default to pubsub.googleapis.com/v1', function() { - pubsub.determineBaseUrl_(); + it('should default to defaultBaseUrl_', function() { + var defaultBaseUrl_ = 'defaulturl'; + pubsub.defaultBaseUrl_ = defaultBaseUrl_; - var expectedBaseUrl = 'https://pubsub.googleapis.com/v1'; - assert.strictEqual(pubsub.baseUrl, expectedBaseUrl); + pubsub.determineBaseUrl_(); + assert.strictEqual(pubsub.baseUrl_, defaultBaseUrl_); }); it('should remove slashes from the baseUrl', function() { - var expectedBaseUrl = 'http://localhost:8080'; + var expectedBaseUrl = 'localhost:8080'; - setHost('http://localhost:8080/'); + setHost('localhost:8080/'); pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.baseUrl, expectedBaseUrl); + assert.strictEqual(pubsub.baseUrl_, expectedBaseUrl); - setHost('http://localhost:8080//'); + setHost('localhost:8080//'); pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.baseUrl, expectedBaseUrl); + assert.strictEqual(pubsub.baseUrl_, expectedBaseUrl); }); - it('should default to http if protocol is unspecified', function() { - setHost('localhost:8080'); + it('should remove the protocol if specified', function() { + setHost('http://localhost:8080'); + pubsub.determineBaseUrl_(); + assert.strictEqual(pubsub.baseUrl_, 'localhost:8080'); + + setHost('https://localhost:8080'); pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.baseUrl, 'http://localhost:8080'); + assert.strictEqual(pubsub.baseUrl_, 'localhost:8080'); }); - it('should not set customEndpoint when using default endpoint', function() { + it('should not set customEndpoint_ when using default baseurl', function() { var pubsub = new PubSub({ projectId: PROJECT_ID }); pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.customEndpoint, undefined); + assert.strictEqual(pubsub.customEndpoint_, undefined); }); describe('with PUBSUB_EMULATOR_HOST environment variable', function() { - var PUBSUB_EMULATOR_HOST = 'http://localhost:9090'; + var PUBSUB_EMULATOR_HOST = 'localhost:9090'; beforeEach(function() { setHost(PUBSUB_EMULATOR_HOST); @@ -718,12 +779,12 @@ describe('PubSub', function() { it('should use the PUBSUB_EMULATOR_HOST env var', function() { pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.baseUrl, PUBSUB_EMULATOR_HOST); + assert.strictEqual(pubsub.baseUrl_, PUBSUB_EMULATOR_HOST); }); - it('should set customEndpoint', function() { + it('should set customEndpoint_', function() { pubsub.determineBaseUrl_(); - assert.strictEqual(pubsub.customEndpoint, true); + assert.strictEqual(pubsub.customEndpoint_, true); }); }); }); diff --git a/test/pubsub/subscription.js b/test/pubsub/subscription.js index b7c9048df9f..168e4e87cba 100644 --- a/test/pubsub/subscription.js +++ b/test/pubsub/subscription.js @@ -18,22 +18,22 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); -var ServiceObject = require('../../lib/common/service-object.js'); +var GrpcServiceObject = require('../../lib/common/grpc-service-object.js'); var util = require('../../lib/common/util.js'); function FakeIAM() { this.calledWith_ = [].slice.call(arguments); } -function FakeServiceObject() { +function FakeGrpcServiceObject() { this.calledWith_ = arguments; - ServiceObject.apply(this, arguments); + GrpcServiceObject.apply(this, arguments); } -nodeutil.inherits(FakeServiceObject, ServiceObject); +nodeutil.inherits(FakeGrpcServiceObject, GrpcServiceObject); var formatMessageOverride; @@ -52,7 +52,7 @@ describe('Subscription', function() { var messageBinary = new Buffer(message).toString('binary'); var messageObj = { receivedMessages: [{ - ackId: 3, + ackId: 'abc', message: { data: messageBuffer, messageId: 7 @@ -60,19 +60,22 @@ describe('Subscription', function() { }] }; var expectedMessage = { - ackId: 3, + ackId: 'abc', data: message, id: 7 }; var expectedMessageAsBinary = { - ackId: 3, + ackId: 'abc', data: messageBinary, id: 7 }; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('./iam.js', FakeIAM); + mockery.registerMock( + '../../lib/common/grpc-service-object.js', + FakeGrpcServiceObject + ); + mockery.registerMock('../../lib/pubsub/iam.js', FakeIAM); mockery.enable({ useCleanCache: true, @@ -166,25 +169,29 @@ describe('Subscription', function() { it('should create an iam object', function() { assert.deepEqual(subscription.iam.calledWith_, [ PUBSUB, - { - baseUrl: '/subscriptions', - id: SUB_NAME - } + SUB_FULL_NAME ]); }); - it('should inherit from ServiceObject', function() { - assert(subscription instanceof ServiceObject); + it('should inherit from GrpcServiceObject', function() { + assert(subscription instanceof GrpcServiceObject); var calledWith = subscription.calledWith_[0]; assert.strictEqual(calledWith.parent, PUBSUB); - assert.strictEqual(calledWith.baseUrl, '/subscriptions'); - assert.strictEqual(calledWith.id, SUB_NAME); + assert.strictEqual(calledWith.id, SUB_FULL_NAME); assert.deepEqual(calledWith.methods, { exists: true, get: true, - getMetadata: true + getMetadata: { + protoOpts: { + service: 'Subscriber', + method: 'getSubscription' + }, + reqOpts: { + subscription: subscription.name + } + } }); }); @@ -205,19 +212,10 @@ describe('Subscription', function() { name: SUB_NAME, topic: topicInstance }); - assert(subscription instanceof ServiceObject); + assert(subscription instanceof GrpcServiceObject); var calledWith = subscription.calledWith_[0]; - - assert.strictEqual(calledWith.parent, pubSubInstance); - assert.strictEqual(calledWith.baseUrl, '/subscriptions'); - assert.strictEqual(calledWith.id, SUB_NAME); - assert.deepEqual(calledWith.methods, { - create: true, - exists: true, - get: true, - getMetadata: true - }); + assert.deepEqual(calledWith.methods.create, true); }); }); @@ -228,7 +226,7 @@ describe('Subscription', function() { var attributes = {}; var msg = Subscription.formatMessage_({ - ackId: 3, + ackId: 'abc', message: { data: stringified, messageId: 7, @@ -237,7 +235,7 @@ describe('Subscription', function() { }); assert.deepEqual(msg, { - ackId: 3, + ackId: 'abc', id: 7, data: obj, attributes: attributes @@ -293,26 +291,34 @@ describe('Subscription', function() { }); it('should make an array out of ids', function(done) { - var ID = 1; - subscription.request = function(reqOpts) { - assert.deepEqual(reqOpts.json.ackIds, [ID]); + var ID = 'abc'; + + subscription.request = function(protoOpts, reqOpts) { + assert.deepEqual(reqOpts.ackIds, [ID]); done(); }; + subscription.ack(ID, assert.ifError); }); it('should make correct api request', function(done) { var IDS = [1, 2, 3]; - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.uri, ':acknowledge'); - assert.deepEqual(reqOpts.json.ackIds, IDS); + + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'acknowledge'); + + assert.strictEqual(reqOpts.subscription, subscription.name); + assert.strictEqual(reqOpts.ackIds, IDS); + done(); }; + subscription.ack(IDS, assert.ifError); }); it('should unmark the ack ids as being in progress', function(done) { - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; @@ -331,7 +337,7 @@ describe('Subscription', function() { }); it('should not unmark if there was an error', function(done) { - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(new Error('Error.')); }; @@ -348,7 +354,7 @@ describe('Subscription', function() { }); it('should refresh paused status', function(done) { - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; @@ -360,7 +366,7 @@ describe('Subscription', function() { it('should pass error to callback', function(done) { var error = new Error('Error.'); - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(error); }; @@ -372,7 +378,7 @@ describe('Subscription', function() { it('should pass apiResponse to callback', function(done) { var resp = { success: true }; - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; subscription.ack(1, function(err, apiResponse) { @@ -384,15 +390,20 @@ describe('Subscription', function() { describe('delete', function() { it('should delete a subscription', function(done) { - FakeServiceObject.prototype.delete = function() { - assert.strictEqual(this, subscription); + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'deleteSubscription'); + + assert.strictEqual(reqOpts.subscription, subscription.name); + done(); }; + subscription.delete(); }); it('should close a subscription once deleted', function() { - FakeServiceObject.prototype.delete = function(callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; subscription.closed = false; @@ -401,7 +412,7 @@ describe('Subscription', function() { }); it('should remove all listeners', function(done) { - FakeServiceObject.prototype.delete = function(callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; subscription.removeAllListeners = function() { @@ -411,7 +422,7 @@ describe('Subscription', function() { }); it('should execute callback when deleted', function(done) { - FakeServiceObject.prototype.delete = function(callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; subscription.delete(done); @@ -419,7 +430,7 @@ describe('Subscription', function() { it('should execute callback with an api error', function(done) { var error = new Error('Error.'); - FakeServiceObject.prototype.delete = function(callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(error); }; subscription.delete(function(err) { @@ -430,7 +441,7 @@ describe('Subscription', function() { it('should execute callback with apiResponse', function(done) { var resp = { success: true }; - FakeServiceObject.prototype.delete = function(callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; subscription.delete(function(err, apiResponse) { @@ -443,7 +454,7 @@ describe('Subscription', function() { describe('pull', function() { beforeEach(function() { subscription.ack = util.noop; - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, messageObj); }; }); @@ -453,28 +464,31 @@ describe('Subscription', function() { }); it('should default returnImmediately to false', function(done) { - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.json.returnImmediately, false); + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(reqOpts.returnImmediately, false); done(); }; subscription.pull({}, assert.ifError); }); it('should honor options', function(done) { - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.json.returnImmediately, true); + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(reqOpts.returnImmediately, true); done(); }; subscription.pull({ returnImmediately: true }, assert.ifError); }); it('should make correct api request', function(done) { - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'POST'); - assert.strictEqual(reqOpts.timeout, 92000); - assert.strictEqual(reqOpts.uri, ':pull'); - assert.strictEqual(reqOpts.json.returnImmediately, false); - assert.strictEqual(reqOpts.json.maxMessages, 1); + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'pull'); + assert.strictEqual(protoOpts.timeout, 92000); + + assert.strictEqual(reqOpts.subscription, subscription.name); + assert.strictEqual(reqOpts.returnImmediately, false); + assert.strictEqual(reqOpts.maxMessages, 1); + done(); }; @@ -489,8 +503,8 @@ describe('Subscription', function() { timeout: timeout }); - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.timeout, 30000); + subscription.request = function(protoOpts) { + assert.strictEqual(protoOpts.timeout, 30000); done(); }; @@ -511,7 +525,7 @@ describe('Subscription', function() { it('should clear the active request', function(done) { var requestInstance = {}; - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { setImmediate(function() { callback(null, {}); assert.strictEqual(subscription.activeRequest_, null); @@ -526,7 +540,7 @@ describe('Subscription', function() { it('should pass error to callback', function(done) { var error = new Error('Error.'); - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(error); }; subscription.pull(function(err) { @@ -536,11 +550,8 @@ describe('Subscription', function() { }); it('should not return messages if request timed out', function(done) { - subscription.request = function(reqOpts, callback) { - var error = new Error(); - error.code = 'ETIMEDOUT'; - error.connect = false; - callback(error); + subscription.request = function(protoOpts, reqOpts, callback) { + callback({ code: 504 }); }; subscription.pull({}, function(err, messages) { @@ -625,7 +636,7 @@ describe('Subscription', function() { }); it('should not autoAck if no messages returned', function(done) { - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, { receivedMessages: [] }); }; subscription.ack = function() { @@ -668,7 +679,7 @@ describe('Subscription', function() { receivedMessages: [{ ackId: 1, message: { - messageId: '123', + messageId: 'abc', data: new Buffer('message').toString('base64') } }] @@ -678,7 +689,7 @@ describe('Subscription', function() { callback(null, { success: true }); }; - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; @@ -692,20 +703,24 @@ describe('Subscription', function() { describe('setAckDeadline', function() { it('should set the ack deadline', function(done) { - subscription.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'POST'); - assert.strictEqual(reqOpts.uri, ':modifyAckDeadline'); - assert.deepEqual(reqOpts.json, { - ackIds: [123], + subscription.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Subscriber'); + assert.strictEqual(protoOpts.method, 'modifyAckDeadline'); + + assert.deepEqual(reqOpts, { + subscription: subscription.name, + ackIds: ['abc'], ackDeadlineSeconds: 10 }); + done(); }; - subscription.setAckDeadline({ ackIds: [123], seconds: 10 }, done); + + subscription.setAckDeadline({ ackIds: ['abc'], seconds: 10 }, done); }); it('should execute the callback', function(done) { - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(); }; subscription.setAckDeadline({}, done); @@ -713,7 +728,7 @@ describe('Subscription', function() { it('should execute the callback with apiResponse', function(done) { var resp = { success: true }; - subscription.request = function(reqOpts, callback) { + subscription.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; subscription.setAckDeadline({}, function(err, apiResponse) { @@ -853,11 +868,11 @@ describe('Subscription', function() { assert.strictEqual(subscription.closed, true); }); - it('should abort the HTTP request when listeners removed', function(done) { + it('should cancel the HTTP request when listeners removed', function(done) { subscription.startPulling_ = util.noop; subscription.activeRequest_ = { - abort: done + cancel: done }; subscription.on('message', util.noop); diff --git a/test/pubsub/topic.js b/test/pubsub/topic.js index 73c46bb964b..98a49e7b7a4 100644 --- a/test/pubsub/topic.js +++ b/test/pubsub/topic.js @@ -18,22 +18,22 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var util = require('../../lib/common/util.js'); -var ServiceObject = require('../../lib/common/service-object.js'); +var GrpcServiceObject = require('../../lib/common/grpc-service-object.js'); function FakeIAM() { this.calledWith_ = [].slice.call(arguments); } -function FakeServiceObject() { +function FakeGrpcServiceObject() { this.calledWith_ = arguments; - ServiceObject.apply(this, arguments); + GrpcServiceObject.apply(this, arguments); } -nodeutil.inherits(FakeServiceObject, ServiceObject); +nodeutil.inherits(FakeGrpcServiceObject, GrpcServiceObject); describe('Topic', function() { var Topic; @@ -48,15 +48,18 @@ describe('Topic', function() { }; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('./iam', FakeIAM); + mockery.registerMock( + '../../lib/common/grpc-service-object.js', + FakeGrpcServiceObject + ); + mockery.registerMock('../../lib/pubsub/iam.js', FakeIAM); mockery.enable({ useCleanCache: true, warnOnUnregistered: false }); - Topic = require('../../lib/pubsub/topic'); + Topic = require('../../lib/pubsub/topic.js'); }); after(function() { @@ -69,41 +72,49 @@ describe('Topic', function() { }); describe('initialization', function() { - it('should inherit from ServiceObject', function(done) { + it('should inherit from GrpcServiceObject', function() { var pubsubInstance = extend({}, PUBSUB, { createTopic: { bind: function(context) { assert.strictEqual(context, pubsubInstance); - done(); } } }); var topic = new Topic(pubsubInstance, TOPIC_NAME); - assert(topic instanceof ServiceObject); + assert(topic instanceof GrpcServiceObject); var calledWith = topic.calledWith_[0]; assert.strictEqual(calledWith.parent, pubsubInstance); - assert.strictEqual(calledWith.baseUrl, '/topics'); - assert.strictEqual(calledWith.id, TOPIC_UNFORMATTED_NAME); + assert.strictEqual(calledWith.id, TOPIC_NAME); assert.deepEqual(calledWith.methods, { create: true, - delete: true, + delete: { + protoOpts: { + service: 'Publisher', + method: 'deleteTopic' + }, + reqOpts: { + topic: TOPIC_NAME + } + }, exists: true, get: true, - getMetadata: true + getMetadata: { + protoOpts: { + service: 'Publisher', + method: 'getTopic' + }, + reqOpts: { + topic: TOPIC_NAME + } + } }); }); it('should create an iam object', function() { - assert.deepEqual(topic.iam.calledWith_, [ - PUBSUB, - { - baseUrl: '/topics', - id: TOPIC_UNFORMATTED_NAME - } - ]); + assert.deepEqual(topic.iam.calledWith_, [PUBSUB, TOPIC_NAME]); }); it('should format name', function(done) { @@ -118,10 +129,6 @@ describe('Topic', function() { it('should assign pubsub object to `this`', function() { assert.deepEqual(topic.pubsub, PUBSUB); }); - - it('should localize the unformatted name', function() { - assert.strictEqual(topic.unformattedName, TOPIC_UNFORMATTED_NAME); - }); }); describe('formatMessage_', function() { @@ -204,14 +211,15 @@ describe('Topic', function() { }); it('should send correct api request', function(done) { - topic.request = function(reqOpts) { - assert.strictEqual(reqOpts.method, 'POST'); - assert.strictEqual(reqOpts.uri, ':publish'); - assert.deepEqual(reqOpts.json, { - messages: [ - { data: new Buffer(JSON.stringify(message)).toString('base64') } - ] - }); + topic.request = function(protoOpts, reqOpts) { + assert.strictEqual(protoOpts.service, 'Publisher'); + assert.strictEqual(protoOpts.method, 'publish'); + + assert.strictEqual(reqOpts.topic, topic.name); + assert.deepEqual(reqOpts.messages, [ + { data: new Buffer(JSON.stringify(message)).toString('base64') } + ]); + done(); }; @@ -219,7 +227,7 @@ describe('Topic', function() { }); it('should execute callback', function(done) { - topic.request = function(reqOpts, callback) { + topic.request = function(protoOpts, reqOpts, callback) { callback(null, {}); }; @@ -230,7 +238,7 @@ describe('Topic', function() { var error = new Error('Error.'); var apiResponse = {}; - topic.request = function(reqOpts, callback) { + topic.request = function(protoOpts, reqOpts, callback) { callback(error, apiResponse); }; @@ -246,7 +254,7 @@ describe('Topic', function() { it('should execute callback with apiResponse', function(done) { var resp = { success: true }; - topic.request = function(reqOpts, callback) { + topic.request = function(protoOpts, reqOpts, callback) { callback(null, resp); }; diff --git a/test/resource/index.js b/test/resource/index.js index fa5b3b8ffc9..a76911a00a2 100644 --- a/test/resource/index.js +++ b/test/resource/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -68,10 +68,10 @@ describe('Resource', function() { var resource; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./project.js', FakeProject); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/resource/project.js', FakeProject); mockery.enable({ useCleanCache: true, diff --git a/test/resource/project.js b/test/resource/project.js index 9c8e49033a6..eead6ad1c28 100644 --- a/test/resource/project.js +++ b/test/resource/project.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -41,7 +41,10 @@ describe('Project', function() { var ID = 'project-id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, diff --git a/test/search/document.js b/test/search/document.js index 7ef166dc79f..07359d0f367 100644 --- a/test/search/document.js +++ b/test/search/document.js @@ -17,7 +17,7 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -40,7 +40,10 @@ describe('Document', function() { var ID = 'document-id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, diff --git a/test/search/index-class.js b/test/search/index-class.js index a2f0041921b..1835abb4f57 100644 --- a/test/search/index-class.js +++ b/test/search/index-class.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -58,9 +58,12 @@ describe('Index', function() { var ID = 'index-id'; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('./document.js', FakeDocument); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/search/document.js', FakeDocument); mockery.enable({ useCleanCache: true, diff --git a/test/search/index.js b/test/search/index.js index 9b3d49ecca1..079139dd68b 100644 --- a/test/search/index.js +++ b/test/search/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var prop = require('propprop'); @@ -69,10 +69,10 @@ describe('Search', function() { var PROJECT_ID = 'project-id'; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('./index-class.js', FakeIndex); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/search/index-class.js', FakeIndex); mockery.enable({ useCleanCache: true, diff --git a/test/storage/bucket.js b/test/storage/bucket.js index cfc1d08109f..c3232e4e6be 100644 --- a/test/storage/bucket.js +++ b/test/storage/bucket.js @@ -21,7 +21,7 @@ var assert = require('assert'); var async = require('async'); var extend = require('extend'); var mime = require('mime-types'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var propAssign = require('prop-assign'); var request = require('request'); @@ -105,10 +105,13 @@ describe('Bucket', function() { before(function() { mockery.registerMock('async', fakeAsync); mockery.registerMock('request', fakeRequest); - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); - mockery.registerMock('./acl.js', FakeAcl); - mockery.registerMock('./file.js', FakeFile); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/storage/acl.js', FakeAcl); + mockery.registerMock('../../lib/storage/file.js', FakeFile); mockery.enable({ useCleanCache: true, diff --git a/test/storage/channel.js b/test/storage/channel.js index 8cc6ee000ef..57bedaafdb0 100644 --- a/test/storage/channel.js +++ b/test/storage/channel.js @@ -21,7 +21,7 @@ 'use strict'; var assert = require('assert'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var ServiceObject = require('../../lib/common/service-object.js'); @@ -42,7 +42,10 @@ describe('Channel', function() { var channel; before(function() { - mockery.registerMock('../common/service-object.js', FakeServiceObject); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); mockery.enable({ useCleanCache: true, diff --git a/test/storage/file.js b/test/storage/file.js index 54103ba11d8..84c360ad469 100644 --- a/test/storage/file.js +++ b/test/storage/file.js @@ -21,7 +21,7 @@ var duplexify; var extend = require('extend'); var format = require('string-format-obj'); var fs = require('fs'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var request = require('request'); var stream = require('stream'); @@ -94,8 +94,11 @@ describe('File', function() { before(function() { mockery.registerMock('gcs-resumable-upload', fakeResumableUpload); mockery.registerMock('request', fakeRequest); - mockery.registerMock('../common/service-object.js', FakeServiceObject); - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true, diff --git a/test/storage/index.js b/test/storage/index.js index 53aa5665649..5848561a187 100644 --- a/test/storage/index.js +++ b/test/storage/index.js @@ -19,7 +19,7 @@ var arrify = require('arrify'); var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var nodeutil = require('util'); var Service = require('../../lib/common/service.js'); @@ -55,9 +55,9 @@ describe('Storage', function() { var Bucket; before(function() { - mockery.registerMock('../common/service.js', FakeService); - mockery.registerMock('../common/util.js', fakeUtil); - mockery.registerMock('../common/stream-router.js', fakeStreamRouter); + mockery.registerMock('../../lib/common/service.js', FakeService); + mockery.registerMock('../../lib/common/util.js', fakeUtil); + mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); mockery.enable({ useCleanCache: true, diff --git a/test/translate/index.js b/test/translate/index.js index 3afa1f4f2b7..88f6a951e03 100644 --- a/test/translate/index.js +++ b/test/translate/index.js @@ -18,7 +18,7 @@ var assert = require('assert'); var extend = require('extend'); -var mockery = require('mockery'); +var mockery = require('mockery-next'); var prop = require('propprop'); var util = require('../../lib/common/util.js'); @@ -41,7 +41,7 @@ describe('Resource', function() { var translate; before(function() { - mockery.registerMock('../common/util.js', fakeUtil); + mockery.registerMock('../../lib/common/util.js', fakeUtil); mockery.enable({ useCleanCache: true,