Skip to content

Commit 80ce3ee

Browse files
committed
test(users): add test suit for get users repository
- add temporary implementation of update user - add temporary implementation of delete user
1 parent 73ad244 commit 80ce3ee

4 files changed

Lines changed: 311 additions & 2 deletions

File tree

api/infrastructure/postgres/_postgresusersrepository.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ async def get_users(
157157
users = [User(**row._mapping) for row in result.all()]
158158

159159
if (user_id is not None or email is not None) and len(users) == 0:
160+
# TODO: change this to return the error and raise it in the use case instead of raising it here
160161
raise UserNotFoundException()
161162

162163
return users

api/tests/integration/endpoints/test_models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ async def test_get_model_should_return_404_when_model_not_found(self, client: As
122122
user_1 = UserSQLFactory()
123123
token = await create_token(db_session, name="my_token", user=user_1)
124124

125-
# Act & Assert
125+
# Act
126126
await db_session.flush()
127127
response = await client.get(url=f"/v1{EndpointRoute.MODELS}/{non_existent_model}", headers={"Authorization": f"Bearer {token.token}"})
128+
128129
# Assert
129130
actual_data = response.json()
130131
assert response.status_code == 404

api/tests/integration/postgres/test_postgresusersrepository.py

Lines changed: 281 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import bcrypt
12
import pytest
3+
from sqlalchemy import select
24

5+
from api.domain.user.entities import User
6+
from api.domain.user.errors import OrganizationNotFoundError, RoleNotFoundError, UserAlreadyExistsError
37
from api.infrastructure.postgres import PostgresUserRepository
4-
from api.tests.integration.factories import UserSQLFactory
8+
from api.sql.models import User as UserTable
9+
from api.tests.integration.factories import OrganizationSQLFactory, RoleSQLFactory, UserSQLFactory
10+
from api.utils.exceptions import UserNotFoundException
511

612

713
@pytest.fixture
@@ -39,3 +45,277 @@ async def test_returns_false_when_user_exists_without_admin_permission(self, rep
3945

4046
# Assert
4147
assert result is False
48+
49+
50+
@pytest.mark.asyncio(loop_scope="session")
51+
class TestCreateUser:
52+
async def test_creates_user_and_returns_user_entity(self, repository, db_session):
53+
# Arrange
54+
role = RoleSQLFactory()
55+
await db_session.flush()
56+
57+
# Act
58+
result = await repository.create_user(email="user@test.com", password="s3cr3t", role_id=role.id)
59+
60+
# Assert
61+
assert isinstance(result, User)
62+
assert result.email == "user@test.com"
63+
assert isinstance(result.id, int)
64+
assert result.role == role.id
65+
66+
async def test_password_is_hashed_in_db(self, repository, db_session):
67+
# Arrange
68+
role = RoleSQLFactory()
69+
await db_session.flush()
70+
71+
# Act
72+
await repository.create_user(email="hashed@test.com", password="plaintext", role_id=role.id)
73+
await db_session.flush()
74+
75+
# Assert
76+
row = (await db_session.execute(select(UserTable.password).where(UserTable.email == "hashed@test.com"))).scalar_one()
77+
assert row != "plaintext"
78+
assert bcrypt.checkpw(b"plaintext", row.encode("utf-8"))
79+
80+
async def test_returns_user_already_exists_error_when_email_is_duplicate(self, repository, db_session):
81+
# Arrange
82+
role = RoleSQLFactory()
83+
await db_session.flush()
84+
await repository.create_user(email="duplicate@test.com", password="s3cr3t", role_id=role.id)
85+
86+
# Act
87+
result = await repository.create_user(email="duplicate@test.com", password="other", role_id=role.id)
88+
89+
# Assert
90+
assert isinstance(result, UserAlreadyExistsError)
91+
assert result.email == "duplicate@test.com"
92+
93+
async def test_returns_role_not_found_error_when_role_does_not_exist(self, repository, db_session):
94+
# Act
95+
result = await repository.create_user(email="user@test.com", password="s3cr3t", role_id=999999)
96+
97+
# Assert
98+
assert isinstance(result, RoleNotFoundError)
99+
assert result.role_id == 999999
100+
101+
async def test_returns_organization_not_found_error_when_organization_does_not_exist(self, repository, db_session):
102+
# Arrange
103+
role = RoleSQLFactory()
104+
await db_session.flush()
105+
106+
# Act
107+
result = await repository.create_user(email="user@test.com", password="s3cr3t", role_id=role.id, organization_id=999999)
108+
109+
# Assert
110+
assert isinstance(result, OrganizationNotFoundError)
111+
assert result.organization_id == 999999
112+
113+
async def test_creates_user_with_all_optional_fields(self, repository, db_session):
114+
# Arrange
115+
role = RoleSQLFactory()
116+
organization = OrganizationSQLFactory()
117+
await db_session.flush()
118+
119+
# Act
120+
result = await repository.create_user(
121+
email="full@test.com",
122+
password="s3cr3t",
123+
role_id=role.id,
124+
name="Full User",
125+
sub="sub-123",
126+
iss="https://issuer.example.com",
127+
organization_id=organization.id,
128+
budget=100.0,
129+
priority=5,
130+
)
131+
132+
# Assert
133+
assert isinstance(result, User)
134+
assert result.name == "Full User"
135+
assert result.sub == "sub-123"
136+
assert result.iss == "https://issuer.example.com"
137+
assert result.organization == organization.id
138+
assert result.budget == 100.0
139+
assert result.priority == 5
140+
141+
142+
@pytest.mark.asyncio(loop_scope="session")
143+
class TestGetUsers:
144+
async def test_returns_all_users_when_no_filter_given(self, repository, db_session):
145+
# Arrange
146+
user_1 = UserSQLFactory()
147+
user_2 = UserSQLFactory()
148+
user_3 = UserSQLFactory()
149+
await db_session.flush()
150+
151+
# Act
152+
result = await repository.get_users()
153+
154+
# Assert
155+
result_ids = {u.id for u in result}
156+
assert {user_1.id, user_2.id, user_3.id}.issubset(result_ids)
157+
158+
async def test_filters_by_email(self, repository, db_session):
159+
# Arrange
160+
user = UserSQLFactory(email="findme@test.com")
161+
UserSQLFactory()
162+
await db_session.flush()
163+
164+
# Act
165+
result = await repository.get_users(email="findme@test.com")
166+
167+
# Assert
168+
assert len(result) == 1
169+
assert result[0].id == user.id
170+
171+
async def test_filters_by_user_id(self, repository, db_session):
172+
# Arrange
173+
user = UserSQLFactory()
174+
UserSQLFactory()
175+
await db_session.flush()
176+
177+
# Act
178+
result = await repository.get_users(user_id=user.id)
179+
180+
# Assert
181+
assert len(result) == 1
182+
assert result[0].id == user.id
183+
184+
async def test_filters_by_role_id(self, repository, db_session):
185+
# Arrange
186+
role = RoleSQLFactory()
187+
user_1 = UserSQLFactory(role=role)
188+
user_2 = UserSQLFactory(role=role)
189+
UserSQLFactory()
190+
await db_session.flush()
191+
192+
# Act
193+
result = await repository.get_users(role_id=role.id)
194+
195+
# Assert
196+
result_ids = {u.id for u in result}
197+
assert result_ids == {user_1.id, user_2.id}
198+
199+
async def test_filters_by_organization_id(self, repository, db_session):
200+
# Arrange
201+
organization = OrganizationSQLFactory()
202+
user_1 = UserSQLFactory(organization=organization)
203+
user_2 = UserSQLFactory(organization=organization)
204+
UserSQLFactory()
205+
await db_session.flush()
206+
207+
# Act
208+
result = await repository.get_users(organization_id=organization.id)
209+
210+
# Assert
211+
result_ids = {u.id for u in result}
212+
assert result_ids == {user_1.id, user_2.id}
213+
214+
async def test_limit_restricts_number_of_results(self, repository, db_session):
215+
# Arrange
216+
UserSQLFactory()
217+
UserSQLFactory()
218+
UserSQLFactory()
219+
await db_session.flush()
220+
221+
# Act
222+
result = await repository.get_users(limit=2)
223+
224+
# Assert
225+
assert len(result) == 2
226+
227+
async def test_offset_skips_results(self, repository, db_session):
228+
# Arrange
229+
role = RoleSQLFactory()
230+
user_1 = UserSQLFactory(role=role)
231+
user_2 = UserSQLFactory(role=role)
232+
user_3 = UserSQLFactory(role=role)
233+
await db_session.flush()
234+
235+
# Act
236+
first_page = await repository.get_users(role_id=role.id, limit=10, offset=0, order_by="id", order_direction="asc")
237+
second_page = await repository.get_users(role_id=role.id, limit=10, offset=1, order_by="id", order_direction="asc")
238+
239+
# Assert
240+
assert first_page[0].id == user_1.id
241+
assert [u.id for u in second_page] == [user_2.id, user_3.id]
242+
243+
async def test_sorts_by_email_asc(self, repository, db_session):
244+
# Arrange
245+
role = RoleSQLFactory()
246+
UserSQLFactory(role=role, email="c@test.com")
247+
UserSQLFactory(role=role, email="a@test.com")
248+
UserSQLFactory(role=role, email="b@test.com")
249+
await db_session.flush()
250+
251+
# Act
252+
result = await repository.get_users(role_id=role.id, order_by="email", order_direction="asc")
253+
254+
# Assert
255+
assert [u.email for u in result] == ["a@test.com", "b@test.com", "c@test.com"]
256+
257+
async def test_sorts_by_email_desc(self, repository, db_session):
258+
# Arrange
259+
role = RoleSQLFactory()
260+
UserSQLFactory(role=role, email="c@test.com")
261+
UserSQLFactory(role=role, email="a@test.com")
262+
UserSQLFactory(role=role, email="b@test.com")
263+
await db_session.flush()
264+
265+
# Act
266+
result = await repository.get_users(role_id=role.id, order_by="email", order_direction="desc")
267+
268+
# Assert
269+
assert [u.email for u in result] == ["c@test.com", "b@test.com", "a@test.com"]
270+
271+
async def test_raises_user_not_found_when_filtering_by_unknown_user_id(self, repository, db_session):
272+
# Act & Assert
273+
with pytest.raises(UserNotFoundException):
274+
await repository.get_users(user_id=999999)
275+
276+
async def test_raises_user_not_found_when_filtering_by_unknown_email(self, repository, db_session):
277+
# Act & Assert
278+
with pytest.raises(UserNotFoundException):
279+
await repository.get_users(email="unknown@test.com")
280+
281+
async def test_returns_empty_list_when_no_users_match_role_id(self, repository, db_session):
282+
# Arrange
283+
role = RoleSQLFactory()
284+
await db_session.flush()
285+
286+
# Act
287+
result = await repository.get_users(role_id=role.id)
288+
289+
# Assert
290+
assert result == []
291+
292+
async def test_returns_empty_list_when_no_users_match_organization_id(self, repository, db_session):
293+
# Arrange
294+
organization = OrganizationSQLFactory()
295+
await db_session.flush()
296+
297+
# Act
298+
result = await repository.get_users(organization_id=organization.id)
299+
300+
# Assert
301+
assert result == []
302+
303+
304+
@pytest.mark.asyncio(loop_scope="session")
305+
class TestUpdateUser:
306+
async def test_raises_not_implemented_error(self, repository, db_session):
307+
# Arrange
308+
user = UserSQLFactory()
309+
await db_session.flush()
310+
311+
# Act & Assert
312+
with pytest.raises(NotImplementedError):
313+
await repository.update_user(user)
314+
315+
316+
@pytest.mark.asyncio(loop_scope="session")
317+
class TestDeleteUser:
318+
async def test_raises_not_implemented_error(self, repository, db_session):
319+
# Act & Assert
320+
with pytest.raises(NotImplementedError):
321+
await repository.delete_user(user_id=1)

0 commit comments

Comments
 (0)