diff --git a/requirements.dev.txt b/requirements.dev.txt index 336b8df..2051ef5 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -6,3 +6,4 @@ pytest pytest-cov pytest-mock testcontainers[postgres] +aioresponses diff --git a/tests/conftest.py b/tests/conftest.py index 029db70..378a275 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ import pytest from _pytest.monkeypatch import MonkeyPatch +from aioresponses import aioresponses from alembic import command from alembic.config import Config as AlembicConfig from fastapi.testclient import TestClient @@ -43,6 +44,13 @@ def session_mp(): mp.undo() +@pytest.fixture(autouse=True) +def aiohttp_mp(): + """Фикстура для перехвата любых aiohttp запросов aiohttp.ClientSession()""" + with aioresponses() as aiohttp_mock: + yield aiohttp_mock + + @pytest.fixture(scope='session') def get_settings_mock(session_mp): """Переопределение get_settings в rating_api/settings.py и перезагрузка base.app.""" @@ -92,16 +100,37 @@ def dbsession(db_container): @pytest.fixture -def client(mocker): - user_mock = mocker.patch('auth_lib.fastapi.UnionAuth.__call__') - user_mock.return_value = { +def authlib_user(): + """ + Данные о пользователе, возвращаемые сервисом auth. + """ + return { "session_scopes": [{"id": 0, "name": "string", "comment": "string"}], "user_scopes": [{"id": 0, "name": "string", "comment": "string"}], "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], "groups": [{"id": 0, "name": "string", "parent_id": 0}], "id": 0, "email": "string", + "userdata": [ + {"category": "Личная информация", "param": "Полное имя", "value": "Тестовый Тест"}, + ], } + + +@pytest.fixture() +def authlib_mock(mocker): + auth_mock = mocker.patch("auth_lib.fastapi.UnionAuth.__call__") + return auth_mock + + +@pytest.fixture() +def user_mock(authlib_mock, authlib_user): + authlib_mock.return_value = authlib_user + return authlib_mock + + +@pytest.fixture +def client(mocker, user_mock): client = TestClient(app) return client @@ -218,7 +247,6 @@ def lecturers(dbsession): dbsession.add(lecturer) dbsession.commit() yield lecturers - for lecturer in lecturers: for row in lecturer.comments: dbsession.delete(row) @@ -227,6 +255,7 @@ def lecturers(dbsession): ) for row in lecturer_user_comments: dbsession.delete(row) + dbsession.flush() dbsession.delete(lecturer) dbsession.commit() diff --git a/tests/test_routes/test_comment.py b/tests/test_routes/test_comment.py index 0ea2692..71ff3ae 100644 --- a/tests/test_routes/test_comment.py +++ b/tests/test_routes/test_comment.py @@ -15,9 +15,35 @@ @pytest.mark.parametrize( - 'body,lecturer_n,response_status', + 'body,lecturer_n,response_status,aiohttp_response_status,achievement_id', [ - ( + ( # тест логики выдачи ачивки за первый комментарий + { + "subject": "test_subject", + "text": "test text", + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + }, + 0, + status.HTTP_200_OK, + status.HTTP_200_OK, + 0, + ), + ( # тест логики блокирующей выдачу ачивки за первый комментарий, если она уже есть у юзера + { + "subject": "test_subject", + "text": "test text", + "mark_kindness": 1, + "mark_freebie": 0, + "mark_clarity": 0, + }, + 0, + status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, + ), + ( # тест логики выдачи ачивки в случае неудачного get-запроса к серверу { "subject": "test_subject", "text": "test text", @@ -27,6 +53,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_500_INTERNAL_SERVER_ERROR, + 0, ), ( { @@ -38,6 +66,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( { @@ -49,6 +79,8 @@ }, 1, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # bad mark { @@ -60,6 +92,8 @@ }, 2, status.HTTP_400_BAD_REQUEST, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # deleted lecturer { @@ -71,6 +105,8 @@ }, 3, status.HTTP_404_NOT_FOUND, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # Anonymous comment { @@ -83,6 +119,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # NotAnonymous comment { @@ -95,6 +133,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # Not provided anonymity { @@ -106,6 +146,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # Bad anonymity { @@ -118,6 +160,8 @@ }, 0, status.HTTP_422_UNPROCESSABLE_ENTITY, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # regex test { @@ -133,6 +177,8 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # forbidden symbols { @@ -147,6 +193,8 @@ }, 0, status.HTTP_400_BAD_REQUEST, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # long comment { @@ -159,6 +207,8 @@ }, 0, status.HTTP_400_BAD_REQUEST, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ( # long comment but not that long { @@ -171,17 +221,60 @@ }, 0, status.HTTP_200_OK, + status.HTTP_200_OK, + settings.FIRST_COMMENT_ACHIEVEMENT_ID, ), ], ) -def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response_status): +def test_create_comment( + client, + dbsession, + lecturers, + authlib_user, + aiohttp_mp, + body, + lecturer_n, + response_status, + aiohttp_response_status, + achievement_id, +): + check_get_response = aiohttp_mp.get( + settings.API_URL + f"achievement/user/{authlib_user.get('id'):}", + status=aiohttp_response_status, + payload={ + "user_id": 0, + "achievement": [ + { + "id": settings.FIRST_COMMENT_ACHIEVEMENT_ID, + } + ], + }, + ) + + check_post_response = aiohttp_mp.post( + settings.API_URL + + f"achievement/achievement/{settings.FIRST_COMMENT_ACHIEVEMENT_ID}/reciever/{authlib_user.get('id'):}", + status=200, + ) + params = {"lecturer_id": lecturers[lecturer_n].id} post_response = client.post(url, json=body, params=params) + assert post_response.status_code == response_status + if response_status == status.HTTP_200_OK: comment = Comment.query(session=dbsession).filter(Comment.uuid == post_response.json()["uuid"]).one_or_none() assert comment is not None + # проверка корректной записи user_id и fullname при анонимных и не анонимных комментариях + if "is_anonymous" in body: + if body.get("is_anonymous"): + assert comment.user_id is None + assert comment.user_fullname is None + else: + assert comment.user_id == authlib_user.get("id") + assert comment.user_fullname == authlib_user.get("userdata")[0]["value"] + if "create_ts" in body: assert comment.create_ts == datetime.datetime.fromisoformat(body["create_ts"]).replace(tzinfo=None) if "update_ts" in body: @@ -194,6 +287,113 @@ def test_create_comment(client, dbsession, lecturers, body, lecturer_n, response ) assert user_comment is not None + # Проверка логики выдачи ачивок + if check_get_response.called: + if aiohttp_response_status == status.HTTP_200_OK: + # Проверяем правильность заголовков get-запроса + headers = check_get_response.requests[0].kwargs["headers"] + assert headers["Accept"] == "application/json" + + if achievement_id != settings.FIRST_COMMENT_ACHIEVEMENT_ID: + assert check_post_response.called + # проверяем правильность заголовков post-запроса + headers = check_post_response.requests[0].kwargs["headers"] + assert headers["Accept"] == "application/json" + assert headers["Authorization"] == settings.ACHIEVEMENT_GIVE_TOKEN + + else: + assert not check_post_response.called + else: + assert not check_post_response.called + + +@pytest.mark.parametrize( + "body, total, response_status", + [ + ( + { + "comments": [ + { + "subject": "string", + "text": "string", + "mark_kindness": 0, + "mark_freebie": 0, + "mark_clarity": 0, + "lecturer_id": 1, + "create_ts": "2026-05-25T11:41:26.777Z", + "update_ts": "2026-05-25T11:41:26.777Z", + }, + { + "subject": "string", + "text": "string", + "mark_kindness": 0, + "mark_freebie": 0, + "mark_clarity": 0, + "lecturer_id": 2, + "create_ts": "2026-05-25T11:41:26.777Z", + "update_ts": "2026-05-25T11:41:26.777Z", + }, + ], + }, + 2, + status.HTTP_200_OK, + ), + ( + {"comments": []}, + 0, + status.HTTP_200_OK, + ), + ( + { + "comments": [ + { + "subject": "string", + "text": "string", + "mark_kindness": 0, + "mark_freebie": 0, + "mark_clarity": 0, + "lecturer_id": 4, + "create_ts": "2026-05-25T11:41:26.777Z", + "update_ts": "2026-05-25T11:41:26.777Z", + }, + ], + }, + 1, + status.HTTP_200_OK, + ), + ( + { + "comments": [ + { + "subdject": "string", + "text": "string", + "mark_kindness": 0, + "mark_freebie": 0, + "mark_clarity": 0, + "lecturer_id": "abc", + }, + ], + }, + None, + status.HTTP_422_UNPROCESSABLE_ENTITY, + ), + ], +) +def test_import_comments(client, dbsession, lecturers, body, total, response_status): + response = client.post(f"{url}/import", json=body) + + assert response.status_code == response_status + + new_comments = response.json() + print(new_comments) + + assert total == new_comments.get("total") + + if new_comments.get("total") and total > 0: + for comment in new_comments.get("comments"): + comment_from_db = Comment.query(session=dbsession).filter(Comment.uuid == comment.get("uuid")).one_or_none() + assert comment_from_db is not None + @pytest.mark.parametrize( "reaction_data, expected_reaction, comment_user_id",