Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

Feature/python testing homework #57

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
91b3d52
feta: add mimesis to poetry
Sep 16, 2023
63829d4
feta: add tests for placeholder module
Sep 16, 2023
a7bb432
feta: add protocols module
Sep 16, 2023
98b438d
feta: add fixtures for tests
Sep 16, 2023
1b84666
refactor: move files
Sep 16, 2023
2d45717
feat: add mock settings
Sep 16, 2023
0693c44
refactor: move protocols
Sep 16, 2023
3ae8d67
refactor: remove extra fixture
Sep 16, 2023
941e2f4
refactor: del typehint to resole import error
Sep 16, 2023
f8fcbc4
feat: add tests for user_create_new.py
Sep 16, 2023
873ed29
refactor: rename new_ids to response, because its UserResponse
Sep 16, 2023
577859c
style: change double quotes to single
Sep 16, 2023
2c77850
style: fix pipeline comments
Sep 16, 2023
f2cfe57
wtf
Sep 16, 2023
21330d0
feat: add test for url_path method
Sep 16, 2023
75f0952
feat: add init for tests
Sep 16, 2023
0f446e9
feat: add type annotation
Sep 16, 2023
188743d
feat: move protocols to plugins
Sep 16, 2023
89faa6e
feat: move protocols to plugins
Sep 16, 2023
e815cd0
feat: add annotations
Sep 16, 2023
e970b74
feat: change import path
Sep 16, 2023
7f513e9
feat: unuse plugin
Sep 16, 2023
b5f96f1
feat: move to plugins
Sep 16, 2023
0337705
feat: add test for _UserManager
Sep 16, 2023
889f180
refactor: change type path
Sep 16, 2023
b20eb6a
refactor: move types to plugins
Sep 16, 2023
3f7952d
refactor: add docstrings and fix pipeline issues
Sep 16, 2023
764029b
refactor: try to fix pipeline issues
Sep 16, 2023
49ecefb
style: use isort
Sep 16, 2023
747684d
feat: add isort lib
Sep 16, 2023
f8f5bb9
fix: try to fix pipelin
Sep 16, 2023
e07740b
fix: try to fix pipeline
Sep 16, 2023
257a5a9
fix: try to fix pipeline
Sep 16, 2023
ff273a3
fix: try to fix pipeline
Sep 16, 2023
7946c12
fix: try to fix pipeline
Sep 16, 2023
fa37aab
fix: try to fix pipeline
Sep 16, 2023
d66bc12
fix: try to fix pipeline
Sep 16, 2023
abe1699
fix: try to fix pipeline
Sep 16, 2023
8e7449b
fix: try to fix pipeline
Sep 16, 2023
c72efe1
refactor: remove extra files
Sep 22, 2023
94e3ca8
feat: registrate a new plugin
Sep 22, 2023
d94f101
refactor: make assertion simple
Sep 22, 2023
8d1e7fc
refactor: move user fixture to plugin
Sep 22, 2023
c2f0b81
feat: add httpretty to poetry
Sep 22, 2023
42b901a
feat: add new fixtures for tests
Sep 22, 2023
a140df8
feat: add assert not lead user
Sep 22, 2023
613da1e
feat: change deprecated method
Sep 22, 2023
f522e09
feat: add fixtures for picture tests
Sep 22, 2023
dfd0357
feat: add new plugins picture
Sep 23, 2023
6143d15
feat: add pictures data factory, picture types and fixtures for testing
Sep 23, 2023
4c32e52
feat: add tests for pictures
Sep 23, 2023
5606ac5
feat: add failed_pydantic_fields fixture and import test_success_vali…
Sep 23, 2023
f842aa0
feat: make dit hierarchy more flatten
Sep 23, 2023
6c02234
refactor: mar django test for whole module
Sep 23, 2023
6e8e430
feat: add tests for user views
Sep 23, 2023
4f243a0
feat: add auth client fixture
Sep 23, 2023
9958c85
feat: reg new plugin for mark slow tests
Sep 23, 2023
1c25f00
feat: add pytest options
Sep 23, 2023
941971d
feat: add slow test plugin
Sep 23, 2023
949c222
feat: mark slow tests
Sep 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ requests = "^2.28"
attrs = "^23.1"
pydantic = "^2.3"
punq = "^0.6"
mimesis = "^11.1.0"
isort = "^5.12.0"
Hyper-glitch marked this conversation as resolved.
Show resolved Hide resolved

[tool.poetry.group.dev.dependencies]
django-debug-toolbar = "^4.2"
Expand Down
6 changes: 3 additions & 3 deletions server/apps/identity/logic/usecases/user_create_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __call__(self, user: User) -> None:
https://sobolevn.me/2019/02/engineering-guide-to-user-stories
"""
new_ids = self._create_lead(user)
return self._update_user_ids(user, new_ids)
self._update_user_ids(user, new_ids)

def _create_lead(self, user: User) -> placeholder.UserResponse:
return placeholder.LeadCreate(
Expand All @@ -41,8 +41,8 @@ def _create_lead(self, user: User) -> placeholder.UserResponse:
def _update_user_ids(
self,
user: User,
new_ids: placeholder.UserResponse,
response: placeholder.UserResponse,
) -> None:
# This can be moved to some other place once this becomes too complex:
user.lead_id = new_ids.id
user.lead_id = response.id
user.save(update_fields=['lead_id'])
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

pytest_plugins = [
# Should be the first custom one:
'plugins.django_settings',
# 'plugins.django_settings',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не очень понятно чем он помешал, по идее, он нужен что бы получить в общий список фикстур, фикстуры из этого файла

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет!
спасибо за ревью

я складывал в плагины, но у меня возник проблемс с импортами, решение их приводило к другой проблеме, которую я не мог фиксануть по бырому, отключение данного плагина спасло мою ситуацию

если у тебя есть в этом экспертиза и свободное время, был бы рад это обсудить в телеге!)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не могу сказать что у меня есть экспертиза, но в целом, если скинешь подробное описание проблемы, по возможности, посмотрю и чем могу помогу :)

моя телега: как ник тут, только маленькими буквами)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

спасибо! я уже доковырялся))


# TODO: add your own plugins here!
]
41 changes: 41 additions & 0 deletions tests/plugins/identity/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import datetime as dt
from typing import Callable, Protocol, TypeAlias, TypedDict, Unpack, final


@final
class UserData(TypedDict, total=False):
"""
Represent the user data that is required to create a new user.

Does not include `password`, because it's special field in Django.
"""

email: str
first_name: str
last_name: str
date_of_birth: dt.datetime
address: str
job_title: str
phone: str
lead_id: int
is_staff: bool
is_active: bool


UserAssertion: TypeAlias = Callable[[str, UserData], None]


@final
class RegistrationData(UserData, total=False):
"""Represent the user data that is required to create a new user."""

password_first: str
password_second: str


@final
class RegistrationDataFactory(Protocol):
"""User data factory protocol."""

def __call__(self, **fields: Unpack[RegistrationData]) -> RegistrationData:
"""User data factory protocol."""
90 changes: 90 additions & 0 deletions tests/test_server/test_apps/test_identity/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from typing import Any, Unpack

import pytest
from mimesis import Field, Schema
from mimesis.locales import Locale

from server.apps.identity.models import User
from tests.plugins.identity.user import (
RegistrationData,
RegistrationDataFactory,
UserAssertion,
UserData,
)


@pytest.fixture()
def registration_data_factory() -> RegistrationDataFactory:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: фабрику лучше было бы положить в плагины, оттуда было бы удобнее переиспользовать в других файлах, так как они попадут в общий список фикстур

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да соглы, выше как раз проблему описал с этим, если подможешь буду признателен

"""Fxture that generate fake registration data."""
def factory(**fields: Unpack[RegistrationData]) -> RegistrationData:
field = Field(locale=Locale.RU, seed=fields.pop('seed'))
password = field('password')
schema = Schema(schema=lambda: {
'email': field('person.email'),
'first_name': field('person.first_name'),
'last_name': field('person.last_name'),
'date_of_birth': field('datetime.date'),
'address': field('address.city'),
'job_title': field('person.occupation'),
'phone': field('person.telephone'),
})
return {
**schema.create()[0],
**{'password_first': password, 'password_second': password},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

могу ошибаться, но django потребует поля с именами password1 и password2

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

у меня локально все мои тесты проходили, по идее значит ок)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Интересно, а проверка User.check_password() есть?

**fields,
}

return factory


@pytest.fixture(scope='session')
def assert_correct_user() -> UserAssertion:
"""Fixture that check created user attrs from database."""
def factory(email: str, expected: UserData) -> None:
user = User.objects.get(email=email)
assert user.id
assert user.is_active
assert not user.is_superuser
assert not user.is_staff
for field, value_name in expected.items():
assert getattr(user, field) == value_name

return factory


@pytest.fixture()
def reg_data(registration_data_factory) -> RegistrationData:
"""Fixture that return user reg data."""
return registration_data_factory(seed=1)


@pytest.fixture()
def expected_user_data(reg_data: RegistrationData) -> dict[str, Any]:
"""Fixture that return exeected user data."""
return {
key: value_name
for key, value_name in reg_data.items()
if not key.startswith('password')
}


@pytest.fixture()
def expected_serialized_user(reg_data: RegistrationData) -> dict[str, Any]:
"""Serialized user's key-values that expected in test."""
return {
'name': reg_data['first_name'],
'last_name': reg_data['last_name'],
'birthday': reg_data['date_of_birth'].strftime('%d.%m.%Y'),
'city_of_birth': reg_data['address'],
'position': reg_data['job_title'],
'email': reg_data['email'],
'phone': reg_data['phone'],
}


@pytest.fixture()
def user(
expected_user_data: RegistrationData,
) -> User:
"""Return created user in database."""
return User.objects.create(**expected_user_data)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# isort: skip_file
# flake8: noqa
from typing import Any

import pytest
from pydantic import ValidationError

from server.apps.identity.intrastructure.services.placeholder import (
LeadCreate,
LeadUpdate,
UserResponse,
_serialize_user,
)
from server.apps.identity.models import User
from server.common.django.types import Settings
from tests.plugins.identity.user import RegistrationData, UserAssertion
Hyper-glitch marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.django_db()
def test_success_lead_create(
user: User,
reg_data: RegistrationData,
expected_user_data: dict[str, Any],
assert_correct_user: UserAssertion,
settings: Settings,
) -> None:
"""Testing service that create lead user."""
assert_correct_user(reg_data['email'], expected_user_data)
actual_id = 11
actual_response = UserResponse(id=actual_id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

для UserResponse было бы хорошо так же реализовать фабрику и передавать как фикстуру, тем самым мы увеличим возможные вариации значений, удобство в переиспользовании и сокрытии подготовки данных внутри теста

Copy link
Author

@Hyper-glitch Hyper-glitch Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

спасибо!
как раз еще хотел подпилить дальше тесты)

expected_id = LeadCreate(
api_timeout=settings.PLACEHOLDER_API_TIMEOUT,
api_url=settings.PLACEHOLDER_API_URL,
)(user=user)
assert actual_response.id == expected_id


def test_success_url_path(settings: Settings) -> None:
"""Testing method that create URL path for the request."""
expected_url_path = 'https://jsonplaceholder.typicode.com/users'
fetcher = LeadCreate(
api_timeout=settings.PLACEHOLDER_API_TIMEOUT,
api_url=settings.PLACEHOLDER_API_URL,
)
actual_url_path = fetcher.url_path()
assert actual_url_path == expected_url_path


@pytest.mark.django_db()
def test_success_lead_update(
user: User,
reg_data: RegistrationData,
expected_user_data: dict[str, Any],
assert_correct_user: UserAssertion,
settings: Settings,
) -> None:
"""Testing service that update lead user."""
assert_correct_user(reg_data['email'], expected_user_data)
LeadUpdate(
api_timeout=settings.PLACEHOLDER_API_TIMEOUT,
api_url=settings.PLACEHOLDER_API_URL,
)(user=user)


def test_success_validate_user_response() -> None:
"""Testing UserResponse model, success case."""
expected_id = 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Может выносить 1 в переменную не имеет большого смысла. Будто бы строчка actual_id == 1, будет доносить эту же информацию

actual_id = UserResponse.model_validate({'id': 1}).id
assert actual_id == expected_id


def test_failed_validate_user_response() -> None:
"""Testing UserResponse model, failed case."""
with pytest.raises(ValidationError) as exc:
UserResponse.model_validate({'TEST': 1}).id # noqa: WPS428
assert 'ValidationError' in exc.typename # noqa: WPS441


@pytest.mark.django_db()
def test_success_serialize_user(
user: User,
expected_serialized_user: dict[str, Any],
) -> None:
"""Testing _serialize_user function."""
actial_serialization = _serialize_user(user=user)
assert actial_serialization == expected_serialized_user
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Any

import pytest

from server.apps.identity.intrastructure.services.placeholder import (
UserResponse,
)
from server.apps.identity.logic.usecases.user_create_new import UserCreateNew
from server.apps.identity.models import User
from server.common.django.types import Settings
from tests.plugins.identity.user import RegistrationData, UserAssertion


@pytest.mark.django_db()
def test_success_create_new_user(
user: User,
settings: Settings,
assert_correct_user: UserAssertion,
reg_data: RegistrationData,
expected_user_data: dict[str, Any],
) -> None:
"""Testing usecase that create new user."""
not_lead_user = User.objects.get(email=reg_data['email'])
assert not_lead_user.lead_id is None
assert_correct_user(reg_data['email'], expected_user_data)

UserCreateNew(settings=settings)(user=user)
lead_user = User.objects.get(email=reg_data['email'])
expected_lead_id = 11
assert lead_user.lead_id == expected_lead_id

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В этом тесте стадии (Arrange-Act-Assert) размазались



@pytest.mark.django_db()
def test_success_update_user_ids(
user: User,
settings: Settings,
assert_correct_user: UserAssertion,
reg_data: RegistrationData,
expected_user_data: dict[str, Any],
) -> None:
"""Testing usecase that update user id."""
assert_correct_user(reg_data['email'], expected_user_data)
response = UserResponse(id=100)
UserCreateNew(settings=settings)._update_user_ids( # noqa: WPS437

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно ли здесь избежать использования приватного метода? Если нет, лучше указать причину рядом с noqa комментом

user=user, response=response,
)
actual_user = User.objects.get(email=reg_data['email'])
assert actual_user.lead_id == response.id
Loading