mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	openapi: Refactor OpenAPIArgumentsTest.
The main things targeted by the refactor are the usage of comments and moving the top-level variables to the scope of the class. The movement of variables was to facilitate allowing us to perform a reverse mapping test from OpenAPI URLs -> Code defined URLs.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							74a72fc422
						
					
				
				
					commit
					718744c22d
				
			@@ -144,12 +144,10 @@ class OpenAPIToolsTest(ZulipTestCase):
 | 
			
		||||
            self.assertFalse(mock_reload.called)
 | 
			
		||||
 | 
			
		||||
class OpenAPIArgumentsTest(ZulipTestCase):
 | 
			
		||||
    def test_openapi_arguments(self) -> None:
 | 
			
		||||
        # Verifies that every REQ-defined argument appears in our API
 | 
			
		||||
        # documentation for the target endpoint where possible.
 | 
			
		||||
 | 
			
		||||
        # These should have docs added
 | 
			
		||||
        PENDING_ENDPOINTS = set([
 | 
			
		||||
    # This will be filled during test_openapi_arguments:
 | 
			
		||||
    checked_endpoints = set()  # type: Set[str]
 | 
			
		||||
    # TODO: These endpoints need to be documented:
 | 
			
		||||
    pending_endpoints = set([
 | 
			
		||||
        '/users/me/avatar',
 | 
			
		||||
        '/user_uploads',
 | 
			
		||||
        '/settings/display',
 | 
			
		||||
@@ -203,55 +201,55 @@ class OpenAPIArgumentsTest(ZulipTestCase):
 | 
			
		||||
        '/invites/<prereg_id>/resend',
 | 
			
		||||
        '/invites/multiuse/<invite_id>',
 | 
			
		||||
        '/messages/<message_id>',
 | 
			
		||||
            '/messages/<message_id>',
 | 
			
		||||
            '/messages/<message_id>',
 | 
			
		||||
        '/messages/<message_id>/history',
 | 
			
		||||
        '/users/me/subscriptions/<stream_id>',
 | 
			
		||||
        '/messages/<message_id>/reactions',
 | 
			
		||||
            '/messages/<message_id>/reactions',
 | 
			
		||||
            '/messages/<message_id>/emoji_reactions/<emoji_name>',
 | 
			
		||||
        '/messages/<message_id>/emoji_reactions/<emoji_name>',
 | 
			
		||||
        '/attachments/<attachment_id>',
 | 
			
		||||
        '/user_groups/<user_group_id>',
 | 
			
		||||
            '/user_groups/<user_group_id>',
 | 
			
		||||
        '/user_groups/<user_group_id>/members',
 | 
			
		||||
        '/users/me/<stream_id>/topics',
 | 
			
		||||
        '/streams/<stream_id>/members',
 | 
			
		||||
        '/streams/<stream_id>',
 | 
			
		||||
            '/streams/<stream_id>',
 | 
			
		||||
        '/streams/<stream_id>/delete_topic',
 | 
			
		||||
        '/default_stream_groups/<group_id>',
 | 
			
		||||
            '/default_stream_groups/<group_id>',
 | 
			
		||||
        '/default_stream_groups/<group_id>/streams',
 | 
			
		||||
        # Regex with an unnamed capturing group.
 | 
			
		||||
        '/users/(?!me/)(?P<email>[^/]*)/presence',
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
        # These endpoints have a mismatch between the documentation
 | 
			
		||||
        # and the actual API.  There are situations where we may want
 | 
			
		||||
        # to have undocumented parameters for e.g. backwards
 | 
			
		||||
        # compatibility, which could be the situation for some of
 | 
			
		||||
        # these, in which case we may want a more clever exclude
 | 
			
		||||
        # system.  This list can serve as a TODO list for such an
 | 
			
		||||
        # investigation.
 | 
			
		||||
        BUGGY_DOCUMENTATION_ENDPOINTS = set([
 | 
			
		||||
    # TODO: These endpoints have a mismatch between the
 | 
			
		||||
    # documentation and the actual API and need to be fixed:
 | 
			
		||||
    buggy_documentation_endpoints = set([
 | 
			
		||||
        '/events',
 | 
			
		||||
        '/users/me/subscriptions/muted_topics',
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
        # First, we import the fancy-Django version of zproject/urls.py
 | 
			
		||||
    def test_openapi_arguments(self) -> None:
 | 
			
		||||
        """This end-to-end API documentation test compares the arguments
 | 
			
		||||
        defined in the actual code using @has_request_variables and
 | 
			
		||||
        REQ(), with the arguments declared in our API documentation
 | 
			
		||||
        for every API endpoint in Zulip.
 | 
			
		||||
 | 
			
		||||
        First, we import the fancy-Django version of zproject/urls.py
 | 
			
		||||
        by doing this, each has_request_variables wrapper around each
 | 
			
		||||
        imported view function gets called to generate the wrapped
 | 
			
		||||
        view function and thus filling the global arguments_map variable.
 | 
			
		||||
        Basically, we're exploiting code execution during import.
 | 
			
		||||
 | 
			
		||||
            Then we need to import some view modules not already imported in
 | 
			
		||||
        urls.py. We use this different syntax because of the linters complaining
 | 
			
		||||
        of an unused import (which is correct, but we do this for triggering the
 | 
			
		||||
        has_request_variables decorator).
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        urlconf = __import__(getattr(settings, "ROOT_URLCONF"), {}, {}, [''])
 | 
			
		||||
        # Import some view modules not already imported in urls.py, we use
 | 
			
		||||
        # this round about manner because of the linters complaining of an
 | 
			
		||||
        # unused import (which is correct, but we do this for triggering the
 | 
			
		||||
        # has_request_variables decorator).
 | 
			
		||||
        __import__('zerver.views.typing')
 | 
			
		||||
        __import__('zerver.views.events_register')
 | 
			
		||||
        __import__('zerver.views.realm_emoji')
 | 
			
		||||
 | 
			
		||||
        # We loop through all the API patterns, looking in particular
 | 
			
		||||
        # those using the rest_dispatch decorator; we then parse its
 | 
			
		||||
        # mapping of (HTTP_METHOD -> FUNCTION).
 | 
			
		||||
        # for those using the rest_dispatch decorator; we then parse
 | 
			
		||||
        # its mapping of (HTTP_METHOD -> FUNCTION).
 | 
			
		||||
        for p in urlconf.v1_api_and_json_patterns:
 | 
			
		||||
            if p.lookup_str != 'zerver.lib.rest.rest_dispatch':
 | 
			
		||||
                continue
 | 
			
		||||
@@ -261,6 +259,7 @@ class OpenAPIArgumentsTest(ZulipTestCase):
 | 
			
		||||
                    tags = set()  # type: Set[str]
 | 
			
		||||
                else:
 | 
			
		||||
                    function, tags = value
 | 
			
		||||
 | 
			
		||||
                # Our accounting logic in the `has_request_variables()`
 | 
			
		||||
                # code means we have the list of all arguments
 | 
			
		||||
                # accepted by every view function in arguments_map.
 | 
			
		||||
@@ -270,10 +269,9 @@ class OpenAPIArgumentsTest(ZulipTestCase):
 | 
			
		||||
                # verify those too!
 | 
			
		||||
                accepted_arguments = set(arguments_map[function])
 | 
			
		||||
 | 
			
		||||
                # The purpose of this block is to match our URL
 | 
			
		||||
                # pattern regular expressions to the corresponding
 | 
			
		||||
                # configuration in OpenAPI.  The means matching
 | 
			
		||||
                #
 | 
			
		||||
                # Convert regular expressions style URL patterns to their
 | 
			
		||||
                # corresponding OpenAPI style formats.
 | 
			
		||||
                # E.G.
 | 
			
		||||
                #     /messages/{message_id} <-> r'^messages/(?P<message_id>[0-9]+)$'
 | 
			
		||||
                #     /events <-> r'^events$'
 | 
			
		||||
                regex_pattern = p.regex.pattern
 | 
			
		||||
@@ -286,8 +284,8 @@ class OpenAPIArgumentsTest(ZulipTestCase):
 | 
			
		||||
                url_patterns = [re.sub(r"\(\?P<(\w+)>[^/]+\)", r"<\1>", url_pattern),
 | 
			
		||||
                                re.sub(r"\(\?P<(\w+)>[^/]+\)", r"{\1}", url_pattern)]
 | 
			
		||||
 | 
			
		||||
                if any([url_patterns[0] in PENDING_ENDPOINTS,
 | 
			
		||||
                        url_patterns[1] in PENDING_ENDPOINTS]):
 | 
			
		||||
                if any([url_patterns[0] in self.pending_endpoints,
 | 
			
		||||
                        url_patterns[1] in self.pending_endpoints]):
 | 
			
		||||
                    continue
 | 
			
		||||
                if "intentionally_undocumented" in tags:
 | 
			
		||||
                    error = AssertionError("We found some OpenAPI \
 | 
			
		||||
@@ -318,15 +316,16 @@ undocumented in the urls." % (method, url_patterns[0] + " or " + url_patterns[1]
 | 
			
		||||
                                             (method, url_patterns[0] + " or " + url_patterns[1]))
 | 
			
		||||
 | 
			
		||||
                # We now have everything we need to understand the
 | 
			
		||||
                # function as defined in our urls.py
 | 
			
		||||
                # function as defined in our urls.py:
 | 
			
		||||
                #
 | 
			
		||||
                # * method is the HTTP method, e.g. GET, POST, or PATCH
 | 
			
		||||
                #
 | 
			
		||||
                # * p.regex.pattern is the URL pattern; might require
 | 
			
		||||
                #   some processing to match with OpenAPI rules
 | 
			
		||||
                #
 | 
			
		||||
                # * accepted_arguments_list is the full set of arguments
 | 
			
		||||
                #   this method accepts.
 | 
			
		||||
                # * accepted_arguments is the full set of arguments
 | 
			
		||||
                #   this method accepts (from the REQ declarations in
 | 
			
		||||
                #   code).
 | 
			
		||||
                #
 | 
			
		||||
                # * The documented parameters for the endpoint as recorded in our
 | 
			
		||||
                #   OpenAPI data in zerver/openapi/zulip.yaml.
 | 
			
		||||
@@ -345,15 +344,15 @@ undocumented in the urls." % (method, url_patterns[0] + " or " + url_patterns[1]
 | 
			
		||||
                          method, function)
 | 
			
		||||
                    print(" +", openapi_parameter_names)
 | 
			
		||||
                    print(" -", accepted_arguments)
 | 
			
		||||
                    assert(any([url_patterns[0] in BUGGY_DOCUMENTATION_ENDPOINTS,
 | 
			
		||||
                                url_patterns[1] in BUGGY_DOCUMENTATION_ENDPOINTS]))
 | 
			
		||||
                    assert(any([url_patterns[0] in self.buggy_documentation_endpoints,
 | 
			
		||||
                                url_patterns[1] in self.buggy_documentation_endpoints]))
 | 
			
		||||
                elif len(accepted_arguments - openapi_parameter_names) > 0:
 | 
			
		||||
                    print("Documented invalid parameters for",
 | 
			
		||||
                          url_patterns[0] + " or " + url_patterns[1],
 | 
			
		||||
                          method, function)
 | 
			
		||||
                    print(" -", openapi_parameter_names)
 | 
			
		||||
                    print(" +", accepted_arguments)
 | 
			
		||||
                    assert(any([url_patterns[0] in BUGGY_DOCUMENTATION_ENDPOINTS,
 | 
			
		||||
                                url_patterns[1] in BUGGY_DOCUMENTATION_ENDPOINTS]))
 | 
			
		||||
                    assert(any([url_patterns[0] in self.buggy_documentation_endpoints,
 | 
			
		||||
                                url_patterns[1] in self.buggy_documentation_endpoints]))
 | 
			
		||||
                else:
 | 
			
		||||
                    self.assertEqual(openapi_parameter_names, accepted_arguments)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user