From 36d688005c1c4326cce7c32bfb86cf2c5a03db3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B5=D0=B2=20=D0=9D?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sat, 8 Feb 2025 19:00:34 +0000 Subject: [PATCH 1/4] added comment check --- rating_api/exceptions.py | 16 ++++++++++++++++ rating_api/routes/comment.py | 16 +++++++++++++++- rating_api/routes/exc_handlers.py | 16 ++++++++++++++++ rating_api/settings.py | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/rating_api/exceptions.py b/rating_api/exceptions.py index c0b57d4..9ed418c 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 - are allowed", + f"Комментарий содержит запрещенный символ. Разрешены буквы английского и русского языков, цифры, . и -", + ) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index 1dfa07c..cb09329 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 @@ -6,7 +7,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 @@ -74,6 +82,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 1f7612c..a486017 100644 --- a/rating_api/settings.py +++ b/rating_api/settings.py @@ -18,6 +18,7 @@ class Settings(BaseSettings): CORS_ALLOW_CREDENTIALS: bool = True CORS_ALLOW_METHODS: list[str] = ['*'] CORS_ALLOW_HEADERS: list[str] = ['*'] + MAX_COMMENT_LENGTH: int = 3000 model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") From 9a28789876c2931aaa590b0c56bb1645b19e15c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B5=D0=B2=20=D0=9D?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 9 Feb 2025 02:05:58 +0000 Subject: [PATCH 2/4] add tests --- rating_api/routes/comment.py | 4 +- tests/test_routes/test_comment.py | 70 +++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/rating_api/routes/comment.py b/rating_api/routes/comment.py index cb09329..15d554a 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -82,10 +82,10 @@ 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: + 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: + if re.search(r"^[a-zA-Zа-яА-Я\d!?,_\-. \n]*$", comment_info.text) is None: raise ForbiddenSymbol() # Сначала добавляем с user_id, который мы получили при авторизации, diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index c891640..88a43f4 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,58 @@ 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): From 0eda7e39a9b0174ea23ab1e0350e3bd1b727a438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B5=D0=B2=20=D0=9D?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 9 Feb 2025 02:08:18 +0000 Subject: [PATCH 3/4] lint --- tests/test_routes/test_comment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 88a43f4..3ee362e 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -156,7 +156,7 @@ ( # long comment { "subject": "test_subject", - "text": 'a'*3001, + "text": 'a' * 3001, "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0, @@ -168,7 +168,7 @@ ( # long comment but not that long { "subject": "test_subject", - "text": 'a'*3000, + "text": 'a' * 3000, "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0, From a3448bb119be8e2887e787fc029acce0882e1ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B5=D0=B2=20=D0=9D?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 9 Feb 2025 15:14:26 +0000 Subject: [PATCH 4/4] added new symbols --- rating_api/exceptions.py | 4 ++-- rating_api/routes/comment.py | 2 +- tests/test_routes/test_comment.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rating_api/exceptions.py b/rating_api/exceptions.py index 9ed418c..8252729 100644 --- a/rating_api/exceptions.py +++ b/rating_api/exceptions.py @@ -76,6 +76,6 @@ def __init__(self, num_symbols: int): class ForbiddenSymbol(RatingAPIError): def __init__(self): super().__init__( - f"The comment contains forbidden symbols. Letters of English and Russian languages, numbers, . and - are allowed", - f"Комментарий содержит запрещенный символ. Разрешены буквы английского и русского языков, цифры, . и -", + 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 15d554a..8758d32 100644 --- a/rating_api/routes/comment.py +++ b/rating_api/routes/comment.py @@ -85,7 +85,7 @@ async def create_comment(lecturer_id: int, comment_info: CommentPost, user=Depen 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: + if re.search(r"^[a-zA-Zа-яА-Я\d!?,_\-.\"\'\[\]{}`~<>^@#№$%;:&*()+=\\\/ \n]*$", comment_info.text) is None: raise ForbiddenSymbol() # Сначала добавляем с user_id, который мы получили при авторизации, diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 3ee362e..110187a 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -130,7 +130,8 @@ "subject": "test_subject", "text": """ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz.,!?- - абвгдежзийклмнопрстуфхцчшщъыьэюя1234567890""", + абвгдежзийклмнопрстуфхцчшщъыьэюя1234567890 + \"\'[]{}`~<>^@#№$%;:&*()+=\\/""", "mark_kindness": 1, "mark_freebie": 0, "mark_clarity": 0,