You've already forked RekomenciBackend
feat(): unit tests for domain entities and basic invariants
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import subprocess # noqa: S404
|
||||
from pathlib import Path
|
||||
|
||||
from template_project.web_api.configuration import load_configuration
|
||||
|
||||
|
||||
@@ -18,8 +19,8 @@ def main() -> None:
|
||||
|
||||
print("Создание дампа таблиц vacancy, vacancy_embedding, key_skills...")
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
subprocess.run( # noqa: S603
|
||||
[ # noqa: S607
|
||||
"pg_dump",
|
||||
db_url,
|
||||
"--table=vacancy",
|
||||
@@ -40,4 +41,3 @@ def main() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@ class ExperienceTypeType(TypeDecorator[ExperienceType]):
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return ExperienceType(value)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid experience_type value: {value}")
|
||||
except ValueError as err:
|
||||
raise ValueError(f"Invalid experience_type value: {value}") from err
|
||||
raise ValueError(f"Cannot convert {type(value)} to ExperienceType")
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user