github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/scripts/juju-txn-helper/test_txn_helper.py (about) 1 import unittest 2 from unittest import mock 3 4 from bson import ObjectId 5 6 import txn_helper 7 8 9 class TestGetModelUuid(unittest.TestCase): 10 11 def test_valid_uuid(self): 12 model_uuid = "01234567-89ab-cdef-0123-456789abcdef" 13 args = mock.Mock() 14 args.model = model_uuid 15 result = txn_helper.get_model_uuid(None, args) 16 self.assertEqual(result, model_uuid) 17 18 def test_name_found(self): 19 model_name = "foo" 20 model_uuid = "01234567-89ab-cdef-0123-456789abcdef" 21 22 client = mock.MagicMock() 23 client.juju.models.find_one.return_value = {"_id": model_uuid} 24 args = mock.Mock() 25 args.model = model_name 26 27 result = txn_helper.get_model_uuid(client, args) 28 self.assertEqual(result, model_uuid) 29 30 def test_name_not_found(self): 31 model_name = "foo" 32 33 client = mock.MagicMock() 34 client.juju.models.find_one.return_value = None 35 args = mock.Mock() 36 args.model = model_name 37 38 with self.assertRaises(SystemExit) as cm: 39 txn_helper.get_model_uuid(client, args) 40 self.assertEqual(cm.exception.code, "Could not find the specified model (foo)") 41 42 43 class BaseTxnQueueTest(unittest.TestCase): 44 SAMPLE_ABORTED_TRANSACTION = { 45 "_id": ObjectId("0123456789abcdef01234567"), 46 "s": 5, 47 "o": [ 48 { 49 "c": "models", 50 "d": "01234567-891b-cdef-0123-456789abcdef", 51 "a": { 52 "life": 0, 53 "migration-mode": "" 54 } 55 }, 56 { 57 "c": "remoteApplications", 58 "d": "01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef", 59 "a": "d-", 60 "i": { 61 "_id": "01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef", 62 "name": "remote-0123456789abcdef0123456789abcdef", 63 "offer-uuid": "01010101-0101-0101-0101-010101010101", 64 "source-model-uuid": "23232323-2323-2323-2323-232323232323", 65 "endpoints": [ 66 { 67 "name": "monitors", 68 "role": "provider", 69 "interface": "monitors", 70 "limit": 0, 71 "scope": "" 72 } 73 ], 74 "spaces": [], 75 "bindings": { 76 77 }, 78 "life": 0, 79 "relationcount": 0, 80 "is-consumer-proxy": True, 81 "version": 0, 82 "model-uuid": "01234567-891b-cdef-0123-456789abcdef" 83 } 84 }, 85 { 86 "c": "applications", 87 "d": "01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef", 88 "a": "d-" 89 }, 90 { 91 "c": "remoteEntities", 92 "d": "01234567-891b-cdef-0123-456789abcdef:application-remote-0123456789abcdef0123456789abcdef", 93 "a": "d-", 94 "i": { 95 "_id": "01234567-891b-cdef-0123-456789abcdef:", 96 "token": "45454545-4545-4545-4545-454545454545", 97 "model-uuid": "01234567-891b-cdef-0123-456789abcdef" 98 } 99 } 100 ], 101 "n": "fedcba98" 102 } 103 104 105 class TestWalkTxnQueue(BaseTxnQueueTest): 106 107 SAMPLE_TRANSACTION_REFERENCES = [ 108 "0123456789abcdef01234567_78787878", 109 ] 110 SAMPLE_TRANSACTIONS = [ 111 BaseTxnQueueTest.SAMPLE_ABORTED_TRANSACTION, 112 ] 113 114 expected_stdout = """\ 115 Retrieved model transaction queue: 116 - 0123456789abcdef01234567_78787878 117 Converting to transaction IDs: 118 - 0123456789abcdef01234567 119 120 Transaction 0123456789abcdef01234567 (state: aborted): 121 Transaction dump: 122 {'_id': ObjectId('0123456789abcdef01234567'), 123 'n': 'fedcba98', 124 'o': [{'a': {'life': 0, 'migration-mode': ''}, 125 'c': 'models', 126 'd': '01234567-891b-cdef-0123-456789abcdef'}, 127 {'a': 'd-', 128 'c': 'remoteApplications', 129 'd': '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef', 130 'i': {'_id': '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef', 131 'bindings': {}, 132 'endpoints': [{'interface': 'monitors', 133 'limit': 0, 134 'name': 'monitors', 135 'role': 'provider', 136 'scope': ''}], 137 'is-consumer-proxy': True, 138 'life': 0, 139 'model-uuid': '01234567-891b-cdef-0123-456789abcdef', 140 'name': 'remote-0123456789abcdef0123456789abcdef', 141 'offer-uuid': '01010101-0101-0101-0101-010101010101', 142 'relationcount': 0, 143 'source-model-uuid': '23232323-2323-2323-2323-232323232323', 144 'spaces': [], 145 'version': 0}}, 146 {'a': 'd-', 147 'c': 'applications', 148 'd': '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef'}, 149 {'a': 'd-', 150 'c': 'remoteEntities', 151 'd': '01234567-891b-cdef-0123-456789abcdef:application-remote-0123456789abcdef0123456789abcdef', 152 'i': {'_id': '01234567-891b-cdef-0123-456789abcdef:', 153 'model-uuid': '01234567-891b-cdef-0123-456789abcdef', 154 'token': '45454545-4545-4545-4545-454545454545'}}], 155 's': 5} 156 157 Op 0: QUERY_DOC assertion passed 158 Collection 'models', ID '01234567-891b-cdef-0123-456789abcdef' 159 Query doc tested was: 160 {'_id': '01234567-891b-cdef-0123-456789abcdef', 'life': 0, 'migration-mode': ''} 161 Existing doc is: 162 {'txn-queue': ['0123456789abcdef01234567_78787878']} 163 164 Op 1: DOC_MISSING assertion FAILED 165 Collection 'remoteApplications', ID '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef' 166 Existing doc is: 167 {'dummy': 'remoteApplication'} 168 Insert doc is: 169 {'_id': '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef', 170 'bindings': {}, 171 'endpoints': [{'interface': 'monitors', 172 'limit': 0, 173 'name': 'monitors', 174 'role': 'provider', 175 'scope': ''}], 176 'is-consumer-proxy': True, 177 'life': 0, 178 'model-uuid': '01234567-891b-cdef-0123-456789abcdef', 179 'name': 'remote-0123456789abcdef0123456789abcdef', 180 'offer-uuid': '01010101-0101-0101-0101-010101010101', 181 'relationcount': 0, 182 'source-model-uuid': '23232323-2323-2323-2323-232323232323', 183 'spaces': [], 184 'version': 0} 185 186 Op 2: DOC_MISSING assertion FAILED 187 Collection 'applications', ID '01234567-891b-cdef-0123-456789abcdef:remote-0123456789abcdef0123456789abcdef' 188 Existing doc is: 189 {'dummy': 'application'} 190 191 Op 3: DOC_MISSING assertion FAILED 192 Collection 'remoteEntities', ID '01234567-891b-cdef-0123-456789abcdef:application-remote-0123456789abcdef0123456789abcdef' 193 Existing doc is: 194 {'dummy': 'remoteEntity'} 195 Insert doc is: 196 {'_id': '01234567-891b-cdef-0123-456789abcdef:', 197 'model-uuid': '01234567-891b-cdef-0123-456789abcdef', 198 'token': '45454545-4545-4545-4545-454545454545'} 199 200 """ 201 202 @mock.patch('txn_helper.print') 203 def test_happy_path_single_match(self, print_mock: mock.MagicMock): 204 client = mock.MagicMock() 205 206 # In the single transaction case, there is next to no difference between querying for a model or going over the 207 # full set of transactions since in either way we control the returned data via a mock. 208 # We'll use the model query path here for the sake of covering slightly more code. 209 model_uuid = "01234567-89ab-cdef-0123-456789abcdef" 210 state_filter = None 211 212 dump_transaction = True 213 include_passes = True 214 max_transaction_count = None 215 216 # Mock out the model to include a model transaction queue 217 client.juju.models.find_one.return_value = {"txn-queue": self.SAMPLE_TRANSACTION_REFERENCES} 218 # Mock the result of the transaction query 219 client.juju.txns.find.return_value = self.SAMPLE_TRANSACTIONS 220 # Mock out other specific objects checked by the assertions 221 client.juju.remoteApplications.find_one.return_value = {"dummy": "remoteApplication"} 222 client.juju.applications.find_one.return_value = {"dummy": "application"} 223 client.juju.remoteEntities.find_one.return_value = {"dummy": "remoteEntity"} 224 225 txn_queue = txn_helper.get_model_transaction_queue(client, model_uuid) 226 txn_helper.walk_transaction_queue(client, txn_queue, state_filter, dump_transaction, include_passes, max_transaction_count) 227 228 captured_stdout = "\n".join([call.args[0] if call.args else "" for call in print_mock.mock_calls]) 229 230 # Yes, this is a very fragile test - any change to stdout can make it fail - but it will work for now. 231 self.assertEqual(captured_stdout, self.expected_stdout) 232 233 # Consider adding: 234 # * Model query with multiple transactions - this would result in iterating over multiple distinct queries' cursors. 235 # * All transactions query with mulitple transactions - this would result in iterating over a single query's cursor. 236 # (The above paths have been directly tested on a test database.)