Coverage for server / utilities / helpers.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-10-04 09:33 +0000

1from datetime import datetime, timezone 

2 

3from bson.objectid import ObjectId 

4 

5 

6def question_serializer(question: dict) -> dict: 

7 return { 

8 "_id": str(question.get("_id", "")), 

9 "question": question.get("question", ""), 

10 "drawingMode": question.get("drawingMode", None), 

11 "questionSubject": question.get("questionSubject", None), # Handle missing key 

12 "questionTopic": question.get("questionTopic", None), # Handle missing key 

13 "choices": question.get("choices", []), # Default to an empty list 

14 "groups": question.get("groups", []), # Single-Stimulus / Multi-Part-Question only; [] for every other type 

15 "rows": question.get("rows", []), # Grid-Question only; [] for every other type 

16 "rowHeaderLabel": question.get("rowHeaderLabel", None), # Grid-Question only — first column header text 

17 "correctAnswer": question.get("correctAnswer", {}), # Default to an empty dict 

18 "questionDetails": question.get("questionDetails", ""), 

19 "assignmentType": question.get("assignmentType", ""), 

20 "questionType": question.get("questionType", ""), 

21 "difficulty": question.get("difficulty", ""), 

22 "studentExpectation": question.get( 

23 "studentExpectation", None 

24 ), # Handle missing key 

25 "keywords": question.get("keywords", []), # Default to an empty list 

26 "questionImages": question.get( 

27 "questionImages", [] 

28 ), # Default to an empty list 

29 "questionGraphs": question.get( 

30 "questionGraphs", [] 

31 ), # Default to an empty list 

32 "teksCode": question.get("teksCode", None), # Handle missing key 

33 "gradeLevel": question.get("gradeLevel", None), # Handle missing key 

34 "releaseDate": question.get("releaseDate", None), # Handle missing key 

35 "points": question.get("points", 0), # Default to 0 

36 "category": question.get("category", None), # Handle missing key 

37 "deleted": question.get("deleted", False), # Default to False 

38 "createdBy": str(question.get("createdBy", "")), # Default to empty string 

39 "createdDate": question.get("createdDate", None), # Handle missing key 

40 "updatedBy": str(question.get("updatedBy", "")), # Default to empty string 

41 "updatedDate": question.get("updatedDate", None), # Handle missing key 

42 "deletedBy": str(question.get("deletedBy", "")), # Default to empty string 

43 "deletedDate": question.get("deletedDate", None), # Handle missing key 

44 } 

45 

46def serialize_profile(profile): 

47 """ 

48 Converts MongoDB profile document to a JSON-serializable format. 

49 

50 Args: 

51 profile (dict): The MongoDB document. 

52 

53 Returns: 

54 dict: Serialized document with ObjectId converted to string. 

55 """ 

56 profile["_id"] = str(profile["_id"]) # Convert ObjectId to string 

57 return profile 

58 

59def sanitize_profile(profile): 

60 """ 

61 Removes sensitive fields from the profile. 

62 

63 Args: 

64 profile (dict): The MongoDB document. 

65 

66 Returns: 

67 dict: A sanitized dictionary without sensitive information. 

68 """ 

69 sensitive_fields = {"password"} # Add other sensitive fields if needed 

70 return {key: value for key, value in profile.items() if key not in sensitive_fields} 

71 

72# BSON dates come back from MongoDB as naive datetimes that are already UTC — 

73# pymongo/motor never attach tzinfo on read. Left alone, FastAPI's JSON encoder 

74# renders those as offset-less ISO strings (e.g. "2025-05-25T00:00:00"), which 

75# browsers parse as LOCAL time instead of UTC. Stamp UTC explicitly so every 

76# response is unambiguous regardless of where the datetime originated. 

77def _serialize_datetime_utc(dt: datetime) -> str: 

78 aware = dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc) 

79 return aware.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") 

80 

81 

82# Convert any remaining ObjectId/datetime fields in the result 

83def serialized_response_object(obj): 

84 if isinstance(obj, ObjectId): 

85 return str(obj) 

86 elif isinstance(obj, datetime): 

87 return _serialize_datetime_utc(obj) 

88 elif isinstance(obj, list): 

89 return [serialized_response_object(item) for item in obj] 

90 elif isinstance(obj, dict): 

91 return {k: serialized_response_object(v) for k, v in obj.items()} 

92 return obj