From 409624629f19f5b331ab0d20fc2214c8c4e17eec Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:04:10 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20rpc=20SetStatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/20250929000000_init_comments.sql | 4 + src/commentservice/constants.py | 3 + src/commentservice/grpc/comment_pb2.py | 24 +++--- src/commentservice/grpc/comment_pb2.pyi | 22 +++++- src/commentservice/grpc/comment_pb2_grpc.py | 47 +++++++----- src/commentservice/handler/delete_comment.py | 13 ---- src/commentservice/handler/handler.py | 12 ++- .../repository/delete_comment.py | 15 ---- src/commentservice/repository/repository.py | 8 +- src/commentservice/repository/set_status.py | 19 +++++ src/commentservice/service/delete_comment.py | 5 -- src/commentservice/service/service.py | 8 +- src/commentservice/service/set_status.py | 5 ++ tests/handler/test_delete_comment.py | 47 ------------ tests/handler/test_handler.py | 19 ++--- tests/handler/test_set_status.py | 76 +++++++++++++++++++ tests/repository/test_delete_comment.py | 68 ----------------- tests/repository/test_set_status.py | 52 +++++++++++++ ...t_delete_comment.py => test_set_status.py} | 15 ++-- 19 files changed, 247 insertions(+), 215 deletions(-) create mode 100644 src/commentservice/constants.py delete mode 100644 src/commentservice/handler/delete_comment.py delete mode 100644 src/commentservice/repository/delete_comment.py create mode 100644 src/commentservice/repository/set_status.py delete mode 100644 src/commentservice/service/delete_comment.py create mode 100644 src/commentservice/service/set_status.py delete mode 100644 tests/handler/test_delete_comment.py create mode 100644 tests/handler/test_set_status.py delete mode 100644 tests/repository/test_delete_comment.py create mode 100644 tests/repository/test_set_status.py rename tests/service/{test_delete_comment.py => test_set_status.py} (50%) diff --git a/migrations/20250929000000_init_comments.sql b/migrations/20250929000000_init_comments.sql index 66135d3..4623149 100644 --- a/migrations/20250929000000_init_comments.sql +++ b/migrations/20250929000000_init_comments.sql @@ -1,10 +1,13 @@ -- +goose Up -- +goose StatementBegin +CREATE TYPE comment_status AS ENUM ('DELETED', 'HIDDEN', 'ON_MODERATION'); + CREATE TABLE IF NOT EXISTS comments ( id BIGSERIAL PRIMARY KEY, mod_id BIGINT NOT NULL, author_id BIGINT NOT NULL, text TEXT NOT NULL, + status comment_status, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), edited_at TIMESTAMPTZ ); @@ -15,4 +18,5 @@ CREATE INDEX IF NOT EXISTS idx_comments_mod_id ON comments (mod_id); -- +goose Down -- +goose StatementBegin DROP TABLE IF EXISTS comments; +DROP TYPE IF EXISTS comment_status; -- +goose StatementEnd diff --git a/src/commentservice/constants.py b/src/commentservice/constants.py new file mode 100644 index 0000000..6222cac --- /dev/null +++ b/src/commentservice/constants.py @@ -0,0 +1,3 @@ +STATUS_DELETED = "DELETED" +STATUS_HIDDEN = "HIDDEN" +STATUS_ON_MODERATION = "ON_MODERATION" diff --git a/src/commentservice/grpc/comment_pb2.py b/src/commentservice/grpc/comment_pb2.py index 7a0be3d..69ca316 100644 --- a/src/commentservice/grpc/comment_pb2.py +++ b/src/commentservice/grpc/comment_pb2.py @@ -25,13 +25,15 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcomment.proto\x12\x07\x63omment\x1a\x1fgoogle/protobuf/timestamp.proto\"\x95\x01\n\x07\x43omment\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x11\n\tauthor_id\x18\x02 \x01(\x03\x12\x0c\n\x04text\x18\x03 \x01(\t\x12.\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12-\n\tedited_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"G\n\x14\x43reateCommentRequest\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\x12\x11\n\tauthor_id\x18\x02 \x01(\x03\x12\x0c\n\x04text\x18\x03 \x01(\t\"+\n\x15\x43reateCommentResponse\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\"$\n\x12GetCommentsRequest\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\"I\n\x13GetCommentsResponse\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\x12\"\n\x08\x63omments\x18\x02 \x03(\x0b\x32\x10.comment.Comment\"*\n\x14\x44\x65leteCommentRequest\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\"(\n\x15\x44\x65leteCommentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"6\n\x12\x45\x64itCommentRequest\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\x12\x0c\n\x04text\x18\x02 \x01(\t\"&\n\x13\x45\x64itCommentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x32\xc4\x02\n\x0e\x43ommentService\x12N\n\rCreateComment\x12\x1d.comment.CreateCommentRequest\x1a\x1e.comment.CreateCommentResponse\x12H\n\x0bGetComments\x12\x1b.comment.GetCommentsRequest\x1a\x1c.comment.GetCommentsResponse\x12N\n\rDeleteComment\x12\x1d.comment.DeleteCommentRequest\x1a\x1e.comment.DeleteCommentResponse\x12H\n\x0b\x45\x64itComment\x12\x1b.comment.EditCommentRequest\x1a\x1c.comment.EditCommentResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rcomment.proto\x12\x07\x63omment\x1a\x1fgoogle/protobuf/timestamp.proto\"\x95\x01\n\x07\x43omment\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x11\n\tauthor_id\x18\x02 \x01(\x03\x12\x0c\n\x04text\x18\x03 \x01(\t\x12.\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12-\n\tedited_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"G\n\x14\x43reateCommentRequest\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\x12\x11\n\tauthor_id\x18\x02 \x01(\x03\x12\x0c\n\x04text\x18\x03 \x01(\t\"+\n\x15\x43reateCommentResponse\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\"$\n\x12GetCommentsRequest\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\"I\n\x13GetCommentsResponse\x12\x0e\n\x06mod_id\x18\x01 \x01(\x03\x12\"\n\x08\x63omments\x18\x02 \x03(\x0b\x32\x10.comment.Comment\"N\n\x10SetStatusRequest\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\x12&\n\x06status\x18\x02 \x01(\x0e\x32\x16.comment.CommentStatus\"$\n\x11SetStatusResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"6\n\x12\x45\x64itCommentRequest\x12\x12\n\ncomment_id\x18\x01 \x01(\x03\x12\x0c\n\x04text\x18\x02 \x01(\t\"&\n\x13\x45\x64itCommentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08*\x88\x01\n\rCommentStatus\x12\x1e\n\x1a\x43OMMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x43OMMENT_STATUS_DELETED\x10\x01\x12\x19\n\x15\x43OMMENT_STATUS_HIDDEN\x10\x02\x12 \n\x1c\x43OMMENT_STATUS_ON_MODERATION\x10\x03\x32\xb8\x02\n\x0e\x43ommentService\x12N\n\rCreateComment\x12\x1d.comment.CreateCommentRequest\x1a\x1e.comment.CreateCommentResponse\x12H\n\x0bGetComments\x12\x1b.comment.GetCommentsRequest\x1a\x1c.comment.GetCommentsResponse\x12\x42\n\tSetStatus\x12\x19.comment.SetStatusRequest\x1a\x1a.comment.SetStatusResponse\x12H\n\x0b\x45\x64itComment\x12\x1b.comment.EditCommentRequest\x1a\x1c.comment.EditCommentResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'comment_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None + _globals['_COMMENTSTATUS']._serialized_start=657 + _globals['_COMMENTSTATUS']._serialized_end=793 _globals['_COMMENT']._serialized_start=60 _globals['_COMMENT']._serialized_end=209 _globals['_CREATECOMMENTREQUEST']._serialized_start=211 @@ -42,14 +44,14 @@ _globals['_GETCOMMENTSREQUEST']._serialized_end=365 _globals['_GETCOMMENTSRESPONSE']._serialized_start=367 _globals['_GETCOMMENTSRESPONSE']._serialized_end=440 - _globals['_DELETECOMMENTREQUEST']._serialized_start=442 - _globals['_DELETECOMMENTREQUEST']._serialized_end=484 - _globals['_DELETECOMMENTRESPONSE']._serialized_start=486 - _globals['_DELETECOMMENTRESPONSE']._serialized_end=526 - _globals['_EDITCOMMENTREQUEST']._serialized_start=528 - _globals['_EDITCOMMENTREQUEST']._serialized_end=582 - _globals['_EDITCOMMENTRESPONSE']._serialized_start=584 - _globals['_EDITCOMMENTRESPONSE']._serialized_end=622 - _globals['_COMMENTSERVICE']._serialized_start=625 - _globals['_COMMENTSERVICE']._serialized_end=949 + _globals['_SETSTATUSREQUEST']._serialized_start=442 + _globals['_SETSTATUSREQUEST']._serialized_end=520 + _globals['_SETSTATUSRESPONSE']._serialized_start=522 + _globals['_SETSTATUSRESPONSE']._serialized_end=558 + _globals['_EDITCOMMENTREQUEST']._serialized_start=560 + _globals['_EDITCOMMENTREQUEST']._serialized_end=614 + _globals['_EDITCOMMENTRESPONSE']._serialized_start=616 + _globals['_EDITCOMMENTRESPONSE']._serialized_end=654 + _globals['_COMMENTSERVICE']._serialized_start=796 + _globals['_COMMENTSERVICE']._serialized_end=1108 # @@protoc_insertion_point(module_scope) diff --git a/src/commentservice/grpc/comment_pb2.pyi b/src/commentservice/grpc/comment_pb2.pyi index 7b44b4f..930e8d1 100644 --- a/src/commentservice/grpc/comment_pb2.pyi +++ b/src/commentservice/grpc/comment_pb2.pyi @@ -2,6 +2,7 @@ import datetime from google.protobuf import timestamp_pb2 as _timestamp_pb2 from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from collections.abc import Iterable as _Iterable, Mapping as _Mapping @@ -9,6 +10,17 @@ from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor +class CommentStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + COMMENT_STATUS_UNSPECIFIED: _ClassVar[CommentStatus] + COMMENT_STATUS_DELETED: _ClassVar[CommentStatus] + COMMENT_STATUS_HIDDEN: _ClassVar[CommentStatus] + COMMENT_STATUS_ON_MODERATION: _ClassVar[CommentStatus] +COMMENT_STATUS_UNSPECIFIED: CommentStatus +COMMENT_STATUS_DELETED: CommentStatus +COMMENT_STATUS_HIDDEN: CommentStatus +COMMENT_STATUS_ON_MODERATION: CommentStatus + class Comment(_message.Message): __slots__ = ("id", "author_id", "text", "created_at", "edited_at") ID_FIELD_NUMBER: _ClassVar[int] @@ -53,13 +65,15 @@ class GetCommentsResponse(_message.Message): comments: _containers.RepeatedCompositeFieldContainer[Comment] def __init__(self, mod_id: _Optional[int] = ..., comments: _Optional[_Iterable[_Union[Comment, _Mapping]]] = ...) -> None: ... -class DeleteCommentRequest(_message.Message): - __slots__ = ("comment_id",) +class SetStatusRequest(_message.Message): + __slots__ = ("comment_id", "status") COMMENT_ID_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] comment_id: int - def __init__(self, comment_id: _Optional[int] = ...) -> None: ... + status: CommentStatus + def __init__(self, comment_id: _Optional[int] = ..., status: _Optional[_Union[CommentStatus, str]] = ...) -> None: ... -class DeleteCommentResponse(_message.Message): +class SetStatusResponse(_message.Message): __slots__ = ("success",) SUCCESS_FIELD_NUMBER: _ClassVar[int] success: bool diff --git a/src/commentservice/grpc/comment_pb2_grpc.py b/src/commentservice/grpc/comment_pb2_grpc.py index cba8422..1e74b2a 100644 --- a/src/commentservice/grpc/comment_pb2_grpc.py +++ b/src/commentservice/grpc/comment_pb2_grpc.py @@ -26,7 +26,8 @@ class CommentServiceStub(object): - """Missing associated documentation comment in .proto file.""" + """Сервис для работы с комментариями: создание, получение, изменение статуса, редактирование + """ def __init__(self, channel): """Constructor. @@ -44,10 +45,10 @@ def __init__(self, channel): request_serializer=comment__pb2.GetCommentsRequest.SerializeToString, response_deserializer=comment__pb2.GetCommentsResponse.FromString, _registered_method=True) - self.DeleteComment = channel.unary_unary( - '/comment.CommentService/DeleteComment', - request_serializer=comment__pb2.DeleteCommentRequest.SerializeToString, - response_deserializer=comment__pb2.DeleteCommentResponse.FromString, + self.SetStatus = channel.unary_unary( + '/comment.CommentService/SetStatus', + request_serializer=comment__pb2.SetStatusRequest.SerializeToString, + response_deserializer=comment__pb2.SetStatusResponse.FromString, _registered_method=True) self.EditComment = channel.unary_unary( '/comment.CommentService/EditComment', @@ -57,28 +58,33 @@ def __init__(self, channel): class CommentServiceServicer(object): - """Missing associated documentation comment in .proto file.""" + """Сервис для работы с комментариями: создание, получение, изменение статуса, редактирование + """ def CreateComment(self, request, context): - """Missing associated documentation comment in .proto file.""" + """Создание комментария + """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def GetComments(self, request, context): - """Missing associated documentation comment in .proto file.""" + """Получение комментариев + """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def DeleteComment(self, request, context): - """Missing associated documentation comment in .proto file.""" + def SetStatus(self, request, context): + """Изменение статуса комментария + """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') def EditComment(self, request, context): - """Missing associated documentation comment in .proto file.""" + """Редактирование комментария + """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') @@ -96,10 +102,10 @@ def add_CommentServiceServicer_to_server(servicer, server): request_deserializer=comment__pb2.GetCommentsRequest.FromString, response_serializer=comment__pb2.GetCommentsResponse.SerializeToString, ), - 'DeleteComment': grpc.unary_unary_rpc_method_handler( - servicer.DeleteComment, - request_deserializer=comment__pb2.DeleteCommentRequest.FromString, - response_serializer=comment__pb2.DeleteCommentResponse.SerializeToString, + 'SetStatus': grpc.unary_unary_rpc_method_handler( + servicer.SetStatus, + request_deserializer=comment__pb2.SetStatusRequest.FromString, + response_serializer=comment__pb2.SetStatusResponse.SerializeToString, ), 'EditComment': grpc.unary_unary_rpc_method_handler( servicer.EditComment, @@ -115,7 +121,8 @@ def add_CommentServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class CommentService(object): - """Missing associated documentation comment in .proto file.""" + """Сервис для работы с комментариями: создание, получение, изменение статуса, редактирование + """ @staticmethod def CreateComment(request, @@ -172,7 +179,7 @@ def GetComments(request, _registered_method=True) @staticmethod - def DeleteComment(request, + def SetStatus(request, target, options=(), channel_credentials=None, @@ -185,9 +192,9 @@ def DeleteComment(request, return grpc.experimental.unary_unary( request, target, - '/comment.CommentService/DeleteComment', - comment__pb2.DeleteCommentRequest.SerializeToString, - comment__pb2.DeleteCommentResponse.FromString, + '/comment.CommentService/SetStatus', + comment__pb2.SetStatusRequest.SerializeToString, + comment__pb2.SetStatusResponse.FromString, options, channel_credentials, insecure, diff --git a/src/commentservice/handler/delete_comment.py b/src/commentservice/handler/delete_comment.py deleted file mode 100644 index 3da9a4d..0000000 --- a/src/commentservice/handler/delete_comment.py +++ /dev/null @@ -1,13 +0,0 @@ -import grpc - -from commentservice.grpc import comment_pb2 -from commentservice.service.service import CommentService - - -async def DeleteComment( - service: CommentService, - request: comment_pb2.DeleteCommentRequest, - _: grpc.ServicerContext, -) -> comment_pb2.DeleteCommentResponse: - success = await service.delete_comment(request.comment_id) - return comment_pb2.DeleteCommentResponse(success=success) diff --git a/src/commentservice/handler/handler.py b/src/commentservice/handler/handler.py index 85b893c..b717bc9 100644 --- a/src/commentservice/handler/handler.py +++ b/src/commentservice/handler/handler.py @@ -4,9 +4,7 @@ from commentservice.handler.create_comment import ( CreateComment as _create_comment, ) -from commentservice.handler.delete_comment import ( - DeleteComment as _delete_comment, -) +from commentservice.handler.set_status import SetStatus as _set_status from commentservice.handler.edit_comment import EditComment as _edit_comment from commentservice.handler.get_comments import GetComments as _get_comments from commentservice.service.service import CommentService @@ -30,12 +28,12 @@ async def EditComment( ) -> comment_pb2.EditCommentResponse: return await _edit_comment(self._service, request, context) - async def DeleteComment( + async def SetStatus( self, - request: comment_pb2.DeleteCommentRequest, + request: comment_pb2.SetStatusRequest, context: grpc.ServicerContext, - ) -> comment_pb2.DeleteCommentResponse: - return await _delete_comment(self._service, request, context) + ) -> comment_pb2.SetStatusResponse: + return await _set_status(self._service, request, context) async def GetComments( self, diff --git a/src/commentservice/repository/delete_comment.py b/src/commentservice/repository/delete_comment.py deleted file mode 100644 index 19c65b4..0000000 --- a/src/commentservice/repository/delete_comment.py +++ /dev/null @@ -1,15 +0,0 @@ -from asyncpg import Pool - - -async def delete_comment(db_pool: Pool, comment_id: int) -> bool: - async with db_pool.acquire() as conn: - deleted_id = await conn.fetchval( - """ - DELETE FROM comments - WHERE id = $1 - RETURNING id - """, - comment_id, - ) - - return deleted_id is not None diff --git a/src/commentservice/repository/repository.py b/src/commentservice/repository/repository.py index ad4d54f..f4b9bd2 100644 --- a/src/commentservice/repository/repository.py +++ b/src/commentservice/repository/repository.py @@ -3,9 +3,7 @@ from commentservice.repository.create_comment import ( create_comment as _create_comment, ) -from commentservice.repository.delete_comment import ( - delete_comment as _delete_comment, -) +from commentservice.repository.set_status import set_status as _set_status from commentservice.repository.edit_comment import ( edit_comment as _edit_comment, ) @@ -30,8 +28,8 @@ async def create_comment( async def edit_comment(self, comment_id: int, new_text: str) -> bool: return await _edit_comment(self._db_pool, comment_id, new_text) - async def delete_comment(self, comment_id: int) -> bool: - return await _delete_comment(self._db_pool, comment_id) + async def set_status(self, comment_id: int, status: str) -> bool: + return await _set_status(self._db_pool, comment_id, status) async def get_comments(self, mod_id: int) -> list[Comment]: return await _get_comments(self._db_pool, mod_id) diff --git a/src/commentservice/repository/set_status.py b/src/commentservice/repository/set_status.py new file mode 100644 index 0000000..26374fa --- /dev/null +++ b/src/commentservice/repository/set_status.py @@ -0,0 +1,19 @@ +from asyncpg import Pool + + +async def set_status(db_pool: Pool, comment_id: int, status: str) -> bool: + try: + async with db_pool.acquire() as conn: + result = await conn.execute( + """ + UPDATE comments + SET status = $1 + WHERE id = $2 + """, + status, + comment_id, + ) + rows_affected = int(result.split()[-1]) if result else 0 + return rows_affected > 0 + except Exception: + return False diff --git a/src/commentservice/service/delete_comment.py b/src/commentservice/service/delete_comment.py deleted file mode 100644 index a993edd..0000000 --- a/src/commentservice/service/delete_comment.py +++ /dev/null @@ -1,5 +0,0 @@ -from commentservice.repository.repository import CommentRepository - - -async def delete_comment(repo: CommentRepository, comment_id: int) -> bool: - return await repo.delete_comment(comment_id) diff --git a/src/commentservice/service/service.py b/src/commentservice/service/service.py index 98cd9f9..e2ff7c6 100644 --- a/src/commentservice/service/service.py +++ b/src/commentservice/service/service.py @@ -3,9 +3,7 @@ from commentservice.service.create_comment import ( create_comment as _create_comment, ) -from commentservice.service.delete_comment import ( - delete_comment as _delete_comment, -) +from commentservice.service.set_status import set_status as _set_status from commentservice.service.edit_comment import edit_comment as _edit_comment from commentservice.service.get_comments import get_comments as _get_comments @@ -22,8 +20,8 @@ async def create_comment( async def edit_comment(self, comment_id: int, new_text: str) -> bool: return await _edit_comment(self._repo, comment_id, new_text) - async def delete_comment(self, comment_id: int) -> bool: - return await _delete_comment(self._repo, comment_id) + async def set_status(self, comment_id: int, status: str) -> bool: + return await _set_status(self._repo, comment_id, status) async def get_comments(self, mod_id: int) -> list[Comment]: return await _get_comments(self._repo, mod_id) diff --git a/src/commentservice/service/set_status.py b/src/commentservice/service/set_status.py new file mode 100644 index 0000000..e8265d0 --- /dev/null +++ b/src/commentservice/service/set_status.py @@ -0,0 +1,5 @@ +from commentservice.repository.repository import CommentRepository + + +async def set_status(repo: CommentRepository, comment_id: int, status: str) -> bool: + return await repo.set_status(comment_id, status) diff --git a/tests/handler/test_delete_comment.py b/tests/handler/test_delete_comment.py deleted file mode 100644 index ee87f94..0000000 --- a/tests/handler/test_delete_comment.py +++ /dev/null @@ -1,47 +0,0 @@ -from unittest.mock import AsyncMock - -import grpc -import pytest -from faker import Faker -from pytest_mock import MockerFixture - -from commentservice.grpc.comment_pb2 import ( - DeleteCommentRequest, - DeleteCommentResponse, -) -from commentservice.handler.delete_comment import DeleteComment -from commentservice.service.service import CommentService - - -@pytest.mark.asyncio -async def test_delete_comment_success( - mocker: MockerFixture, faker: Faker -) -> None: - ctx = mocker.Mock(spec=grpc.ServicerContext) - fake_service = mocker.Mock(spec=CommentService) - fake_service.delete_comment = AsyncMock(return_value=True) - - comment_id = faker.random_int(min=1, max=100000) - request = DeleteCommentRequest(comment_id=comment_id) - - response = await DeleteComment(fake_service, request, ctx) - - assert isinstance(response, DeleteCommentResponse) - assert response.success is True - fake_service.delete_comment.assert_awaited_once_with(comment_id) - - -@pytest.mark.asyncio -async def test_delete_comment_not_found( - mocker: MockerFixture, faker: Faker -) -> None: - ctx = mocker.Mock(spec=grpc.ServicerContext) - fake_service = mocker.Mock(spec=CommentService) - fake_service.delete_comment = AsyncMock(return_value=False) - - comment_id = faker.random_int(min=1, max=100000) - request = DeleteCommentRequest(comment_id=comment_id) - response = await DeleteComment(fake_service, request, ctx) - - assert response.success is False - fake_service.delete_comment.assert_awaited_once_with(comment_id) diff --git a/tests/handler/test_handler.py b/tests/handler/test_handler.py index 442df42..4fe185e 100644 --- a/tests/handler/test_handler.py +++ b/tests/handler/test_handler.py @@ -45,14 +45,15 @@ def _build_edit_pair( return request, response -def _build_delete_pair( +def _build_set_status_pair( faker: Faker, -) -> tuple[ - comment_pb2.DeleteCommentRequest, comment_pb2.DeleteCommentResponse -]: +) -> tuple[comment_pb2.SetStatusRequest, comment_pb2.SetStatusResponse]: comment_id = faker.random_int(min=1, max=100000) - response = comment_pb2.DeleteCommentResponse(success=True) - request = comment_pb2.DeleteCommentRequest(comment_id=comment_id) + response = comment_pb2.SetStatusResponse(success=True) + request = comment_pb2.SetStatusRequest( + comment_id=comment_id, + status=comment_pb2.CommentStatus.COMMENT_STATUS_DELETED, + ) return request, response @@ -89,9 +90,9 @@ class HandlerCase: _build_edit_pair, ), HandlerCase( - "DeleteComment", - "_delete_comment", - _build_delete_pair, + "SetStatus", + "_set_status", + _build_set_status_pair, ), HandlerCase( "GetComments", diff --git a/tests/handler/test_set_status.py b/tests/handler/test_set_status.py new file mode 100644 index 0000000..f024955 --- /dev/null +++ b/tests/handler/test_set_status.py @@ -0,0 +1,76 @@ +from unittest.mock import AsyncMock + +import grpc +import pytest +from faker import Faker +from pytest_mock import MockerFixture + +from commentservice.grpc import comment_pb2 +from commentservice.handler.set_status import SetStatus +from commentservice.service.service import CommentService + + +@pytest.mark.asyncio +async def test_set_status_success(mocker: MockerFixture, faker: Faker) -> None: + context = mocker.Mock(spec=grpc.ServicerContext) + service = mocker.Mock(spec=CommentService) + service.set_status = AsyncMock(return_value=True) + + comment_id = faker.random_int(min=1, max=100000) + request = comment_pb2.SetStatusRequest( + comment_id=comment_id, + status=comment_pb2.CommentStatus.COMMENT_STATUS_DELETED, + ) + + response = await SetStatus(service, request, context) + + assert isinstance(response, comment_pb2.SetStatusResponse) + assert response.success is True + service.set_status.assert_awaited_once_with(comment_id, "DELETED") + context.set_code.assert_not_called() + context.set_details.assert_not_called() + + +@pytest.mark.asyncio +async def test_set_status_invalid_enum_sets_error( + mocker: MockerFixture, faker: Faker +) -> None: + context = mocker.Mock(spec=grpc.ServicerContext) + service = mocker.Mock(spec=CommentService) + service.set_status = AsyncMock() + + request = comment_pb2.SetStatusRequest( + comment_id=faker.random_int(min=1, max=100000), + status=comment_pb2.CommentStatus.COMMENT_STATUS_UNSPECIFIED, + ) + + response = await SetStatus(service, request, context) + + assert response.success is False + service.set_status.assert_not_called() + context.set_code.assert_called_once_with(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details.assert_called_once_with("Status must be specified") + + +@pytest.mark.asyncio +async def test_set_status_internal_error_sets_context( + mocker: MockerFixture, faker: Faker +) -> None: + context = mocker.Mock(spec=grpc.ServicerContext) + service = mocker.Mock(spec=CommentService) + error = RuntimeError(faker.sentence()) + service.set_status = AsyncMock(side_effect=error) + + request = comment_pb2.SetStatusRequest( + comment_id=faker.random_int(min=1, max=100000), + status=comment_pb2.CommentStatus.COMMENT_STATUS_HIDDEN, + ) + + response = await SetStatus(service, request, context) + + assert response.success is False + context.set_code.assert_called_once_with(grpc.StatusCode.INTERNAL) + assert ( + context.set_details.call_args.args[0] + == f"Failed to set status: {error!s}" + ) diff --git a/tests/repository/test_delete_comment.py b/tests/repository/test_delete_comment.py deleted file mode 100644 index b5f48c0..0000000 --- a/tests/repository/test_delete_comment.py +++ /dev/null @@ -1,68 +0,0 @@ -import textwrap -from unittest.mock import AsyncMock - -import pytest -from faker import Faker -from pytest_mock import MockerFixture - -from commentservice.repository.repository import CommentRepository - - -@pytest.mark.asyncio -async def test_repo_delete_comment_success( - mocker: MockerFixture, faker: Faker -) -> None: - conn = mocker.Mock() - conn.fetchval = AsyncMock() - pool = mocker.Mock() - pool.acquire = mocker.Mock() - pool.acquire.return_value = AsyncMock() - pool.acquire.return_value.__aenter__.return_value = conn - pool.acquire.return_value.__aexit__.return_value = None - repo = CommentRepository(pool) - - cid = faker.random_int(min=1, max=100000) - conn.fetchval.return_value = cid - - ok = await repo.delete_comment(comment_id=cid) - assert ok is True - expected_sql = """ - DELETE FROM comments - WHERE id = $1 - RETURNING id - """ - actual_sql = conn.fetchval.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) - assert conn.fetchval.await_args.args[1:] == (cid,) - - -@pytest.mark.asyncio -async def test_repo_delete_comment_not_found( - mocker: MockerFixture, faker: Faker -) -> None: - conn = mocker.Mock() - conn.fetchval = AsyncMock(return_value=None) - pool = mocker.Mock() - pool.acquire = mocker.Mock() - pool.acquire.return_value = AsyncMock() - pool.acquire.return_value.__aenter__.return_value = conn - pool.acquire.return_value.__aexit__.return_value = None - repo = CommentRepository(pool) - - cid = faker.random_int(min=1, max=100000) - ok = await repo.delete_comment(comment_id=cid) - assert ok is False - expected_sql = """ - DELETE FROM comments - WHERE id = $1 - RETURNING id - """ - actual_sql = conn.fetchval.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) - assert conn.fetchval.await_args.args[1:] == (cid,) diff --git a/tests/repository/test_set_status.py b/tests/repository/test_set_status.py new file mode 100644 index 0000000..17774af --- /dev/null +++ b/tests/repository/test_set_status.py @@ -0,0 +1,52 @@ +import textwrap + +import pytest +from faker import Faker +from pytest_mock import MockerFixture + +from commentservice.repository.set_status import set_status + + +@pytest.mark.asyncio +async def test_set_status_returns_true_on_update( + mocker: MockerFixture, faker: Faker +) -> None: + conn = mocker.Mock() + conn.execute = mocker.AsyncMock(return_value="UPDATE 1") + acquire_cm = mocker.AsyncMock() + acquire_cm.__aenter__.return_value = conn + acquire_cm.__aexit__.return_value = None + pool = mocker.Mock() + pool.acquire.return_value = acquire_cm + + comment_id = faker.random_int(min=1, max=100000) + status = "DELETED" + + result = await set_status(pool, comment_id, status) + + assert result is True + expected_sql = """ + UPDATE comments + SET status = $1 + WHERE id = $2 + """ + actual_sql = conn.execute.await_args.args[0] + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) + assert conn.execute.await_args.args[1:] == (status, comment_id) + + +@pytest.mark.asyncio +async def test_set_status_returns_false_on_exception( + mocker: MockerFixture, faker: Faker +) -> None: + pool = mocker.Mock() + pool.acquire.side_effect = RuntimeError("boom") + + result = await set_status( + pool, faker.random_int(min=1, max=100000), "HIDDEN" + ) + + assert result is False diff --git a/tests/service/test_delete_comment.py b/tests/service/test_set_status.py similarity index 50% rename from tests/service/test_delete_comment.py rename to tests/service/test_set_status.py index 5db3a93..340f782 100644 --- a/tests/service/test_delete_comment.py +++ b/tests/service/test_set_status.py @@ -9,16 +9,19 @@ @pytest.mark.asyncio -async def test_service_delete_comment( +async def test_service_set_status_uses_helper( mocker: MockerFixture, faker: Faker ) -> None: - fake_repo = mocker.Mock(spec=CommentRepository) - fake_repo.delete_comment = AsyncMock(return_value=True) + repo = mocker.Mock(spec=CommentRepository) + helper = AsyncMock(return_value=True) + mocker.patch("commentservice.service.service._set_status", helper) + + service = CommentService(repo) comment_id = faker.random_int(min=1, max=100000) + status = "DELETED" - service = CommentService(fake_repo) - result = await service.delete_comment(comment_id=comment_id) + result = await service.set_status(comment_id, status) assert result is True - fake_repo.delete_comment.assert_awaited_once_with(comment_id) + helper.assert_awaited_once_with(repo, comment_id, status) From 126d16d84ced0085c917cd9ba2cf1b305b46abdb Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:04:31 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D1=8E=D1=89=D0=B5?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commentservice/handler/set_status.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/commentservice/handler/set_status.py diff --git a/src/commentservice/handler/set_status.py b/src/commentservice/handler/set_status.py new file mode 100644 index 0000000..99bdcfc --- /dev/null +++ b/src/commentservice/handler/set_status.py @@ -0,0 +1,32 @@ +import grpc + +from commentservice.constants import STATUS_DELETED, STATUS_HIDDEN, STATUS_ON_MODERATION +from commentservice.grpc import comment_pb2 +from commentservice.service.service import CommentService + +_ENUM_TO_DB_STATUS_BY_VALUE: dict[int, str] = { + comment_pb2.CommentStatus.COMMENT_STATUS_DELETED: STATUS_DELETED, + comment_pb2.CommentStatus.COMMENT_STATUS_HIDDEN: STATUS_HIDDEN, + comment_pb2.CommentStatus.COMMENT_STATUS_ON_MODERATION: STATUS_ON_MODERATION, +} + + +def _convert_enum_to_status(status_value: int) -> str: + if status_value == comment_pb2.CommentStatus.COMMENT_STATUS_UNSPECIFIED: + raise ValueError("Нужно указать статус") + return _ENUM_TO_DB_STATUS_BY_VALUE[status_value] + + +async def SetStatus( + service: CommentService, + request: comment_pb2.SetStatusRequest, + context: grpc.ServicerContext, # noqa: ARG001 +) -> comment_pb2.SetStatusResponse: + try: + status_str = _convert_enum_to_status(request.status) + success = await service.set_status(request.comment_id, status_str) + return comment_pb2.SetStatusResponse(success=success) + except Exception as e: + context.set_code(grpc.StatusCode.INTERNAL) + context.set_details(f"Ошибка при указании статуса {e!s}") + return comment_pb2.SetStatusResponse(success=False) From b58408b67d0a7361636af5510a1e7a09aa08c775 Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:32:27 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=9B=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 8 ++++---- src/commentservice/handler/create_comment.py | 4 +--- src/commentservice/handler/get_comments.py | 4 +--- src/commentservice/handler/handler.py | 6 ++---- src/commentservice/handler/set_status.py | 6 +++++- .../repository/create_comment.py | 4 +--- src/commentservice/repository/repository.py | 6 ++---- src/commentservice/server.py | 4 +--- src/commentservice/service/create_comment.py | 4 +--- src/commentservice/service/edit_comment.py | 4 +--- src/commentservice/service/service.py | 10 +++------- tests/handler/test_create_comment.py | 12 +++--------- tests/handler/test_edit_comment.py | 8 ++------ tests/handler/test_get_comments.py | 4 +--- tests/handler/test_handler.py | 12 +++--------- tests/handler/test_set_status.py | 13 +++---------- tests/repository/test_create_comment.py | 13 +++---------- tests/repository/test_edit_comment.py | 18 ++++-------------- tests/repository/test_get_comments.py | 9 ++------- tests/repository/test_set_status.py | 17 ++++------------- tests/service/test_create_comment.py | 8 ++------ tests/service/test_edit_comment.py | 8 ++------ tests/service/test_get_comments.py | 4 +--- tests/service/test_set_status.py | 4 +--- 24 files changed, 53 insertions(+), 137 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f367c1b..081b5fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ distribution = true [tool.black] -line-length = 79 +line-length = 120 target-version = ["py313"] skip-string-normalization = false exclude = ''' @@ -19,7 +19,7 @@ exclude = ''' [tool.isort] profile = "black" -line_length = 79 +line_length = 120 known_first_party = ["app"] multi_line_output = 3 include_trailing_comma = true @@ -29,7 +29,7 @@ ensure_newline_before_comments = true skip = [".venv", "src/commentservice/grpc"] [tool.flake8] -max-line-length = 79 +max-line-length = 120 extend-ignore = ["E203", "W503"] per-file-ignores = [ "__init__.py:F401", @@ -44,7 +44,7 @@ exclude = [ ] [tool.ruff] -line-length = 79 +line-length = 120 target-version = "py313" preview = true exclude = [".venv", "build", "dist", "src/commentservice/grpc"] diff --git a/src/commentservice/handler/create_comment.py b/src/commentservice/handler/create_comment.py index ccb671b..5e159c1 100644 --- a/src/commentservice/handler/create_comment.py +++ b/src/commentservice/handler/create_comment.py @@ -9,9 +9,7 @@ async def CreateComment( request: comment_pb2.CreateCommentRequest, _: grpc.ServicerContext, ) -> comment_pb2.CreateCommentResponse: - id = await service.create_comment( - request.mod_id, request.author_id, request.text - ) + id = await service.create_comment(request.mod_id, request.author_id, request.text) return comment_pb2.CreateCommentResponse( comment_id=id, ) diff --git a/src/commentservice/handler/get_comments.py b/src/commentservice/handler/get_comments.py index faa96c6..4c19400 100644 --- a/src/commentservice/handler/get_comments.py +++ b/src/commentservice/handler/get_comments.py @@ -26,9 +26,7 @@ async def GetComments( _: grpc.ServicerContext, ) -> comment_pb2.GetCommentsResponse: comments = await service.get_comments(mod_id=request.mod_id) - return comment_pb2.GetCommentsResponse( - mod_id=request.mod_id, comments=convertCommentsToProto(comments) - ) + return comment_pb2.GetCommentsResponse(mod_id=request.mod_id, comments=convertCommentsToProto(comments)) def convertCommentToProto(comment: Comment) -> comment_pb2.Comment: diff --git a/src/commentservice/handler/handler.py b/src/commentservice/handler/handler.py index b717bc9..ba3c5ad 100644 --- a/src/commentservice/handler/handler.py +++ b/src/commentservice/handler/handler.py @@ -1,12 +1,10 @@ import grpc from commentservice.grpc import comment_pb2, comment_pb2_grpc -from commentservice.handler.create_comment import ( - CreateComment as _create_comment, -) -from commentservice.handler.set_status import SetStatus as _set_status +from commentservice.handler.create_comment import CreateComment as _create_comment from commentservice.handler.edit_comment import EditComment as _edit_comment from commentservice.handler.get_comments import GetComments as _get_comments +from commentservice.handler.set_status import SetStatus as _set_status from commentservice.service.service import CommentService diff --git a/src/commentservice/handler/set_status.py b/src/commentservice/handler/set_status.py index 99bdcfc..d00a08a 100644 --- a/src/commentservice/handler/set_status.py +++ b/src/commentservice/handler/set_status.py @@ -1,6 +1,10 @@ import grpc -from commentservice.constants import STATUS_DELETED, STATUS_HIDDEN, STATUS_ON_MODERATION +from commentservice.constants import ( + STATUS_DELETED, + STATUS_HIDDEN, + STATUS_ON_MODERATION, +) from commentservice.grpc import comment_pb2 from commentservice.service.service import CommentService diff --git a/src/commentservice/repository/create_comment.py b/src/commentservice/repository/create_comment.py index d891206..45739a7 100644 --- a/src/commentservice/repository/create_comment.py +++ b/src/commentservice/repository/create_comment.py @@ -3,9 +3,7 @@ from asyncpg import Pool -async def create_comment( - db_pool: Pool, mod_id: int, author_id: int, text: str -) -> int: +async def create_comment(db_pool: Pool, mod_id: int, author_id: int, text: str) -> int: async with db_pool.acquire() as conn: comment_id = await conn.fetchval( """ diff --git a/src/commentservice/repository/repository.py b/src/commentservice/repository/repository.py index f4b9bd2..7514e1a 100644 --- a/src/commentservice/repository/repository.py +++ b/src/commentservice/repository/repository.py @@ -3,7 +3,6 @@ from commentservice.repository.create_comment import ( create_comment as _create_comment, ) -from commentservice.repository.set_status import set_status as _set_status from commentservice.repository.edit_comment import ( edit_comment as _edit_comment, ) @@ -11,6 +10,7 @@ get_comments as _get_comments, ) from commentservice.repository.model import Comment +from commentservice.repository.set_status import set_status as _set_status class CommentRepository: @@ -20,9 +20,7 @@ def __init__(self, db_pool: asyncpg.Pool): async def close(self) -> None: await self._db_pool.close() - async def create_comment( - self, mod_id: int, author_id: int, text: str - ) -> int: + async def create_comment(self, mod_id: int, author_id: int, text: str) -> int: return await _create_comment(self._db_pool, mod_id, author_id, text) async def edit_comment(self, comment_id: int, new_text: str) -> bool: diff --git a/src/commentservice/server.py b/src/commentservice/server.py index 26416a0..672b8b6 100644 --- a/src/commentservice/server.py +++ b/src/commentservice/server.py @@ -29,9 +29,7 @@ async def serve() -> None: handler = CommentHandler(service) server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=5)) - comment_pb2_grpc.add_CommentServiceServicer_to_server( - handler, server - ) # type: ignore[no-untyped-call] + comment_pb2_grpc.add_CommentServiceServicer_to_server(handler, server) # type: ignore[no-untyped-call] SERVICE_NAMES = ( comment_pb2.DESCRIPTOR.services_by_name["CommentService"].full_name, diff --git a/src/commentservice/service/create_comment.py b/src/commentservice/service/create_comment.py index e9ffbe7..9045fdb 100644 --- a/src/commentservice/service/create_comment.py +++ b/src/commentservice/service/create_comment.py @@ -1,7 +1,5 @@ from commentservice.repository.repository import CommentRepository -async def create_comment( - repo: CommentRepository, mod_id: int, author_id: int, text: str -) -> int: +async def create_comment(repo: CommentRepository, mod_id: int, author_id: int, text: str) -> int: return await repo.create_comment(mod_id, author_id, text) diff --git a/src/commentservice/service/edit_comment.py b/src/commentservice/service/edit_comment.py index 5367774..e2d1495 100644 --- a/src/commentservice/service/edit_comment.py +++ b/src/commentservice/service/edit_comment.py @@ -1,7 +1,5 @@ from commentservice.repository.repository import CommentRepository -async def edit_comment( - repo: CommentRepository, comment_id: int, text: str -) -> bool: +async def edit_comment(repo: CommentRepository, comment_id: int, text: str) -> bool: return await repo.edit_comment(comment_id, text) diff --git a/src/commentservice/service/service.py b/src/commentservice/service/service.py index e2ff7c6..fee60ee 100644 --- a/src/commentservice/service/service.py +++ b/src/commentservice/service/service.py @@ -1,20 +1,16 @@ from commentservice.repository.model import Comment from commentservice.repository.repository import CommentRepository -from commentservice.service.create_comment import ( - create_comment as _create_comment, -) -from commentservice.service.set_status import set_status as _set_status +from commentservice.service.create_comment import create_comment as _create_comment from commentservice.service.edit_comment import edit_comment as _edit_comment from commentservice.service.get_comments import get_comments as _get_comments +from commentservice.service.set_status import set_status as _set_status class CommentService: def __init__(self, repo: CommentRepository): self._repo = repo - async def create_comment( - self, mod_id: int, author_id: int, text: str - ) -> int: + async def create_comment(self, mod_id: int, author_id: int, text: str) -> int: return await _create_comment(self._repo, mod_id, author_id, text) async def edit_comment(self, comment_id: int, new_text: str) -> bool: diff --git a/tests/handler/test_create_comment.py b/tests/handler/test_create_comment.py index c0d901b..3420e86 100644 --- a/tests/handler/test_create_comment.py +++ b/tests/handler/test_create_comment.py @@ -14,9 +14,7 @@ @pytest.mark.asyncio -async def test_create_comment_success( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_create_comment_success(mocker: MockerFixture, faker: Faker) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) new_id = faker.random_int(min=1, max=100000) @@ -25,15 +23,11 @@ async def test_create_comment_success( mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - request = CreateCommentRequest( - mod_id=mod_id, author_id=author_id, text=text - ) + request = CreateCommentRequest(mod_id=mod_id, author_id=author_id, text=text) response = await CreateComment(fake_service, request, ctx) assert isinstance(response, CreateCommentResponse) assert response.comment_id == new_id - fake_service.create_comment.assert_awaited_once_with( - mod_id, author_id, text - ) + fake_service.create_comment.assert_awaited_once_with(mod_id, author_id, text) diff --git a/tests/handler/test_edit_comment.py b/tests/handler/test_edit_comment.py index 24345d8..1dc620f 100644 --- a/tests/handler/test_edit_comment.py +++ b/tests/handler/test_edit_comment.py @@ -14,9 +14,7 @@ @pytest.mark.asyncio -async def test_edit_comment_success( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_edit_comment_success(mocker: MockerFixture, faker: Faker) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) fake_service.edit_comment = AsyncMock(return_value=True) @@ -32,9 +30,7 @@ async def test_edit_comment_success( @pytest.mark.asyncio -async def test_edit_comment_not_found( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_edit_comment_not_found(mocker: MockerFixture, faker: Faker) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) fake_service.edit_comment = AsyncMock(return_value=False) diff --git a/tests/handler/test_get_comments.py b/tests/handler/test_get_comments.py index 4d4937b..f631ee8 100644 --- a/tests/handler/test_get_comments.py +++ b/tests/handler/test_get_comments.py @@ -15,9 +15,7 @@ @pytest.mark.asyncio -async def test_get_comments_success( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_get_comments_success(mocker: MockerFixture, faker: Faker) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) diff --git a/tests/handler/test_handler.py b/tests/handler/test_handler.py index 4fe185e..72009d1 100644 --- a/tests/handler/test_handler.py +++ b/tests/handler/test_handler.py @@ -16,15 +16,11 @@ def _build_create_pair( faker: Faker, -) -> tuple[ - comment_pb2.CreateCommentRequest, comment_pb2.CreateCommentResponse -]: +) -> tuple[comment_pb2.CreateCommentRequest, comment_pb2.CreateCommentResponse]: mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - response = comment_pb2.CreateCommentResponse( - comment_id=faker.random_int(min=1, max=100000) - ) + response = comment_pb2.CreateCommentResponse(comment_id=faker.random_int(min=1, max=100000)) request = comment_pb2.CreateCommentRequest( mod_id=mod_id, author_id=author_id, @@ -39,9 +35,7 @@ def _build_edit_pair( comment_id = faker.random_int(min=1, max=100000) new_text = faker.sentence() response = comment_pb2.EditCommentResponse(success=True) - request = comment_pb2.EditCommentRequest( - comment_id=comment_id, text=new_text - ) + request = comment_pb2.EditCommentRequest(comment_id=comment_id, text=new_text) return request, response diff --git a/tests/handler/test_set_status.py b/tests/handler/test_set_status.py index f024955..3e63d68 100644 --- a/tests/handler/test_set_status.py +++ b/tests/handler/test_set_status.py @@ -32,9 +32,7 @@ async def test_set_status_success(mocker: MockerFixture, faker: Faker) -> None: @pytest.mark.asyncio -async def test_set_status_invalid_enum_sets_error( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_set_status_invalid_enum_sets_error(mocker: MockerFixture, faker: Faker) -> None: context = mocker.Mock(spec=grpc.ServicerContext) service = mocker.Mock(spec=CommentService) service.set_status = AsyncMock() @@ -53,9 +51,7 @@ async def test_set_status_invalid_enum_sets_error( @pytest.mark.asyncio -async def test_set_status_internal_error_sets_context( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_set_status_internal_error_sets_context(mocker: MockerFixture, faker: Faker) -> None: context = mocker.Mock(spec=grpc.ServicerContext) service = mocker.Mock(spec=CommentService) error = RuntimeError(faker.sentence()) @@ -70,7 +66,4 @@ async def test_set_status_internal_error_sets_context( assert response.success is False context.set_code.assert_called_once_with(grpc.StatusCode.INTERNAL) - assert ( - context.set_details.call_args.args[0] - == f"Failed to set status: {error!s}" - ) + assert context.set_details.call_args.args[0] == f"Failed to set status: {error!s}" diff --git a/tests/repository/test_create_comment.py b/tests/repository/test_create_comment.py index 564d5c5..eee6549 100644 --- a/tests/repository/test_create_comment.py +++ b/tests/repository/test_create_comment.py @@ -9,9 +9,7 @@ @pytest.mark.asyncio -async def test_repo_create_comment_returns_id( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_repo_create_comment_returns_id(mocker: MockerFixture, faker: Faker) -> None: comment_id = faker.random_int(min=1, max=100000) conn = mocker.Mock() conn.fetchval = AsyncMock(return_value=comment_id) @@ -26,9 +24,7 @@ async def test_repo_create_comment_returns_id( mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - new_id = await repo.create_comment( - mod_id=mod_id, author_id=author_id, text=text - ) + new_id = await repo.create_comment(mod_id=mod_id, author_id=author_id, text=text) assert new_id == comment_id assert conn.fetchval.await_count == 1 @@ -38,8 +34,5 @@ async def test_repo_create_comment_returns_id( RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) + assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() assert conn.fetchval.await_args.args[1:] == (mod_id, author_id, text) diff --git a/tests/repository/test_edit_comment.py b/tests/repository/test_edit_comment.py index 198dea4..5528115 100644 --- a/tests/repository/test_edit_comment.py +++ b/tests/repository/test_edit_comment.py @@ -9,9 +9,7 @@ @pytest.mark.asyncio -async def test_repo_edit_comment_success( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_repo_edit_comment_success(mocker: MockerFixture, faker: Faker) -> None: conn = mocker.Mock() conn.fetchval = AsyncMock() pool = mocker.Mock() @@ -33,17 +31,12 @@ async def test_repo_edit_comment_success( RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) + assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() assert conn.fetchval.await_args.args[1:] == (new_text, cid) @pytest.mark.asyncio -async def test_repo_edit_comment_not_found( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_repo_edit_comment_not_found(mocker: MockerFixture, faker: Faker) -> None: conn = mocker.Mock() conn.fetchval = AsyncMock(return_value=None) pool = mocker.Mock() @@ -64,8 +57,5 @@ async def test_repo_edit_comment_not_found( RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) + assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() assert conn.fetchval.await_args.args[1:] == (new_text, cid) diff --git a/tests/repository/test_get_comments.py b/tests/repository/test_get_comments.py index 03981b5..53f2866 100644 --- a/tests/repository/test_get_comments.py +++ b/tests/repository/test_get_comments.py @@ -10,9 +10,7 @@ @pytest.mark.asyncio -async def test_repo_get_comments_maps_rows( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_repo_get_comments_maps_rows(mocker: MockerFixture, faker: Faker) -> None: rows = [ { "id": faker.random_int(), @@ -54,8 +52,5 @@ async def test_repo_get_comments_maps_rows( WHERE mod_id = $1 """ actual_sql = conn.fetch.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) + assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() assert conn.fetch.await_args.args[1:] == (mod_id,) diff --git a/tests/repository/test_set_status.py b/tests/repository/test_set_status.py index 17774af..9f81b05 100644 --- a/tests/repository/test_set_status.py +++ b/tests/repository/test_set_status.py @@ -8,9 +8,7 @@ @pytest.mark.asyncio -async def test_set_status_returns_true_on_update( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_set_status_returns_true_on_update(mocker: MockerFixture, faker: Faker) -> None: conn = mocker.Mock() conn.execute = mocker.AsyncMock(return_value="UPDATE 1") acquire_cm = mocker.AsyncMock() @@ -31,22 +29,15 @@ async def test_set_status_returns_true_on_update( WHERE id = $2 """ actual_sql = conn.execute.await_args.args[0] - assert ( - textwrap.dedent(actual_sql).strip() - == textwrap.dedent(expected_sql).strip() - ) + assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() assert conn.execute.await_args.args[1:] == (status, comment_id) @pytest.mark.asyncio -async def test_set_status_returns_false_on_exception( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_set_status_returns_false_on_exception(mocker: MockerFixture, faker: Faker) -> None: pool = mocker.Mock() pool.acquire.side_effect = RuntimeError("boom") - result = await set_status( - pool, faker.random_int(min=1, max=100000), "HIDDEN" - ) + result = await set_status(pool, faker.random_int(min=1, max=100000), "HIDDEN") assert result is False diff --git a/tests/service/test_create_comment.py b/tests/service/test_create_comment.py index 4f6e216..2c65c76 100644 --- a/tests/service/test_create_comment.py +++ b/tests/service/test_create_comment.py @@ -9,9 +9,7 @@ @pytest.mark.asyncio -async def test_service_create_comment( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_service_create_comment(mocker: MockerFixture, faker: Faker) -> None: fake_repo = mocker.Mock(spec=CommentRepository) new_id = faker.random_int(min=1, max=100000) fake_repo.create_comment = AsyncMock(return_value=new_id) @@ -21,9 +19,7 @@ async def test_service_create_comment( text = faker.sentence() service = CommentService(fake_repo) - result = await service.create_comment( - mod_id=mod_id, author_id=author_id, text=text - ) + result = await service.create_comment(mod_id=mod_id, author_id=author_id, text=text) assert result == new_id fake_repo.create_comment.assert_awaited_once_with(mod_id, author_id, text) diff --git a/tests/service/test_edit_comment.py b/tests/service/test_edit_comment.py index c9d750d..0fb03f5 100644 --- a/tests/service/test_edit_comment.py +++ b/tests/service/test_edit_comment.py @@ -9,9 +9,7 @@ @pytest.mark.asyncio -async def test_service_edit_comment( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_service_edit_comment(mocker: MockerFixture, faker: Faker) -> None: fake_repo = mocker.Mock(spec=CommentRepository) fake_repo.edit_comment = AsyncMock(return_value=True) @@ -19,9 +17,7 @@ async def test_service_edit_comment( new_text = faker.sentence() service = CommentService(fake_repo) - result = await service.edit_comment( - comment_id=comment_id, new_text=new_text - ) + result = await service.edit_comment(comment_id=comment_id, new_text=new_text) assert result is True fake_repo.edit_comment.assert_awaited_once_with(comment_id, new_text) diff --git a/tests/service/test_get_comments.py b/tests/service/test_get_comments.py index 6ad2343..d3e99af 100644 --- a/tests/service/test_get_comments.py +++ b/tests/service/test_get_comments.py @@ -11,9 +11,7 @@ @pytest.mark.asyncio -async def test_service_get_comments( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_service_get_comments(mocker: MockerFixture, faker: Faker) -> None: fake_repo = mocker.Mock(spec=CommentRepository) comments = [ Comment( diff --git a/tests/service/test_set_status.py b/tests/service/test_set_status.py index 340f782..f94a2cb 100644 --- a/tests/service/test_set_status.py +++ b/tests/service/test_set_status.py @@ -9,9 +9,7 @@ @pytest.mark.asyncio -async def test_service_set_status_uses_helper( - mocker: MockerFixture, faker: Faker -) -> None: +async def test_service_set_status_uses_helper(mocker: MockerFixture, faker: Faker) -> None: repo = mocker.Mock(spec=CommentRepository) helper = AsyncMock(return_value=True) mocker.patch("commentservice.service.service._set_status", helper) From 9c28db96dc247e3c1be6e496b2da8e2f9232c0a1 Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:36:14 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D0=9B=D0=B8=D0=BD=D1=82=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commentservice/repository/repository.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/commentservice/repository/repository.py b/src/commentservice/repository/repository.py index 7514e1a..558120b 100644 --- a/src/commentservice/repository/repository.py +++ b/src/commentservice/repository/repository.py @@ -1,14 +1,8 @@ import asyncpg -from commentservice.repository.create_comment import ( - create_comment as _create_comment, -) -from commentservice.repository.edit_comment import ( - edit_comment as _edit_comment, -) -from commentservice.repository.get_comments import ( - get_comments as _get_comments, -) +from commentservice.repository.create_comment import create_comment as _create_comment +from commentservice.repository.edit_comment import edit_comment as _edit_comment +from commentservice.repository.get_comments import get_comments as _get_comments from commentservice.repository.model import Comment from commentservice.repository.set_status import set_status as _set_status From 1d728acef9abb0b7494881d88a582d488a5a652b Mon Sep 17 00:00:00 2001 From: Tecquo <46904988+Tecquo@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:41:05 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=85=D0=B5=D0=BD=D0=B4=D0=BB=D0=B5=D1=80=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commentservice/handler/set_status.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/commentservice/handler/set_status.py b/src/commentservice/handler/set_status.py index d00a08a..aa0eea2 100644 --- a/src/commentservice/handler/set_status.py +++ b/src/commentservice/handler/set_status.py @@ -17,7 +17,7 @@ def _convert_enum_to_status(status_value: int) -> str: if status_value == comment_pb2.CommentStatus.COMMENT_STATUS_UNSPECIFIED: - raise ValueError("Нужно указать статус") + raise ValueError("Status must be specified") return _ENUM_TO_DB_STATUS_BY_VALUE[status_value] @@ -30,7 +30,11 @@ async def SetStatus( status_str = _convert_enum_to_status(request.status) success = await service.set_status(request.comment_id, status_str) return comment_pb2.SetStatusResponse(success=success) + except ValueError as e: + context.set_code(grpc.StatusCode.INVALID_ARGUMENT) + context.set_details(str(e)) + return comment_pb2.SetStatusResponse(success=False) except Exception as e: context.set_code(grpc.StatusCode.INTERNAL) - context.set_details(f"Ошибка при указании статуса {e!s}") + context.set_details(f"Failed to set status: {e!s}") return comment_pb2.SetStatusResponse(success=False) From 5939d73ba2d7a085cb94dff8825e3182d5d20e87 Mon Sep 17 00:00:00 2001 From: Andrey Kataev Date: Tue, 25 Nov 2025 15:23:45 +0300 Subject: [PATCH 6/6] Codestyle --- pyproject.toml | 8 ++++---- src/commentservice/handler/create_comment.py | 4 +++- src/commentservice/handler/get_comments.py | 4 +++- src/commentservice/handler/handler.py | 4 +++- src/commentservice/handler/set_status.py | 9 +++++---- .../repository/create_comment.py | 4 +++- src/commentservice/repository/repository.py | 16 ++++++++++++---- src/commentservice/server.py | 5 ++++- src/commentservice/service/create_comment.py | 4 +++- src/commentservice/service/edit_comment.py | 4 +++- src/commentservice/service/service.py | 8 ++++++-- src/commentservice/service/set_status.py | 4 +++- tests/handler/test_create_comment.py | 12 +++++++++--- tests/handler/test_edit_comment.py | 8 ++++++-- tests/handler/test_get_comments.py | 4 +++- tests/handler/test_handler.py | 12 +++++++++--- tests/handler/test_set_status.py | 13 ++++++++++--- tests/repository/test_create_comment.py | 13 ++++++++++--- tests/repository/test_edit_comment.py | 18 ++++++++++++++---- tests/repository/test_get_comments.py | 9 +++++++-- tests/repository/test_set_status.py | 17 +++++++++++++---- tests/service/test_create_comment.py | 8 ++++++-- tests/service/test_edit_comment.py | 8 ++++++-- tests/service/test_get_comments.py | 4 +++- tests/service/test_set_status.py | 4 +++- 25 files changed, 151 insertions(+), 53 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 081b5fa..f367c1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ distribution = true [tool.black] -line-length = 120 +line-length = 79 target-version = ["py313"] skip-string-normalization = false exclude = ''' @@ -19,7 +19,7 @@ exclude = ''' [tool.isort] profile = "black" -line_length = 120 +line_length = 79 known_first_party = ["app"] multi_line_output = 3 include_trailing_comma = true @@ -29,7 +29,7 @@ ensure_newline_before_comments = true skip = [".venv", "src/commentservice/grpc"] [tool.flake8] -max-line-length = 120 +max-line-length = 79 extend-ignore = ["E203", "W503"] per-file-ignores = [ "__init__.py:F401", @@ -44,7 +44,7 @@ exclude = [ ] [tool.ruff] -line-length = 120 +line-length = 79 target-version = "py313" preview = true exclude = [".venv", "build", "dist", "src/commentservice/grpc"] diff --git a/src/commentservice/handler/create_comment.py b/src/commentservice/handler/create_comment.py index 5e159c1..ccb671b 100644 --- a/src/commentservice/handler/create_comment.py +++ b/src/commentservice/handler/create_comment.py @@ -9,7 +9,9 @@ async def CreateComment( request: comment_pb2.CreateCommentRequest, _: grpc.ServicerContext, ) -> comment_pb2.CreateCommentResponse: - id = await service.create_comment(request.mod_id, request.author_id, request.text) + id = await service.create_comment( + request.mod_id, request.author_id, request.text + ) return comment_pb2.CreateCommentResponse( comment_id=id, ) diff --git a/src/commentservice/handler/get_comments.py b/src/commentservice/handler/get_comments.py index 4c19400..faa96c6 100644 --- a/src/commentservice/handler/get_comments.py +++ b/src/commentservice/handler/get_comments.py @@ -26,7 +26,9 @@ async def GetComments( _: grpc.ServicerContext, ) -> comment_pb2.GetCommentsResponse: comments = await service.get_comments(mod_id=request.mod_id) - return comment_pb2.GetCommentsResponse(mod_id=request.mod_id, comments=convertCommentsToProto(comments)) + return comment_pb2.GetCommentsResponse( + mod_id=request.mod_id, comments=convertCommentsToProto(comments) + ) def convertCommentToProto(comment: Comment) -> comment_pb2.Comment: diff --git a/src/commentservice/handler/handler.py b/src/commentservice/handler/handler.py index ba3c5ad..3a96b14 100644 --- a/src/commentservice/handler/handler.py +++ b/src/commentservice/handler/handler.py @@ -1,7 +1,9 @@ import grpc from commentservice.grpc import comment_pb2, comment_pb2_grpc -from commentservice.handler.create_comment import CreateComment as _create_comment +from commentservice.handler.create_comment import ( + CreateComment as _create_comment, +) from commentservice.handler.edit_comment import EditComment as _edit_comment from commentservice.handler.get_comments import GetComments as _get_comments from commentservice.handler.set_status import SetStatus as _set_status diff --git a/src/commentservice/handler/set_status.py b/src/commentservice/handler/set_status.py index aa0eea2..0d8fb62 100644 --- a/src/commentservice/handler/set_status.py +++ b/src/commentservice/handler/set_status.py @@ -6,17 +6,18 @@ STATUS_ON_MODERATION, ) from commentservice.grpc import comment_pb2 +from commentservice.grpc.comment_pb2 import CommentStatus from commentservice.service.service import CommentService _ENUM_TO_DB_STATUS_BY_VALUE: dict[int, str] = { - comment_pb2.CommentStatus.COMMENT_STATUS_DELETED: STATUS_DELETED, - comment_pb2.CommentStatus.COMMENT_STATUS_HIDDEN: STATUS_HIDDEN, - comment_pb2.CommentStatus.COMMENT_STATUS_ON_MODERATION: STATUS_ON_MODERATION, + CommentStatus.COMMENT_STATUS_DELETED: STATUS_DELETED, + CommentStatus.COMMENT_STATUS_HIDDEN: STATUS_HIDDEN, + CommentStatus.COMMENT_STATUS_ON_MODERATION: STATUS_ON_MODERATION, } def _convert_enum_to_status(status_value: int) -> str: - if status_value == comment_pb2.CommentStatus.COMMENT_STATUS_UNSPECIFIED: + if status_value == CommentStatus.COMMENT_STATUS_UNSPECIFIED: raise ValueError("Status must be specified") return _ENUM_TO_DB_STATUS_BY_VALUE[status_value] diff --git a/src/commentservice/repository/create_comment.py b/src/commentservice/repository/create_comment.py index 45739a7..d891206 100644 --- a/src/commentservice/repository/create_comment.py +++ b/src/commentservice/repository/create_comment.py @@ -3,7 +3,9 @@ from asyncpg import Pool -async def create_comment(db_pool: Pool, mod_id: int, author_id: int, text: str) -> int: +async def create_comment( + db_pool: Pool, mod_id: int, author_id: int, text: str +) -> int: async with db_pool.acquire() as conn: comment_id = await conn.fetchval( """ diff --git a/src/commentservice/repository/repository.py b/src/commentservice/repository/repository.py index 558120b..0655b76 100644 --- a/src/commentservice/repository/repository.py +++ b/src/commentservice/repository/repository.py @@ -1,8 +1,14 @@ import asyncpg -from commentservice.repository.create_comment import create_comment as _create_comment -from commentservice.repository.edit_comment import edit_comment as _edit_comment -from commentservice.repository.get_comments import get_comments as _get_comments +from commentservice.repository.create_comment import ( + create_comment as _create_comment, +) +from commentservice.repository.edit_comment import ( + edit_comment as _edit_comment, +) +from commentservice.repository.get_comments import ( + get_comments as _get_comments, +) from commentservice.repository.model import Comment from commentservice.repository.set_status import set_status as _set_status @@ -14,7 +20,9 @@ def __init__(self, db_pool: asyncpg.Pool): async def close(self) -> None: await self._db_pool.close() - async def create_comment(self, mod_id: int, author_id: int, text: str) -> int: + async def create_comment( + self, mod_id: int, author_id: int, text: str + ) -> int: return await _create_comment(self._db_pool, mod_id, author_id, text) async def edit_comment(self, comment_id: int, new_text: str) -> bool: diff --git a/src/commentservice/server.py b/src/commentservice/server.py index 672b8b6..7c5a936 100644 --- a/src/commentservice/server.py +++ b/src/commentservice/server.py @@ -29,7 +29,10 @@ async def serve() -> None: handler = CommentHandler(service) server = grpc.aio.server(futures.ThreadPoolExecutor(max_workers=5)) - comment_pb2_grpc.add_CommentServiceServicer_to_server(handler, server) # type: ignore[no-untyped-call] + comment_pb2_grpc.add_CommentServiceServicer_to_server( + handler, + server, + ) # type: ignore[no-untyped-call] SERVICE_NAMES = ( comment_pb2.DESCRIPTOR.services_by_name["CommentService"].full_name, diff --git a/src/commentservice/service/create_comment.py b/src/commentservice/service/create_comment.py index 9045fdb..e9ffbe7 100644 --- a/src/commentservice/service/create_comment.py +++ b/src/commentservice/service/create_comment.py @@ -1,5 +1,7 @@ from commentservice.repository.repository import CommentRepository -async def create_comment(repo: CommentRepository, mod_id: int, author_id: int, text: str) -> int: +async def create_comment( + repo: CommentRepository, mod_id: int, author_id: int, text: str +) -> int: return await repo.create_comment(mod_id, author_id, text) diff --git a/src/commentservice/service/edit_comment.py b/src/commentservice/service/edit_comment.py index e2d1495..5367774 100644 --- a/src/commentservice/service/edit_comment.py +++ b/src/commentservice/service/edit_comment.py @@ -1,5 +1,7 @@ from commentservice.repository.repository import CommentRepository -async def edit_comment(repo: CommentRepository, comment_id: int, text: str) -> bool: +async def edit_comment( + repo: CommentRepository, comment_id: int, text: str +) -> bool: return await repo.edit_comment(comment_id, text) diff --git a/src/commentservice/service/service.py b/src/commentservice/service/service.py index fee60ee..650ad9c 100644 --- a/src/commentservice/service/service.py +++ b/src/commentservice/service/service.py @@ -1,6 +1,8 @@ from commentservice.repository.model import Comment from commentservice.repository.repository import CommentRepository -from commentservice.service.create_comment import create_comment as _create_comment +from commentservice.service.create_comment import ( + create_comment as _create_comment, +) from commentservice.service.edit_comment import edit_comment as _edit_comment from commentservice.service.get_comments import get_comments as _get_comments from commentservice.service.set_status import set_status as _set_status @@ -10,7 +12,9 @@ class CommentService: def __init__(self, repo: CommentRepository): self._repo = repo - async def create_comment(self, mod_id: int, author_id: int, text: str) -> int: + async def create_comment( + self, mod_id: int, author_id: int, text: str + ) -> int: return await _create_comment(self._repo, mod_id, author_id, text) async def edit_comment(self, comment_id: int, new_text: str) -> bool: diff --git a/src/commentservice/service/set_status.py b/src/commentservice/service/set_status.py index e8265d0..2ac4d8a 100644 --- a/src/commentservice/service/set_status.py +++ b/src/commentservice/service/set_status.py @@ -1,5 +1,7 @@ from commentservice.repository.repository import CommentRepository -async def set_status(repo: CommentRepository, comment_id: int, status: str) -> bool: +async def set_status( + repo: CommentRepository, comment_id: int, status: str +) -> bool: return await repo.set_status(comment_id, status) diff --git a/tests/handler/test_create_comment.py b/tests/handler/test_create_comment.py index 3420e86..c0d901b 100644 --- a/tests/handler/test_create_comment.py +++ b/tests/handler/test_create_comment.py @@ -14,7 +14,9 @@ @pytest.mark.asyncio -async def test_create_comment_success(mocker: MockerFixture, faker: Faker) -> None: +async def test_create_comment_success( + mocker: MockerFixture, faker: Faker +) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) new_id = faker.random_int(min=1, max=100000) @@ -23,11 +25,15 @@ async def test_create_comment_success(mocker: MockerFixture, faker: Faker) -> No mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - request = CreateCommentRequest(mod_id=mod_id, author_id=author_id, text=text) + request = CreateCommentRequest( + mod_id=mod_id, author_id=author_id, text=text + ) response = await CreateComment(fake_service, request, ctx) assert isinstance(response, CreateCommentResponse) assert response.comment_id == new_id - fake_service.create_comment.assert_awaited_once_with(mod_id, author_id, text) + fake_service.create_comment.assert_awaited_once_with( + mod_id, author_id, text + ) diff --git a/tests/handler/test_edit_comment.py b/tests/handler/test_edit_comment.py index 1dc620f..24345d8 100644 --- a/tests/handler/test_edit_comment.py +++ b/tests/handler/test_edit_comment.py @@ -14,7 +14,9 @@ @pytest.mark.asyncio -async def test_edit_comment_success(mocker: MockerFixture, faker: Faker) -> None: +async def test_edit_comment_success( + mocker: MockerFixture, faker: Faker +) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) fake_service.edit_comment = AsyncMock(return_value=True) @@ -30,7 +32,9 @@ async def test_edit_comment_success(mocker: MockerFixture, faker: Faker) -> None @pytest.mark.asyncio -async def test_edit_comment_not_found(mocker: MockerFixture, faker: Faker) -> None: +async def test_edit_comment_not_found( + mocker: MockerFixture, faker: Faker +) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) fake_service.edit_comment = AsyncMock(return_value=False) diff --git a/tests/handler/test_get_comments.py b/tests/handler/test_get_comments.py index f631ee8..4d4937b 100644 --- a/tests/handler/test_get_comments.py +++ b/tests/handler/test_get_comments.py @@ -15,7 +15,9 @@ @pytest.mark.asyncio -async def test_get_comments_success(mocker: MockerFixture, faker: Faker) -> None: +async def test_get_comments_success( + mocker: MockerFixture, faker: Faker +) -> None: ctx = mocker.Mock(spec=grpc.ServicerContext) fake_service = mocker.Mock(spec=CommentService) diff --git a/tests/handler/test_handler.py b/tests/handler/test_handler.py index 72009d1..4fe185e 100644 --- a/tests/handler/test_handler.py +++ b/tests/handler/test_handler.py @@ -16,11 +16,15 @@ def _build_create_pair( faker: Faker, -) -> tuple[comment_pb2.CreateCommentRequest, comment_pb2.CreateCommentResponse]: +) -> tuple[ + comment_pb2.CreateCommentRequest, comment_pb2.CreateCommentResponse +]: mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - response = comment_pb2.CreateCommentResponse(comment_id=faker.random_int(min=1, max=100000)) + response = comment_pb2.CreateCommentResponse( + comment_id=faker.random_int(min=1, max=100000) + ) request = comment_pb2.CreateCommentRequest( mod_id=mod_id, author_id=author_id, @@ -35,7 +39,9 @@ def _build_edit_pair( comment_id = faker.random_int(min=1, max=100000) new_text = faker.sentence() response = comment_pb2.EditCommentResponse(success=True) - request = comment_pb2.EditCommentRequest(comment_id=comment_id, text=new_text) + request = comment_pb2.EditCommentRequest( + comment_id=comment_id, text=new_text + ) return request, response diff --git a/tests/handler/test_set_status.py b/tests/handler/test_set_status.py index 3e63d68..f024955 100644 --- a/tests/handler/test_set_status.py +++ b/tests/handler/test_set_status.py @@ -32,7 +32,9 @@ async def test_set_status_success(mocker: MockerFixture, faker: Faker) -> None: @pytest.mark.asyncio -async def test_set_status_invalid_enum_sets_error(mocker: MockerFixture, faker: Faker) -> None: +async def test_set_status_invalid_enum_sets_error( + mocker: MockerFixture, faker: Faker +) -> None: context = mocker.Mock(spec=grpc.ServicerContext) service = mocker.Mock(spec=CommentService) service.set_status = AsyncMock() @@ -51,7 +53,9 @@ async def test_set_status_invalid_enum_sets_error(mocker: MockerFixture, faker: @pytest.mark.asyncio -async def test_set_status_internal_error_sets_context(mocker: MockerFixture, faker: Faker) -> None: +async def test_set_status_internal_error_sets_context( + mocker: MockerFixture, faker: Faker +) -> None: context = mocker.Mock(spec=grpc.ServicerContext) service = mocker.Mock(spec=CommentService) error = RuntimeError(faker.sentence()) @@ -66,4 +70,7 @@ async def test_set_status_internal_error_sets_context(mocker: MockerFixture, fak assert response.success is False context.set_code.assert_called_once_with(grpc.StatusCode.INTERNAL) - assert context.set_details.call_args.args[0] == f"Failed to set status: {error!s}" + assert ( + context.set_details.call_args.args[0] + == f"Failed to set status: {error!s}" + ) diff --git a/tests/repository/test_create_comment.py b/tests/repository/test_create_comment.py index eee6549..564d5c5 100644 --- a/tests/repository/test_create_comment.py +++ b/tests/repository/test_create_comment.py @@ -9,7 +9,9 @@ @pytest.mark.asyncio -async def test_repo_create_comment_returns_id(mocker: MockerFixture, faker: Faker) -> None: +async def test_repo_create_comment_returns_id( + mocker: MockerFixture, faker: Faker +) -> None: comment_id = faker.random_int(min=1, max=100000) conn = mocker.Mock() conn.fetchval = AsyncMock(return_value=comment_id) @@ -24,7 +26,9 @@ async def test_repo_create_comment_returns_id(mocker: MockerFixture, faker: Fake mod_id = faker.random_int(min=1, max=100000) author_id = faker.random_int(min=1, max=100000) text = faker.sentence() - new_id = await repo.create_comment(mod_id=mod_id, author_id=author_id, text=text) + new_id = await repo.create_comment( + mod_id=mod_id, author_id=author_id, text=text + ) assert new_id == comment_id assert conn.fetchval.await_count == 1 @@ -34,5 +38,8 @@ async def test_repo_create_comment_returns_id(mocker: MockerFixture, faker: Fake RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) assert conn.fetchval.await_args.args[1:] == (mod_id, author_id, text) diff --git a/tests/repository/test_edit_comment.py b/tests/repository/test_edit_comment.py index 5528115..198dea4 100644 --- a/tests/repository/test_edit_comment.py +++ b/tests/repository/test_edit_comment.py @@ -9,7 +9,9 @@ @pytest.mark.asyncio -async def test_repo_edit_comment_success(mocker: MockerFixture, faker: Faker) -> None: +async def test_repo_edit_comment_success( + mocker: MockerFixture, faker: Faker +) -> None: conn = mocker.Mock() conn.fetchval = AsyncMock() pool = mocker.Mock() @@ -31,12 +33,17 @@ async def test_repo_edit_comment_success(mocker: MockerFixture, faker: Faker) -> RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) assert conn.fetchval.await_args.args[1:] == (new_text, cid) @pytest.mark.asyncio -async def test_repo_edit_comment_not_found(mocker: MockerFixture, faker: Faker) -> None: +async def test_repo_edit_comment_not_found( + mocker: MockerFixture, faker: Faker +) -> None: conn = mocker.Mock() conn.fetchval = AsyncMock(return_value=None) pool = mocker.Mock() @@ -57,5 +64,8 @@ async def test_repo_edit_comment_not_found(mocker: MockerFixture, faker: Faker) RETURNING id """ actual_sql = conn.fetchval.await_args.args[0] - assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) assert conn.fetchval.await_args.args[1:] == (new_text, cid) diff --git a/tests/repository/test_get_comments.py b/tests/repository/test_get_comments.py index 53f2866..03981b5 100644 --- a/tests/repository/test_get_comments.py +++ b/tests/repository/test_get_comments.py @@ -10,7 +10,9 @@ @pytest.mark.asyncio -async def test_repo_get_comments_maps_rows(mocker: MockerFixture, faker: Faker) -> None: +async def test_repo_get_comments_maps_rows( + mocker: MockerFixture, faker: Faker +) -> None: rows = [ { "id": faker.random_int(), @@ -52,5 +54,8 @@ async def test_repo_get_comments_maps_rows(mocker: MockerFixture, faker: Faker) WHERE mod_id = $1 """ actual_sql = conn.fetch.await_args.args[0] - assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) assert conn.fetch.await_args.args[1:] == (mod_id,) diff --git a/tests/repository/test_set_status.py b/tests/repository/test_set_status.py index 9f81b05..17774af 100644 --- a/tests/repository/test_set_status.py +++ b/tests/repository/test_set_status.py @@ -8,7 +8,9 @@ @pytest.mark.asyncio -async def test_set_status_returns_true_on_update(mocker: MockerFixture, faker: Faker) -> None: +async def test_set_status_returns_true_on_update( + mocker: MockerFixture, faker: Faker +) -> None: conn = mocker.Mock() conn.execute = mocker.AsyncMock(return_value="UPDATE 1") acquire_cm = mocker.AsyncMock() @@ -29,15 +31,22 @@ async def test_set_status_returns_true_on_update(mocker: MockerFixture, faker: F WHERE id = $2 """ actual_sql = conn.execute.await_args.args[0] - assert textwrap.dedent(actual_sql).strip() == textwrap.dedent(expected_sql).strip() + assert ( + textwrap.dedent(actual_sql).strip() + == textwrap.dedent(expected_sql).strip() + ) assert conn.execute.await_args.args[1:] == (status, comment_id) @pytest.mark.asyncio -async def test_set_status_returns_false_on_exception(mocker: MockerFixture, faker: Faker) -> None: +async def test_set_status_returns_false_on_exception( + mocker: MockerFixture, faker: Faker +) -> None: pool = mocker.Mock() pool.acquire.side_effect = RuntimeError("boom") - result = await set_status(pool, faker.random_int(min=1, max=100000), "HIDDEN") + result = await set_status( + pool, faker.random_int(min=1, max=100000), "HIDDEN" + ) assert result is False diff --git a/tests/service/test_create_comment.py b/tests/service/test_create_comment.py index 2c65c76..4f6e216 100644 --- a/tests/service/test_create_comment.py +++ b/tests/service/test_create_comment.py @@ -9,7 +9,9 @@ @pytest.mark.asyncio -async def test_service_create_comment(mocker: MockerFixture, faker: Faker) -> None: +async def test_service_create_comment( + mocker: MockerFixture, faker: Faker +) -> None: fake_repo = mocker.Mock(spec=CommentRepository) new_id = faker.random_int(min=1, max=100000) fake_repo.create_comment = AsyncMock(return_value=new_id) @@ -19,7 +21,9 @@ async def test_service_create_comment(mocker: MockerFixture, faker: Faker) -> No text = faker.sentence() service = CommentService(fake_repo) - result = await service.create_comment(mod_id=mod_id, author_id=author_id, text=text) + result = await service.create_comment( + mod_id=mod_id, author_id=author_id, text=text + ) assert result == new_id fake_repo.create_comment.assert_awaited_once_with(mod_id, author_id, text) diff --git a/tests/service/test_edit_comment.py b/tests/service/test_edit_comment.py index 0fb03f5..c9d750d 100644 --- a/tests/service/test_edit_comment.py +++ b/tests/service/test_edit_comment.py @@ -9,7 +9,9 @@ @pytest.mark.asyncio -async def test_service_edit_comment(mocker: MockerFixture, faker: Faker) -> None: +async def test_service_edit_comment( + mocker: MockerFixture, faker: Faker +) -> None: fake_repo = mocker.Mock(spec=CommentRepository) fake_repo.edit_comment = AsyncMock(return_value=True) @@ -17,7 +19,9 @@ async def test_service_edit_comment(mocker: MockerFixture, faker: Faker) -> None new_text = faker.sentence() service = CommentService(fake_repo) - result = await service.edit_comment(comment_id=comment_id, new_text=new_text) + result = await service.edit_comment( + comment_id=comment_id, new_text=new_text + ) assert result is True fake_repo.edit_comment.assert_awaited_once_with(comment_id, new_text) diff --git a/tests/service/test_get_comments.py b/tests/service/test_get_comments.py index d3e99af..6ad2343 100644 --- a/tests/service/test_get_comments.py +++ b/tests/service/test_get_comments.py @@ -11,7 +11,9 @@ @pytest.mark.asyncio -async def test_service_get_comments(mocker: MockerFixture, faker: Faker) -> None: +async def test_service_get_comments( + mocker: MockerFixture, faker: Faker +) -> None: fake_repo = mocker.Mock(spec=CommentRepository) comments = [ Comment( diff --git a/tests/service/test_set_status.py b/tests/service/test_set_status.py index f94a2cb..340f782 100644 --- a/tests/service/test_set_status.py +++ b/tests/service/test_set_status.py @@ -9,7 +9,9 @@ @pytest.mark.asyncio -async def test_service_set_status_uses_helper(mocker: MockerFixture, faker: Faker) -> None: +async def test_service_set_status_uses_helper( + mocker: MockerFixture, faker: Faker +) -> None: repo = mocker.Mock(spec=CommentRepository) helper = AsyncMock(return_value=True) mocker.patch("commentservice.service.service._set_status", helper)