diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 1962433..a423d27 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -3,13 +3,13 @@ from uuid import UUID from auth_lib.fastapi import UnionAuth -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, HTTPException, Query from fastapi_sqlalchemy import db from rating_api.exceptions import ForbiddenAction, ObjectNotFound, TooManyCommentRequests from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus from rating_api.schemas.base import StatusResponseModel -from rating_api.schemas.models import CommentGet, CommentGetAll, CommentPost +from rating_api.schemas.models import CommentGet, CommentGetAll, CommentPost, CommentUpdate from rating_api.settings import Settings, get_settings @@ -133,7 +133,7 @@ async def get_comments( return result -@comment.patch("/{uuid}", response_model=CommentGet) +@comment.patch("/{uuid}/review", response_model=CommentGet) async def review_comment( uuid: UUID, review_status: Literal[ReviewStatus.APPROVED, ReviewStatus.DISMISSED] = ReviewStatus.DISMISSED, @@ -155,6 +155,35 @@ async def review_comment( return CommentGet.model_validate(Comment.update(session=db.session, id=uuid, review_status=review_status)) +@comment.patch("/{uuid}", response_model=CommentGet) +async def update_comment(uuid: UUID, comment_update: CommentUpdate, user=Depends(UnionAuth())) -> CommentGet: + """Позволяет изменить свой неанонимный комментарий""" + comment: Comment = Comment.get(session=db.session, id=uuid) # Ошибка, если не найден + + if comment.user_id != user.get("id") or comment.user_id is None: + raise ForbiddenAction(Comment) + + # Если не передано ни одного параметра + if not comment_update.model_dump(exclude_unset=True): + raise HTTPException(status_code=409, detail="Provide any parametr") # 409 + + # Если хоть одно поле было передано неизменным + if set(comment.__dict__.items()).intersection(set(comment_update.model_dump(exclude_unset=True).items())): + raise HTTPException(status_code=426, detail="No changes detected") # 426 + + # Проверить поле update_ts create comment на null + return CommentGet.model_validate( + Comment.update( + session=db.session, + id=uuid, + # Исключаем атрибуты, котрые не переданы + **comment_update.model_dump(exclude_unset=True), + update_ts=datetime.datetime.utcnow(), + review_status=ReviewStatus.PENDING, + ) + ) + + @comment.delete("/{uuid}", response_model=StatusResponseModel) async def delete_comment( uuid: UUID, _=Depends(UnionAuth(scopes=["rating.comment.delete"], allow_none=False, auto_error=True)) diff --git a/rating_api/schemas/models.py b/rating_api/schemas/models.py index 6cd1ac5..a7b38d9 100644 --- a/rating_api/schemas/models.py +++ b/rating_api/schemas/models.py @@ -39,6 +39,21 @@ def validate_mark(cls, value): return value +class CommentUpdate(Base): + subject: str = None + text: str = None + mark_kindness: int = None + mark_freebie: int = None + mark_clarity: int = None + + @field_validator('mark_kindness', 'mark_freebie', 'mark_clarity') + @classmethod + def validate_mark(cls, value): + if value not in [-2, -1, 0, 1, 2]: + raise WrongMark() + return value + + class CommentGetAll(Base): comments: list[CommentGet] = [] limit: int diff --git a/tests/conftest.py b/tests/conftest.py index f5d7f16..d02ef1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,6 +81,26 @@ def unreviewed_comment(dbsession, lecturer): dbsession.commit() +@pytest.fixture +def nonanonymous_comment(dbsession, lecturer): + _comment = Comment( + subject="subject", + text="comment", + mark_kindness=1, + mark_clarity=1, + mark_freebie=1, + lecturer_id=lecturer.id, + review_status=ReviewStatus.APPROVED, + user_id=0, + ) + dbsession.add(_comment) + dbsession.commit() + yield _comment + dbsession.refresh(_comment) + dbsession.delete(_comment) + dbsession.commit() + + @pytest.fixture(scope='function') def lecturers(dbsession): """ diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index d093d92..0a062fa 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -25,7 +25,6 @@ "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0, - "is_anonymous": False, }, 0, status.HTTP_200_OK, @@ -48,7 +47,6 @@ "mark_kindness": -2, "mark_freebie": -2, "mark_clarity": -2, - "is_anonymous": False, }, 1, status.HTTP_200_OK, @@ -60,7 +58,6 @@ "mark_kindness": 5, "mark_freebie": -2, "mark_clarity": 0, - "is_anonymous": False, }, 2, status.HTTP_400_BAD_REQUEST, @@ -72,7 +69,6 @@ "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, - "is_anonymous": False, }, 3, status.HTTP_404_NOT_FOUND, @@ -154,10 +150,10 @@ { "subject": "test_subject", "text": "test_text", + "create_ts": "wasd", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, - "is_anonymous": 'asd', }, 0, status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -257,13 +253,91 @@ def test_comments_by_user_id(client, lecturers_with_comments, user_id, response_ def test_review_comment(client, dbsession, unreviewed_comment, comment, review_status, response_status, is_reviewed): commment_to_reivew = comment if is_reviewed else unreviewed_comment query = {"review_status": review_status} - response = client.patch(f"{url}/{commment_to_reivew.uuid}", params=query) + response = client.patch(f"{url}/{commment_to_reivew.uuid}/review", params=query) assert response.status_code == response_status if response.status_code == status.HTTP_200_OK: dbsession.refresh(commment_to_reivew) assert commment_to_reivew.review_status == ReviewStatus(review_status) +@pytest.mark.parametrize( + 'body, response_status', + [ + ( + { + "subject": "test_subject", + "text": "test_text", + "mark_kindness": 0, + "mark_freebie": -2, + "mark_clarity": 0, + }, + status.HTTP_200_OK, + ), + ( + { + "subject": 0, + "text": "test_text", + "mark_kindness": 0, + "mark_freebie": -2, + "mark_clarity": 0, + }, + status.HTTP_422_UNPROCESSABLE_ENTITY, + ), + ( # Отсутсвует одно поле + { + "subject": "test_subject", + "mark_kindness": 0, + "mark_freebie": -2, + "mark_clarity": 0, + }, + status.HTTP_200_OK, + ), + ( + { + "subject": "test_subject", + "text": "test_text", + "mark_kindness": 5, + "mark_freebie": -2, + "mark_clarity": 0, + }, + status.HTTP_400_BAD_REQUEST, + ), + ( # Отсутсвует все поля + {}, + status.HTTP_409_CONFLICT, + ), + ( # Переданы НЕизмененные поля + { + "subject": "subject", + "text": "comment", + "mark_kindness": 1, + "mark_clarity": 1, + "mark_freebie": 1, + }, + status.HTTP_426_UPGRADE_REQUIRED, + ), + ( # НЕизмененным перелано одно поле + { + "subject": "asf", + "text": "asf", + "mark_kindness": 2, + "mark_clarity": 2, + "mark_freebie": 1, + }, + status.HTTP_426_UPGRADE_REQUIRED, + ), + ], +) +def test_update_comment(client, dbsession, nonanonymous_comment, body, response_status): + response = client.patch(f"{url}/{nonanonymous_comment.uuid}", json=body) + assert response.status_code == response_status + if response.status_code == status.HTTP_200_OK: + dbsession.refresh(nonanonymous_comment) + assert nonanonymous_comment.review_status == ReviewStatus.PENDING + for k, v in body.items(): + assert getattr(nonanonymous_comment, k, None) == v # Есть ли изменения в БД + + def test_delete_comment(client, dbsession, comment): response = client.delete(f'{url}/{comment.uuid}') assert response.status_code == status.HTTP_200_OK