From 9edd3d6df70cbc8acd381c5ba244192da2b94725 Mon Sep 17 00:00:00 2001
From: Stephen Sawchuk <sawchuk@gmail.com>
Date: Sat, 25 Jul 2015 13:46:12 -0400
Subject: [PATCH] pubsub: remove autoCreate, add getMetadata

---
 lib/pubsub/index.js   |  59 +++----------
 lib/pubsub/topic.js   |  73 ++++++----------
 system-test/pubsub.js |  26 ++----
 test/pubsub/index.js  |  20 ++++-
 test/pubsub/topic.js  | 199 ++++++++++++++----------------------------
 5 files changed, 128 insertions(+), 249 deletions(-)

diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js
index 1de3f6622b5..57db7cff075 100644
--- a/lib/pubsub/index.js
+++ b/lib/pubsub/index.js
@@ -174,9 +174,9 @@ PubSub.prototype.getTopics = function(query, callback) {
       return;
     }
     var topics = (result.topics || []).map(function(item) {
-      return new Topic(self, {
-        name: item.name
-      });
+      var topicInstance = self.topic(item.name);
+      topicInstance.metadata = item;
+      return topicInstance;
     });
     var nextQuery = null;
     if (result.nextPageToken) {
@@ -199,16 +199,10 @@ PubSub.prototype.getTopics = function(query, callback) {
  *
  * @example
  * pubsub.createTopic('my-new-topic', function(err, topic, apiResponse) {
- *   topic.publish({
- *     data: 'New message!'
- *   }, function(err) {});
+ *   if (!err) {
+ *     // The topic was created successfully.
+ *   }
  * });
- *
- * //-
- * // <strong>Note:</strong> For cases like the one above, it is simpler to use
- * // {module:pubsub#topic}, which will create the topic for you at the time you
- * // publish a message.
- * //-
  */
 PubSub.prototype.createTopic = function(name, callback) {
   callback = callback || util.noop;
@@ -359,56 +353,27 @@ PubSub.prototype.subscription = function(name, options) {
 };
 
 /**
- * Create a Topic object to reference an existing topic.
+ * Create a Topic object to reference an existing topic. See
+ * {module:pubsub/createTopic} to create a topic.
  *
  * @throws {Error} If a name is not provided.
  *
  * @param {string} name - The name of the topic.
- * @param {object=} options - Configuration object.
- * @param {boolean} options.autoCreate - Automatically create topic if it
- *     doesn't exist. Note that messages published to a topic with no
- *     subscribers will not be delivered. Default: true.
  * @return {module:pubsub/topic}
  *
  * @example
- * //-
- * // By default, it isn't required to specify a topic that already exists. The
- * // first time you publish a message, the topic will be created for you.
- * //
- * // This will only cost one additional API request at the time of publishing.
- * // If the topic doesn't need to be created, there is no performance penalty.
- * //-
- * var topic = pubsub.topic('my-topic');
+ * var topic = pubsub.topic('my-existing-topic');
  *
  * topic.publish({
  *   data: 'New message!'
  * }, function(err) {});
- *
- * //-
- * // If you prefer an error when trying to publish to a topic that doesn't
- * // exist, set `autoCreate` to `false`.
- * //-
- * var nonExistentTopic = pubsub.topic('my-non-existent-topic', {
- *   autoCreate: false
- * });
- *
- * nonExistentTopic.publish({
- *   data: 'New message!'
- * }, function(err) {
- *   if (err) {
- *     // API error from trying to publish a message to a non-existent topic.
- *   }
- * });
  */
-PubSub.prototype.topic = function(name, options) {
+PubSub.prototype.topic = function(name) {
   if (!name) {
     throw new Error('A name must be specified for a new topic.');
   }
-  options = options || {};
-  return new Topic(this, {
-    name: name,
-    autoCreate: options.autoCreate
-  });
+
+  return new Topic(this, name);
 };
 
 /**
diff --git a/lib/pubsub/topic.js b/lib/pubsub/topic.js
index 602ce68f810..e610909babb 100644
--- a/lib/pubsub/topic.js
+++ b/lib/pubsub/topic.js
@@ -29,11 +29,7 @@ var util = require('../common/util.js');
 /*! Developer Documentation
  *
  * @param {module:pubsub} pubsub - PubSub object.
- * @param {object} options - Configuration object.
- * @param {boolean=} options.autoCreate - Automatically create topic if it
- *     doesn't exist. Note that messages published to a topic with no
- *     subscribers will not be delivered. Default: true.
- * @param {string} options.name - Name of the topic.
+ * @param {string} name - Name of the topic.
  */
 /**
  * A Topic object allows you to interact with a Google Cloud Pub/Sub topic.
@@ -48,13 +44,14 @@ var util = require('../common/util.js');
  *
  * var topic = pubsub.topic('my-topic');
  */
-function Topic(pubsub, options) {
-  this.autoCreate = options.autoCreate !== false;
-  this.name = Topic.formatName_(pubsub.projectId, options.name);
+function Topic(pubsub, name) {
+  this.name = Topic.formatName_(pubsub.projectId, name);
 
   this.projectId = pubsub.projectId;
   this.pubsub = pubsub;
-  this.unformattedName = options.name;
+  this.unformattedName = name;
+
+  this.makeReq_ = this.pubsub.makeReq_.bind(this.pubsub);
 }
 
 /**
@@ -181,6 +178,28 @@ Topic.prototype.delete = function(callback) {
   this.makeReq_('DELETE', this.name, null, null, callback);
 };
 
+/**
+ * Get the official representation of this topic from the API.
+ *
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.metadata - The metadata of the Topic.
+ * @param {object} callback.apiResponse - The full API response.
+ */
+Topic.prototype.getMetadata = function(callback) {
+  var self = this;
+
+  this.makeReq_('GET', this.name, null, null, function(err, resp) {
+    if (err) {
+      callback(err, null, resp);
+      return;
+    }
+
+    self.metadata = resp;
+    callback(null, self.metadata, resp);
+  });
+};
+
 /**
  * Get a list of the subscriptions registered to this topic. You may optionally
  * provide a query object as the first argument to customize the response.
@@ -305,40 +324,4 @@ Topic.prototype.subscription = function(name, options) {
   return this.pubsub.subscription(name, options);
 };
 
-/**
- * Make an API request using the parent PubSub object's `makeReq_`. If the Topic
- * instance has `autoCreate: true` set, this method will first try to create the
- * Topic in the event of a 404.
- *
- * @private
- *
- * @param {string} method - Action.
- * @param {string} path - Request path.
- * @param {*} query - Request query object.
- * @param {*} body - Request body contents.
- * @param {function} callback - The callback function.
- */
-Topic.prototype.makeReq_ = function(method, path, query, body, callback) {
-  var self = this;
-
-  function createTopicThenRetryRequest() {
-    self.pubsub.createTopic(self.unformattedName, function(err, topic, res) {
-      if (err) {
-        callback(err, null, res);
-        return;
-      }
-
-      self.pubsub.makeReq_(method, path, query, body, callback);
-    });
-  }
-
-  this.pubsub.makeReq_(method, path, query, body, function(err, res) {
-    if (self.autoCreate && err && err.code === 404 && method !== 'DELETE') {
-      createTopicThenRetryRequest();
-    } else {
-      callback(err, res);
-    }
-  });
-};
-
 module.exports = Topic;
diff --git a/system-test/pubsub.js b/system-test/pubsub.js
index f772251acf6..3cc751e2fca 100644
--- a/system-test/pubsub.js
+++ b/system-test/pubsub.js
@@ -115,30 +115,20 @@ describe('pubsub', function() {
       });
     });
 
-    it('should lazily create by default', function(done) {
-      var newTopicName = generateTopicName();
-      var newTopic = pubsub.topic(newTopicName);
-
-      newTopic.publish({ data: 'message from me' }, function(err) {
+    it('should publish a message', function(done) {
+      var topic = pubsub.topic(TOPIC_NAMES[0]);
+      topic.publish({ data: 'message from me' }, function(err, messageIds) {
         assert.ifError(err);
-
-        pubsub.getTopics(function(err, topics) {
-          assert.ifError(err);
-
-          assert(topics.some(function(topic) {
-            return topic.name.indexOf(newTopicName) > -1;
-          }));
-
-          newTopic.delete(done);
-        });
+        assert.equal(messageIds.length, 1);
+        done();
       });
     });
 
-    it('should publish a message', function(done) {
+    it('should get the metadata of a topic', function(done) {
       var topic = pubsub.topic(TOPIC_NAMES[0]);
-      topic.publish({ data: 'message from me' }, function(err, messageIds) {
+      topic.getMetadata(function(err, metadata) {
         assert.ifError(err);
-        assert.equal(messageIds.length, 1);
+        assert.strictEqual(metadata.name, topic.name);
         done();
       });
     });
diff --git a/test/pubsub/index.js b/test/pubsub/index.js
index 68901156a35..5089f4a44c4 100644
--- a/test/pubsub/index.js
+++ b/test/pubsub/index.js
@@ -99,9 +99,12 @@ describe('PubSub', function() {
   });
 
   describe('getTopics', function() {
+    var topicName = 'fake-topic';
+    var apiResponse = { topics: [{ name: topicName }]};
+
     beforeEach(function() {
       pubsub.makeReq_ = function(method, path, q, body, callback) {
-        callback(null, { topics: [{ name: 'fake-topic' }] });
+        callback(null, apiResponse);
       };
     });
 
@@ -121,10 +124,19 @@ describe('PubSub', function() {
       pubsub.getTopics(function() {});
     });
 
-    it('should return Topic instances', function() {
+    it('should return Topic instances with metadata', function(done) {
+      var topic = {};
+
+      pubsub.topic = function(name) {
+        assert.strictEqual(name, topicName);
+        return topic;
+      };
+
       pubsub.getTopics(function(err, topics) {
         assert.ifError(err);
-        assert(topics[0] instanceof Topic);
+        assert.strictEqual(topics[0], topic);
+        assert.strictEqual(topics[0].metadata, apiResponse.topics[0]);
+        done();
       });
     });
 
@@ -237,7 +249,7 @@ describe('PubSub', function() {
         'projects/' + PROJECT_ID + '/topics/' + TOPIC_NAME + '/subscriptions';
 
       before(function() {
-        TOPIC = new Topic(pubsub, { name: TOPIC_NAME });
+        TOPIC = new Topic(pubsub, TOPIC_NAME);
       });
 
       it('should subscribe to a topic by string', function(done) {
diff --git a/test/pubsub/topic.js b/test/pubsub/topic.js
index 3a057fd9ac5..57905d9c8e9 100644
--- a/test/pubsub/topic.js
+++ b/test/pubsub/topic.js
@@ -30,7 +30,7 @@ describe('Topic', function() {
   var topic;
 
   beforeEach(function() {
-    topic = new Topic(pubsubMock, { name: TOPIC_NAME });
+    topic = new Topic(pubsubMock, TOPIC_NAME);
   });
 
   describe('initialization', function() {
@@ -40,7 +40,7 @@ describe('Topic', function() {
         Topic.formatName_ = formatName_;
         done();
       };
-      new Topic(pubsubMock, { name: TOPIC_NAME });
+      new Topic(pubsubMock, TOPIC_NAME);
     });
 
     it('should assign projectId to `this`', function() {
@@ -50,18 +50,6 @@ describe('Topic', function() {
     it('should assign pubsub object to `this`', function() {
       assert.deepEqual(topic.pubsub, pubsubMock);
     });
-
-    it('should set `autoCreate` to true by default', function() {
-      assert.strictEqual(topic.autoCreate, true);
-    });
-
-    it('should allow overriding autoCreate', function() {
-      var topic = new Topic(pubsubMock, {
-        name: TOPIC_NAME,
-        autoCreate: false
-      });
-      assert.strictEqual(topic.autoCreate, false);
-    });
   });
 
   describe('formatMessage_', function() {
@@ -186,6 +174,68 @@ describe('Topic', function() {
     });
   });
 
+  describe('getMetadata', function() {
+    it('should make the correct API request', function(done) {
+      topic.makeReq_ = function(method, path, query, body) {
+        assert.strictEqual(method, 'GET');
+        assert.strictEqual(path, topic.name);
+        assert.strictEqual(query, null);
+        assert.strictEqual(body, null);
+
+        done();
+      };
+
+      topic.getMetadata(assert.ifError);
+    });
+
+    describe('error', function() {
+      var error = new Error('Error.');
+      var apiResponse = { a: 'b', c: 'd' };
+
+      beforeEach(function() {
+        topic.makeReq_ = function(method, path, query, body, callback) {
+          callback(error, apiResponse);
+        };
+      });
+
+      it('should execute callback with error & API response', function(done) {
+        topic.getMetadata(function(err, metadata, apiResponse_) {
+          assert.strictEqual(err, error);
+          assert.strictEqual(metadata, null);
+          assert.strictEqual(apiResponse_, apiResponse);
+          done();
+        });
+      });
+    });
+
+    describe('success', function() {
+      var apiResponse = { a: 'b', c: 'd' };
+
+      beforeEach(function() {
+        topic.makeReq_ = function(method, path, query, body, callback) {
+          callback(null, apiResponse);
+        };
+      });
+
+      it('should assign the response to the metadata property', function(done) {
+        topic.getMetadata(function(err) {
+          assert.ifError(err);
+          assert.strictEqual(topic.metadata, apiResponse);
+          done();
+        });
+      });
+
+      it('should exec callback with metadata & API response', function(done) {
+        topic.getMetadata(function(err, metadata, apiResponse_) {
+          assert.ifError(err);
+          assert.strictEqual(metadata, apiResponse);
+          assert.strictEqual(apiResponse_, apiResponse);
+          done();
+        });
+      });
+    });
+  });
+
   describe('getSubscriptions', function() {
     it('should accept just a callback', function(done) {
       topic.pubsub.getSubscriptions = function(options, callback) {
@@ -250,125 +300,4 @@ describe('Topic', function() {
       doneFn();
     });
   });
-
-  describe('makeReq_', function() {
-    var method = 'POST';
-    var path = '/path';
-    var query = 'query';
-    var body = 'body';
-
-    it('should call through to pubsub.makeReq_', function(done) {
-      topic.pubsub.makeReq_ = function(m, p, q, b) {
-        assert.equal(m, method);
-        assert.equal(p, path);
-        assert.equal(q, query);
-        assert.equal(b, body);
-
-        done();
-      };
-
-      topic.makeReq_(method, path, query, body, util.noop);
-    });
-
-    describe('autoCreate: false', function() {
-      it('should execute callback with response', function(done) {
-        var error = new Error('Error.');
-        var apiResponse = { a: 'b', c: 'd' };
-
-        topic.pubsub.makeReq_ = function(method, path, query, body, callback) {
-          callback(error, apiResponse);
-        };
-
-        topic.makeReq_(method, path, query, body, function(err, apiResp) {
-          assert.deepEqual(err, error);
-          assert.deepEqual(apiResp, apiResponse);
-
-          done();
-        });
-      });
-    });
-
-    describe('autoCreate: true', function() {
-      it('should not create a topic if doing a DELETE', function(done) {
-        var topicCreated = false;
-
-        topic.pubsub.createTopic = function() {
-          topicCreated = true;
-        };
-
-        topic.pubsub.makeReq_ = function(method, path, query, body, callback) {
-          callback({ code: 404 });
-        };
-
-        topic.makeReq_('DELETE', path, query, body, function() {
-          assert.strictEqual(topicCreated, false);
-
-          done();
-        });
-      });
-
-      it('should create a non-DELETE API request returns 404', function(done) {
-        topic.pubsub.createTopic = function(topicName) {
-          assert.equal(topicName, TOPIC_NAME);
-
-          done();
-        };
-
-        topic.pubsub.makeReq_ = function(method, path, query, body, callback) {
-          callback({ code: 404 });
-        };
-
-        topic.makeReq_(method, path, query, body, util.noop);
-      });
-
-      describe('creating topic failed', function() {
-        var error = new Error('Error.');
-        var apiResponse = { a: 'b', c: 'd' };
-
-        beforeEach(function() {
-          topic.pubsub.createTopic = function(topicName, callback) {
-            callback(error, null, apiResponse);
-          };
-
-          topic.pubsub.makeReq_ = function(m, p, q, b, callback) {
-            callback({ code: 404 });
-          };
-        });
-
-        it('should execute the callback with error & apiResp', function(done) {
-          topic.makeReq_(method, path, query, body, function(err, t, apiResp) {
-            assert.deepEqual(err, error);
-            assert.strictEqual(t, null);
-            assert.deepEqual(apiResp, apiResponse);
-
-            done();
-          });
-        });
-      });
-
-      describe('creating topic succeeded', function() {
-        it('should call makeReq_ again with the original args', function(done) {
-          topic.pubsub.createTopic = function(topicName, callback) {
-            callback();
-          };
-
-          topic.pubsub.makeReq_ = function(m, p, q, b, callback) {
-            // Overwrite the method to confirm it is called again.
-            topic.pubsub.makeReq_ = function(m, p, q, b, callback) {
-              assert.equal(m, method);
-              assert.equal(p, path);
-              assert.equal(q, query);
-              assert.equal(b, body);
-
-              callback(); // (should be the done function)
-            };
-
-            callback({ code: 404 });
-          };
-
-          topic.makeReq_(method, path, query, body, done);
-        });
-      });
-    });
-  });
 });