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
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
1from datetime import datetime, timezone
3from bson.objectid import ObjectId
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 }
46def serialize_profile(profile):
47 """
48 Converts MongoDB profile document to a JSON-serializable format.
50 Args:
51 profile (dict): The MongoDB document.
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
59def sanitize_profile(profile):
60 """
61 Removes sensitive fields from the profile.
63 Args:
64 profile (dict): The MongoDB document.
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}
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")
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