diff --git a/API.md b/API.md index 504a65fd..638ff3e6 100644 --- a/API.md +++ b/API.md @@ -41,7 +41,7 @@ - Comments - ~~Listing comments~~ - [Creating comment](#creating-comment) - - ~~Updating comment~~ + - [Updating comment](#updating-comment) - ~~Getting comment~~ - ~~Deleting comment~~ - ~~Rating comment~~ @@ -698,7 +698,7 @@ data. - **Errors** - - post does not exist + - the post does not exist - comment text is empty - privileges are too low @@ -707,6 +707,39 @@ data. Creates a new comment under given post. +## Updating comment +- **Request** + + `PUT /comment/` + +- **Input** + + ```json5 + { + "text": // mandatory + } + ``` + +- **Output** + + ```json5 + { + "comment": + } + ``` + ...where `` is a [comment resource](#comment). + +- **Errors** + + - the comment does not exist + - new comment text is empty + - privileges are too low + +- **Description** + + Updates an existing comment text. + + ## Listing users - **Request** @@ -1225,7 +1258,7 @@ A comment under a post. ```json5 { - "id": , + "id": , "post": , "user": "text": , diff --git a/server/szurubooru/api/comment_api.py b/server/szurubooru/api/comment_api.py index 2dba81f9..23eb0b10 100644 --- a/server/szurubooru/api/comment_api.py +++ b/server/szurubooru/api/comment_api.py @@ -1,3 +1,4 @@ +import datetime from szurubooru.api.base_api import BaseApi from szurubooru.func import auth, comments, posts @@ -23,7 +24,23 @@ class CommentDetailApi(BaseApi): raise NotImplementedError() def put(self, ctx, comment_id): - raise NotImplementedError() + comment = comments.get_comment_by_id(comment_id) + if not comment: + raise comments.CommentNotFoundError( + 'Comment %r not found.' % comment_id) + + if ctx.user.user_id == comment.user_id: + infix = 'self' + else: + infix = 'any' + + comment.last_edit_time = datetime.datetime.now() + auth.verify_privilege(ctx.user, 'comments:edit:%s' % infix) + text = ctx.get_param_as_string('text', required=True) + comments.update_comment_text(comment, text) + + ctx.session.commit() + return {'comment': comments.serialize_comment(comment, ctx.user)} def delete(self, ctx, comment_id): raise NotImplementedError() diff --git a/server/szurubooru/func/comments.py b/server/szurubooru/func/comments.py index 2ee34397..873bc7dc 100644 --- a/server/szurubooru/func/comments.py +++ b/server/szurubooru/func/comments.py @@ -15,6 +15,12 @@ def serialize_comment(comment, authenticated_user): 'lastEditTime': comment.last_edit_time, } +def get_comment_by_id(comment_id): + return db.session \ + .query(db.Comment) \ + .filter(db.Comment.comment_id == comment_id) \ + .one_or_none() + def create_comment(user, post, text): comment = db.Comment() comment.user = user diff --git a/server/szurubooru/tests/api/test_comment_updating.py b/server/szurubooru/tests/api/test_comment_updating.py new file mode 100644 index 00000000..a0e0a4b5 --- /dev/null +++ b/server/szurubooru/tests/api/test_comment_updating.py @@ -0,0 +1,97 @@ +import datetime +import pytest +from szurubooru import api, db, errors +from szurubooru.func import util, comments + +@pytest.fixture +def test_ctx(config_injector, context_factory, user_factory, comment_factory): + config_injector({ + 'ranks': ['anonymous', 'regular_user', 'mod'], + 'rank_names': {'anonymous': 'Peasant', 'regular_user': 'Lord', 'mod': 'King'}, + 'privileges': { + 'comments:edit:self': 'regular_user', + 'comments:edit:any': 'mod', + }, + 'thumbnails': {'avatar_width': 200}, + }) + db.session.flush() + ret = util.dotdict() + ret.context_factory = context_factory + ret.user_factory = user_factory + ret.comment_factory = comment_factory + ret.api = api.CommentDetailApi() + return ret + +def test_simple_updating(test_ctx, fake_datetime): + user = test_ctx.user_factory(rank='regular_user') + comment = test_ctx.comment_factory(user=user) + db.session.add(comment) + db.session.commit() + with fake_datetime('1997-12-01'): + result = test_ctx.api.put( + test_ctx.context_factory(input={'text': 'new text'}, user=user), + comment.comment_id) + assert result['comment']['text'] == 'new text' + comment = db.session.query(db.Comment).one() + assert comment is not None + assert comment.text == 'new text' + assert comment.last_edit_time is not None + +@pytest.mark.parametrize('input,expected_exception', [ + ({'text': None}, comments.EmptyCommentTextError), + ({'text': ''}, comments.EmptyCommentTextError), + ({'text': []}, comments.EmptyCommentTextError), + ({'text': [None]}, errors.ValidationError), + ({'text': ['']}, comments.EmptyCommentTextError), +]) +def test_trying_to_pass_invalid_input(test_ctx, input, expected_exception): + user = test_ctx.user_factory() + comment = test_ctx.comment_factory(user=user) + db.session.add(comment) + db.session.commit() + with pytest.raises(expected_exception): + test_ctx.api.put( + test_ctx.context_factory(input=input, user=user), + comment.comment_id) + +def test_trying_to_omit_mandatory_field(test_ctx): + user = test_ctx.user_factory() + comment = test_ctx.comment_factory(user=user) + db.session.add(comment) + db.session.commit() + with pytest.raises(errors.ValidationError): + test_ctx.api.put( + test_ctx.context_factory(input={}, user=user), + comment.comment_id) + +def test_trying_to_update_non_existing(test_ctx): + with pytest.raises(comments.CommentNotFoundError): + test_ctx.api.put( + test_ctx.context_factory( + input={'text': 'new text'}, + user=test_ctx.user_factory(rank='regular_user')), + 5) + +def test_trying_to_update_someones_comment_without_privileges(test_ctx): + user = test_ctx.user_factory(rank='regular_user') + user2 = test_ctx.user_factory(rank='regular_user') + comment = test_ctx.comment_factory(user=user) + db.session.add(comment) + db.session.commit() + with pytest.raises(errors.AuthError): + test_ctx.api.put( + test_ctx.context_factory(input={'text': 'new text'}, user=user2), + comment.comment_id) + +def test_updating_someones_comment_with_privileges(test_ctx): + user = test_ctx.user_factory(rank='regular_user') + user2 = test_ctx.user_factory(rank='mod') + comment = test_ctx.comment_factory(user=user) + db.session.add(comment) + db.session.commit() + try: + test_ctx.api.put( + test_ctx.context_factory(input={'text': 'new text'}, user=user2), + comment.comment_id) + except: + pytest.fail()