Coverage for tests/services/test_user_service.py: 100%
177 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 02:49 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 02:49 +0000
1import pytest
2from unittest.mock import patch, MagicMock
3from app.services import user_service
4from app.models.errors import DatabaseError
5from datetime import datetime
8@patch("app.services.user_service.client", new_callable=MagicMock)
9def test_update_user_class_status_with_class_id(mock_client, app):
10 mock_execute = MagicMock()
11 mock_execute.data = [{"id": "user123"}]
12 mock_client.table.return_value.update.return_value.match.return_value.execute.return_value = (
13 mock_execute
14 )
16 user_service.update_user_class_status("user123", "ACTIVE", "class456")
18 mock_client.table.assert_called_once_with("class_users")
19 mock_client.table.return_value.update.assert_called_once_with(
20 {"user_class_status": "ACTIVE"}
21 )
22 mock_client.table.return_value.update.return_value.match.assert_called_once_with(
23 {"student_id": "user123", "class_id": "class456"}
24 )
25 mock_client.table.return_value.update.return_value.match.return_value.execute.assert_called_once()
28@patch("app.services.user_service.client", new_callable=MagicMock)
29def test_update_user_class_status_with_class_id_error(mock_client, app):
30 mock_execute = MagicMock()
31 mock_execute.data = None
32 mock_client.table.return_value.update.return_value.match.return_value.execute.return_value = (
33 mock_execute
34 )
36 with pytest.raises(DatabaseError):
37 user_service.update_user_class_status("user123", "ACTIVE", "class456")
39 mock_client.table.assert_called_once_with("class_users")
40 mock_client.table.return_value.update.assert_called_once_with(
41 {"user_class_status": "ACTIVE"}
42 )
43 mock_client.table.return_value.update.return_value.match.assert_called_once_with(
44 {"student_id": "user123", "class_id": "class456"}
45 )
46 mock_client.table.return_value.update.return_value.match.return_value.execute.assert_called_once()
49@patch("app.services.user_service.client", new_callable=MagicMock)
50def test_update_user_class_status_without_class_id(mock_client, app):
51 mock_execute = MagicMock()
52 mock_execute.data = [{"id": "user123"}]
53 mock_client.table.return_value.update.return_value.eq.return_value.execute.return_value = (
54 mock_execute
55 )
57 user_service.update_user_class_status("user123", "INACTIVE")
59 mock_client.table.assert_called_once_with("users")
60 mock_client.table.return_value.update.assert_called_once_with(
61 {"status": "INACTIVE"}
62 )
63 mock_client.table.return_value.update.return_value.eq.assert_called_once_with(
64 "id", "user123"
65 )
66 mock_client.table.return_value.update.return_value.eq.return_value.execute.assert_called_once()
69@patch("app.services.user_service.client", new_callable=MagicMock)
70def test_update_user_class_status_without_class_id_error(mock_client, app):
71 mock_execute = MagicMock()
72 mock_execute.data = None
73 mock_client.table.return_value.update.return_value.eq.return_value.execute.return_value = (
74 mock_execute
75 )
77 with pytest.raises(DatabaseError):
78 user_service.update_user_class_status("user123", "INACTIVE")
80 mock_client.table.assert_called_once_with("users")
81 mock_client.table.return_value.update.assert_called_once_with(
82 {"status": "INACTIVE"}
83 )
84 mock_client.table.return_value.update.return_value.eq.assert_called_once_with(
85 "id", "user123"
86 )
87 mock_client.table.return_value.update.return_value.eq.return_value.execute.assert_called_once()
90@patch("app.services.user_service.client", new_callable=MagicMock)
91def test_get_user_class_status_success(mock_client, app):
92 mock_execute = MagicMock()
93 mock_execute.data = [{"user_class_status": "ACTIVE"}]
94 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.return_value = (
95 mock_execute
96 )
98 result = user_service.get_user_class_status("user123", "class456")
100 assert result == {"user_class_status": "ACTIVE"}
101 mock_client.table.assert_called_once_with("class_users")
102 mock_client.table.return_value.select.assert_called_once_with("user_class_status")
103 mock_client.table.return_value.select.return_value.eq.assert_called_with(
104 "student_id", "user123"
105 )
106 mock_client.table.return_value.select.return_value.eq.return_value.eq.assert_called_with(
107 "class_id", "class456"
108 )
109 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.assert_called_once()
112@patch("app.services.user_service.client", new_callable=MagicMock)
113def test_get_user_class_status_no_data(mock_client, app):
114 mock_execute = MagicMock()
115 mock_execute.data = []
116 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.return_value = (
117 mock_execute
118 )
120 result = user_service.get_user_class_status("user123", "class456")
122 assert result is None
125@patch("app.services.user_service.client", new_callable=MagicMock)
126def test_create_user_section_with_class_id(mock_client, app):
127 mock_execute = MagicMock()
128 mock_execute.data = [{"section_id": "section123"}]
129 mock_client.table.return_value.insert.return_value.execute.return_value = (
130 mock_execute
131 )
133 result = user_service.create_user_section("user123", "class456")
135 assert result == "section123"
136 mock_client.table.assert_called_once_with("user_sections")
137 inserted_data = mock_client.table.return_value.insert.call_args[0][0]
138 assert inserted_data["user_id"] == "user123"
139 assert inserted_data["class_id"] == "class456"
140 assert inserted_data["status"] == "ACTIVE"
141 assert "started_at" in inserted_data
144@patch("app.services.user_service.client", new_callable=MagicMock)
145def test_create_user_section_without_class_id(mock_client, app):
146 mock_execute = MagicMock()
147 mock_execute.data = [{"section_id": "section123"}]
148 mock_client.table.return_value.insert.return_value.execute.return_value = (
149 mock_execute
150 )
152 result = user_service.create_user_section("user123")
154 assert result == "section123"
155 mock_client.table.assert_called_once_with("user_sections")
156 inserted_data = mock_client.table.return_value.insert.call_args[0][0]
157 assert inserted_data["user_id"] == "user123"
158 assert "class_id" not in inserted_data
159 assert inserted_data["status"] == "ACTIVE"
160 assert "started_at" in inserted_data
163@patch("app.services.user_service.client", new_callable=MagicMock)
164def test_get_user_section_existing(mock_client, app):
165 mock_execute = MagicMock()
166 mock_execute.data = [{"section_id": "section123"}]
167 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.eq.return_value.execute.return_value = (
168 mock_execute
169 )
171 result = user_service.get_user_section("user123", "class456")
173 assert result == "section123"
176@patch("app.services.user_service.create_user_section", new_callable=MagicMock)
177@patch("app.services.user_service.client", new_callable=MagicMock)
178def test_get_user_section_creates_new(mock_client, mock_create_user_section, app):
179 mock_execute = MagicMock()
180 mock_execute.data = []
181 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.eq.return_value.execute.return_value = (
182 mock_execute
183 )
184 mock_create_user_section.return_value = "new_section123"
186 result = user_service.get_user_section("user123", "class456")
188 assert result == "new_section123"
189 mock_create_user_section.assert_called_once_with("user123", "class456")
192@patch("app.services.user_service.client", new_callable=MagicMock)
193def test_update_user_section(mock_client, app):
194 mock_execute = MagicMock()
195 mock_execute.data = [{"section_id": "section123"}]
196 mock_client.table.return_value.update.return_value.eq.return_value.execute.return_value = (
197 mock_execute
198 )
200 user_service.update_user_section("COMPLETE", "section123")
202 mock_client.table.assert_called_once_with("user_sections")
203 mock_client.table.return_value.update.assert_called_once()
204 update_data = mock_client.table.return_value.update.call_args[0][0]
205 assert update_data["status"] == "COMPLETE"
206 assert "ended_at" in update_data
207 mock_client.table.return_value.update.return_value.eq.assert_called_once_with(
208 "section_id", "section123"
209 )
210 mock_client.table.return_value.update.return_value.eq.return_value.execute.assert_called_once()
213@patch("app.services.user_service.client", new_callable=MagicMock)
214def test_get_classes_by_user_id_returns_classes(mock_client):
215 mock_execute = MagicMock()
216 mock_execute.data = [
217 {
218 "user_class_status": "ACTIVE",
219 "classes": {"id": "class123", "name": "Math", "teacher_id": "teacher123"},
220 }
221 ]
222 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.return_value = (
223 mock_execute
224 )
226 result = user_service.get_classes_by_user_id("user123")
228 expected = [
229 {
230 "userClass": {"id": "class123", "name": "Math", "teacher_id": "teacher123"},
231 "studentStatus": "ACTIVE",
232 }
233 ]
235 assert result == expected
238@patch("app.services.user_service.client", new_callable=MagicMock)
239def test_get_classes_by_user_id_no_data(mock_client):
240 mock_execute = MagicMock()
241 mock_execute.data = []
242 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.return_value = (
243 mock_execute
244 )
246 result = user_service.get_classes_by_user_id("user123")
248 assert result == []
251@patch("app.services.user_service.client", new_callable=MagicMock)
252def test_get_classes_by_user_id_raises_error(mock_client):
253 mock_client.table.return_value.select.return_value.eq.return_value.eq.return_value.execute.side_effect = Exception(
254 "DB failure"
255 )
257 with pytest.raises(DatabaseError) as exc_info:
258 user_service.get_classes_by_user_id("user123")
260 assert "Failed to retrieve user classes" in str(exc_info.value)
263@patch("app.services.user_service.get_service_client", new_callable=MagicMock)
264@patch("app.services.user_service.client", new_callable=MagicMock)
265def test_get_all_users_merges_sources(mock_client, mock_get_service_client, app):
266 # Mock database users
267 mock_client.table.return_value.select.return_value.execute.return_value.data = [
268 {"id": "user1", "name": "Alice"},
269 {"id": "user2", "name": "Bob"},
270 ]
272 # Mock auth users
273 mock_auth_user1 = MagicMock(
274 id="user1",
275 email="alice@example.com",
276 created_at="2021-01-01T00:00:00Z",
277 updated_at="2021-01-01T01:00:00Z",
278 last_sign_in_at="2021-01-02T00:00:00Z",
279 app_metadata={"providers": ["email"]},
280 user_metadata={"avatar_url": "http://example.com/alice.png"},
281 )
282 mock_auth_user3 = MagicMock(
283 id="user3",
284 email="charlie@example.com",
285 created_at="2021-03-01T00:00:00Z",
286 updated_at="2021-03-01T01:00:00Z",
287 last_sign_in_at="2021-03-02T00:00:00Z",
288 app_metadata={"providers": ["github"]},
289 user_metadata={"avatar_url": "http://example.com/charlie.png"},
290 )
291 mock_get_service_client.return_value.auth.admin.list_users.return_value = [
292 mock_auth_user1,
293 mock_auth_user3,
294 ]
296 result = user_service.get_all_users()
298 assert len(result) == 3
300 # Check merged user
301 merged = next(u for u in result if u["id"] == "user1")
302 assert merged["name"] == "Alice"
303 assert merged["auth_email"] == "alice@example.com"
304 assert merged["source"] == "both"
306 # Check users-only
307 users_only = next(u for u in result if u["id"] == "user2")
308 assert users_only["name"] == "Bob"
309 assert users_only["auth_email"] is None
310 assert users_only["source"] == "users_only"
312 # Check auth-only
313 auth_only = next(u for u in result if u["id"] == "user3")
314 assert auth_only["auth_email"] == "charlie@example.com"
315 assert "name" not in auth_only
316 assert auth_only["source"] == "auth_only"
319@patch("app.services.user_service.get_service_client", new_callable=MagicMock)
320@patch("app.services.user_service.client", new_callable=MagicMock)
321def test_get_all_users_handles_empty_sources(mock_client, mock_get_service_client, app):
322 mock_client.table.return_value.select.return_value.execute.return_value.data = []
323 mock_get_service_client.return_value.auth.admin.list_users.return_value = []
325 result = user_service.get_all_users()
326 assert result == []
329@patch(
330 "app.services.user_service.get_service_client",
331 side_effect=Exception("auth fail"),
332 new_callable=MagicMock,
333)
334@patch("app.services.user_service.client", new_callable=MagicMock)
335def test_get_all_users_raises_database_error(mock_client, mock_get_service_client, app):
336 with pytest.raises(DatabaseError) as exc_info:
337 user_service.get_all_users()
338 assert "Failed to retrieve all users" in str(exc_info.value)