Skip to content

Commit 2a29fa3

Browse files
committed
Добавлены документация и тесты для модуля users
1 parent 7012cae commit 2a29fa3

18 files changed

Lines changed: 1380 additions & 153 deletions

docs/modules/users.md

Lines changed: 274 additions & 1 deletion
Large diffs are not rendered by default.

users/services/users_activity.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ def __prepare_user_data(self, user: CustomUser) -> dict[str, Any]:
9292

9393
def __get_user_queryset(self) -> QuerySet[CustomUser]:
9494
user_content_type = ContentType.objects.get_for_model(CustomUser)
95+
program_profiles_count_subquery = (
96+
PartnerProgramUserProfile.objects
97+
.filter(user_id=OuterRef("id"))
98+
.values("user_id")
99+
.annotate(total=Count("id"))
100+
.values("total")
101+
)
95102
projects_in_program_subquery = (
96103
PartnerProgramUserProfile.objects
97104
.filter(user_id=OuterRef("id"))
@@ -114,13 +121,6 @@ def __get_user_queryset(self) -> QuerySet[CustomUser]:
114121
.values("total_likes")
115122
)
116123

117-
projects_in_program_subquery = (
118-
CustomUser.objects
119-
.filter(partner_program_profiles__user_id=OuterRef("id"))
120-
.annotate(total_proj=Count("id"))
121-
.values("total_proj")
122-
)
123-
124124
users: QuerySet[CustomUser] = (
125125
CustomUser.objects
126126
.prefetch_related(
@@ -140,7 +140,7 @@ def __get_user_queryset(self) -> QuerySet[CustomUser]:
140140
output_field=IntegerField(),
141141
),
142142
program_profiles_count=Coalesce(
143-
Subquery(projects_in_program_subquery, output_field=IntegerField()),
143+
Subquery(program_profiles_count_subquery, output_field=IntegerField()),
144144
Value(0),
145145
output_field=IntegerField(),
146146
),

users/tests.py

Lines changed: 0 additions & 113 deletions
This file was deleted.

users/tests/__init__.py

Whitespace-only changes.

users/tests/helpers.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from datetime import date, timedelta
2+
3+
from django.contrib.contenttypes.models import ContentType
4+
from django.utils import timezone
5+
6+
from core.models import Skill, SkillCategory, SkillToObject, Specialization, SpecializationCategory
7+
from files.models import UserFile
8+
from partner_programs.models import PartnerProgram, PartnerProgramUserProfile
9+
from projects.models import Project
10+
from users.models import CustomUser
11+
12+
13+
def build_user(
14+
email: str = "user@example.com",
15+
*,
16+
password: str = "very_strong_password",
17+
first_name: str = "Иван",
18+
last_name: str = "Иванов",
19+
user_type: int = CustomUser.MEMBER,
20+
is_active: bool = True,
21+
**extra_fields,
22+
) -> CustomUser:
23+
defaults = {
24+
"email": email,
25+
"password": password,
26+
"first_name": first_name,
27+
"last_name": last_name,
28+
"birthday": date(2000, 1, 1),
29+
"user_type": user_type,
30+
"is_active": is_active,
31+
}
32+
defaults.update(extra_fields)
33+
return CustomUser.objects.create_user(**defaults)
34+
35+
36+
def build_superuser(email: str = "admin@example.com") -> CustomUser:
37+
return CustomUser.objects.create_superuser(
38+
email=email,
39+
password="very_strong_password",
40+
first_name="Админ",
41+
last_name="Админов",
42+
)
43+
44+
45+
def build_skill(name: str = "Python") -> Skill:
46+
category, _ = SkillCategory.objects.get_or_create(name="Backend")
47+
return Skill.objects.create(name=name, category=category)
48+
49+
50+
def attach_skill(user: CustomUser, skill: Skill) -> SkillToObject:
51+
return SkillToObject.objects.create(
52+
skill=skill,
53+
content_type=ContentType.objects.get_for_model(CustomUser),
54+
object_id=user.id,
55+
)
56+
57+
58+
def build_specialization(name: str = "Backend developer") -> Specialization:
59+
category, _ = SpecializationCategory.objects.get_or_create(name="IT")
60+
return Specialization.objects.create(name=name, category=category)
61+
62+
63+
def build_user_file(
64+
user: CustomUser,
65+
*,
66+
link: str = "https://cdn.example.com/file.pdf",
67+
extension: str = "pdf",
68+
size: int = 1024,
69+
) -> UserFile:
70+
return UserFile.objects.create(
71+
user=user,
72+
link=link,
73+
name="file",
74+
extension=extension,
75+
mime_type="application/pdf",
76+
size=size,
77+
)
78+
79+
80+
def build_project(
81+
leader: CustomUser,
82+
*,
83+
name: str = "Проект",
84+
draft: bool = False,
85+
) -> Project:
86+
return Project.objects.create(name=name, leader=leader, draft=draft)
87+
88+
89+
def build_partner_program(
90+
*,
91+
name: str = "Программа",
92+
tag: str = "program",
93+
draft: bool = False,
94+
) -> PartnerProgram:
95+
now = timezone.now()
96+
return PartnerProgram.objects.create(
97+
name=name,
98+
tag=tag,
99+
city="Екатеринбург",
100+
draft=draft,
101+
datetime_started=now - timedelta(days=1),
102+
datetime_registration_ends=now + timedelta(days=10),
103+
datetime_finished=now + timedelta(days=20),
104+
)
105+
106+
107+
def add_user_to_program(
108+
user: CustomUser,
109+
program: PartnerProgram,
110+
*,
111+
project: Project | None = None,
112+
) -> PartnerProgramUserProfile:
113+
return PartnerProgramUserProfile.objects.create(
114+
user=user,
115+
project=project,
116+
partner_program=program,
117+
partner_program_data={},
118+
)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from django.test import TestCase
2+
from rest_framework.test import APIClient
3+
4+
from users.models import UserAchievement
5+
6+
from .helpers import build_user, build_user_file
7+
8+
9+
class UserAchievementAPITests(TestCase):
10+
def setUp(self):
11+
self.client = APIClient()
12+
self.user = build_user(email="achievements@example.com")
13+
self.client.force_authenticate(user=self.user)
14+
15+
def test_user_can_create_achievement_with_owned_file(self):
16+
user_file = build_user_file(self.user)
17+
18+
response = self.client.post(
19+
"/auth/users/achievements/",
20+
{
21+
"title": "Победа",
22+
"status": "Первое место",
23+
"year": 2024,
24+
"file_links": [user_file.link],
25+
},
26+
format="json",
27+
)
28+
29+
self.assertEqual(response.status_code, 201)
30+
achievement = UserAchievement.objects.get(user=self.user)
31+
self.assertEqual(achievement.files.get(), user_file)
32+
33+
def test_user_cannot_create_achievement_for_another_user(self):
34+
other_user = build_user(email="achievement-owner@example.com")
35+
36+
response = self.client.post(
37+
"/auth/users/achievements/",
38+
{
39+
"user": other_user.id,
40+
"title": "Победа",
41+
"status": "Первое место",
42+
"year": 2024,
43+
},
44+
format="json",
45+
)
46+
47+
self.assertEqual(response.status_code, 403)
48+
self.assertFalse(UserAchievement.objects.filter(user=other_user).exists())
49+
50+
def test_user_cannot_attach_foreign_file_to_achievement(self):
51+
other_user = build_user(email="file-owner@example.com")
52+
foreign_file = build_user_file(
53+
other_user,
54+
link="https://cdn.example.com/foreign.pdf",
55+
)
56+
57+
response = self.client.post(
58+
"/auth/users/achievements/",
59+
{
60+
"title": "Победа",
61+
"status": "Первое место",
62+
"year": 2024,
63+
"file_links": [foreign_file.link],
64+
},
65+
format="json",
66+
)
67+
68+
self.assertEqual(response.status_code, 400)
69+
self.assertFalse(UserAchievement.objects.filter(user=self.user).exists())
70+
71+
def test_profile_update_replaces_achievements_and_validates_owned_files(self):
72+
old_achievement = UserAchievement.objects.create(
73+
user=self.user,
74+
title="Старое достижение",
75+
status="Участник",
76+
year=2023,
77+
)
78+
user_file = build_user_file(self.user)
79+
80+
response = self.client.patch(
81+
f"/auth/users/{self.user.id}/",
82+
{
83+
"achievements": [
84+
{
85+
"title": "Новое достижение",
86+
"status": "Победитель",
87+
"year": 2024,
88+
"file_links": [user_file.link],
89+
}
90+
]
91+
},
92+
format="json",
93+
)
94+
95+
self.assertEqual(response.status_code, 200)
96+
self.assertFalse(UserAchievement.objects.filter(id=old_achievement.id).exists())
97+
achievement = UserAchievement.objects.get(user=self.user)
98+
self.assertEqual(achievement.title, "Новое достижение")
99+
self.assertEqual(achievement.files.get(), user_file)
100+
101+
def test_profile_update_rejects_duplicate_achievements(self):
102+
response = self.client.patch(
103+
f"/auth/users/{self.user.id}/",
104+
{
105+
"achievements": [
106+
{"title": "Победа", "status": "Первое место", "year": 2024},
107+
{"title": "Победа", "status": "Первое место", "year": 2024},
108+
]
109+
},
110+
format="json",
111+
)
112+
113+
self.assertEqual(response.status_code, 400)
114+
self.assertFalse(UserAchievement.objects.filter(user=self.user).exists())

0 commit comments

Comments
 (0)