Browse Source

Add graphql tests. Move schema. Make all requests login_required. Don't let users get id or file fields from document

pull/170/head
ariannedee 5 years ago
parent
commit
9d867cb790
6 changed files with 268 additions and 90 deletions
  1. +5
    -0
      edivorce/apps/core/authenticators.py
  2. +178
    -11
      edivorce/apps/core/tests/test_api.py
  3. +0
    -72
      edivorce/apps/core/views/api.py
  4. +82
    -0
      edivorce/apps/core/views/graphql.py
  5. +0
    -4
      edivorce/settings/base.py
  6. +3
    -3
      edivorce/urls.py

+ 5
- 0
edivorce/apps/core/authenticators.py View File

@ -1,5 +1,7 @@
from rest_framework import authentication
from edivorce.apps.core.models import BceidUser
class BCeIDAuthentication(authentication.BaseAuthentication):
"""
@ -15,3 +17,6 @@ class BCeIDAuthentication(authentication.BaseAuthentication):
except:
request.user = request._request.user # pylint: disable=protected-access
return (request.user, None)
def get_user(self, pk):
return BceidUser.objects.get(pk=pk)

+ 178
- 11
edivorce/apps/core/tests/test_api.py View File

@ -1,8 +1,10 @@
import json
from unittest.util import safe_repr
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import modify_settings
from django.test import modify_settings, override_settings
from django.urls import reverse
from graphene_django.utils import GraphQLTestCase
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
@ -34,7 +36,7 @@ class APITest(APITestCase):
url = reverse('documents')
self.assertEqual(Document.objects.count(), 0)
file = self._create_file()
file = _create_file()
data = {
'file': file,
'doc_type': 'AAI',
@ -59,7 +61,7 @@ class APITest(APITestCase):
def test_post_duplicate_files_not_allowed(self):
url = reverse('documents')
file = self._create_file()
file = _create_file()
data = {
'file': file,
'doc_type': 'AAI',
@ -80,7 +82,7 @@ class APITest(APITestCase):
def test_post_field_validation(self):
url = reverse('documents')
file = self._create_file(extension='HEIC')
file = _create_file(extension='HEIC')
data = {
'file': file,
'doc_type': 'INVALID',
@ -277,17 +279,11 @@ class APITest(APITestCase):
doc_type = self.default_doc_type
if not party_code:
party_code = self.default_party_code
new_file = self._create_file()
new_file = _create_file()
document = Document(file=new_file, bceid_user=self.user, party_code=party_code, doc_type=doc_type)
document.save()
return document
@staticmethod
def _create_file(extension='jpg'):
num_documents = Document.objects.count()
new_file = SimpleUploadedFile(f'test_file_{num_documents + 1}.{extension}', b'test content')
return new_file
def _response_data_equals_document(self, response_doc, document_object):
self.assertEqual(response_doc['doc_type'], document_object.doc_type)
self.assertEqual(response_doc['party_code'], document_object.party_code)
@ -296,3 +292,174 @@ class APITest(APITestCase):
self.assertEqual(response_doc['rotation'], document_object.rotation)
self.assertEqual(response_doc['sort_order'], document_object.sort_order)
self.assertEqual(response_doc['file_url'], document_object.get_file_url())
@override_settings(AUTHENTICATION_BACKENDS=('edivorce.apps.core.authenticators.BCeIDAuthentication',))
@modify_settings(MIDDLEWARE={'remove': 'edivorce.apps.core.middleware.bceid_middleware.BceidMiddleware', })
class GraphQLAPITest(GraphQLTestCase):
GRAPHQL_URL = reverse('graphql')
def setUp(self):
self.user = BceidUser.objects.create(user_guid='1234')
self.another_user = BceidUser.objects.create(user_guid='5678')
self.default_doc_type = 'MC'
self.default_party_code = 0
def _login(self):
self._client.force_login(self.user)
def test_not_logged_in(self):
response = self.query('{documents{filename}}')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
response = self.query('''
mutation ($input: DocumentMetaDataInput!) {
updateMetadata(input: $input) {
documents {
filename
}
}
}''', input_data={})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_cannot_get_excluded_fields(self):
self._login()
response = self.query('{documents{id file}}')
print(response.content)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertContainsError('Cannot query field "id"', response)
self.assertContainsError('Cannot query field "file"', response)
def test_must_specify_doctype_partycode(self):
self._login()
response = self.query('{documents{filename}}')
print(response.content)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertContainsError('argument "docType" of type "String!" is required but not provided', response)
self.assertContainsError('argument "partyCode" of type "Int!" is required but not provided', response)
def test_get_only_returns_user_form_docs(self):
self._login()
doc = self._create_document()
self._create_document(user=self.another_user)
self._create_document(doc_type='AAI')
self._create_document(party_code=2)
response = self.query('''
{
documents (docType: "MC", partyCode: 0) {
filename
}
}''')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertResponseNoErrors(response)
content = json.loads(response.content)['data']
self.assertEqual(len(content['documents']), 1)
self.assertEqual(content['documents'][0]['filename'], doc.filename)
def test_update_metadata(self):
doc_1 = self._create_document()
doc_2 = self._create_document()
input_data = {
"docType": doc_1.doc_type,
"partyCode": doc_1.party_code,
"files": [
{
'filename': doc_2.filename,
'size': doc_2.size,
'width': 600,
'height': 800
},
{
'filename': doc_1.filename,
'size': doc_1.size,
'rotation': 270
},
]
}
query = '''
mutation ($input: DocumentMetaDataInput!) {
updateMetadata(input: $input) {
documents {
filename
}
}
}
'''
self._login()
response = self.query(query, input_data=input_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertResponseNoErrors(response)
doc_1.refresh_from_db()
doc_2.refresh_from_db()
self.assertEqual(doc_1.sort_order, 2)
self.assertEqual(doc_1.rotation, 270)
self.assertEqual(doc_2.sort_order, 1)
self.assertEqual(doc_2.width, 600)
self.assertEqual(doc_2.height, 800)
def test_update_metadata_too_few_files(self):
doc_1 = self._create_document()
doc_2 = self._create_document()
input_data = {
"docType": doc_1.doc_type,
"partyCode": doc_1.party_code,
"files": [
{
'filename': doc_2.filename,
'size': doc_2.size,
'rotation': 180,
'width': 600,
'height': 800
}
]
}
query = '''
mutation ($input: DocumentMetaDataInput!) {
updateMetadata(input: $input) {
documents {
filename
}
}
}
'''
self._login()
response = self.query(query, input_data=input_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertResponseHasErrors(response)
self.assertContainsError('there must be the same number of files', response)
def assertContainsError(self, msg, response):
content = json.loads(response.content)
errors = [error['message'] for error in content['errors']]
for error in errors:
if msg in error:
break
else:
error_msgs = "\n".join([safe_repr(error) for error in errors])
fail_message = f'Message: {safe_repr(msg)}\nNot found in errors:\n{error_msgs}'
self.fail(fail_message)
def _create_document(self, user=None, doc_type=None, party_code=None):
if not doc_type:
doc_type = self.default_doc_type
if not party_code:
party_code = self.default_party_code
if not user:
user = self.user
new_file = _create_file()
document = Document(file=new_file, bceid_user=user, party_code=party_code, doc_type=doc_type)
document.save()
return document
def _create_file(extension='jpg'):
num_documents = Document.objects.count()
new_file = SimpleUploadedFile(f'test_file_{num_documents + 1}.{extension}', b'test content')
return new_file

+ 0
- 72
edivorce/apps/core/views/api.py View File

@ -1,9 +1,6 @@
import re
import graphene
import graphene_django
from django.http import Http404, HttpResponse, HttpResponseGone, HttpResponseNotFound
from graphql import GraphQLError
from rest_framework import permissions, status
from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.views import APIView
@ -127,72 +124,3 @@ def _content_type_from_filename(filename):
if not content_type:
raise TypeError(f'Filetype "{extension}" not supported')
return content_type
class DocumentType(graphene_django.DjangoObjectType):
file_url = graphene.String(source='get_file_url')
class Meta:
model = Document
class Query(graphene.ObjectType):
documents = graphene.List(DocumentType, doc_type=graphene.String(required=True), party_code=graphene.Int(required=True))
def resolve_documents(self, info, **kwargs):
if info.context.user.is_anonymous:
raise GraphQLError('Unauthorized')
q = Document.objects.filter(bceid_user=info.context.user, **kwargs)
return q
class DocumentInput(graphene.InputObjectType):
filename = graphene.String(required=True)
size = graphene.Int(required=True)
width = graphene.Int()
height = graphene.Int()
rotation = graphene.Int()
class DocumentMetaDataInput(graphene.InputObjectType):
files = graphene.List(DocumentInput, required=True)
doc_type = graphene.String(required=True)
party_code = graphene.Int(required=True)
class UpdateMetadata(graphene.Mutation):
class Arguments:
input = DocumentMetaDataInput(required=True)
documents = graphene.List(DocumentType)
def mutate(self, info, **kwargs):
if info.context.user.is_anonymous:
raise GraphQLError('Unauthorized')
input_ = kwargs['input']
documents = Document.objects.filter(bceid_user=info.context.user, doc_type=input_['doc_type'], party_code=input_['party_code'])
unique_files = [dict(s) for s in set(frozenset(d.items()) for d in input_['files'])]
if documents.count() != len(input_['files']) or documents.count() != len(unique_files):
raise GraphQLError("Invalid input: there must be the same number of files")
for i, file in enumerate(input_['files']):
try:
doc = documents.get(filename=file['filename'], size=file['size'])
doc.sort_order = i + 1
doc.width = file.get('width', file.width)
doc.height = file.get('height', file.height)
doc.rotation = file.get('rotation', file.rotation)
if doc.rotation not in [0, 90, 180, 270]:
raise GraphQLError(f"Invalid rotation {doc.rotation}, must be 0, 90, 180, 270")
doc.save()
except Document.DoesNotExist:
raise GraphQLError(f"Couldn't find document '{file['filename']}' with size '{file['size']}'")
return UpdateMetadata(documents=documents.all())
class Mutations(graphene.ObjectType):
update_metadata = UpdateMetadata.Field()
graphql_schema = graphene.Schema(query=Query, mutation=Mutations)

+ 82
- 0
edivorce/apps/core/views/graphql.py View File

@ -0,0 +1,82 @@
import graphene
import graphene_django
from django.http import HttpResponseForbidden
from graphene_django.views import GraphQLView
from graphql import GraphQLError
from edivorce.apps.core.models import Document
class PrivateGraphQLView(GraphQLView):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs)
class DocumentType(graphene_django.DjangoObjectType):
file_url = graphene.String(source='get_file_url')
class Meta:
model = Document
exclude = ('id', 'file')
class Query(graphene.ObjectType):
documents = graphene.List(DocumentType, doc_type=graphene.String(required=True), party_code=graphene.Int(required=True))
def resolve_documents(self, info, **kwargs):
if info.context.user.is_anonymous:
raise GraphQLError('Unauthorized')
q = Document.objects.filter(bceid_user=info.context.user, **kwargs)
return q
class DocumentInput(graphene.InputObjectType):
filename = graphene.String(required=True)
size = graphene.Int(required=True)
width = graphene.Int()
height = graphene.Int()
rotation = graphene.Int()
class DocumentMetaDataInput(graphene.InputObjectType):
files = graphene.List(DocumentInput, required=True)
doc_type = graphene.String(required=True)
party_code = graphene.Int(required=True)
class UpdateMetadata(graphene.Mutation):
class Arguments:
input = DocumentMetaDataInput(required=True)
documents = graphene.List(DocumentType)
def mutate(self, info, **kwargs):
input_ = kwargs['input']
documents = Document.objects.filter(bceid_user=info.context.user, doc_type=input_['doc_type'], party_code=input_['party_code'])
unique_files = [dict(s) for s in set(frozenset(d.items()) for d in input_['files'])]
if documents.count() != len(input_['files']) or documents.count() != len(unique_files):
raise GraphQLError("Invalid input: there must be the same number of files")
for i, file in enumerate(input_['files']):
try:
doc = documents.get(filename=file['filename'], size=file['size'])
doc.sort_order = i + 1
doc.width = file.get('width', doc.width)
doc.height = file.get('height', doc.height)
doc.rotation = file.get('rotation', doc.rotation)
if doc.rotation not in [0, 90, 180, 270]:
raise GraphQLError(f"Invalid rotation {doc.rotation}, must be 0, 90, 180, 270")
doc.save()
except Document.DoesNotExist:
raise GraphQLError(f"Couldn't find document '{file['filename']}' with size '{file['size']}'")
return UpdateMetadata(documents=documents.all())
class Mutations(graphene.ObjectType):
update_metadata = UpdateMetadata.Field()
graphql_schema = graphene.Schema(query=Query, mutation=Mutations)

+ 0
- 4
edivorce/settings/base.py View File

@ -105,10 +105,6 @@ REST_FRAMEWORK = {
]
}
GRAPHENE = {
'SCHEMA': 'edivorce.apps.core.views.api.graphql_schema'
}
LOGGING = {
'version': 1,


+ 3
- 3
edivorce/urls.py View File

@ -3,9 +3,9 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from .apps.core.views import main
from .apps.core.views.graphql import PrivateGraphQLView, graphql_schema
urlpatterns = []
@ -13,9 +13,9 @@ if settings.ENVIRONMENT in ['localdev', 'dev', 'test', 'minishift']:
import debug_toolbar
urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls)),)
urlpatterns.append(url(r'^poc/', include('edivorce.apps.poc.urls')))
urlpatterns.append(path('api/graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))))
urlpatterns.append(path('api/graphql/', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, schema=graphql_schema)), name='graphql'))
else:
urlpatterns.append(path('api/graphql/', csrf_exempt(GraphQLView.as_view(graphiql=False))))
urlpatterns.append(path('api/graphql/', csrf_exempt(PrivateGraphQLView.as_view(graphiql=False, schema=graphql_schema)), name='graphql'))
if settings.ENVIRONMENT in ['localdev', 'minishift']:
urlpatterns.append(url(r'^admin/', admin.site.urls))


Loading…
Cancel
Save