diff --git a/openlobby/core/api/mutations.py b/openlobby/core/api/mutations.py index afd86b293c9ffcf5815005da509b0445100de2b8..e0e1c2bb9201c84e4f6c213ce5003d53ce3e9e70 100644 --- a/openlobby/core/api/mutations.py +++ b/openlobby/core/api/mutations.py @@ -1,3 +1,4 @@ +import arrow import graphene from graphene import relay from graphene.types.datetime import DateTime @@ -134,9 +135,57 @@ class CreateReport(relay.ClientIDMutation): return CreateReport(report=types.Report.from_db(report)) +class UpdateReport(relay.ClientIDMutation): + + class Input: + id = graphene.ID(required=True) + title = graphene.String(required=True) + body = graphene.String(required=True) + received_benefit = graphene.String() + provided_benefit = graphene.String() + our_participants = graphene.String() + other_participants = graphene.String() + date = DateTime(required=True) + is_draft = graphene.Boolean(default_value=False) + + report = graphene.Field(types.Report) + + @classmethod + def mutate_and_get_payload(cls, root, info, **input): + if not info.context.user.is_authenticated: + raise Exception('User must be logged in to perform this mutation.') + + author = info.context.user + type, id = from_global_id(input.get('id')) + + try: + report = Report.objects.select_related('author').get(id=id, author_id=author.id) + except Report.DoesNotExist: + raise Exception('Viewer is not the Author of this Report or Report does not exist.') + + is_draft = input.get('is_draft') + + if is_draft and not report.is_draft: + raise Exception('You cannot update published Report with draft.') + + report.published = arrow.utcnow().datetime + report.date = input.get('date') + report.title = strip_all_tags(input.get('title', '')) + report.body = strip_all_tags(input.get('body', '')) + report.received_benefit = strip_all_tags(input.get('received_benefit', '')) + report.provided_benefit = strip_all_tags(input.get('provided_benefit', '')) + report.our_participants = strip_all_tags(input.get('our_participants', '')) + report.other_participants = strip_all_tags(input.get('other_participants', '')) + report.is_draft = is_draft + report.save() + + return UpdateReport(report=types.Report.from_db(report)) + + class Mutation: login = Login.Field() login_by_shortcut = LoginByShortcut.Field() # TODO # logout = Logout.Field() create_report = CreateReport.Field() + update_report = UpdateReport.Field() diff --git a/tests/dummy.py b/tests/dummy.py index ddd0fe018823076ad5c5201d44aa2b5e812d18c7..9e8a1be6305ee5ee938391a294618703efede2c5 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -103,3 +103,8 @@ def prepare_reports(): def prepare_author(): User.objects.create(**authors[0]) + + +def prepare_report(is_draft=False): + author = User.objects.create(**authors[0]) + Report.objects.create(author=author, is_draft=is_draft, **reports[0]) diff --git a/tests/mutations/snapshots/snap_test_update_report.py b/tests/mutations/snapshots/snap_test_update_report.py new file mode 100644 index 0000000000000000000000000000000000000000..18f47b8d0343843a2491a80a47b5539808704c58 --- /dev/null +++ b/tests/mutations/snapshots/snap_test_update_report.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +# snapshottest: v1 - https://goo.gl/zC4yUc +from __future__ import unicode_literals + +from snapshottest import Snapshot + + +snapshots = Snapshot() + +snapshots['test_unauthorized 1'] = { + 'data': { + 'updateReport': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 5, + 'line': 3 + } + ], + 'message': 'User must be logged in to perform this mutation.' + } + ] +} + +snapshots['test_not_author 1'] = { + 'data': { + 'updateReport': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 5, + 'line': 3 + } + ], + 'message': 'Viewer is not the Author of this Report or Report does not exist.' + } + ] +} + +snapshots['test_report_does_not_exist 1'] = { + 'data': { + 'updateReport': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 5, + 'line': 3 + } + ], + 'message': 'Viewer is not the Author of this Report or Report does not exist.' + } + ] +} + +snapshots['test_update_published_with_draft 1'] = { + 'data': { + 'updateReport': None + }, + 'errors': [ + { + 'locations': [ + { + 'column': 5, + 'line': 3 + } + ], + 'message': 'You cannot update published Report with draft.' + } + ] +} + +snapshots['test_update_draft_with_draft 1'] = { + 'data': { + 'updateReport': { + 'report': { + 'author': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 0 + }, + 'body': 'I visited Tesla factory and talked with Elon Musk.', + 'date': '2018-03-03 00:00:00+00:00', + 'extra': None, + 'id': 'UmVwb3J0OjE=', + 'otherParticipants': 'Elon Musk', + 'ourParticipants': 'me', + 'providedBenefit': 'nothing', + 'published': '2018-03-08 00:00:00+00:00', + 'receivedBenefit': 'Tesla Model S', + 'title': 'Free Tesla' + } + } + } +} + +snapshots['test_update_draft_with_published 1'] = { + 'data': { + 'updateReport': { + 'report': { + 'author': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 1 + }, + 'body': 'I visited Tesla factory and talked with Elon Musk.', + 'date': '2018-03-03 00:00:00+00:00', + 'extra': None, + 'id': 'UmVwb3J0OjE=', + 'otherParticipants': 'Elon Musk', + 'ourParticipants': 'me', + 'providedBenefit': 'nothing', + 'published': '2018-03-08 00:00:00+00:00', + 'receivedBenefit': 'Tesla Model S', + 'title': 'Free Tesla' + } + } + } +} + +snapshots['test_update_published_with_published 1'] = { + 'data': { + 'updateReport': { + 'report': { + 'author': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 1 + }, + 'body': 'I visited Tesla factory and talked with Elon Musk.', + 'date': '2018-03-03 00:00:00+00:00', + 'extra': None, + 'id': 'UmVwb3J0OjE=', + 'otherParticipants': 'Elon Musk', + 'ourParticipants': 'me', + 'providedBenefit': 'nothing', + 'published': '2018-03-08 00:00:00+00:00', + 'receivedBenefit': 'Tesla Model S', + 'title': 'Free Tesla' + } + } + } +} + +snapshots['test_input_sanitization 1'] = { + 'data': { + 'updateReport': { + 'report': { + 'author': { + 'extra': '{"movies": 1}', + 'firstName': 'Winston', + 'id': 'QXV0aG9yOjE=', + 'lastName': 'Wolfe', + 'totalReports': 1 + }, + 'body': 'some link in body', + 'date': '2018-03-03 00:00:00+00:00', + 'extra': None, + 'id': 'UmVwb3J0OjE=', + 'otherParticipants': 'you!', + 'ourParticipants': 'me, myself', + 'providedBenefit': 'tea', + 'published': '2018-03-08 00:00:00+00:00', + 'receivedBenefit': 'coffee', + 'title': 'No tags' + } + } + } +} diff --git a/tests/mutations/test_update_report.py b/tests/mutations/test_update_report.py new file mode 100644 index 0000000000000000000000000000000000000000..c0e1ff2fbb87b8e8f19c26d0e6ed87cbbacc654b --- /dev/null +++ b/tests/mutations/test_update_report.py @@ -0,0 +1,161 @@ +import pytest +import arrow +from graphql_relay import to_global_id +from unittest.mock import patch + +from openlobby.core.models import User, Report + +from ..dummy import prepare_report +from ..utils import call_api + + +pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures('django_es')] + + +query = """ +mutation updateReport ($input: UpdateReportInput!) { + updateReport (input: $input) { + report { + id + date + published + title + body + receivedBenefit + providedBenefit + ourParticipants + otherParticipants + extra + author { + id + firstName + lastName + totalReports + extra + } + } + } +} +""" + +published = arrow.get(2018, 3, 8) + +date = arrow.get(2018, 3, 3) +title = 'Free Tesla' +body = 'I visited Tesla factory and talked with Elon Musk.' +received_benefit = 'Tesla Model S' +provided_benefit = 'nothing' +our_participants = 'me' +other_participants = 'Elon Musk' + + +def get_input(is_draft=False, id=1): + return { + 'id': to_global_id('Report', id), + 'title': title, + 'body': body, + 'receivedBenefit': received_benefit, + 'providedBenefit': provided_benefit, + 'ourParticipants': our_participants, + 'otherParticipants': other_participants, + 'date': date.isoformat(), + 'isDraft': is_draft, + } + + +def assert_report(is_draft=False): + report = Report.objects.get(id=1) + assert report.author_id == 1 + assert report.date == date.datetime + assert report.published == published.datetime + assert report.title == title + assert report.body == body + assert report.received_benefit == received_benefit + assert report.provided_benefit == provided_benefit + assert report.our_participants == our_participants + assert report.other_participants == other_participants + assert report.extra is None + assert report.is_draft is is_draft + + +def test_unauthorized(client, snapshot): + prepare_report() + input = get_input() + response = call_api(client, query, input) + snapshot.assert_match(response) + + +def test_not_author(client, snapshot): + prepare_report() + User.objects.create(id=2, username='hacker') + input = get_input() + response = call_api(client, query, input, 'hacker') + snapshot.assert_match(response) + + +def test_report_does_not_exist(client, snapshot): + prepare_report() + input = get_input(id=666) + response = call_api(client, query, input, 'wolf') + snapshot.assert_match(response) + + +def test_update_published_with_draft(client, snapshot): + prepare_report() + input = get_input(is_draft=True) + response = call_api(client, query, input, 'wolf') + snapshot.assert_match(response) + + +def test_update_draft_with_draft(client, snapshot): + prepare_report(is_draft=True) + input = get_input(is_draft=True) + with patch('openlobby.core.api.mutations.arrow.utcnow', return_value=published): + response = call_api(client, query, input, 'wolf') + snapshot.assert_match(response) + assert_report(is_draft=True) + + +def test_update_draft_with_published(client, snapshot): + prepare_report(is_draft=True) + input = get_input() + with patch('openlobby.core.api.mutations.arrow.utcnow', return_value=published): + response = call_api(client, query, input, 'wolf') + snapshot.assert_match(response) + assert_report() + + +def test_update_published_with_published(client, snapshot): + prepare_report() + input = get_input() + with patch('openlobby.core.api.mutations.arrow.utcnow', return_value=published): + response = call_api(client, query, input, 'wolf') + snapshot.assert_match(response) + assert_report() + + +def test_input_sanitization(client, snapshot): + prepare_report() + input = { + 'id': to_global_id('Report', 1), + 'title': '<s>No</s> tags', + 'body': 'some <a href="http://foo">link</a> <br>in body', + 'receivedBenefit': '<b>coffee</b>', + 'providedBenefit': '<li>tea', + 'ourParticipants': 'me, <u>myself</u>', + 'otherParticipants': '<strong>you!</strong>', + 'date': date.isoformat(), + } + + with patch('openlobby.core.api.mutations.arrow.utcnow', return_value=published): + response = call_api(client, query, input, 'wolf') + + snapshot.assert_match(response) + + report = Report.objects.get() + assert report.title == 'No tags' + assert report.body == 'some link in body' + assert report.received_benefit == 'coffee' + assert report.provided_benefit == 'tea' + assert report.our_participants == 'me, myself' + assert report.other_participants == 'you!'