feat(): unit tests for domain entities and basic invariants

This commit is contained in:
gitgernit
2025-11-23 09:34:09 +03:00
parent 25b35a3ccd
commit fbceb9fefe
9 changed files with 541 additions and 12 deletions
+93
View File
@@ -0,0 +1,93 @@
from datetime import UTC, datetime, timedelta
from uuid import UUID, uuid4
import pytest
from template_project.application.access_token.entity import AccessToken
from template_project.application.access_token.errors import AccessTokenExpiredError
from template_project.application.user.entity import UserId
def test_access_token_factory_creates_valid_token() -> None:
user_id = UserId(uuid4())
expires_in = timedelta(days=7)
token = AccessToken.factory(
user_id=user_id,
expires_in=expires_in,
)
assert isinstance(token.id, UUID)
assert token.user_id == user_id
assert token.revoked is False
assert token.expires_in > datetime.now(tz=UTC)
assert token.deleted_at is None
assert token.created_at.tzinfo == UTC
def test_access_token_expired_predicate_not_expired() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=7),
)
assert not token.expired_predicate()
def test_access_token_expired_predicate_expired_by_time() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=-1),
)
assert token.expired_predicate()
def test_access_token_expired_predicate_revoked() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=7),
)
token.revoke()
assert token.expired_predicate()
def test_access_token_ensure_expired_raises_when_expired() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=-1),
)
with pytest.raises(AccessTokenExpiredError):
token.ensure_expired()
def test_access_token_ensure_expired_raises_when_revoked() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=7),
)
token.revoke()
with pytest.raises(AccessTokenExpiredError):
token.ensure_expired()
def test_access_token_revoke() -> None:
user_id = UserId(uuid4())
token = AccessToken.factory(
user_id=user_id,
expires_in=timedelta(days=7),
)
token.revoke()
assert token.revoked is True
assert token.deleted_at is not None
assert token.deleted_at.tzinfo == UTC
+36
View File
@@ -0,0 +1,36 @@
from datetime import UTC
from uuid import UUID, uuid4
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
from template_project.application.user.entity import UserId
def test_auth_identity_factory_creates_valid_identity() -> None:
user_id = UserId(uuid4())
identity = AuthIdentity.factory(
user_id=user_id,
method=AuthMethod.EMAIL,
identifier="test@example.com",
secret_key="hashed_password",
)
assert isinstance(identity.id, UUID)
assert identity.user_id == user_id
assert identity.method == AuthMethod.EMAIL
assert identity.identifier == "test@example.com"
assert identity.secret_key == "hashed_password" # noqa: S105
assert identity.deleted_at is None
assert identity.created_at.tzinfo == UTC
def test_auth_identity_factory_without_secret_key() -> None:
user_id = UserId(uuid4())
identity = AuthIdentity.factory(
user_id=user_id,
method=AuthMethod.YANDEX,
identifier="yandex_id_123",
)
assert identity.secret_key is None
@@ -0,0 +1,20 @@
from datetime import UTC
from uuid import UUID, uuid4
from template_project.application.notification_device.entity import NotificationDevice
from template_project.application.user.entity import UserId
def test_notification_device_factory_creates_valid_device() -> None:
user_id = UserId(uuid4())
device = NotificationDevice.factory(
user_id=user_id,
device_id="device_123",
)
assert isinstance(device.id, UUID)
assert device.user_id == user_id
assert device.device_id == "device_123"
assert device.deleted_at is None
assert device.created_at.tzinfo == UTC
+44
View File
@@ -0,0 +1,44 @@
from datetime import UTC
from uuid import UUID, uuid4
from template_project.application.user.entity import UserId
from template_project.application.user.profile.entity import Profile
def test_profile_factory_creates_valid_profile() -> None:
user_id = UserId(uuid4())
profile = Profile.factory(
user_id=user_id,
email="test@example.com",
display_name="Test User",
first_name="Test",
last_name="User",
avatar_url="https://example.com/avatar.jpg",
phone="+1234567890",
)
assert isinstance(profile.id, UUID)
assert profile.user_id == user_id
assert profile.email == "test@example.com"
assert profile.display_name == "Test User"
assert profile.first_name == "Test"
assert profile.last_name == "User"
assert profile.avatar_url == "https://example.com/avatar.jpg"
assert profile.phone == "+1234567890"
assert profile.deleted_at is None
assert profile.created_at.tzinfo == UTC
def test_profile_factory_with_minimal_fields() -> None:
user_id = UserId(uuid4())
profile = Profile.factory(user_id=user_id)
assert profile.user_id == user_id
assert profile.email is None
assert profile.display_name is None
assert profile.first_name is None
assert profile.last_name is None
assert profile.avatar_url is None
assert profile.phone is None
+229
View File
@@ -0,0 +1,229 @@
from datetime import UTC
from decimal import Decimal
from uuid import UUID, uuid4
from template_project.application.common.enums import EducationGrade, ExperienceType
from template_project.application.resume.entity import (
Resume,
ResumeEducation,
ResumeEmbedding,
ResumeExperience,
ResumeId,
ResumePrediction,
ResumeProject,
)
from template_project.application.user.entity import UserId
def test_resume_factory_creates_valid_resume() -> None:
user_id = UserId(uuid4())
resume = Resume.factory(
user_id=user_id,
position="Python Developer",
location="Moscow",
about_me="Experienced developer",
key_skills=["Python", "FastAPI"],
experience_type=ExperienceType.BETWEEN_3_AND_6,
)
assert isinstance(resume.id, UUID)
assert resume.user_id == user_id
assert resume.position == "Python Developer"
assert resume.location == "Moscow"
assert resume.about_me == "Experienced developer"
assert resume.key_skills == ["Python", "FastAPI"]
assert resume.experience_type == ExperienceType.BETWEEN_3_AND_6
assert resume.down_resume_id is None
assert resume.up_resume_id is None
assert resume.deleted_at is None
assert resume.created_at.tzinfo == UTC
def test_resume_factory_with_history_links() -> None:
user_id = UserId(uuid4())
old_resume_id = ResumeId(uuid4())
resume = Resume.factory(
user_id=user_id,
position="Python Developer",
location="Moscow",
about_me="Updated",
key_skills=["Python"],
experience_type=ExperienceType.BETWEEN_3_AND_6,
down_resume_id=old_resume_id,
)
assert resume.down_resume_id == old_resume_id
def test_resume_embedding_factory_creates_valid_embedding() -> None:
resume_id = ResumeId(uuid4())
vector = [0.1, 0.2, 0.3, 0.4, 0.5]
embedding = ResumeEmbedding.factory(
resume_id=resume_id,
vector=vector,
)
assert isinstance(embedding.id, UUID)
assert embedding.resume_id == resume_id
assert embedding.vector == vector
assert embedding.deleted_at is None
assert embedding.created_at.tzinfo == UTC
def test_resume_embedding_vector_dimension() -> None:
resume_id = ResumeId(uuid4())
vector = [0.1] * 384
embedding = ResumeEmbedding.factory(
resume_id=resume_id,
vector=vector,
)
assert len(embedding.vector) == 384
assert all(isinstance(x, float) for x in embedding.vector)
def test_resume_prediction_factory_creates_valid_prediction() -> None:
resume_id = ResumeId(uuid4())
prediction = ResumePrediction.factory(
resume_id=resume_id,
from_salary=Decimal(100000),
to_salary=Decimal(150000),
recommended_skills=["Kubernetes", "Docker"],
)
assert isinstance(prediction.id, UUID)
assert prediction.resume_id == resume_id
assert prediction.from_salary == Decimal(100000)
assert prediction.to_salary == Decimal(150000)
assert prediction.recommended_skills == ["Kubernetes", "Docker"]
assert prediction.deleted_at is None
assert prediction.created_at.tzinfo == UTC
def test_resume_prediction_salary_order() -> None:
resume_id = ResumeId(uuid4())
prediction = ResumePrediction.factory(
resume_id=resume_id,
from_salary=Decimal(100000),
to_salary=Decimal(150000),
recommended_skills=[],
)
assert prediction.from_salary <= prediction.to_salary
def test_resume_prediction_empty_recommended_skills() -> None:
resume_id = ResumeId(uuid4())
prediction = ResumePrediction.factory(
resume_id=resume_id,
from_salary=Decimal(100000),
to_salary=Decimal(150000),
recommended_skills=[],
)
assert prediction.recommended_skills == []
def test_resume_experience_positive_duration() -> None:
resume_id = ResumeId(uuid4())
experience = ResumeExperience.factory(
resume_id=resume_id,
place="Company",
description="Work",
months_duration=12,
)
assert experience.months_duration > 0
def test_resume_key_skills_empty_list() -> None:
user_id = UserId(uuid4())
resume = Resume.factory(
user_id=user_id,
position="Developer",
location="Moscow",
about_me="Test",
key_skills=[],
experience_type=ExperienceType.NO_EXPERIENCE,
)
assert resume.key_skills == []
def test_resume_experience_factory_creates_valid_experience() -> None:
resume_id = ResumeId(uuid4())
experience = ResumeExperience.factory(
resume_id=resume_id,
place="T-bank",
description="Backend development",
months_duration=24,
)
assert isinstance(experience.id, UUID)
assert experience.resume_id == resume_id
assert experience.place == "T-bank"
assert experience.description == "Backend development"
assert experience.months_duration == 24
assert experience.months_duration > 0
assert experience.deleted_at is None
assert experience.created_at.tzinfo == UTC
def test_resume_education_factory_creates_valid_education() -> None:
resume_id = ResumeId(uuid4())
education = ResumeEducation.factory(
resume_id=resume_id,
place="University",
grade=EducationGrade.BACHELOR,
specialization="Computer Science",
description="Optional description",
)
assert isinstance(education.id, UUID)
assert education.resume_id == resume_id
assert education.place == "University"
assert education.grade == EducationGrade.BACHELOR
assert education.specialization == "Computer Science"
assert education.description == "Optional description"
assert education.deleted_at is None
assert education.created_at.tzinfo == UTC
def test_resume_education_factory_without_description() -> None:
resume_id = ResumeId(uuid4())
education = ResumeEducation.factory(
resume_id=resume_id,
place="University",
grade=EducationGrade.MASTER,
specialization="Data Science",
)
assert education.description is None
def test_resume_project_factory_creates_valid_project() -> None:
resume_id = ResumeId(uuid4())
project = ResumeProject.factory(
resume_id=resume_id,
name="ML Service",
description="Machine learning service",
)
assert isinstance(project.id, UUID)
assert project.resume_id == resume_id
assert project.name == "ML Service"
assert project.description == "Machine learning service"
assert project.deleted_at is None
assert project.created_at.tzinfo == UTC
+12
View File
@@ -0,0 +1,12 @@
from datetime import UTC
from uuid import UUID
from template_project.application.user.entity import User
def test_user_factory_creates_valid_user() -> None:
user = User.factory()
assert isinstance(user.id, UUID)
assert user.deleted_at is None
assert user.created_at.tzinfo == UTC
+95
View File
@@ -0,0 +1,95 @@
from datetime import UTC
from decimal import Decimal
from uuid import UUID, uuid4
from template_project.application.common.enums import ExperienceType
from template_project.application.vacancy.entity import Vacancy, VacancyEmbedding, VacancyId
def test_vacancy_factory_creates_valid_vacancy() -> None:
vacancy = Vacancy.factory(
position="Python Developer",
from_salary=Decimal(100000),
to_salary=Decimal(150000),
experience_type=ExperienceType.BETWEEN_3_AND_6,
description="Backend development",
key_skills=["Python", "FastAPI"],
)
assert isinstance(vacancy.id, UUID)
assert vacancy.position == "Python Developer"
assert vacancy.from_salary == Decimal(100000)
assert vacancy.to_salary == Decimal(150000)
assert vacancy.experience_type == ExperienceType.BETWEEN_3_AND_6
assert vacancy.description == "Backend development"
assert vacancy.key_skills == ["Python", "FastAPI"]
assert vacancy.deleted_at is None
assert vacancy.created_at.tzinfo == UTC
def test_vacancy_salary_order() -> None:
vacancy = Vacancy.factory(
position="Developer",
from_salary=Decimal(100000),
to_salary=Decimal(150000),
experience_type=ExperienceType.BETWEEN_1_AND_3,
description="Test",
key_skills=[],
)
assert vacancy.from_salary <= vacancy.to_salary
def test_vacancy_equal_salaries() -> None:
vacancy = Vacancy.factory(
position="Developer",
from_salary=Decimal(100000),
to_salary=Decimal(100000),
experience_type=ExperienceType.BETWEEN_1_AND_3,
description="Test",
key_skills=[],
)
assert vacancy.from_salary == vacancy.to_salary
def test_vacancy_empty_key_skills() -> None:
vacancy = Vacancy.factory(
position="Developer",
from_salary=Decimal(100000),
to_salary=Decimal(150000),
experience_type=ExperienceType.NO_EXPERIENCE,
description="Test",
key_skills=[],
)
assert vacancy.key_skills == []
def test_vacancy_embedding_vector_dimension() -> None:
vacancy_id = VacancyId(uuid4())
vector = [0.1] * 384
embedding = VacancyEmbedding.factory(
vacancy_id=vacancy_id,
vector=vector,
)
assert len(embedding.vector) == 384
assert all(isinstance(x, float) for x in embedding.vector)
def test_vacancy_embedding_factory_creates_valid_embedding() -> None:
vacancy_id = VacancyId(uuid4())
vector = [0.1, 0.2, 0.3, 0.4, 0.5]
embedding = VacancyEmbedding.factory(
vacancy_id=vacancy_id,
vector=vector,
)
assert isinstance(embedding.id, UUID)
assert embedding.vacancy_id == vacancy_id
assert embedding.vector == vector
assert embedding.deleted_at is None
assert embedding.created_at.tzinfo == UTC