diff --git a/README.md b/README.md index eb60e95955851f70ff9d4a49135fdf1d6f0ce5f5..e96068b4602ecb7e0627e849edbeccb6fafc4cf8 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ # Open Lobby Server -Open Lobby is register of lobby meetings. It's being developed for and tested -on [Czech Pirate Party](https://www.pirati.cz) but later it may be used by +Open Lobby is register of lobby meetings. It's being developed for and tested +on [Czech Pirate Party](https://www.pirati.cz) but later it may be used by any party, organization, agency, ... -_Open Lobby is in early beta version now. Not for production use._ - -This is core of the register - server with [GraphQL API](http://graphql.org). -Over API are connected application interfaces. Default web application is -available at +This is core of the register - server with [GraphQL API](http://graphql.org). +Over API are connected application interfaces. Default web application is +available at [openlobby/openlobby-app](https://github.com/openlobby/openlobby-app). -Register is built on top of -[Elasticsearch](https://www.elastic.co/products/elasticsearch). For now it's -intended for search in Czech language with custom Czech text analyzer. There is -prepared Elasticsearch Docker container with Czech support at +Register is built on top of +[Elasticsearch](https://www.elastic.co/products/elasticsearch). For now it's +intended for search in Czech language with custom Czech text analyzer. There is +prepared Elasticsearch Docker container with Czech support at [openlobby/openlobby-es-czech](https://github.com/openlobby/openlobby-es-czech). ## Configuration @@ -29,24 +27,26 @@ Configuration is done by environment variables: - `REDIRECT_URI` - redirect URI used in OpenID Connect authentication (default: `http://localhost:8010/login-redirect`) - put there address where you run server, but keep there `/login-redirect` - this is the Redirect URI for static client registration at OpenID Provider + - `FREE_EDIT_MINUTES` - edit will save historical revision after this time + since last edit (or publishing) of report (default: `60`) ### Login shortcuts aka preregistered OpenID Clients -Some OpenID Providers does not allow dynamic client registration. You can still -use them. Register client with `REDIRECT_URI` and save client's credentials into -database. You can do it in admin interface running at `/admin`. It's standard +Some OpenID Providers does not allow dynamic client registration. You can still +use them. Register client with `REDIRECT_URI` and save client's credentials into +database. You can do it in admin interface running at `/admin`. It's standard Django admin (create superuser for yourself like `./manage.py createsuperuser`). ## Docker -Docker image is at Docker Hub -[openlobby/openlobby-server](https://hub.docker.com/r/openlobby/openlobby-server/). -It exposes server on port 8010. You should provide it environment variables for +Docker image is at Docker Hub +[openlobby/openlobby-server](https://hub.docker.com/r/openlobby/openlobby-server/). +It exposes server on port 8010. You should provide it environment variables for configuration (at least `SECRET_KEY`). ## Demo -Demo of Open Lobby with instructions is in repository +Demo of Open Lobby with instructions is in repository [openlobby/demo](https://github.com/openlobby/demo). ## Local run and development @@ -55,13 +55,13 @@ Demo of Open Lobby with instructions is in repository You need to have Python 3 installed. -Run PostgreSQL database on `localhost:5432` with user `db`, password `db` and -database `openlobby`. You can provide different address in environment variable +Run PostgreSQL database on `localhost:5432` with user `db`, password `db` and +database `openlobby`. You can provide different address in environment variable `DATABASE_DSN`. -Run Elasticsearch server +Run Elasticsearch server [openlobby/openlobby-es-czech](https://github.com/openlobby/openlobby-es-czech) -on `http://localhost:9200`. You can provide different address in environment +on `http://localhost:9200`. You can provide different address in environment variable `ELASTICSEARCH_DSN`. ### Local run @@ -74,7 +74,7 @@ Clone this repository and run: 4. `make migrate` - runs database migrations and rebuilds Elasticsearch index 5. `make run` - runs development server on port `8010` -Now you can use GraphQL API endpoint and GraphiQL web interface at +Now you can use GraphQL API endpoint and GraphiQL web interface at `http://localhost:8010/graphql` Next time you can just do steps 2 and 5. @@ -83,14 +83,14 @@ Next time you can just do steps 2 and 5. Run: `pytest` -For full test suite run you have to provide OpenID Provider issuer URL which -allows client registration. For example you can run Keycloak sever locally: +For full test suite run you have to provide OpenID Provider issuer URL which +allows client registration. For example you can run Keycloak sever locally: `docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=pass -p 8080:8080 --rm jboss/keycloak` -Login into Keycloak admin console `http://localhost:8080/auth/admin/` +Login into Keycloak admin console `http://localhost:8080/auth/admin/` (as admin/pass) and go to Realm Settings -> Client Registration -> Client Registration Policies -> Trusted Hosts. There add `localhost` to "Trusted -Hosts", turn off "Host Sending Client Registration Request Must Match" and save +Hosts", turn off "Host Sending Client Registration Request Must Match" and save it. Now run: `pytest --issuer=http://localhost:8080/auth/realms/master` diff --git a/openlobby/core/api/mutations.py b/openlobby/core/api/mutations.py index fa4e3a276d46e8b22e7b17423c2f2a8671e2108f..8eb754319edf2cc398062a1d51c9c3374e6f546f 100644 --- a/openlobby/core/api/mutations.py +++ b/openlobby/core/api/mutations.py @@ -1,4 +1,5 @@ import arrow +from django.conf import settings import graphene from graphene import relay from graphene.types.datetime import DateTime @@ -178,11 +179,21 @@ class UpdateReport(relay.ClientIDMutation): if is_draft and not report.is_draft: raise Exception("You cannot update published Report with draft.") - # TODO updating published report older than like a hour should create - # new revision in history of report + free_edit_deadline = arrow.utcnow().shift(minutes=-settings.FREE_EDIT_MINUTES) + if ( + not (is_draft or report.is_draft) + and free_edit_deadline.datetime > report.edited + ): + original_id = report.id + # save historical revision copy + report.id = None + report.superseded_by_id = original_id + report.save() + # restore id so this can be updated + report.id = original_id + report.superseded_by_id = None report.edited = arrow.utcnow().datetime - if is_draft or report.is_draft: report.published = report.edited diff --git a/openlobby/core/api/types.py b/openlobby/core/api/types.py index de692b88e1611d6fb504023bcc60aebda9c54dcb..a3d9966790f9ff7c3428c46f5da460b80c44afee 100644 --- a/openlobby/core/api/types.py +++ b/openlobby/core/api/types.py @@ -1,4 +1,3 @@ -from django.db.models import Count, Q from elasticsearch import NotFoundError import graphene from graphene import relay diff --git a/openlobby/settings.py b/openlobby/settings.py index 1b08799bbdda0094d8579b936c5d35e4ded5c581..ff9786daf95d0ff697f4ec0efa4b33b3901fe8ec 100644 --- a/openlobby/settings.py +++ b/openlobby/settings.py @@ -150,3 +150,6 @@ SITE_NAME = os.environ.get("SITE_NAME", "Open Lobby") # redirect URI used in OpenID authentication REDIRECT_URI = os.environ.get("REDIRECT_URI", "http://localhost:8010/login-redirect") + +# edit will save historical revision after this time since last edit +FREE_EDIT_MINUTES = os.environ.get("FREE_EDIT_MINUTES", 60) diff --git a/tests/mutations/snapshots/snap_test_update_report.py b/tests/mutations/snapshots/snap_test_update_report.py index 77a4e76e6ecdf5813d397744c5159d9fed62e756..3c88475c22af04b113378b91713bd29c39924f1c 100644 --- a/tests/mutations/snapshots/snap_test_update_report.py +++ b/tests/mutations/snapshots/snap_test_update_report.py @@ -58,22 +58,22 @@ snapshots["test_update_draft_with_draft 1"] = { "author": { "extra": '{"movies": 1}', "firstName": "Winston", - "id": "QXV0aG9yOjE=", + "id": "QXV0aG9yOjQy", "lastName": "Wolfe", "totalReports": 0, }, - "body": "I visited Tesla factory and talked with Elon Musk.", + "body": "Rewrited", "date": "2018-03-03 00:00:00+00:00", - "edited": "2018-03-08 12:00:00+00:00", + "edited": "2018-01-02 05:50:00+00:00", "extra": None, - "id": "UmVwb3J0OjE=", + "id": "UmVwb3J0OjY2Ng==", "isDraft": True, - "otherParticipants": "Elon Musk", - "ourParticipants": "me", - "providedBenefit": "nothing", - "published": "2018-03-08 12:00:00+00:00", - "receivedBenefit": "Tesla Model S", - "title": "Free Tesla", + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", + "published": "2018-01-02 05:50:00+00:00", + "receivedBenefit": "cake", + "title": "New title", } } } @@ -86,22 +86,22 @@ snapshots["test_update_draft_with_published 1"] = { "author": { "extra": '{"movies": 1}', "firstName": "Winston", - "id": "QXV0aG9yOjE=", + "id": "QXV0aG9yOjQy", "lastName": "Wolfe", "totalReports": 1, }, - "body": "I visited Tesla factory and talked with Elon Musk.", + "body": "Rewrited", "date": "2018-03-03 00:00:00+00:00", - "edited": "2018-03-08 12:00:00+00:00", + "edited": "2018-01-02 05:50:00+00:00", "extra": None, - "id": "UmVwb3J0OjE=", + "id": "UmVwb3J0OjY2Ng==", "isDraft": False, - "otherParticipants": "Elon Musk", - "ourParticipants": "me", - "providedBenefit": "nothing", - "published": "2018-03-08 12:00:00+00:00", - "receivedBenefit": "Tesla Model S", - "title": "Free Tesla", + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", + "published": "2018-01-02 05:50:00+00:00", + "receivedBenefit": "cake", + "title": "New title", } } } @@ -114,22 +114,22 @@ snapshots["test_update_published_with_published 1"] = { "author": { "extra": '{"movies": 1}', "firstName": "Winston", - "id": "QXV0aG9yOjE=", + "id": "QXV0aG9yOjQy", "lastName": "Wolfe", "totalReports": 1, }, - "body": "I visited Tesla factory and talked with Elon Musk.", + "body": "Rewrited", "date": "2018-03-03 00:00:00+00:00", - "edited": "2018-03-08 12:00:00+00:00", + "edited": "2018-01-02 05:50:00+00:00", "extra": None, - "id": "UmVwb3J0OjE=", + "id": "UmVwb3J0OjY2Ng==", "isDraft": False, - "otherParticipants": "Elon Musk", - "ourParticipants": "me", - "providedBenefit": "nothing", + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", "published": "2018-01-02 00:00:00+00:00", - "receivedBenefit": "Tesla Model S", - "title": "Free Tesla", + "receivedBenefit": "cake", + "title": "New title", } } } @@ -142,15 +142,15 @@ snapshots["test_input_sanitization 1"] = { "author": { "extra": '{"movies": 1}', "firstName": "Winston", - "id": "QXV0aG9yOjE=", + "id": "QXV0aG9yOjQy", "lastName": "Wolfe", "totalReports": 1, }, "body": "some link in body", "date": "2018-03-03 00:00:00+00:00", - "edited": "2018-03-08 12:00:00+00:00", + "edited": "2018-01-02 05:50:00+00:00", "extra": None, - "id": "UmVwb3J0OjE=", + "id": "UmVwb3J0OjY2Ng==", "isDraft": False, "otherParticipants": "you!", "ourParticipants": "me, myself", @@ -162,3 +162,235 @@ snapshots["test_input_sanitization 1"] = { } } } + +snapshots["test_update_draft_with_draft__late_edit 1"] = { + "data": { + "updateReport": { + "report": { + "author": { + "extra": '{"movies": 1}', + "firstName": "Winston", + "id": "QXV0aG9yOjQy", + "lastName": "Wolfe", + "totalReports": 0, + }, + "body": "Rewrited", + "date": "2018-03-03 00:00:00+00:00", + "edited": "2018-01-02 06:10:00+00:00", + "extra": None, + "id": "UmVwb3J0OjY2Ng==", + "isDraft": True, + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", + "published": "2018-01-02 06:10:00+00:00", + "receivedBenefit": "cake", + "title": "New title", + } + } + } +} + +snapshots["test_update_draft_with_published__late_edit 1"] = { + "data": { + "updateReport": { + "report": { + "author": { + "extra": '{"movies": 1}', + "firstName": "Winston", + "id": "QXV0aG9yOjQy", + "lastName": "Wolfe", + "totalReports": 1, + }, + "body": "Rewrited", + "date": "2018-03-03 00:00:00+00:00", + "edited": "2018-01-02 06:10:00+00:00", + "extra": None, + "id": "UmVwb3J0OjY2Ng==", + "isDraft": False, + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", + "published": "2018-01-02 06:10:00+00:00", + "receivedBenefit": "cake", + "title": "New title", + } + } + } +} + +snapshots["test_update_draft_with_draft 2"] = [ + { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T05:50:00+00:00", + "extra": None, + "id": 666, + "is_draft": True, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T05:50:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", + } +] + +snapshots["test_update_draft_with_draft__late_edit 2"] = [ + { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T06:10:00+00:00", + "extra": None, + "id": 666, + "is_draft": True, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T06:10:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", + } +] + +snapshots["test_update_draft_with_published 2"] = [ + { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T05:50:00+00:00", + "extra": None, + "id": 666, + "is_draft": False, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T05:50:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", + } +] + +snapshots["test_update_draft_with_published__late_edit 2"] = [ + { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T06:10:00+00:00", + "extra": None, + "id": 666, + "is_draft": False, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T06:10:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", + } +] + +snapshots["test_update_published_with_published 2"] = [ + { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T05:50:00+00:00", + "extra": None, + "id": 666, + "is_draft": False, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T00:00:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", + } +] + +snapshots["test_update_published_with_published__late_edit 1"] = { + "data": { + "updateReport": { + "report": { + "author": { + "extra": '{"movies": 1}', + "firstName": "Winston", + "id": "QXV0aG9yOjQy", + "lastName": "Wolfe", + "totalReports": 1, + }, + "body": "Rewrited", + "date": "2018-03-03 00:00:00+00:00", + "edited": "2018-01-02 06:10:00+00:00", + "extra": None, + "id": "UmVwb3J0OjY2Ng==", + "isDraft": False, + "otherParticipants": "grandchilds", + "ourParticipants": "kids", + "providedBenefit": "water", + "published": "2018-01-02 00:00:00+00:00", + "receivedBenefit": "cake", + "title": "New title", + } + } + } +} + +snapshots["test_update_published_with_published__late_edit 2"] = { + "author_id": 42, + "body": "Rewrited", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T06:10:00+00:00", + "extra": None, + "id": 666, + "is_draft": False, + "other_participants": "grandchilds", + "our_participants": "kids", + "provided_benefit": "water", + "published": "2018-01-02T00:00:00+00:00", + "received_benefit": "cake", + "superseded_by_id": None, + "title": "New title", +} + +snapshots["test_input_sanitization 2"] = [ + { + "author_id": 42, + "body": "some link in body", + "date": "2018-03-03T00:00:00+00:00", + "edited": "2018-01-02T05:50:00+00:00", + "extra": None, + "id": 666, + "is_draft": False, + "other_participants": "you!", + "our_participants": "me, myself", + "provided_benefit": "tea", + "published": "2018-01-02T00:00:00+00:00", + "received_benefit": "coffee", + "superseded_by_id": None, + "title": "No tags", + } +] + +snapshots["test_update_published_with_published__late_edit 3"] = { + "author_id": 42, + "body": "Previous body.", + "date": "2018-01-01T00:00:00+00:00", + "edited": "2018-01-02T05:00:00+00:00", + "extra": None, + "id": "__STRIPPED__", + "is_draft": False, + "other_participants": "grandma", + "our_participants": "grandpa", + "provided_benefit": "old tea", + "published": "2018-01-02T00:00:00+00:00", + "received_benefit": "old coffee", + "superseded_by_id": 666, + "title": "Original", +} diff --git a/tests/mutations/test_update_report.py b/tests/mutations/test_update_report.py index d68f23892f372b12ce7098bdebf30b53f32a781d..88a92e0ba548317e8d75ffd93654ef38d94c3ee0 100644 --- a/tests/mutations/test_update_report.py +++ b/tests/mutations/test_update_report.py @@ -3,10 +3,9 @@ import arrow from graphql_relay import to_global_id from unittest.mock import patch -from openlobby.core.models import User, Report +from openlobby.core.models import Report -from ..dummy import prepare_report -from ..utils import call_api +from ..utils import dates_to_iso, strip_value pytestmark = [pytest.mark.django_db, pytest.mark.usefixtures("django_es")] @@ -40,126 +39,162 @@ mutation updateReport ($input: UpdateReportInput!) { } """ -published = arrow.get(2018, 1, 2) -edited = arrow.get(2018, 3, 8, 12) +original_edited = arrow.get(2018, 1, 2, 5) +edited = original_edited.shift(minutes=50) +late_edited = original_edited.shift(minutes=70) -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" +@pytest.fixture +def original_report(author_fix, report_factory): + return report_factory( + id=666, + author=author_fix, + date=arrow.get(2018, 1, 1).datetime, + published=arrow.get(2018, 1, 2).datetime, + edited=original_edited.datetime, + title="Original", + body="Previous body.", + received_benefit="old coffee", + provided_benefit="old tea", + our_participants="grandpa", + other_participants="grandma", + ) -def get_input(is_draft=False, id=1): + +@pytest.fixture +def original_report_draft(original_report): + original_report.is_draft = True + original_report.save() + return original_report + + +def prepare_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(), + "title": "New title", + "body": "Rewrited", + "receivedBenefit": "cake", + "providedBenefit": "water", + "ourParticipants": "kids", + "otherParticipants": "grandchilds", + "date": arrow.get(2018, 3, 3).isoformat(), "isDraft": is_draft, } -def assert_report(is_draft, published, edited): - report = Report.objects.get(id=1) - assert report.author_id == 1 - assert report.date == date.datetime - assert report.published == published.datetime - assert report.edited == edited.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) +def test_unauthorized(call_api, snapshot, original_report): + input = prepare_input(id=original_report.id) + response = call_api(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") +def test_not_author(call_api, snapshot, original_report, user): + input = prepare_input(id=original_report.id) + response = call_api(query, input, user) 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") +def test_report_does_not_exist(call_api, snapshot, author_fix): + input = prepare_input(id=789) + response = call_api(query, input, author_fix) 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") +def test_update_published_with_draft(call_api, snapshot, original_report): + input = prepare_input(id=original_report.id, is_draft=True) + response = call_api(query, input, original_report.author) snapshot.assert_match(response) -def test_update_draft_with_draft(client, snapshot): - prepare_report(is_draft=True) - input = get_input(is_draft=True) +def test_update_draft_with_draft(call_api, snapshot, original_report_draft): + input = prepare_input(id=original_report_draft.id, is_draft=True) + with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=edited): - response = call_api(client, query, input, "wolf") + response = call_api(query, input, original_report_draft.author) + snapshot.assert_match(response) - assert_report(True, edited, edited) + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) + + +def test_update_draft_with_draft__late_edit(call_api, snapshot, original_report_draft): + input = prepare_input(id=original_report_draft.id, is_draft=True) + with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=late_edited): + response = call_api(query, input, original_report_draft.author) + + snapshot.assert_match(response) + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) + + +def test_update_draft_with_published(call_api, snapshot, original_report_draft): + input = prepare_input(id=original_report_draft.id) -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=edited): - response = call_api(client, query, input, "wolf") + response = call_api(query, input, original_report_draft.author) + snapshot.assert_match(response) - assert_report(False, edited, edited) + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) + +def test_update_draft_with_published__late_edit( + call_api, snapshot, original_report_draft +): + input = prepare_input(id=original_report_draft.id) + + with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=late_edited): + response = call_api(query, input, original_report_draft.author) + + snapshot.assert_match(response) + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) + + +def test_update_published_with_published(call_api, snapshot, original_report): + input = prepare_input(id=original_report.id) -def test_update_published_with_published(client, snapshot): - prepare_report() - input = get_input() with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=edited): - response = call_api(client, query, input, "wolf") + response = call_api(query, input, original_report.author) + snapshot.assert_match(response) - assert_report(False, published, edited) + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) -def test_input_sanitization(client, snapshot): - prepare_report() +def test_update_published_with_published__late_edit( + call_api, snapshot, original_report +): + input = prepare_input(id=original_report.id) + + with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=late_edited): + response = call_api(query, input, original_report.author) + + snapshot.assert_match(response) + assert Report.objects.count() == 2 + updated = Report.objects.filter(id=original_report.id).values()[0] + original = Report.objects.filter(superseded_by_id=original_report.id).values()[0] + strip_value(original, "id") + snapshot.assert_match(dates_to_iso(updated)) + snapshot.assert_match(dates_to_iso(original)) + + +def test_input_sanitization(call_api, snapshot, original_report): input = { - "id": to_global_id("Report", 1), + "id": to_global_id("Report", original_report.id), "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(), + "date": arrow.get(2018, 3, 3).isoformat(), } with patch("openlobby.core.api.mutations.arrow.utcnow", return_value=edited): - response = call_api(client, query, input, "wolf") + response = call_api(query, input, original_report.author) 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!" + reports = list(map(dates_to_iso, Report.objects.all().values())) + snapshot.assert_match(reports) diff --git a/tests/utils.py b/tests/utils.py index e836d9db592b058b87e65a4d0ed613d8f575bf70..f214c05cbc902bf4683f360634693dd3c2e0d3aa 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,4 @@ +from datetime import datetime import json from openlobby.core.auth import create_access_token @@ -26,3 +27,10 @@ def strip_value(data, *path): return value else: return strip_value(value, *path[1:]) + + +def dates_to_iso(data): + for key, val in data.items(): + if isinstance(val, datetime): + data[key] = val.isoformat() + return data