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

1import pytest 

2from unittest.mock import patch, MagicMock 

3from app.services import user_service 

4from app.models.errors import DatabaseError 

5from datetime import datetime 

6 

7 

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 ) 

15 

16 user_service.update_user_class_status("user123", "ACTIVE", "class456") 

17 

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() 

26 

27 

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 ) 

35 

36 with pytest.raises(DatabaseError): 

37 user_service.update_user_class_status("user123", "ACTIVE", "class456") 

38 

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() 

47 

48 

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 ) 

56 

57 user_service.update_user_class_status("user123", "INACTIVE") 

58 

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() 

67 

68 

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 ) 

76 

77 with pytest.raises(DatabaseError): 

78 user_service.update_user_class_status("user123", "INACTIVE") 

79 

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() 

88 

89 

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 ) 

97 

98 result = user_service.get_user_class_status("user123", "class456") 

99 

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() 

110 

111 

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 ) 

119 

120 result = user_service.get_user_class_status("user123", "class456") 

121 

122 assert result is None 

123 

124 

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 ) 

132 

133 result = user_service.create_user_section("user123", "class456") 

134 

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 

142 

143 

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 ) 

151 

152 result = user_service.create_user_section("user123") 

153 

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 

161 

162 

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 ) 

170 

171 result = user_service.get_user_section("user123", "class456") 

172 

173 assert result == "section123" 

174 

175 

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" 

185 

186 result = user_service.get_user_section("user123", "class456") 

187 

188 assert result == "new_section123" 

189 mock_create_user_section.assert_called_once_with("user123", "class456") 

190 

191 

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 ) 

199 

200 user_service.update_user_section("COMPLETE", "section123") 

201 

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() 

211 

212 

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 ) 

225 

226 result = user_service.get_classes_by_user_id("user123") 

227 

228 expected = [ 

229 { 

230 "userClass": {"id": "class123", "name": "Math", "teacher_id": "teacher123"}, 

231 "studentStatus": "ACTIVE", 

232 } 

233 ] 

234 

235 assert result == expected 

236 

237 

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 ) 

245 

246 result = user_service.get_classes_by_user_id("user123") 

247 

248 assert result == [] 

249 

250 

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 ) 

256 

257 with pytest.raises(DatabaseError) as exc_info: 

258 user_service.get_classes_by_user_id("user123") 

259 

260 assert "Failed to retrieve user classes" in str(exc_info.value) 

261 

262 

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 ] 

271 

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 ] 

295 

296 result = user_service.get_all_users() 

297 

298 assert len(result) == 3 

299 

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" 

305 

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" 

311 

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" 

317 

318 

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 = [] 

324 

325 result = user_service.get_all_users() 

326 assert result == [] 

327 

328 

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)