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
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-05 14:02 +0000
1"""Test for the students route."""
3# pylint: disable=redefined-outer-name
4# flake8: noqa: F811
6import os
7import sys
8import uuid
9from unittest.mock import patch
10from itsdangerous import URLSafeSerializer
11import pytest
12from dotenv import load_dotenv
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)
19from core import shared
20from core.database_mongo_manager import DatabaseMongoManager
22os.environ["IS_TEST"] = "True"
24load_dotenv()
27@pytest.fixture()
28def client():
29 """Fixture to create a test client."""
30 from ...app import app # pylint: disable=import-outside-toplevel
32 app.config["TESTING"] = True
33 return app.test_client()
36@pytest.fixture()
37def database():
38 """Fixture to create a test database."""
40 database = DatabaseMongoManager(
41 shared.getenv("MONGO_URI"), shared.getenv("MONGO_DB_TEST", "cs3528_testing")
42 )
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 }
55 for collection in collections:
56 database.delete_all(collection)
58 yield database
60 for collection, items in collections.items():
61 database.delete_all(collection)
62 for item in items:
63 database.insert(collection, item)
65 # Cleanup code
66 database.connection.close()
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")
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 }
84 database.insert("students", student)
85 url = "/students/login"
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")))
96 with client.session_transaction() as session:
97 otp = otp_serializer.loads(session["OTP"])
99 client.post(
100 "/students/otp",
101 data={"otp": otp},
102 content_type="application/x-www-form-urlencoded",
103 )
105 yield client
107 database.delete_all_by_field("students", "email", "dummy@dummy.com")
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")
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 }
144 module1 = {
145 "_id": "BI1012",
146 "module_id": "BI1012",
147 "module_name": "Module 1",
148 "module_description": "Module 1 description",
149 }
151 module2 = {
152 "_id": "BI1512",
153 "module_id": "BI1512",
154 "module_name": "Module 2",
155 "module_description": "Module 2 description",
156 }
158 database.insert("modules", module1)
159 database.insert("modules", module2)
161 course = {
162 "_id": "G401",
163 "course_id": "G401",
164 "course_name": "Course 1",
165 "course_description": "Course 1 description",
166 }
168 database.insert("courses", course)
170 skill1 = {
171 "_id": "015d0008994611ef8360bec4b589d035",
172 "skill_name": "Skill 1",
173 "skill_description": "Skill 1 description",
174 }
176 skill2 = {
177 "_id": "f087df2893d211ef84b1bbe7a6a5be1f",
178 "skill_name": "Skill 2",
179 "skill_description": "Skill 2 description",
180 }
182 database.insert("skills", skill1)
183 database.insert("skills", skill2)
185 attempted_skill = {
186 "_id": "cc4e7ecc9a0e11ef8a1a43569002b932",
187 "skill_name": "Attempted Skill",
188 "skill_description": "Attempted Skill description",
189 "used": 5,
190 }
192 database.insert("attempted_skills", attempted_skill)
194 database.insert("students", student)
195 url = "/students/login"
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")))
206 with client.session_transaction() as session:
207 otp = otp_serializer.loads(session["OTP"])
209 client.post(
210 "/students/otp",
211 data={"otp": otp},
212 content_type="application/x-www-form-urlencoded",
213 )
215 yield client
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")
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")
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 }
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 ]
281 courses = [
282 {
283 "_id": "G401",
284 "course_id": "G401",
285 "course_name": "Course 1",
286 "course_description": "Course 1 description",
287 }
288 ]
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 ]
303 attempted_skills = [
304 {
305 "_id": "cc4e7ecc9a0e11ef8a1a43569002b932",
306 "skill_name": "Attempted Skill",
307 "skill_description": "Attempted Skill description",
308 "used": 5,
309 }
310 ]
312 employer = {
313 "_id": uuid.uuid4().hex,
314 "company_name": "Company 1",
315 "email": "dummy@dummy.com",
316 }
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 ]
390 for module in modules:
391 database.insert("modules", module)
393 for course in courses:
394 database.insert("courses", course)
396 for skill in skills:
397 database.insert("skills", skill)
399 for attempted_skill in attempted_skills:
400 database.insert("attempted_skills", attempted_skill)
402 database.insert("employers", employer)
404 for opportunity in opportunities:
405 database.insert("opportunities", opportunity)
407 database.insert("students", student)
408 url = "/students/login"
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")))
419 with client.session_transaction() as session:
420 otp = otp_serializer.loads(session["OTP"])
422 client.post(
423 "/students/otp",
424 data={"otp": otp},
425 content_type="application/x-www-form-urlencoded",
426 )
428 yield client
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")
444def test_student_update_successful(student_logged_in_client):
445 """Test student update success route."""
446 url = "/students/update_success"
448 response = student_logged_in_client.get(url)
450 assert response.status_code == 200
453def test_past_deadline(student_logged_in_client):
454 """Test student update route."""
455 url = "/students/passed_deadline"
457 response = student_logged_in_client.get(url)
459 assert response.status_code == 200
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
466 response = student_logged_in_client.get(url)
468 assert response.status_code == 302 # Redirect response
471def test_student_details_deadline_redirects(student_logged_in_client):
472 """Test redirects if deadlines have passed."""
473 url = "/students/details/11111111"
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")
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 )
490def test_student_details_update_post(student_logged_in_client):
491 """Test updating student details."""
492 url = "/students/details/11111111"
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 }
504 response = student_logged_in_client.post(
505 url,
506 data=student_update_data,
507 content_type="application/x-www-form-urlencoded",
508 )
510 assert response.status_code == 200
513def test_student_details_update_get(student_logged_in_client):
514 """Test updating student details."""
515 url = "/students/details/11111111"
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)
522 assert response.status_code == 200
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"
529 response = student_logged_in_client.get(url)
531 assert response.status_code == 302
534def test_rank_preferences_deadline_redirects(student_logged_in_client):
535 """Test redirects if ranking deadline has passed."""
536 url = "/students/rank_preferences/11111111"
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
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"
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
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"
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 )
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")
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"
580 mock_opportunities = [
581 {"_id": "opp1"},
582 {"_id": "opp2"},
583 {"_id": "opp3"},
584 {"_id": "opp4"},
585 {"_id": "opp5"},
586 {"_id": "opp6"},
587 ]
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 ):
598 response = student_logged_in_client.post(
599 url,
600 data={"ranks": "rank_opp1"},
601 content_type="application/x-www-form-urlencoded",
602 )
604 assert response.status_code == 400
605 assert response.json == {"error": "Please rank at least 5 opportunities"}
607 database.delete_all_by_field("students", "student_id", "11111111")
610def test_rank_preferences_update_get(student_logged_in_client_after_details):
611 """Test updating student rank preferences."""
612 url = "/students/rank_preferences/11111111"
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)
619 assert response.status_code == 200