github.com/apache/beam/sdks/v2@v2.48.2/python/apache_beam/io/gcp/tests/pubsub_matcher_test.py (about) 1 # 2 # Licensed to the Apache Software Foundation (ASF) under one or more 3 # contributor license agreements. See the NOTICE file distributed with 4 # this work for additional information regarding copyright ownership. 5 # The ASF licenses this file to You under the Apache License, Version 2.0 6 # (the "License"); you may not use this file except in compliance with 7 # the License. You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 """Unit test for PubSub verifier.""" 19 20 # pytype: skip-file 21 22 import logging 23 import unittest 24 25 import mock 26 from hamcrest import assert_that as hc_assert_that 27 28 from apache_beam.io.gcp.pubsub import PubsubMessage 29 from apache_beam.io.gcp.tests.pubsub_matcher import PubSubMessageMatcher 30 from apache_beam.testing.test_utils import PullResponseMessage 31 from apache_beam.testing.test_utils import create_pull_response 32 33 try: 34 from google.cloud import pubsub 35 except ImportError: 36 pubsub = None 37 38 39 @unittest.skipIf(pubsub is None, 'PubSub dependencies are not installed.') 40 @mock.patch('time.sleep', return_value=None) 41 @mock.patch('google.cloud.pubsub.SubscriberClient') 42 class PubSubMatcherTest(unittest.TestCase): 43 def setUp(self): 44 self.mock_presult = mock.MagicMock() 45 46 def init_matcher( 47 self, expected_msg=None, with_attributes=False, strip_attributes=None): 48 self.pubsub_matcher = PubSubMessageMatcher( 49 'mock_project', 50 'mock_sub_name', 51 expected_msg, 52 with_attributes=with_attributes, 53 strip_attributes=strip_attributes) 54 55 def init_counter_matcher(self, expected_msg_len=1): 56 self.pubsub_matcher = PubSubMessageMatcher( 57 'mock_project', 'mock_sub_name', expected_msg_len=expected_msg_len) 58 59 def test_message_matcher_success(self, mock_get_sub, unsued_mock): 60 self.init_matcher(expected_msg=[b'a', b'b']) 61 mock_sub = mock_get_sub.return_value 62 mock_sub.pull.side_effect = [ 63 create_pull_response([PullResponseMessage(b'a', {})]), 64 create_pull_response([PullResponseMessage(b'b', {})]), 65 ] 66 hc_assert_that(self.mock_presult, self.pubsub_matcher) 67 self.assertEqual(mock_sub.pull.call_count, 2) 68 self.assertEqual(mock_sub.acknowledge.call_count, 2) 69 70 def test_message_matcher_attributes_success(self, mock_get_sub, unsued_mock): 71 self.init_matcher( 72 expected_msg=[PubsubMessage(b'a', {'k': 'v'})], with_attributes=True) 73 mock_sub = mock_get_sub.return_value 74 mock_sub.pull.side_effect = [ 75 create_pull_response([PullResponseMessage(b'a', {'k': 'v'})]) 76 ] 77 hc_assert_that(self.mock_presult, self.pubsub_matcher) 78 self.assertEqual(mock_sub.pull.call_count, 1) 79 self.assertEqual(mock_sub.acknowledge.call_count, 1) 80 81 def test_message_matcher_attributes_fail(self, mock_get_sub, unsued_mock): 82 self.init_matcher( 83 expected_msg=[PubsubMessage(b'a', {})], with_attributes=True) 84 mock_sub = mock_get_sub.return_value 85 # Unexpected attribute 'k'. 86 mock_sub.pull.side_effect = [ 87 create_pull_response([PullResponseMessage(b'a', {'k': 'v'})]) 88 ] 89 with self.assertRaisesRegex(AssertionError, r'Unexpected'): 90 hc_assert_that(self.mock_presult, self.pubsub_matcher) 91 self.assertEqual(mock_sub.pull.call_count, 1) 92 self.assertEqual(mock_sub.acknowledge.call_count, 1) 93 94 def test_message_matcher_strip_success(self, mock_get_sub, unsued_mock): 95 self.init_matcher( 96 expected_msg=[PubsubMessage(b'a', {'k': 'v'})], 97 with_attributes=True, 98 strip_attributes=['id', 'timestamp']) 99 mock_sub = mock_get_sub.return_value 100 mock_sub.pull.side_effect = [ 101 create_pull_response([ 102 PullResponseMessage( 103 b'a', { 104 'id': 'foo', 'timestamp': 'bar', 'k': 'v' 105 }) 106 ]) 107 ] 108 hc_assert_that(self.mock_presult, self.pubsub_matcher) 109 self.assertEqual(mock_sub.pull.call_count, 1) 110 self.assertEqual(mock_sub.acknowledge.call_count, 1) 111 112 def test_message_matcher_strip_fail(self, mock_get_sub, unsued_mock): 113 self.init_matcher( 114 expected_msg=[PubsubMessage(b'a', {'k': 'v'})], 115 with_attributes=True, 116 strip_attributes=['id', 'timestamp']) 117 mock_sub = mock_get_sub.return_value 118 # Message is missing attribute 'timestamp'. 119 mock_sub.pull.side_effect = [ 120 create_pull_response( 121 [PullResponseMessage(b'a', { 122 'id': 'foo', 'k': 'v' 123 })]) 124 ] 125 with self.assertRaisesRegex(AssertionError, r'Stripped attributes'): 126 hc_assert_that(self.mock_presult, self.pubsub_matcher) 127 self.assertEqual(mock_sub.pull.call_count, 1) 128 self.assertEqual(mock_sub.acknowledge.call_count, 1) 129 130 def test_message_matcher_mismatch(self, mock_get_sub, unused_mock): 131 self.init_matcher(expected_msg=[b'a']) 132 mock_sub = mock_get_sub.return_value 133 mock_sub.pull.side_effect = [ 134 create_pull_response( 135 [PullResponseMessage(b'c', {}), PullResponseMessage(b'd', {})]), 136 ] 137 with self.assertRaises(AssertionError) as error: 138 hc_assert_that(self.mock_presult, self.pubsub_matcher) 139 self.assertEqual(mock_sub.pull.call_count, 1) 140 self.assertCountEqual([b'c', b'd'], self.pubsub_matcher.messages) 141 self.assertIn( 142 '\nExpected: Expected 1 messages.\n but: Got 2 messages.', 143 str(error.exception.args[0])) 144 self.assertEqual(mock_sub.pull.call_count, 1) 145 self.assertEqual(mock_sub.acknowledge.call_count, 1) 146 147 def test_message_matcher_timeout(self, mock_get_sub, unused_mock): 148 self.init_matcher(expected_msg=[b'a']) 149 mock_sub = mock_get_sub.return_value 150 mock_sub.return_value.full_name.return_value = 'mock_sub' 151 self.pubsub_matcher.timeout = 0.1 152 with self.assertRaisesRegex(AssertionError, r'Expected 1.*\n.*Got 0'): 153 hc_assert_that(self.mock_presult, self.pubsub_matcher) 154 self.assertTrue(mock_sub.pull.called) 155 self.assertEqual(mock_sub.acknowledge.call_count, 0) 156 157 def test_message_count_matcher_below_fail(self, mock_get_sub, unused_mock): 158 self.init_counter_matcher(expected_msg_len=1) 159 mock_sub = mock_get_sub.return_value 160 mock_sub.pull.side_effect = [ 161 create_pull_response( 162 [PullResponseMessage(b'c', {}), PullResponseMessage(b'd', {})]), 163 ] 164 with self.assertRaises(AssertionError) as error: 165 hc_assert_that(self.mock_presult, self.pubsub_matcher) 166 self.assertEqual(mock_sub.pull.call_count, 1) 167 self.assertIn( 168 '\nExpected: Expected 1 messages.\n but: Got 2 messages.', 169 str(error.exception.args[0])) 170 171 def test_message_count_matcher_above_fail(self, mock_get_sub, unused_mock): 172 self.init_counter_matcher(expected_msg_len=1) 173 mock_sub = mock_get_sub.return_value 174 self.pubsub_matcher.timeout = 0.1 175 with self.assertRaisesRegex(AssertionError, r'Expected 1.*\n.*Got 0'): 176 hc_assert_that(self.mock_presult, self.pubsub_matcher) 177 self.assertTrue(mock_sub.pull.called) 178 self.assertEqual(mock_sub.acknowledge.call_count, 0) 179 180 def test_message_count_matcher_success(self, mock_get_sub, unused_mock): 181 self.init_counter_matcher(expected_msg_len=15) 182 mock_sub = mock_get_sub.return_value 183 mock_sub.pull.side_effect = [ 184 create_pull_response( 185 [PullResponseMessage(b'a', {'foo': 'bar'}) for _ in range(15)]) 186 ] 187 hc_assert_that(self.mock_presult, self.pubsub_matcher) 188 self.assertEqual(mock_sub.pull.call_count, 1) 189 self.assertEqual(mock_sub.acknowledge.call_count, 1) 190 191 192 if __name__ == '__main__': 193 logging.getLogger().setLevel(logging.INFO) 194 unittest.main()