Coverage for tests/route_tests/test_students.py: 100%

183 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-05 14:02 +0000

1"""Test for the students route.""" 

2 

3# pylint: disable=redefined-outer-name 

4# flake8: noqa: F811 

5 

6import os 

7import sys 

8import uuid 

9from unittest.mock import patch 

10from itsdangerous import URLSafeSerializer 

11import pytest 

12from dotenv import load_dotenv 

13 

14# Add the root directory to the Python path 

15sys.path.append( 

16 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 

17) 

18 

19from core import shared 

20from core.database_mongo_manager import DatabaseMongoManager 

21 

22os.environ["IS_TEST"] = "True" 

23 

24load_dotenv() 

25 

26 

27@pytest.fixture() 

28def client(): 

29 """Fixture to create a test client.""" 

30 from ...app import app # pylint: disable=import-outside-toplevel 

31 

32 app.config["TESTING"] = True 

33 return app.test_client() 

34 

35 

36@pytest.fixture() 

37def database(): 

38 """Fixture to create a test database.""" 

39 

40 database = DatabaseMongoManager( 

41 shared.getenv("MONGO_URI"), shared.getenv("MONGO_DB_TEST", "cs3528_testing") 

42 ) 

43 

44 collections = { 

45 "deadline": database.get_all("deadline"), 

46 "students": database.get_all("students"), 

47 "courses": database.get_all("courses"), 

48 "modules": database.get_all("modules"), 

49 "skills": database.get_all("skills"), 

50 "attempted_skills": database.get_all("attempted_skills"), 

51 "employers": database.get_all("employers"), 

52 "opportunities": database.get_all("opportunities"), 

53 } 

54 

55 for collection in collections: 

56 database.delete_all(collection) 

57 

58 yield database 

59 

60 for collection, items in collections.items(): 

61 database.delete_all(collection) 

62 for item in items: 

63 database.insert(collection, item) 

64 

65 # Cleanup code 

66 database.connection.close() 

67 

68 

69@pytest.fixture() 

70def student_logged_in_client(client, database: DatabaseMongoManager): 

71 """Fixture to login a student.""" 

72 database.add_table("students") 

73 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

74 

75 student = { 

76 "_id": uuid.uuid1().hex, 

77 "first_name": "Dummy", 

78 "last_name": "Student", 

79 "email": "dummy@dummy.com", 

80 "student_id": "11111111", 

81 "modules": [], 

82 } 

83 

84 database.insert("students", student) 

85 url = "/students/login" 

86 

87 client.post( 

88 url, 

89 data={ 

90 "student_id": "11111111", 

91 }, 

92 content_type="application/x-www-form-urlencoded", 

93 ) 

94 otp_serializer = URLSafeSerializer(str(shared.getenv("SECRET_KEY", "secret"))) 

95 

96 with client.session_transaction() as session: 

97 otp = otp_serializer.loads(session["OTP"]) 

98 

99 client.post( 

100 "/students/otp", 

101 data={"otp": otp}, 

102 content_type="application/x-www-form-urlencoded", 

103 ) 

104 

105 yield client 

106 

107 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

108 

109 

110@pytest.fixture() 

111def student_logged_in_client_after_details(client, database: DatabaseMongoManager): 

112 """Fixture to login a student.""" 

113 database.add_table("students") 

114 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

115 

116 student = { 

117 "_id": uuid.uuid1().hex, 

118 "first_name": "Dummy", 

119 "last_name": "Student", 

120 "email": "dummy@dummy.com", 

121 "student_id": "11111111", 

122 "attempted_skills": ["cc4e7ecc9a0e11ef8a1a43569002b932"], 

123 "comments": "Test attempt", 

124 "course": "G401", 

125 "has_car": "false", 

126 "modules": [ 

127 "BI1012", 

128 "BI1512", 

129 ], 

130 "placement_duration": [ 

131 "1_day", 

132 "1_month", 

133 "1_week", 

134 "12_months", 

135 "3_months", 

136 "6_months", 

137 ], 

138 "skills": [ 

139 "015d0008994611ef8360bec4b589d035", 

140 "f087df2893d211ef84b1bbe7a6a5be1f", 

141 ], 

142 } 

143 

144 module1 = { 

145 "_id": "BI1012", 

146 "module_id": "BI1012", 

147 "module_name": "Module 1", 

148 "module_description": "Module 1 description", 

149 } 

150 

151 module2 = { 

152 "_id": "BI1512", 

153 "module_id": "BI1512", 

154 "module_name": "Module 2", 

155 "module_description": "Module 2 description", 

156 } 

157 

158 database.insert("modules", module1) 

159 database.insert("modules", module2) 

160 

161 course = { 

162 "_id": "G401", 

163 "course_id": "G401", 

164 "course_name": "Course 1", 

165 "course_description": "Course 1 description", 

166 } 

167 

168 database.insert("courses", course) 

169 

170 skill1 = { 

171 "_id": "015d0008994611ef8360bec4b589d035", 

172 "skill_name": "Skill 1", 

173 "skill_description": "Skill 1 description", 

174 } 

175 

176 skill2 = { 

177 "_id": "f087df2893d211ef84b1bbe7a6a5be1f", 

178 "skill_name": "Skill 2", 

179 "skill_description": "Skill 2 description", 

180 } 

181 

182 database.insert("skills", skill1) 

183 database.insert("skills", skill2) 

184 

185 attempted_skill = { 

186 "_id": "cc4e7ecc9a0e11ef8a1a43569002b932", 

187 "skill_name": "Attempted Skill", 

188 "skill_description": "Attempted Skill description", 

189 "used": 5, 

190 } 

191 

192 database.insert("attempted_skills", attempted_skill) 

193 

194 database.insert("students", student) 

195 url = "/students/login" 

196 

197 client.post( 

198 url, 

199 data={ 

200 "student_id": "11111111", 

201 }, 

202 content_type="application/x-www-form-urlencoded", 

203 ) 

204 otp_serializer = URLSafeSerializer(str(shared.getenv("SECRET_KEY", "secret"))) 

205 

206 with client.session_transaction() as session: 

207 otp = otp_serializer.loads(session["OTP"]) 

208 

209 client.post( 

210 "/students/otp", 

211 data={"otp": otp}, 

212 content_type="application/x-www-form-urlencoded", 

213 ) 

214 

215 yield client 

216 

217 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

218 database.delete_all_by_field("modules", "module_id", "BI1012") 

219 database.delete_all_by_field("modules", "module_id", "BI1512") 

220 database.delete_all_by_field("courses", "course_id", "G401") 

221 database.delete_all_by_field("skills", "skill_name", "Skill 1") 

222 database.delete_all_by_field("skills", "skill_name", "Skill 2") 

223 database.delete_all_by_field("attempted_skills", "skill_name", "Attempted Skill") 

224 

225 

226@pytest.fixture() 

227def student_logged_in_client_after_preferences(client, database: DatabaseMongoManager): 

228 """Fixture to login a student.""" 

229 database.add_table("students") 

230 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

231 

232 student = { 

233 "_id": uuid.uuid1().hex, 

234 "first_name": "Dummy", 

235 "last_name": "Student", 

236 "email": "dummy@dummy.com", 

237 "student_id": "11111111", 

238 "attempted_skills": ["cc4e7ecc9a0e11ef8a1a43569002b932"], 

239 "comments": "Test attempt", 

240 "course": "G401", 

241 "has_car": "false", 

242 "modules": [ 

243 "BI1012", 

244 "BI1512", 

245 ], 

246 "placement_duration": [ 

247 "1_day", 

248 "1_month", 

249 "1_week", 

250 "12_months", 

251 "3_months", 

252 "6_months", 

253 ], 

254 "skills": [ 

255 "015d0008994611ef8360bec4b589d035", 

256 "f087df2893d211ef84b1bbe7a6a5be1f", 

257 ], 

258 "preferences": [ 

259 "a7f0c09bb90841f783836c6c9e369f6e", 

260 "c9ac8f2a59224cfb95a043dcf4937f91", 

261 "8bf8ddf3383240bea1e78f7a3fec36cf", 

262 "f57b251588c8498cbb9ab3e0fe76846e", 

263 ], 

264 } 

265 

266 modules = [ 

267 { 

268 "_id": "BI1012", 

269 "module_id": "BI1012", 

270 "module_name": "Module 1", 

271 "module_description": "Module 1 description", 

272 }, 

273 { 

274 "_id": "BI1512", 

275 "module_id": "BI1512", 

276 "module_name": "Module 2", 

277 "module_description": "Module 2 description", 

278 }, 

279 ] 

280 

281 courses = [ 

282 { 

283 "_id": "G401", 

284 "course_id": "G401", 

285 "course_name": "Course 1", 

286 "course_description": "Course 1 description", 

287 } 

288 ] 

289 

290 skills = [ 

291 { 

292 "_id": "015d0008994611ef8360bec4b589d035", 

293 "skill_name": "Skill 1", 

294 "skill_description": "Skill 1 description", 

295 }, 

296 { 

297 "_id": "f087df2893d211ef84b1bbe7a6a5be1f", 

298 "skill_name": "Skill 2", 

299 "skill_description": "Skill 2 description", 

300 }, 

301 ] 

302 

303 attempted_skills = [ 

304 { 

305 "_id": "cc4e7ecc9a0e11ef8a1a43569002b932", 

306 "skill_name": "Attempted Skill", 

307 "skill_description": "Attempted Skill description", 

308 "used": 5, 

309 } 

310 ] 

311 

312 employer = { 

313 "_id": uuid.uuid4().hex, 

314 "company_name": "Company 1", 

315 "email": "dummy@dummy.com", 

316 } 

317 

318 opportunities = [ 

319 { 

320 "_id": "a7f0c09bb90841f783836c6c9e369f6e", 

321 "title": "Opportunity 1", 

322 "description": "Assist customers with inquiries, complaints, and product information.", 

323 "url": "www.anatomyassistant.com", 

324 "employer_id": employer["_id"], 

325 "location": "909 Health Sciences Blvd", 

326 "modules_required": [], 

327 "courses_required": [ 

328 "H205", 

329 "WV63", 

330 "G401", 

331 "M1N4", 

332 "C901", 

333 "LL63", 

334 "WX33", 

335 "G403", 

336 ], 

337 "spots_available": 7, 

338 "duration": "1_month", 

339 }, 

340 { 

341 "_id": "c9ac8f2a59224cfb95a043dcf4937f91", 

342 "title": "Opportunity 2", 

343 "description": "Assist customers with inquiries, complaints, and product information.", 

344 "url": "www.anatomyassistant.com", 

345 "employer_id": employer["_id"], 

346 "location": "909 Health Sciences Blvd", 

347 "modules_required": [], 

348 "courses_required": [ 

349 "H205", 

350 "WV63", 

351 "G401", 

352 ], 

353 "spots_available": 7, 

354 "duration": "1_month", 

355 }, 

356 { 

357 "_id": "8bf8ddf3383240bea1e78f7a3fec36cf", 

358 "title": "Opportunity 3", 

359 "description": "Assist customers with inquiries, complaints, and product information.", 

360 "url": "www.anatomyassistant.com", 

361 "employer_id": employer["_id"], 

362 "location": "909 Health Sciences Blvd", 

363 "modules_required": [], 

364 "courses_required": [ 

365 "H205", 

366 "WV63", 

367 "G401", 

368 ], 

369 "spots_available": 7, 

370 "duration": "1_month", 

371 }, 

372 { 

373 "_id": "f57b251588c8498cbb9ab3e0fe76846e", 

374 "title": "Opportunity 4", 

375 "description": "Assist customers with inquiries, complaints, and product information.", 

376 "url": "www.anatomyassistant.com", 

377 "employer_id": employer["_id"], 

378 "location": "909 Health Sciences Blvd", 

379 "modules_required": [], 

380 "courses_required": [ 

381 "H205", 

382 "WV63", 

383 "G401", 

384 ], 

385 "spots_available": 7, 

386 "duration": "1_month", 

387 }, 

388 ] 

389 

390 for module in modules: 

391 database.insert("modules", module) 

392 

393 for course in courses: 

394 database.insert("courses", course) 

395 

396 for skill in skills: 

397 database.insert("skills", skill) 

398 

399 for attempted_skill in attempted_skills: 

400 database.insert("attempted_skills", attempted_skill) 

401 

402 database.insert("employers", employer) 

403 

404 for opportunity in opportunities: 

405 database.insert("opportunities", opportunity) 

406 

407 database.insert("students", student) 

408 url = "/students/login" 

409 

410 client.post( 

411 url, 

412 data={ 

413 "student_id": "11111111", 

414 }, 

415 content_type="application/x-www-form-urlencoded", 

416 ) 

417 otp_serializer = URLSafeSerializer(str(shared.getenv("SECRET_KEY", "secret"))) 

418 

419 with client.session_transaction() as session: 

420 otp = otp_serializer.loads(session["OTP"]) 

421 

422 client.post( 

423 "/students/otp", 

424 data={"otp": otp}, 

425 content_type="application/x-www-form-urlencoded", 

426 ) 

427 

428 yield client 

429 

430 database.delete_all_by_field("students", "email", "dummy@dummy.com") 

431 database.delete_all_by_field("modules", "module_id", "BI1012") 

432 database.delete_all_by_field("modules", "module_id", "BI1512") 

433 database.delete_all_by_field("courses", "course_id", "G401") 

434 database.delete_all_by_field("skills", "skill_name", "Skill 1") 

435 database.delete_all_by_field("skills", "skill_name", "Skill 2") 

436 database.delete_all_by_field("attempted_skills", "skill_name", "Attempted Skill") 

437 database.delete_all_by_field("employers", "email", "dummy@dummy.com") 

438 database.delete_all_by_field("opportunities", "title", "Opportunity 1") 

439 database.delete_all_by_field("opportunities", "title", "Opportunity 2") 

440 database.delete_all_by_field("opportunities", "title", "Opportunity 3") 

441 database.delete_all_by_field("opportunities", "title", "Opportunity 4") 

442 

443 

444def test_student_update_successful(student_logged_in_client): 

445 """Test student update success route.""" 

446 url = "/students/update_success" 

447 

448 response = student_logged_in_client.get(url) 

449 

450 assert response.status_code == 200 

451 

452 

453def test_past_deadline(student_logged_in_client): 

454 """Test student update route.""" 

455 url = "/students/passed_deadline" 

456 

457 response = student_logged_in_client.get(url) 

458 

459 assert response.status_code == 200 

460 

461 

462def test_student_details_wrong_student_redirect(student_logged_in_client): 

463 """Test redirect if student tries to access another student's details.""" 

464 url = "/students/details/123" # Trying to access details for student 123 

465 

466 response = student_logged_in_client.get(url) 

467 

468 assert response.status_code == 302 # Redirect response 

469 

470 

471def test_student_details_deadline_redirects(student_logged_in_client): 

472 """Test redirects if deadlines have passed.""" 

473 url = "/students/details/11111111" 

474 

475 with patch( 

476 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=True 

477 ): 

478 response = student_logged_in_client.get(url, follow_redirects=False) 

479 assert response.status_code == 302 

480 assert response.headers["Location"].endswith("/students/passed_deadline") 

481 

482 with patch("app.DEADLINE_MANAGER.is_past_details_deadline", return_value=True): 

483 response = student_logged_in_client.get(url, follow_redirects=False) 

484 assert response.status_code == 302 

485 assert response.headers["Location"].endswith( 

486 "/students/rank_preferences/11111111" 

487 ) 

488 

489 

490def test_student_details_update_post(student_logged_in_client): 

491 """Test updating student details.""" 

492 url = "/students/details/11111111" 

493 

494 student_update_data = { 

495 "comments": "Updated comment", 

496 "skills": ["Python", "C++"], 

497 "attempted_skills": ["Django"], 

498 "has_car": True, 

499 "placement_duration": ["6_months"], 

500 "modules": ["AI"], 

501 "course": "CS", 

502 } 

503 

504 response = student_logged_in_client.post( 

505 url, 

506 data=student_update_data, 

507 content_type="application/x-www-form-urlencoded", 

508 ) 

509 

510 assert response.status_code == 200 

511 

512 

513def test_student_details_update_get(student_logged_in_client): 

514 """Test updating student details.""" 

515 url = "/students/details/11111111" 

516 

517 with patch( 

518 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=False 

519 ), patch("app.DEADLINE_MANAGER.is_past_details_deadline", return_value=False): 

520 response = student_logged_in_client.get(url) 

521 

522 assert response.status_code == 200 

523 

524 

525def test_rank_preference_different_student_id(student_logged_in_client): 

526 """Test rank preferences route when student is not in session.""" 

527 url = "/students/rank_preferences/123" 

528 

529 response = student_logged_in_client.get(url) 

530 

531 assert response.status_code == 302 

532 

533 

534def test_rank_preferences_deadline_redirects(student_logged_in_client): 

535 """Test redirects if ranking deadline has passed.""" 

536 url = "/students/rank_preferences/11111111" 

537 

538 with patch( 

539 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=True 

540 ): 

541 response = student_logged_in_client.get(url, follow_redirects=False) 

542 assert response.status_code == 302 

543 

544 

545def test_rank_preferences_details_deadline_redirect(student_logged_in_client): 

546 """Test redirect if student has not completed details.""" 

547 url = "/students/rank_preferences/11111111" 

548 

549 with patch("app.DEADLINE_MANAGER.is_past_details_deadline", return_value=False): 

550 response = student_logged_in_client.get(url, follow_redirects=False) 

551 assert response.status_code == 302 

552 

553 

554def test_rank_preferences_update_post( 

555 student_logged_in_client_after_preferences, database 

556): 

557 """Test updating student rank preferences.""" 

558 url = "/students/rank_preferences/11111111" 

559 

560 with patch( 

561 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=False 

562 ), patch("app.DEADLINE_MANAGER.is_past_details_deadline", return_value=True): 

563 response = student_logged_in_client_after_preferences.post( 

564 url, 

565 data={"ranks": "rank_opp3,rank_opp2,rank_opp1"}, 

566 content_type="application/x-www-form-urlencoded", 

567 ) 

568 

569 assert response.status_code == 200 

570 assert database.get_one_by_field("students", "student_id", "11111111")[ 

571 "preferences" 

572 ] == ["opp3", "opp2", "opp1"] 

573 database.delete_all_by_field("students", "student_id", "11111111") 

574 

575 

576def test_rank_preferences_update_post_one_ranking(student_logged_in_client, database): 

577 """Test updating student rank preferences.""" 

578 url = "/students/rank_preferences/11111111" 

579 

580 mock_opportunities = [ 

581 {"_id": "opp1"}, 

582 {"_id": "opp2"}, 

583 {"_id": "opp3"}, 

584 {"_id": "opp4"}, 

585 {"_id": "opp5"}, 

586 {"_id": "opp6"}, 

587 ] 

588 

589 with patch( 

590 "students.routes_student.Student.get_opportunities_by_student", 

591 return_value=mock_opportunities, 

592 ), patch( 

593 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=False 

594 ), patch( 

595 "app.DEADLINE_MANAGER.is_past_details_deadline", return_value=True 

596 ): 

597 

598 response = student_logged_in_client.post( 

599 url, 

600 data={"ranks": "rank_opp1"}, 

601 content_type="application/x-www-form-urlencoded", 

602 ) 

603 

604 assert response.status_code == 400 

605 assert response.json == {"error": "Please rank at least 5 opportunities"} 

606 

607 database.delete_all_by_field("students", "student_id", "11111111") 

608 

609 

610def test_rank_preferences_update_get(student_logged_in_client_after_details): 

611 """Test updating student rank preferences.""" 

612 url = "/students/rank_preferences/11111111" 

613 

614 with patch( 

615 "app.DEADLINE_MANAGER.is_past_student_ranking_deadline", return_value=False 

616 ), patch("app.DEADLINE_MANAGER.is_past_details_deadline", return_value=True): 

617 response = student_logged_in_client_after_details.get(url) 

618 

619 assert response.status_code == 200