Skip to content
This repository was archived by the owner on Mar 19, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
198 changes: 106 additions & 92 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ dennis = "^1.1"
dump-env = "^1.3"
ipython = "^8.15"
import-linter = "^1.11"
mimesis = "^11.1.0"

Choose a reason for hiding this comment

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

Минорный момент: лучше не продуктовые зависимости добавлять в dev группу.
Например, это можно сделать командой poetry add --group dev xxx или вручную переставить в файле.

Copy link
Author

Choose a reason for hiding this comment

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

оно вроде и есть в dev-зависимостях?

Choose a reason for hiding this comment

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

Точно, проглядел.
My bad :(


[tool.poetry.group.docs]
optional = true
Expand Down
4 changes: 1 addition & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"""

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

# TODO: add your own plugins here!
'plugins.identity.user',
]
10 changes: 10 additions & 0 deletions tests/plugins/django_settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from random import SystemRandom

import pytest
from django.conf import LazySettings
from django.core.cache import BaseCache, caches

MAX_RANDOM_INT = 999999


@pytest.fixture(autouse=True)
def _media_root(
Expand Down Expand Up @@ -36,6 +40,12 @@ def _debug(settings: LazySettings) -> None:
template['OPTIONS']['debug'] = True


@pytest.fixture(scope='session', autouse=True)
def faker_seed() -> int:
"""Create a random seed for the Faker library."""
return SystemRandom().randint(0, MAX_RANDOM_INT)


@pytest.fixture(autouse=True)
def cache(settings: LazySettings) -> BaseCache:
"""Modifies how cache is used in Django tests."""
Expand Down
Empty file.
87 changes: 87 additions & 0 deletions tests/plugins/identity/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Identity data related plugins."""
from datetime import datetime
from typing import Callable, Protocol, TypeAlias, TypedDict, Unpack, final

import pytest
from django.contrib.auth import get_user_model
from mimesis.locales import Locale
from mimesis.schema import Field, Schema


@final
class ProfileData(TypedDict, total=False):
"""Represent the simplified profile data."""

first_name: str
last_name: str
date_of_birth: datetime
address: str
job_title: str
phone: str


ProfileAssertion: TypeAlias = Callable[[str, ProfileData], None]


class ProfileDataFactory(Protocol):
"""Factory for representation of the simplified profile data."""

def __call__(self, **fields: Unpack[ProfileData]) -> ProfileData:
"""User data factory protocol."""


@pytest.fixture()
def profile_data_factory(
faker_seed: int,
) -> ProfileDataFactory:
"""Returns factory for fake random profile data."""

def factory(**fields: Unpack[ProfileData]) -> ProfileData:
mf = Field(locale=Locale.EN, seed=faker_seed)
schema = Schema(
schema=lambda: {
'first_name': mf('person.first_name'),
'last_name': mf('person.last_name'),
'date_of_birth': mf('datetime.date'),
'address': mf('address.city'),
'job_title': mf('person.occupation'),
'phone': mf('person.telephone'),
},
iterations=1,
)
return {
**schema.create()[0], # type: ignore[typeddict-item]
**fields,
}

return factory


@pytest.fixture(scope='session')
def assert_correct_profile() -> ProfileAssertion:
"""All profile fields are equal to reference."""

def factory(email: str, expected: ProfileData) -> None:
user = get_user_model().objects.get(email=email)
assert user.id
assert user.is_active
for field_name, data_value in expected.items():
assert getattr(user, field_name) == data_value
return factory


@pytest.fixture(scope='session')
def assert_incorrect_profile() -> ProfileAssertion:
"""At least one field does not match."""

def factory(email: str, expected: ProfileData) -> None:
user = get_user_model().objects.get(email=email)
assert user.id
assert user.is_active
matches = []
for field_name, data_value in expected.items():
matches.append(getattr(user, field_name) == data_value)

assert not all(matches)

return factory
61 changes: 61 additions & 0 deletions tests/test_server/test_urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from http import HTTPStatus

import pytest
from django.contrib.auth.models import User
from django.test import Client
from plugins.identity.user import ProfileAssertion, ProfileDataFactory


@pytest.mark.django_db()()
Expand Down Expand Up @@ -43,6 +45,65 @@ def test_admin_docs_authorized(admin_client: Client) -> None:
assert b'docutils' not in response.content


def test_picture_pages_unauthorized(client: Client) -> None:
"""This test ensures that picture management pages require auth."""
response = client.get('/pictures/dashboard')
assert response.status_code == HTTPStatus.FOUND

response = client.get('/pictures/favourites')
assert response.status_code == HTTPStatus.FOUND


@pytest.mark.django_db()
def test_picture_pages_authorized(
client: Client,
django_user_model: User,
) -> None:
"""Ensures picture management pages are accessible for authorized user."""
password, email = 'password', '[email protected]'
user = django_user_model.objects.create_user(
email,
password,
)
client.force_login(user)

response = client.get('/pictures/dashboard')
assert response.status_code == HTTPStatus.OK

response = client.get('/pictures/favourites')
Copy link

@alkurbatov alkurbatov Sep 17, 2023

Choose a reason for hiding this comment

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

Тут чтобы меньше повторяться, можно применить pytest.mark.parametrize, например:

@pytest.mark.parametrize('endpoint', [
    '/pictures/dashboard',
    '/pictures/favourites',
])
def test_picture_pages_authorized(
    client: Client,
    django_user_model: User,
    endpoint: str,
) -> None:
...

Copy link
Author

Choose a reason for hiding this comment

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

согласен, спасибо!

assert response.status_code == HTTPStatus.OK


@pytest.mark.django_db()
def test_profile_update_authorized(
client: Client,
django_user_model: User,
profile_data_factory: 'ProfileDataFactory',
assert_correct_profile: 'ProfileAssertion',
assert_incorrect_profile: 'ProfileAssertion',
) -> None:
"""This test ensures profile updating for an authorized user."""
user_data = profile_data_factory()

password, email = 'password', '[email protected]'
user = django_user_model.objects.create_user(
email,
password,
)
client.force_login(user)

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.

верно, спасибо!


# there might be a probability of accidental match, but disregard it for now
assert_incorrect_profile(email, user_data)

response = client.post(
'/identity/update',
data=user_data,
)
assert response.status_code == HTTPStatus.FOUND
assert response.get('Location') == '/identity/update'
assert_correct_profile(email, user_data)


@pytest.mark.parametrize('page', [
'/robots.txt',
'/humans.txt',
Expand Down