From de672f0842a19e2e3107f3d0ad6c1ff5e3a9b9ef Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 29 Mar 2016 09:45:21 -0400 Subject: [PATCH 1/6] Handle subscriptions w/ deleted topics cleanly. Problem observed when trying to clean up subscriptions after failing system tests: the API returns '_deleted-topic_' for the topic path, which prevented us from un-marshalling the subscription. --- gcloud/pubsub/subscription.py | 16 ++++++++++------ gcloud/pubsub/test_subscription.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 1bcfa48b06ee..292b0fb0d941 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -67,12 +67,16 @@ def from_api_repr(cls, resource, client, topics=None): if topics is None: topics = {} topic_path = resource['topic'] - topic = topics.get(topic_path) - if topic is None: - # NOTE: This duplicates behavior from Topic.from_api_repr to avoid - # an import cycle. - topic_name = topic_name_from_path(topic_path, client.project) - topic = topics[topic_path] = client.topic(topic_name) + if topic_path == '_deleted-topic_': + # Use a name which cannot conflict ('#' not allowed). + topic = client.topic('###DELETED-TOPIC###') + else: + topic = topics.get(topic_path) + if topic is None: + # NOTE: This duplicates behavior from Topic.from_api_repr to + # avoid an import cycle. + topic_name = topic_name_from_path(topic_path, client.project) + topic = topics[topic_path] = client.topic(topic_name) _, _, _, name = resource['name'].split('/') ack_deadline = resource.get('ackDeadlineSeconds') push_config = resource.get('pushConfig', {}) diff --git a/gcloud/pubsub/test_subscription.py b/gcloud/pubsub/test_subscription.py index 1b8ea750643a..edbee889af14 100644 --- a/gcloud/pubsub/test_subscription.py +++ b/gcloud/pubsub/test_subscription.py @@ -68,6 +68,29 @@ def test_from_api_repr_no_topics(self): self.assertEqual(subscription.ack_deadline, DEADLINE) self.assertEqual(subscription.push_endpoint, ENDPOINT) + def test_from_api_repr_w_deleted_topic(self): + from gcloud.pubsub.topic import Topic + PROJECT = 'PROJECT' + TOPIC_PATH = '_deleted-topic_' + SUB_NAME = 'sub_name' + SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) + DEADLINE = 42 + ENDPOINT = 'https://api.example.com/push' + resource = {'topic': TOPIC_PATH, + 'name': SUB_PATH, + 'ackDeadlineSeconds': DEADLINE, + 'pushConfig': {'pushEndpoint': ENDPOINT}} + klass = self._getTargetClass() + client = _Client(project=PROJECT) + subscription = klass.from_api_repr(resource, client) + self.assertEqual(subscription.name, SUB_NAME) + topic = subscription.topic + self.assertTrue(isinstance(topic, Topic)) + self.assertEqual(topic.name, '###DELETED-TOPIC###') + self.assertEqual(topic.project, PROJECT) + self.assertEqual(subscription.ack_deadline, DEADLINE) + self.assertEqual(subscription.push_endpoint, ENDPOINT) + def test_from_api_repr_w_topics_no_topic_match(self): from gcloud.pubsub.topic import Topic TOPIC_NAME = 'topic_name' From d30509c0246317194f31a635e73e36130614435b Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 29 Mar 2016 14:11:58 -0400 Subject: [PATCH 2/6] Define/document a constant for the special '_deleted-topic_' path. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1671#discussion_r57762192. --- gcloud/pubsub/subscription.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 292b0fb0d941..9615e64f6d1a 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -40,6 +40,14 @@ class Subscription(object): :param push_endpoint: URL to which messages will be pushed by the back-end. If not set, the application must pull messages. """ + + _DELETED_TOPIC_PATH = '_deleted-topic_' + """Value of ``projects.subscriptions.topic`` when topic has been deleted. + + See: + https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions#Subscription.FIELDS.topic + """ + def __init__(self, name, topic, ack_deadline=None, push_endpoint=None): self.name = name self.topic = topic @@ -67,7 +75,7 @@ def from_api_repr(cls, resource, client, topics=None): if topics is None: topics = {} topic_path = resource['topic'] - if topic_path == '_deleted-topic_': + if topic_path == cls._DELETED_TOPIC_PATH: # Use a name which cannot conflict ('#' not allowed). topic = client.topic('###DELETED-TOPIC###') else: From dd0774d3f742555b411869eae36b5389f30e1e67 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 29 Mar 2016 14:12:37 -0400 Subject: [PATCH 3/6] Use None for deleted topic name. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1671#discussion_r57762146. --- gcloud/pubsub/subscription.py | 3 +-- gcloud/pubsub/test_subscription.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 9615e64f6d1a..1d39f2b4555a 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -76,8 +76,7 @@ def from_api_repr(cls, resource, client, topics=None): topics = {} topic_path = resource['topic'] if topic_path == cls._DELETED_TOPIC_PATH: - # Use a name which cannot conflict ('#' not allowed). - topic = client.topic('###DELETED-TOPIC###') + topic = client.topic(name=None) else: topic = topics.get(topic_path) if topic is None: diff --git a/gcloud/pubsub/test_subscription.py b/gcloud/pubsub/test_subscription.py index edbee889af14..d79f8978a0ed 100644 --- a/gcloud/pubsub/test_subscription.py +++ b/gcloud/pubsub/test_subscription.py @@ -86,7 +86,7 @@ def test_from_api_repr_w_deleted_topic(self): self.assertEqual(subscription.name, SUB_NAME) topic = subscription.topic self.assertTrue(isinstance(topic, Topic)) - self.assertEqual(topic.name, '###DELETED-TOPIC###') + self.assertEqual(topic.name, None) self.assertEqual(topic.project, PROJECT) self.assertEqual(subscription.ack_deadline, DEADLINE) self.assertEqual(subscription.push_endpoint, ENDPOINT) From 1fe6ba120864515c1e5794fd078d4ff636edcbf0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 4 Apr 2016 12:36:21 -0400 Subject: [PATCH 4/6] Allow 'topic' passed to 'Subscription' to be None. In that case (representing subscriptions whose topic has been deleted), caller must pass in an explicit 'client' (which is otherwised not allowed). Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1671#discussion_r57917080 --- gcloud/pubsub/subscription.py | 34 +- gcloud/pubsub/test_subscription.py | 583 ++++++++++++----------------- 2 files changed, 272 insertions(+), 345 deletions(-) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 1d39f2b4555a..2b9aa831e51c 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -29,8 +29,9 @@ class Subscription(object): :type name: string :param name: the name of the subscription - :type topic: :class:`gcloud.pubsub.topic.Topic` - :param topic: the topic to which the subscription belongs.. + :type topic: :class:`gcloud.pubsub.topic.Topic` or ``NoneType`` + :param topic: the topic to which the subscription belongs; if ``None``, + the subscription's topic has been deleted. :type ack_deadline: int :param ack_deadline: the deadline (in seconds) by which messages pulled @@ -39,6 +40,10 @@ class Subscription(object): :type push_endpoint: string :param push_endpoint: URL to which messages will be pushed by the back-end. If not set, the application must pull messages. + + :type client: :class:`gcloud.pubsub.client.Client` or ``NoneType`` + :param client: the client to use. If not passed, falls back to the + ``client`` stored on the topic. """ _DELETED_TOPIC_PATH = '_deleted-topic_' @@ -48,9 +53,19 @@ class Subscription(object): https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions#Subscription.FIELDS.topic """ - def __init__(self, name, topic, ack_deadline=None, push_endpoint=None): + def __init__(self, name, topic=None, ack_deadline=None, push_endpoint=None, + client=None): + + if client is None and topic is None: + raise TypeError("Pass only one of 'topic' or 'client'.") + + if client is not None and topic is not None: + raise TypeError("Pass only one of 'topic' or 'client'.") + self.name = name self.topic = topic + self._client = client or topic._client + self._project = self._client.project self.ack_deadline = ack_deadline self.push_endpoint = push_endpoint @@ -76,7 +91,7 @@ def from_api_repr(cls, resource, client, topics=None): topics = {} topic_path = resource['topic'] if topic_path == cls._DELETED_TOPIC_PATH: - topic = client.topic(name=None) + topic = None else: topic = topics.get(topic_path) if topic is None: @@ -88,13 +103,20 @@ def from_api_repr(cls, resource, client, topics=None): ack_deadline = resource.get('ackDeadlineSeconds') push_config = resource.get('pushConfig', {}) push_endpoint = push_config.get('pushEndpoint') + if topic is None: + return cls(name, ack_deadline=ack_deadline, + push_endpoint=push_endpoint, client=client) return cls(name, topic, ack_deadline, push_endpoint) + @property + def project(self): + """Project bound to the subscription.""" + return self._client.project + @property def path(self): """URL path for the subscription's APIs""" - project = self.topic.project - return '/projects/%s/subscriptions/%s' % (project, self.name) + return '/projects/%s/subscriptions/%s' % (self.project, self.name) def _require_client(self, client): """Check client or verify over-ride. diff --git a/gcloud/pubsub/test_subscription.py b/gcloud/pubsub/test_subscription.py index d79f8978a0ed..d187e8cd07d6 100644 --- a/gcloud/pubsub/test_subscription.py +++ b/gcloud/pubsub/test_subscription.py @@ -16,6 +16,13 @@ class TestSubscription(unittest2.TestCase): + PROJECT = 'PROJECT' + TOPIC_NAME = 'topic_name' + TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) + SUB_NAME = 'sub_name' + SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) + DEADLINE = 42 + ENDPOINT = 'https://api.example.com/push' def _getTargetClass(self): from gcloud.pubsub.subscription import Subscription @@ -25,296 +32,244 @@ def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_ctor_defaults(self): - SUB_NAME = 'sub_name' - topic = object() - subscription = self._makeOne(SUB_NAME, topic) - self.assertEqual(subscription.name, SUB_NAME) + client = _Client(project=self.PROJECT) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) + self.assertEqual(subscription.name, self.SUB_NAME) self.assertTrue(subscription.topic is topic) self.assertEqual(subscription.ack_deadline, None) self.assertEqual(subscription.push_endpoint, None) def test_ctor_explicit(self): - SUB_NAME = 'sub_name' - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - topic = object() - subscription = self._makeOne(SUB_NAME, topic, DEADLINE, ENDPOINT) - self.assertEqual(subscription.name, SUB_NAME) + client = _Client(project=self.PROJECT) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic, + self.DEADLINE, self.ENDPOINT) + self.assertEqual(subscription.name, self.SUB_NAME) self.assertTrue(subscription.topic is topic) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) + + def test_ctor_w_client_wo_topic(self): + client = _Client(project=self.PROJECT) + subscription = self._makeOne(self.SUB_NAME, client=client) + self.assertEqual(subscription.name, self.SUB_NAME) + self.assertTrue(subscription.topic is None) + + def test_ctor_w_both_topic_and_client(self): + client1 = _Client(project=self.PROJECT) + client2 = _Client(project=self.PROJECT) + topic = _Topic(self.TOPIC_NAME, client=client1) + with self.assertRaises(TypeError): + self._makeOne(self.SUB_NAME, topic, client=client2) + + def test_ctor_w_neither_topic_nor_client(self): + with self.assertRaises(TypeError): + self._makeOne(self.SUB_NAME) def test_from_api_repr_no_topics(self): from gcloud.pubsub.topic import Topic - TOPIC_NAME = 'topic_name' - PROJECT = 'PROJECT' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - resource = {'topic': TOPIC_PATH, - 'name': SUB_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}} + resource = {'topic': self.TOPIC_PATH, + 'name': self.SUB_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}} klass = self._getTargetClass() - client = _Client(project=PROJECT) + client = _Client(project=self.PROJECT) subscription = klass.from_api_repr(resource, client) - self.assertEqual(subscription.name, SUB_NAME) + self.assertEqual(subscription.name, self.SUB_NAME) topic = subscription.topic self.assertTrue(isinstance(topic, Topic)) - self.assertEqual(topic.name, TOPIC_NAME) - self.assertEqual(topic.project, PROJECT) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertEqual(topic.name, self.TOPIC_NAME) + self.assertEqual(topic.project, self.PROJECT) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) def test_from_api_repr_w_deleted_topic(self): - from gcloud.pubsub.topic import Topic - PROJECT = 'PROJECT' - TOPIC_PATH = '_deleted-topic_' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - resource = {'topic': TOPIC_PATH, - 'name': SUB_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}} klass = self._getTargetClass() - client = _Client(project=PROJECT) + resource = {'topic': klass._DELETED_TOPIC_PATH, + 'name': self.SUB_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}} + klass = self._getTargetClass() + client = _Client(project=self.PROJECT) subscription = klass.from_api_repr(resource, client) - self.assertEqual(subscription.name, SUB_NAME) - topic = subscription.topic - self.assertTrue(isinstance(topic, Topic)) - self.assertEqual(topic.name, None) - self.assertEqual(topic.project, PROJECT) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertEqual(subscription.name, self.SUB_NAME) + self.assertTrue(subscription.topic is None) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) def test_from_api_repr_w_topics_no_topic_match(self): from gcloud.pubsub.topic import Topic - TOPIC_NAME = 'topic_name' - PROJECT = 'PROJECT' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - resource = {'topic': TOPIC_PATH, - 'name': SUB_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}} + resource = {'topic': self.TOPIC_PATH, + 'name': self.SUB_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}} topics = {} klass = self._getTargetClass() - client = _Client(project=PROJECT) + client = _Client(project=self.PROJECT) subscription = klass.from_api_repr(resource, client, topics=topics) - self.assertEqual(subscription.name, SUB_NAME) + self.assertEqual(subscription.name, self.SUB_NAME) topic = subscription.topic self.assertTrue(isinstance(topic, Topic)) - self.assertTrue(topic is topics[TOPIC_PATH]) - self.assertEqual(topic.name, TOPIC_NAME) - self.assertEqual(topic.project, PROJECT) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertTrue(topic is topics[self.TOPIC_PATH]) + self.assertEqual(topic.name, self.TOPIC_NAME) + self.assertEqual(topic.project, self.PROJECT) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) def test_from_api_repr_w_topics_w_topic_match(self): - TOPIC_NAME = 'topic_name' - PROJECT = 'PROJECT' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - resource = {'topic': TOPIC_PATH, - 'name': SUB_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}} - topic = object() - topics = {TOPIC_PATH: topic} + resource = {'topic': self.TOPIC_PATH, + 'name': self.SUB_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}} + client = _Client(project=self.PROJECT) + topic = _Topic(self.TOPIC_NAME, client=client) + topics = {self.TOPIC_PATH: topic} klass = self._getTargetClass() - client = _Client(project=PROJECT) subscription = klass.from_api_repr(resource, client, topics=topics) - self.assertEqual(subscription.name, SUB_NAME) + self.assertEqual(subscription.name, self.SUB_NAME) self.assertTrue(subscription.topic is topic) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) def test_create_pull_wo_ack_deadline_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - BODY = {'topic': TOPIC_PATH} - conn = _Connection({'name': SUB_PATH}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + PATH = '/%s' % (self.SUB_PATH,) + BODY = {'topic': self.TOPIC_PATH} + conn = _Connection({'name': self.SUB_PATH}) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) subscription.create() self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'PUT') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], BODY) def test_create_push_w_ack_deadline_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - BODY = {'topic': TOPIC_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}} - conn1 = _Connection({'name': SUB_PATH}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) - conn2 = _Connection({'name': SUB_PATH}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic, DEADLINE, ENDPOINT) - subscription.create(client=CLIENT2) + PATH = '/%s' % (self.SUB_PATH,) + BODY = {'topic': self.TOPIC_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}} + conn1 = _Connection({'name': self.SUB_PATH}) + client1 = _Client(project=self.PROJECT, connection=conn1) + conn2 = _Connection({'name': self.SUB_PATH}) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic, + self.DEADLINE, self.ENDPOINT) + subscription.create(client=client2) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'PUT') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], BODY) def test_exists_miss_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s' % (self.SUB_PATH,) conn = _Connection() - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) self.assertFalse(subscription.exists()) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req.get('query_params'), None) def test_exists_hit_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - conn1 = _Connection({'name': SUB_PATH, 'topic': TOPIC_PATH}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) - conn2 = _Connection({'name': SUB_PATH, 'topic': TOPIC_PATH}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) - self.assertTrue(subscription.exists(client=CLIENT2)) + PATH = '/%s' % (self.SUB_PATH,) + conn1 = _Connection({'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}) + client1 = _Client(project=self.PROJECT, connection=conn1) + conn2 = _Connection({'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) + self.assertTrue(subscription.exists(client=client2)) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req.get('query_params'), None) def test_reload_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' - conn = _Connection({'name': SUB_PATH, - 'topic': TOPIC_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + PATH = '/%s' % (self.SUB_PATH,) + conn = _Connection({'name': self.SUB_PATH, + 'topic': self.TOPIC_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}}) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) subscription.reload() - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) def test_reload_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - DEADLINE = 42 - ENDPOINT = 'https://api.example.com/push' + PATH = '/%s' % (self.SUB_PATH,) conn1 = _Connection() - CLIENT1 = _Client(project=PROJECT, connection=conn1) - conn2 = _Connection({'name': SUB_PATH, - 'topic': TOPIC_PATH, - 'ackDeadlineSeconds': DEADLINE, - 'pushConfig': {'pushEndpoint': ENDPOINT}}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) - subscription.reload(client=CLIENT2) - self.assertEqual(subscription.ack_deadline, DEADLINE) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + client1 = _Client(project=self.PROJECT, connection=conn1) + conn2 = _Connection({'name': self.SUB_PATH, + 'topic': self.TOPIC_PATH, + 'ackDeadlineSeconds': self.DEADLINE, + 'pushConfig': {'pushEndpoint': self.ENDPOINT}}) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.reload(client=client2) + self.assertEqual(subscription.ack_deadline, self.DEADLINE) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) def test_modify_push_config_w_endpoint_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - ENDPOINT = 'https://api.example.com/push' + PATH = '/%s:modifyPushConfig' % (self.SUB_PATH,) conn = _Connection({}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) - subscription.modify_push_configuration(push_endpoint=ENDPOINT) - self.assertEqual(subscription.push_endpoint, ENDPOINT) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.modify_push_configuration(push_endpoint=self.ENDPOINT) + self.assertEqual(subscription.push_endpoint, self.ENDPOINT) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:modifyPushConfig' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], - {'pushConfig': {'pushEndpoint': ENDPOINT}}) + {'pushConfig': {'pushEndpoint': self.ENDPOINT}}) def test_modify_push_config_wo_endpoint_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' - ENDPOINT = 'https://api.example.com/push' + PATH = '/%s:modifyPushConfig' % (self.SUB_PATH,) conn1 = _Connection({}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection({}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic, push_endpoint=ENDPOINT) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic, + push_endpoint=self.ENDPOINT) subscription.modify_push_configuration(push_endpoint=None, - client=CLIENT2) + client=client2) self.assertEqual(subscription.push_endpoint, None) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:modifyPushConfig' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'pushConfig': {}}) def test_pull_wo_return_immediately_max_messages_w_bound_client(self): import base64 from gcloud.pubsub.message import Message - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:pull' % (self.SUB_PATH,) ACK_ID = 'DEADBEEF' MSG_ID = 'BEADCAFE' PAYLOAD = b'This is the message text' @@ -322,9 +277,9 @@ def test_pull_wo_return_immediately_max_messages_w_bound_client(self): MESSAGE = {'messageId': MSG_ID, 'data': B64} REC_MESSAGE = {'ackId': ACK_ID, 'message': MESSAGE} conn = _Connection({'receivedMessages': [REC_MESSAGE]}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) pulled = subscription.pull() self.assertEqual(len(pulled), 1) ack_id, message = pulled[0] @@ -336,17 +291,14 @@ def test_pull_wo_return_immediately_max_messages_w_bound_client(self): self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:pull' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'returnImmediately': False, 'maxMessages': 1}) def test_pull_w_return_immediately_w_max_messages_w_alt_client(self): import base64 from gcloud.pubsub.message import Message - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:pull' % (self.SUB_PATH,) ACK_ID = 'DEADBEEF' MSG_ID = 'BEADCAFE' PAYLOAD = b'This is the message text' @@ -354,13 +306,13 @@ def test_pull_w_return_immediately_w_max_messages_w_alt_client(self): MESSAGE = {'messageId': MSG_ID, 'data': B64, 'attributes': {'a': 'b'}} REC_MESSAGE = {'ackId': ACK_ID, 'message': MESSAGE} conn1 = _Connection() - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection({'receivedMessages': [REC_MESSAGE]}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) pulled = subscription.pull(return_immediately=True, max_messages=3, - client=CLIENT2) + client=client2) self.assertEqual(len(pulled), 1) ack_id, message = pulled[0] self.assertEqual(ack_id, ACK_ID) @@ -372,140 +324,117 @@ def test_pull_w_return_immediately_w_max_messages_w_alt_client(self): self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:pull' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'returnImmediately': True, 'maxMessages': 3}) def test_pull_wo_receivedMessages(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:pull' % (self.SUB_PATH,) conn = _Connection({}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) pulled = subscription.pull(return_immediately=False) self.assertEqual(len(pulled), 0) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:pull' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'returnImmediately': False, 'maxMessages': 1}) def test_acknowledge_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:acknowledge' % (self.SUB_PATH,) ACK_ID1 = 'DEADBEEF' ACK_ID2 = 'BEADCAFE' conn = _Connection({}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) subscription.acknowledge([ACK_ID1, ACK_ID2]) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:acknowledge' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'ackIds': [ACK_ID1, ACK_ID2]}) def test_acknowledge_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:acknowledge' % (self.SUB_PATH,) ACK_ID1 = 'DEADBEEF' ACK_ID2 = 'BEADCAFE' conn1 = _Connection({}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection({}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) - subscription.acknowledge([ACK_ID1, ACK_ID2], client=CLIENT2) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.acknowledge([ACK_ID1, ACK_ID2], client=client2) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:acknowledge' % SUB_PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'ackIds': [ACK_ID1, ACK_ID2]}) def test_modify_ack_deadline_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:modifyAckDeadline' % (self.SUB_PATH,) ACK_ID = 'DEADBEEF' - DEADLINE = 42 + SENT = {'ackIds': [ACK_ID], 'ackDeadlineSeconds': self.DEADLINE} conn = _Connection({}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) - subscription.modify_ack_deadline(ACK_ID, DEADLINE) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.modify_ack_deadline(ACK_ID, self.DEADLINE) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:modifyAckDeadline' % SUB_PATH) - self.assertEqual(req['data'], - {'ackIds': [ACK_ID], 'ackDeadlineSeconds': DEADLINE}) + self.assertEqual(req['path'], PATH) + self.assertEqual(req['data'], SENT) def test_modify_ack_deadline_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s:modifyAckDeadline' % (self.SUB_PATH,) ACK_ID = 'DEADBEEF' - DEADLINE = 42 + SENT = {'ackIds': [ACK_ID], 'ackDeadlineSeconds': self.DEADLINE} conn1 = _Connection({}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection({}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) - subscription.modify_ack_deadline(ACK_ID, DEADLINE, client=CLIENT2) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.modify_ack_deadline(ACK_ID, self.DEADLINE, client=client2) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s:modifyAckDeadline' % SUB_PATH) - self.assertEqual(req['data'], - {'ackIds': [ACK_ID], 'ackDeadlineSeconds': DEADLINE}) + self.assertEqual(req['path'], PATH) + self.assertEqual(req['data'], SENT) def test_delete_w_bound_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s' % (self.SUB_PATH,) conn = _Connection({}) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) subscription.delete() self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'DELETE') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) def test_delete_w_alternate_client(self): - PROJECT = 'PROJECT' - SUB_NAME = 'sub_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) - TOPIC_NAME = 'topic_name' + PATH = '/%s' % (self.SUB_PATH,) conn1 = _Connection({}) - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection({}) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) - subscription.delete(client=CLIENT2) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) + subscription.delete(client=client2) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'DELETE') - self.assertEqual(req['path'], '/%s' % SUB_PATH) + self.assertEqual(req['path'], PATH) def test_get_iam_policy_w_bound_client(self): from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE @@ -524,16 +453,12 @@ def test_get_iam_policy_w_bound_client(self): {'role': VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]}, ], } - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:getIamPolicy' % ( - PROJECT, SUB_NAME) + PATH = '/%s:getIamPolicy' % (self.SUB_PATH,) conn = _Connection(POLICY) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) policy = subscription.get_iam_policy() @@ -546,26 +471,22 @@ def test_get_iam_policy_w_bound_client(self): self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) def test_get_iam_policy_w_alternate_client(self): POLICY = { 'etag': 'ACAB', } - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:getIamPolicy' % ( - PROJECT, SUB_NAME) + PATH = '/%s:getIamPolicy' % (self.SUB_PATH,) conn1 = _Connection() conn2 = _Connection(POLICY) - CLIENT1 = _Client(project=PROJECT, connection=conn1) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) + client1 = _Client(project=self.PROJECT, connection=conn1) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) - policy = subscription.get_iam_policy(client=CLIENT2) + policy = subscription.get_iam_policy(client=client2) self.assertEqual(policy.etag, 'ACAB') self.assertEqual(policy.version, None) @@ -577,7 +498,7 @@ def test_get_iam_policy_w_alternate_client(self): self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) def test_set_iam_policy_w_bound_client(self): from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE @@ -600,16 +521,12 @@ def test_set_iam_policy_w_bound_client(self): RESPONSE = POLICY.copy() RESPONSE['etag'] = 'ABACABAF' RESPONSE['version'] = 18 - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:setIamPolicy' % ( - PROJECT, SUB_NAME) + PATH = '/%s:setIamPolicy' % (self.SUB_PATH,) conn = _Connection(RESPONSE) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) policy = Policy('DEADBEEF', 17) policy.owners.add(OWNER1) policy.owners.add(OWNER2) @@ -629,27 +546,23 @@ def test_set_iam_policy_w_bound_client(self): self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'policy': POLICY}) def test_set_iam_policy_w_alternate_client(self): from gcloud.pubsub.iam import Policy RESPONSE = {'etag': 'ACAB'} - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:setIamPolicy' % ( - PROJECT, SUB_NAME) + PATH = '/%s:setIamPolicy' % (self.SUB_PATH,) conn1 = _Connection() conn2 = _Connection(RESPONSE) - CLIENT1 = _Client(project=PROJECT, connection=conn1) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) + client1 = _Client(project=self.PROJECT, connection=conn1) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) policy = Policy() - new_policy = subscription.set_iam_policy(policy, client=CLIENT2) + new_policy = subscription.set_iam_policy(policy, client=client2) self.assertEqual(new_policy.etag, 'ACAB') self.assertEqual(new_policy.version, None) @@ -661,17 +574,13 @@ def test_set_iam_policy_w_alternate_client(self): self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], {'policy': {}}) def test_check_iam_permissions_w_bound_client(self): from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:testIamPermissions' % ( - PROJECT, SUB_NAME) ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE] + PATH = '/%s:testIamPermissions' % (self.SUB_PATH,) REQUESTED = { 'permissions': ROLES, } @@ -679,9 +588,9 @@ def test_check_iam_permissions_w_bound_client(self): 'permissions': ROLES[:-1], } conn = _Connection(RESPONSE) - CLIENT = _Client(project=PROJECT, connection=conn) - topic = _Topic(TOPIC_NAME, client=CLIENT) - subscription = self._makeOne(SUB_NAME, topic) + client = _Client(project=self.PROJECT, connection=conn) + topic = _Topic(self.TOPIC_NAME, client=client) + subscription = self._makeOne(self.SUB_NAME, topic) allowed = subscription.check_iam_permissions(ROLES) @@ -689,36 +598,32 @@ def test_check_iam_permissions_w_bound_client(self): self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], REQUESTED) def test_check_iam_permissions_w_alternate_client(self): from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE - PROJECT = 'PROJECT' - TOPIC_NAME = 'topic_name' - SUB_NAME = 'sub_name' - PATH = 'projects/%s/subscriptions/%s:testIamPermissions' % ( - PROJECT, SUB_NAME) ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE] + PATH = '/%s:testIamPermissions' % (self.SUB_PATH,) REQUESTED = { 'permissions': ROLES, } RESPONSE = {} conn1 = _Connection() - CLIENT1 = _Client(project=PROJECT, connection=conn1) + client1 = _Client(project=self.PROJECT, connection=conn1) conn2 = _Connection(RESPONSE) - CLIENT2 = _Client(project=PROJECT, connection=conn2) - topic = _Topic(TOPIC_NAME, client=CLIENT1) - subscription = self._makeOne(SUB_NAME, topic) + client2 = _Client(project=self.PROJECT, connection=conn2) + topic = _Topic(self.TOPIC_NAME, client=client1) + subscription = self._makeOne(self.SUB_NAME, topic) - allowed = subscription.check_iam_permissions(ROLES, client=CLIENT2) + allowed = subscription.check_iam_permissions(ROLES, client=client2) self.assertEqual(len(allowed), 0) self.assertEqual(len(conn1._requested), 0) self.assertEqual(len(conn2._requested), 1) req = conn2._requested[0] self.assertEqual(req['method'], 'POST') - self.assertEqual(req['path'], '/%s' % PATH) + self.assertEqual(req['path'], PATH) self.assertEqual(req['data'], REQUESTED) From 4dbdb750e425fda98b25564c27297caa43abeab3 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 4 Apr 2016 13:48:33 -0400 Subject: [PATCH 5/6] Add system test for 'subscription w/ deleted topic' case. Fix bug in 'Subscription._require_client' exposed thereby. --- gcloud/pubsub/subscription.py | 2 +- system_tests/pubsub.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 2b9aa831e51c..5eecbc115f83 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -130,7 +130,7 @@ def _require_client(self, client): :returns: The client passed in or the currently bound client. """ if client is None: - client = self.topic._client + client = self._client return client def create(self, client=None): diff --git a/system_tests/pubsub.py b/system_tests/pubsub.py index 0adae0a1b0a3..839dc25c45ac 100644 --- a/system_tests/pubsub.py +++ b/system_tests/pubsub.py @@ -207,3 +207,25 @@ def test_subscription_iam_policy(self): policy.viewers.add(policy.user('jjg@google.com')) new_policy = subscription.set_iam_policy(policy) self.assertEqual(new_policy.viewers, policy.viewers) + + def test_fetch_delete_subscription_w_deleted_topic(self): + TO_DELETE = 'delete-me-%d' % (1000 * time.time(),) + ORPHANED = 'orphaned-%d' % (1000 * time.time(),) + topic = Config.CLIENT.topic(TO_DELETE) + topic.create() + subscription = topic.subscription(ORPHANED) + subscription.create() + topic.delete() + + all_subs = [] + token = None + while True: + subs, token = Config.CLIENT.list_subscriptions(page_token=token) + all_subs.extend(subs) + if token is None: + break + + created = [subscription for subscription in all_subs + if subscription.name == ORPHANED] + self.assertEqual(len(created), 1) + created[0].delete() From f117c3abf36f22869b460228aaf8e75be7b3cb87 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 4 Apr 2016 14:53:38 -0400 Subject: [PATCH 6/6] Assert that 'self.topic' is None for orphaned subscription. Addresses: https://github.com/GoogleCloudPlatform/gcloud-python/pull/1671#discussion_r58417556 --- system_tests/pubsub.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system_tests/pubsub.py b/system_tests/pubsub.py index 839dc25c45ac..9786ca372239 100644 --- a/system_tests/pubsub.py +++ b/system_tests/pubsub.py @@ -228,4 +228,6 @@ def test_fetch_delete_subscription_w_deleted_topic(self): created = [subscription for subscription in all_subs if subscription.name == ORPHANED] self.assertEqual(len(created), 1) - created[0].delete() + orphaned = created[0] + self.assertTrue(orphaned.topic is None) + orphaned.delete()