diff --git a/rating_api/exceptions.py b/rating_api/exceptions.py index c0b57d4..8252729 100644 --- a/rating_api/exceptions.py +++ b/rating_api/exceptions.py @@ -63,3 +63,19 @@ def __init__(self): super().__init__( f"Ratings can only take values: -2, -1, 0, 1, 2", f"Оценки могут принимать только значения: -2, -1, 0, 1, 2" ) + + +class CommentTooLong(RatingAPIError): + def __init__(self, num_symbols: int): + super().__init__( + f"The comment is too long. Maximum of {num_symbols} is allowed", + f"Комментарий слишком длинный. Разрешено максимум {num_symbols}", + ) + + +class ForbiddenSymbol(RatingAPIError): + def __init__(self): + super().__init__( + f"The comment contains forbidden symbols. Letters of English and Russian languages, numbers and punctuation marks are allowed", + f"Комментарий содержит запрещенный символ. Разрешены буквы английского и русского языков, цифры и знаки препинания", + ) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index ee58be5..cfb2377 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -1,4 +1,5 @@ import datetime +import re from typing import Literal from uuid import UUID @@ -7,7 +8,14 @@ from fastapi import APIRouter, Depends, Query from fastapi_sqlalchemy import db -from rating_api.exceptions import ForbiddenAction, ObjectNotFound, TooManyCommentRequests, TooManyCommentsToLecturer +from rating_api.exceptions import ( + CommentTooLong, + ForbiddenAction, + ForbiddenSymbol, + ObjectNotFound, + TooManyCommentRequests, + TooManyCommentsToLecturer, +) from rating_api.models import Comment, Lecturer, LecturerUserComment, ReviewStatus from rating_api.schemas.base import StatusResponseModel from rating_api.schemas.models import CommentGet, CommentGetAll, CommentImportAll, CommentPost @@ -75,6 +83,12 @@ async def create_comment(lecturer_id: int, comment_info: CommentPost, user=Depen settings.COMMENT_LECTURER_FREQUENCE_IN_MONTH, settings.COMMENT_TO_LECTURER_LIMIT ) + if len(comment_info.text) > settings.MAX_COMMENT_LENGTH: + raise CommentTooLong(settings.MAX_COMMENT_LENGTH) + + if re.search(r"^[a-zA-Zа-яА-Я\d!?,_\-.\"\'\[\]{}`~<>^@#№$%;:&*()+=\\\/ \n]*$", comment_info.text) is None: + raise ForbiddenSymbol() + # Сначала добавляем с user_id, который мы получили при авторизации, # в LecturerUserComment, чтобы нельзя было слишком быстро добавлять комментарии create_ts = datetime.datetime(now.year, now.month, 1) diff --git a/rating_api/routes/exc_handlers.py b/rating_api/routes/exc_handlers.py index a0f0d4b..8c8e277 100644 --- a/rating_api/routes/exc_handlers.py +++ b/rating_api/routes/exc_handlers.py @@ -3,7 +3,9 @@ from rating_api.exceptions import ( AlreadyExists, + CommentTooLong, ForbiddenAction, + ForbiddenSymbol, ObjectNotFound, TooManyCommentRequests, TooManyCommentsToLecturer, @@ -54,3 +56,17 @@ async def wrong_mark_handler(req: starlette.requests.Request, exc: WrongMark): return JSONResponse( content=StatusResponseModel(status="Error", message=exc.eng, ru=exc.ru).model_dump(), status_code=400 ) + + +@app.exception_handler(CommentTooLong) +async def comment_too_long_handler(req: starlette.requests.Request, exc: CommentTooLong): + return JSONResponse( + content=StatusResponseModel(status="Error", message=exc.eng, ru=exc.ru).model_dump(), status_code=400 + ) + + +@app.exception_handler(ForbiddenSymbol) +async def forbidden_symbol_handler(req: starlette.requests.Request, exc: ForbiddenSymbol): + return JSONResponse( + content=StatusResponseModel(status="Error", message=exc.eng, ru=exc.ru).model_dump(), status_code=400 + ) diff --git a/rating_api/settings.py b/rating_api/settings.py index a67692b..91e47ad 100644 --- a/rating_api/settings.py +++ b/rating_api/settings.py @@ -25,6 +25,7 @@ class Settings(BaseSettings): CORS_ALLOW_CREDENTIALS: bool = True CORS_ALLOW_METHODS: list[str] = ['*'] CORS_ALLOW_HEADERS: list[str] = ['*'] + MAX_COMMENT_LENGTH: int = 3000 LOGGING_MARKETING_URL: str = LOGGING_MARKETING_URLS.get( os.getenv("APP_VERSION", "dev"), LOGGING_MARKETING_URLS["test"] ) diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index c891640..110187a 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -21,7 +21,7 @@ ( { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0, @@ -33,7 +33,7 @@ ( { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0, @@ -44,7 +44,7 @@ ( { "subject": "test1_subject", - "text": "test_text", + "text": "test text", "mark_kindness": -2, "mark_freebie": -2, "mark_clarity": -2, @@ -56,7 +56,7 @@ ( # bad mark { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 5, "mark_freebie": -2, "mark_clarity": 0, @@ -68,7 +68,7 @@ ( # deleted lecturer { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, @@ -80,7 +80,7 @@ ( # Anonymous comment { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, @@ -92,7 +92,7 @@ ( # NotAnonymous comment { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, @@ -104,7 +104,7 @@ ( # Bad anonymity { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, @@ -116,7 +116,7 @@ ( # Not provided anonymity { "subject": "test_subject", - "text": "test_text", + "text": "test text", "mark_kindness": 1, "mark_freebie": -2, "mark_clarity": 0, @@ -125,6 +125,59 @@ 0, status.HTTP_422_UNPROCESSABLE_ENTITY, ), + ( # regex test + { + "subject": "test_subject", + "text": """ABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyz.,!?- + абвгдежзийклмнопрстуфхцчшщъыьэюя1234567890 + \"\'[]{}`~<>^@#№$%;:&*()+=\\/""", + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + "is_anonymous": False, + }, + 0, + status.HTTP_200_OK, + ), + ( # forbidden symbols + { + "subject": "test_subject", + "text": """ABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyz.,!?- + абвгдежзийк☻☺☺лмнопрстуфхцчшщъыьэюя1234567890""", + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + "is_anonymous": False, + }, + 0, + status.HTTP_400_BAD_REQUEST, + ), + ( # long comment + { + "subject": "test_subject", + "text": 'a' * 3001, + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + "is_anonymous": False, + }, + 0, + status.HTTP_400_BAD_REQUEST, + ), + ( # long comment but not that long + { + "subject": "test_subject", + "text": 'a' * 3000, + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + "is_anonymous": False, + }, + 0, + status.HTTP_200_OK, + ), ], ) def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response_status):