class RabbitMQInstanceTestCase(TestCase):
    """
    Test cases for RabbitMQInstanceMixin
    """

    def setUp(self):
        super().setUp()
        with patch(
                'instance.tests.models.factories.openedx_instance.OpenEdXInstance._write_metadata_to_consul',
                return_value=(1, True)
        ):
            self.instance = OpenEdXInstanceFactory()

    @responses.activate
    @ddt.data(
        ('GET', ['overview'], '/api/overview'),
        ('PUT', ['users', 'testuser'], '/api/users/testuser'),
        ('DELETE', ['permissions', '/some_vhost', 'testuser'], '/api/permissions/%2Fsome_vhost/testuser')
    )
    @ddt.unpack
    def test_rabbitmq_request(self, method, url_parts, expected_url, mock_consul):
        """
        Test to make sure the _rabbitmq_request parameters form the correct URLs
        """
        url = '{service_url}{path}'.format(
            service_url=self.instance.rabbitmq_server.api_url,
            path=expected_url
        )
        expected_body = {'info': 'This is a mocked request to URL {url}'.format(url=url)}

        with patch(
                'instance.tests.models.factories.openedx_instance.OpenEdXInstance._write_metadata_to_consul',
                return_value=(1, True)
        ):
            # Mock the URL with a uniquely identifying body so that we can verify that the
            # correct URL is formed and called.
            responses.add(method, url, json=expected_body)
            self.instance = OpenEdXInstanceFactory()
            response = self.instance._rabbitmq_request(method.lower(), *url_parts)

        self.assertDictEqual(
            response.json(),
            expected_body
        )

    @responses.activate
    def test_provision_rabbitmq(self, mock_consul):
        """
        Record the calls to the RabbitMQ API and make sure a new vhost along with
        two new users are created during provision and deleted during deprovision.

        The use of `responses.RequestsMock` raises an exception during context deconstruction
        if any of the URLs added to the `responses` object aren't ever called. Also,
        if any RabbitMQ API URLs are called that haven't been mocked, a `RabbitMQAPIError`
        should be raised (given the default `.env.test` configuration).

        So, this test should pass if and only if all of the specifically mocked URLs are
        called during both provision and deprovision.
        """
        rabbitmq_users = [self.instance.rabbitmq_provider_user, self.instance.rabbitmq_consumer_user]
        rabbitmq_vhost = urllib.parse.quote(self.instance.rabbitmq_vhost, safe='')

        vhosts_calls = ['vhosts/{}'.format(rabbitmq_vhost)]
        users_calls = ['users/{}'.format(user) for user in rabbitmq_users]
        permissions_calls = ['permissions/{}/{}'.format(rabbitmq_vhost, user) for user in rabbitmq_users]

        provision_calls = [
            '{}/api/{}'.format(self.instance.rabbitmq_server.api_url, url)
            for url in vhosts_calls + users_calls + permissions_calls
        ]
        deprovision_calls = [
            '{}/api/{}'.format(self.instance.rabbitmq_server.api_url, url)
            for url in vhosts_calls + users_calls
        ]

        # Spec the provisioning calls
        with responses.RequestsMock() as rsps:
            for url in provision_calls:
                rsps.add(
                    responses.PUT,
                    url,
                    content_type='application/json',
                    body='{}'
                )
            self.instance.provision_rabbitmq()

        # Spec the deprovisioning calls
        with responses.RequestsMock() as rsps:
            for url in deprovision_calls:
                rsps.add(
                    responses.DELETE,
                    url,
                    content_type='application/json',
                    body='{}'
                )
            self.instance.deprovision_rabbitmq()

    @responses.activate
    def test_rabbitmq_api_error(self, mock_consul):
        """
        Test that RabbitMQAPIError is thrown during auth issues
        """
        with responses.RequestsMock() as rsps:
            # Emulate 401 Unauthorized
            rsps.add(
                responses.GET,
                '{}/api/overview'.format(self.instance.rabbitmq_server.api_url),
                content_type='application/json',
                body='{}',
                status=401
            )
            with self.assertRaises(RabbitMQAPIError):
                self.instance._rabbitmq_request('get', 'overview')

    @ddt.data(
        ({'name': 'test'}, 'test'),
        ({'name': 'test', 'description': 'test description'}, 'test (test description)')
    )
    @ddt.unpack
    def test_string_representation(self, fields, representation, mock_consul):
        """
        Test that the str method returns the appropriate values.
        """
        rabbitmq = self.instance.rabbitmq_server
        for name, value in fields.items():
            setattr(rabbitmq, name, value)
        rabbitmq.save()
        self.assertEqual(str(rabbitmq), representation)

    @patch('instance.models.mixins.rabbitmq.RabbitMQInstanceMixin._rabbitmq_request')
    def test_deprovision_rabbitmq(self, mock_rabbitmq_request, mock_consul):
        """
        Test deprovision_rabbitmq does correct calls.
        """
        self.instance.rabbitmq_provisioned = True
        self.instance.deprovision_rabbitmq()
        mock_rabbitmq_request.assert_any_call('delete', 'vhosts', self.instance.rabbitmq_vhost)
        mock_rabbitmq_request.assert_any_call('delete', 'users', self.instance.rabbitmq_consumer_user.username)
        mock_rabbitmq_request.assert_any_call('delete', 'users', self.instance.rabbitmq_provider_user.username)

    @patch('instance.models.mixins.rabbitmq.RabbitMQInstanceMixin._rabbitmq_request', side_effect=RabbitMQAPIError())
    def test_ignore_errors_deprovision_rabbitmq(self, mock_rabbitmq_request, mock_consul):
        """
        Test rabbitmq is set as deprovision when ignoring errors.
        """
        self.instance.rabbitmq_provisioned = True
        self.instance.deprovision_rabbitmq(ignore_errors=True)
        self.assertFalse(self.instance.rabbitmq_provisioned)
Beispiel #2
0
class RabbitMQInstanceTestCase(TestCase):
    """
    Test cases for RabbitMQInstanceMixin
    """
    def setUp(self):
        super().setUp()
        self.instance = None

    @responses.activate
    @ddt.data(
        ('GET', ['overview'], '/api/overview'),
        ('PUT', ['users', 'testuser'], '/api/users/testuser'),
        ('DELETE', ['permissions', '/some_vhost', 'testuser'], '/api/permissions/%2Fsome_vhost/testuser')
    )
    @ddt.unpack
    def test_rabbitmq_request(self, method, url_parts, expected_url):
        """
        Test to make sure the _rabbitmq_request parameters form the correct URLs
        """
        url = '{service_url}{path}'.format(
            service_url=settings.RABBITMQ_API_URL,
            path=expected_url
        )
        expected_body = {'info': 'This is a mocked request to URL {url}'.format(url=url)}

        # Mock the URL with a uniquely identifying body so that we can verify that the
        # correct URL is formed and called.
        responses.add(method, url, json=expected_body)
        self.instance = OpenEdXInstanceFactory(use_ephemeral_databases=False)
        response = self.instance._rabbitmq_request(method.lower(), *url_parts)

        self.assertDictEqual(
            response.json(),
            expected_body
        )

    @responses.activate
    def test_provision_rabbitmq(self):
        """
        Record the calls to the RabbitMQ API and make sure a new vhost along with
        two new users are created during provision and deleted during deprobision.

        The use of `responses.RequestsMock` raises an exception during context deconstruction
        if any of the URLs added to the `responses` object aren't ever called. Also,
        if any RabbitMQ API URLs are called that haven't been mocked, a `RabbitMQAPIError`
        should be raised (given the default `.env.test` configuration).

        So, this test should pass if and only if all of the specifically mocked URLs are
        called during both provision and deprovision.
        """
        self.instance = OpenEdXInstanceFactory(use_ephemeral_databases=False)
        rabbitmq_users = [self.instance.rabbitmq_provider_user, self.instance.rabbitmq_consumer_user]
        rabbitmq_vhost = urllib.parse.quote(self.instance.rabbitmq_vhost, safe='')

        vhosts_calls = ['vhosts/{}'.format(rabbitmq_vhost)]
        users_calls = ['users/{}'.format(user) for user in rabbitmq_users]
        permissions_calls = ['permissions/{}/{}'.format(rabbitmq_vhost, user) for user in rabbitmq_users]

        provision_calls = [
            '{}/api/{}'.format(settings.RABBITMQ_API_URL, url)
            for url in vhosts_calls + users_calls + permissions_calls
        ]
        deprovision_calls = [
            '{}/api/{}'.format(settings.RABBITMQ_API_URL, url)
            for url in vhosts_calls + users_calls
        ]

        # Spec the provisioning calls
        with responses.RequestsMock() as rsps:
            for url in provision_calls:
                rsps.add(
                    responses.PUT,
                    url,
                    content_type='application/json',
                    body='{}'
                )
            self.instance.provision_rabbitmq()

        # Spec the deprovisioning calls
        with responses.RequestsMock() as rsps:
            for url in deprovision_calls:
                rsps.add(
                    responses.DELETE,
                    url,
                    content_type='application/json',
                    body='{}'
                )
            self.instance.deprovision_rabbitmq()

    @override_settings(RABBITMQ_ADMIN_PASSWORD='******')
    def test_rabbitmq_api_error(self):
        """
        Test that RabbitMQAPIError is thrown during auth issues
        """
        self.instance = OpenEdXInstanceFactory(use_ephemeral_databases=False)
        with self.assertRaises(RabbitMQAPIError):
            self.instance._rabbitmq_request('get', 'overview')