feat: added e2e, unit tests and improved tests pipeline

This commit is contained in:
ITQ
2025-11-20 19:39:20 +03:00
parent 4ac902cf3b
commit f93c5a5966
7 changed files with 310 additions and 13 deletions
+36
View File
@@ -0,0 +1,36 @@
from datetime import UTC, datetime
from uuid import UUID, uuid4
import pytest
from template_project.application.common.entity import Entity, to_entity
from template_project.application.common.errors import EntityAlreadyDeletedError
@to_entity
class DummyEntity(Entity[UUID]):
name: str
def test_entity_allows_not_deleted_entities() -> None:
entity = DummyEntity(
id=uuid4(),
created_at=datetime.now(tz=UTC),
name="Alice",
)
entity.ensure_not_deleted()
def test_entity_raise_for_deleted_entities() -> None:
entity = DummyEntity(
id=uuid4(),
created_at=datetime.now(tz=UTC),
deleted_at=datetime.now(tz=UTC),
name="Bob",
)
with pytest.raises(EntityAlreadyDeletedError) as exc_info:
entity.ensure_not_deleted()
assert str(exc_info.value) == "Entity 'DummyEntity' already deleted"
+107
View File
@@ -0,0 +1,107 @@
from http import HTTPStatus as status
from uuid import uuid4
from dishka import FromDishka
from httpx import AsyncClient, Response
from tests.web_api.ioc import DatabaseClearer, inject
DEFAULT_PASSWORD = "Sup3rSecret" # noqa: S105
async def _sign_up_email(client: AsyncClient, email: str, password: str = DEFAULT_PASSWORD) -> None:
response = await client.post(
"/auth/sign_up/email",
json={"email": email, "password": password},
)
assert response.status_code == status.OK, response.text
async def _sign_in_email(client: AsyncClient, email: str, password: str = DEFAULT_PASSWORD) -> Response:
return await client.post(
"/auth/sign_in/email",
json={"email": email, "password": password},
)
def _unique_email() -> str:
return f"user-{uuid4().hex}@example.com"
@inject
async def test_email_sign_up_creates_user(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
response = await http_client.post(
"/auth/sign_up/email",
json={"email": email, "password": DEFAULT_PASSWORD},
)
body = response.json()
assert response.status_code == status.OK
assert isinstance(body["access_token"], str)
assert body["access_token"]
@inject
async def test_email_sign_up_existing_user_conflict(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
await _sign_up_email(http_client, email)
response = await http_client.post(
"/auth/sign_up/email",
json={"email": email, "password": DEFAULT_PASSWORD},
)
assert response.status_code == status.CONFLICT
@inject
async def test_email_sign_in_returns_token(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
await _sign_up_email(http_client, email)
response = await _sign_in_email(http_client, email)
body = response.json()
assert response.status_code == status.OK
assert isinstance(body["access_token"], str)
assert body["access_token"]
@inject
async def test_email_sign_in_invalid_password(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
await _sign_up_email(http_client, email)
response = await _sign_in_email(http_client, email, password="wrong-password")
assert response.status_code == status.UNAUTHORIZED
@inject
async def test_email_sign_in_user_not_found(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
response = await _sign_in_email(http_client, email=_unique_email())
assert response.status_code == status.NOT_FOUND
+3
View File
@@ -1,3 +1,5 @@
from http import HTTPStatus as status
from dishka import FromDishka
from httpx import AsyncClient
@@ -11,4 +13,5 @@ async def test_healthcheck(
response = await http_client.get("/healthcheck")
response_json = response.json()
assert response.status_code == status.OK
assert response_json["ok"]
+108
View File
@@ -0,0 +1,108 @@
from collections.abc import Mapping
from http import HTTPStatus as status
from uuid import uuid4
from dishka import FromDishka
from httpx import AsyncClient, Response
from tests.web_api.ioc import DatabaseClearer, inject
DEFAULT_PASSWORD = "Sup3rSecret" # noqa: S105
def _unique_email() -> str:
return f"user-{uuid4().hex}@example.com"
def _auth_headers(token: str) -> Mapping[str, str]:
return {"Authorization": f"Bearer {token}"}
async def _sign_up_and_get_token(client: AsyncClient, email: str) -> str:
response = await client.post(
"/auth/sign_up/email",
json={"email": email, "password": DEFAULT_PASSWORD},
)
assert response.status_code == status.OK, response.text
body = response.json()
return str(body["access_token"])
async def _get_profile(client: AsyncClient, token: str) -> Response:
return await client.get("/profile", headers=_auth_headers(token))
async def _patch_profile(
client: AsyncClient,
token: str,
payload: Mapping[str, str | None],
) -> Response:
return await client.patch("/profile", headers=_auth_headers(token), json=payload)
@inject
async def test_get_profile_returns_current_user(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
token = await _sign_up_and_get_token(http_client, email)
response = await _get_profile(http_client, token)
body = response.json()
assert response.status_code == status.OK
assert body["email"] == email
assert body["display_name"] is None
@inject
async def test_patch_profile_updates_profile_data(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
email = _unique_email()
token = await _sign_up_and_get_token(http_client, email)
payload = {
"display_name": "Alice",
"first_name": "Alice",
"last_name": "Smith",
"avatar_url": "https://example.com/avatar.png",
"phone": "+1234567890",
}
patch_response = await _patch_profile(http_client, token, payload)
patch_body = patch_response.json()
assert patch_response.status_code == status.OK
assert patch_body["display_name"] == payload["display_name"]
assert patch_body["first_name"] == payload["first_name"]
assert patch_body["last_name"] == payload["last_name"]
assert patch_body["avatar_url"] == payload["avatar_url"]
assert patch_body["phone"] == payload["phone"]
assert patch_body["email"] == email
get_response = await _get_profile(http_client, token)
get_body = get_response.json()
assert get_body["display_name"] == payload["display_name"]
assert get_body["first_name"] == payload["first_name"]
assert get_body["last_name"] == payload["last_name"]
assert get_body["avatar_url"] == payload["avatar_url"]
assert get_body["phone"] == payload["phone"]
@inject
async def test_profile_routes_require_authentication(
http_client: FromDishka[AsyncClient],
database_clearer: FromDishka[DatabaseClearer],
) -> None:
await database_clearer.clear()
response_get = await http_client.get("/profile")
response_patch = await http_client.patch("/profile", json={})
assert response_get.status_code == status.FORBIDDEN
assert response_patch.status_code == status.FORBIDDEN