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.)