22import pytest
33from django .conf .urls import url
44from django .test import RequestFactory , TestCase , override_settings
5+ from rest_condition import And , Not , Or
56from rest_framework import VERSION as DRFVERSION
67from rest_framework .authentication import BasicAuthentication , SessionAuthentication
8+ from rest_framework .permissions import (
9+ AllowAny ,
10+ DjangoModelPermissions ,
11+ IsAdminUser ,
12+ IsAuthenticated
13+ )
714from rest_framework .request import Request
815
916from rest_framework_json_api .optional import OAuth2Authentication , TokenMatchesOASRequirements
17+ from rest_framework_json_api .views import ModelViewSet
1018
11- from example import views
19+ from example import models , serializers , views
1220
1321drf_version = tuple (int (x ) for x in DRFVERSION .split ('.' ))
1422
@@ -806,40 +814,42 @@ def test_delete_request(self):
806814 }
807815
808816 @pytest .mark .skipif (TokenMatchesOASRequirements is None , reason = "requires oauth" )
809- def test_schema_security (self ):
817+ class OauthProtectedAuthorViewSet (views .AuthorViewSet ):
818+ authentication_classes = (OAuth2Authentication , BasicAuthentication ,
819+ SessionAuthentication )
820+ permission_classes = (TokenMatchesOASRequirements , IsAuthenticated )
821+ required_alternate_scopes = {
822+ 'GET' : [['scope1' , 'scope2' ], ['scope3' , 'scope4' ]],
823+ }
824+
825+ oauth2_server = 'oauth.example.com'
826+ oauth2_config = {
827+ 'authorization_endpoint' : oauth2_server + '/authorize' ,
828+ 'token_endpoint' : oauth2_server + '/token' ,
829+ 'scopes_supported' : ['scope1' , 'scope2' , 'scope3' , 'scope4' ],
830+ 'grant_types_supported' : ['implicit' , 'authorization_code' , 'client_credentials' ,
831+ 'password' ],
832+ }
833+
834+ def test_schema_security_list (self ):
810835 """
811- Checks for security object
812- :return:
836+ Checks for security objects
813837 """
814- class OauthProtectedAuthorViewSet (views .AuthorViewSet ):
815- authentication_classes = (OAuth2Authentication , BasicAuthentication ,
816- SessionAuthentication )
817- permission_classes = (TokenMatchesOASRequirements ,)
818- required_alternate_scopes = {
819- 'GET' : [['scope1' , 'scope2' ], ['scope3' , 'scope4' ]],
820- }
838+ # TODO: also test permission combinations with rest_condition & DRF bitwise operators
821839
822- oauth2_server = 'oauth.example.com'
823- oauth2_config = {
824- 'authorization_endpoint' : oauth2_server + '/authorize' ,
825- 'token_endpoint' : oauth2_server + '/token' ,
826- 'scopes_supported' : ['scope1' , 'scope2' , 'scope3' , 'scope4' ],
827- 'grant_types_supported' : ['implicit' , 'authorization_code' , 'client_credentials' ,
828- 'password' ],
829- }
830840 path = '/authors/'
831841 method = 'GET'
832842
833843 view = create_view_with_kw (
834- OauthProtectedAuthorViewSet ,
844+ TestOperationIntrospection . OauthProtectedAuthorViewSet ,
835845 method ,
836846 create_request (path ),
837847 {'get' : 'list' }
838848 )
839849 inspector = AutoSchema ()
840850 inspector .view = view
841851
842- with override_settings (OAUTH2_CONFIG = oauth2_config ):
852+ with override_settings (OAUTH2_CONFIG = TestOperationIntrospection . oauth2_config ):
843853 operation = inspector .get_operation (path , method )
844854
845855 assert 'security' in operation
@@ -849,39 +859,73 @@ class OauthProtectedAuthorViewSet(views.AuthorViewSet):
849859 assert operation ['security' ][2 ] == {'basicAuth' : []}
850860 assert operation ['security' ][3 ] == {'cookieAuth' : []}
851861
852- # TODO: figure these out
853- # def test_retrieve_relationships(self):
854- # path = '/authors/{id}/relationships/bio/'
855- # method = 'GET'
856- #
857- # view = create_view_with_kw(
858- # views.AuthorRelationshipView,
859- # method,
860- # create_request(path),
861- # {'get': 'retrieve'}
862- # )
863- # inspector = AutoSchema()
864- # inspector.view = view
865- #
866- # operation = inspector.get_operation(path, method)
867- # assert operation == {}
862+ def test_schema_security_drf_condition (self ):
863+ """
864+ Checks for security objects with DRF bitwise conditional operators
865+ """
866+ class DRF_Cond_ViewSet (TestOperationIntrospection .OauthProtectedAuthorViewSet ):
867+ # this is a crazy example just to make sure all the recursive code is covered
868+ permission_classes = [
869+ (IsAuthenticated & DjangoModelPermissions ) |
870+ ~ (TokenMatchesOASRequirements & AllowAny ),
871+ ~ AllowAny | (IsAdminUser & IsAuthenticated ),
872+ (TokenMatchesOASRequirements & AllowAny ) |
873+ (IsAuthenticated & DjangoModelPermissions ),
874+ ~ TokenMatchesOASRequirements
875+ ]
868876
869- # def test_retrieve_related(self):
870- # path = '/authors/{id}/{related_field}/'
871- # method = 'GET'
872- #
873- # view = create_view_with_kw(
874- # views.AuthorViewSet,
875- # method,
876- # create_request(path),
877- # {'get': 'retrieve_related',
878- # 'related_field': 'bio'}
879- # )
880- # inspector = AutoSchema()
881- # inspector.view = view
882- #
883- # operation = inspector.get_operation(path, method)
884- # assert operation == {}
877+ path = '/authors/'
878+ method = 'GET'
879+
880+ view = create_view_with_kw (
881+ DRF_Cond_ViewSet ,
882+ method ,
883+ create_request (path ),
884+ {'get' : 'list' }
885+ )
886+ inspector = AutoSchema ()
887+ inspector .view = view
888+
889+ with override_settings (OAUTH2_CONFIG = TestOperationIntrospection .oauth2_config ):
890+ operation = inspector .get_operation (path , method )
891+
892+ assert 'security' in operation
893+ assert {'oauth' : ['scope1' , 'scope2' ]} in operation ['security' ]
894+ assert {'oauth' : ['scope3' , 'scope4' ]} in operation ['security' ]
895+ assert {'basicAuth' : []} in operation ['security' ]
896+ assert {'cookieAuth' : []} in operation ['security' ]
897+
898+ def test_schema_security_rest_condition (self ):
899+ """
900+ Checks for security objects with rest_condition operator methods
901+ """
902+ class Rest_Cond_ViewSet (TestOperationIntrospection .OauthProtectedAuthorViewSet ):
903+ permission_classes = [
904+ Or (
905+ And (IsAuthenticated , DjangoModelPermissions ),
906+ And (Not (TokenMatchesOASRequirements ), AllowAny )),
907+ ]
908+
909+ path = '/authors/'
910+ method = 'GET'
911+
912+ view = create_view_with_kw (
913+ Rest_Cond_ViewSet ,
914+ method ,
915+ create_request (path ),
916+ {'get' : 'list' }
917+ )
918+ inspector = AutoSchema ()
919+ inspector .view = view
920+
921+ with override_settings (OAUTH2_CONFIG = TestOperationIntrospection .oauth2_config ):
922+ operation = inspector .get_operation (path , method )
923+
924+ assert 'security' in operation
925+ assert {'oauth' : ['scope1' , 'scope2' ]} in operation ['security' ]
926+ assert {'oauth' : ['scope3' , 'scope4' ]} in operation ['security' ]
927+ assert {'basicAuth' : []} in operation ['security' ]
928+ assert {'cookieAuth' : []} in operation ['security' ]
885929
886930
887931@override_settings (REST_FRAMEWORK = {
@@ -901,3 +945,46 @@ def test_schema_construction(self):
901945 assert 'info' in schema
902946 assert 'paths' in schema
903947 assert 'components' in schema
948+
949+ # TODO: figure these out
950+ def test_schema_related (self ):
951+ class AuthorBioViewSet (ModelViewSet ):
952+ queryset = models .AuthorBio .objects .all ()
953+ serializer_class = serializers .AuthorBioSerializer
954+
955+ patterns = [
956+ url (r'^authors/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$' ,
957+ views .AuthorViewSet .as_view ({'get' : 'retrieve_related' }),
958+ name = 'author-related' ),
959+ url (r'^bios/(?P<pk>[^/.]+)/$' ,
960+ AuthorBioViewSet ,
961+ name = 'author-bio' )
962+ ]
963+ generator = SchemaGenerator (patterns = patterns )
964+
965+ request = create_request ('/authors/123/bio/' )
966+ schema = generator .get_schema (request = request )
967+ # TODO: finish this test
968+ print (schema )
969+
970+ # def test_retrieve_relationships(self):
971+ # path = '/authors/{id}/relationships/bio/'
972+ # method = 'GET'
973+ #
974+ # view = create_view_with_kw(
975+ # views.AuthorViewSet,
976+ # method,
977+ # create_request(path),
978+ # {'get': 'retrieve_related'}
979+ # )
980+ # inspector = AutoSchema()
981+ # inspector.view = view
982+ #
983+ # operation = inspector.get_operation(path, method)
984+ # assert 'responses' in operation
985+ # assert '200' in operation['responses']
986+ # resp = operation['responses']['200']['content']
987+ # data = resp['application/vnd.api+json']['schema']['properties']['data']
988+ # assert data['type'] == 'object'
989+ # assert data['required'] == ['type', 'id']
990+ # assert data['properties']['type'] == {'$ref': '#/components/schemas/type'}
0 commit comments