diff --git a/app.py b/app.py index e7be31f..abac8ac 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,8 @@ from chalicelib.api.announcements import announcements_api from chalicelib.api.insights import insights_api from chalicelib.api.members import members_api -from chalicelib.api.events import events_api +from chalicelib.api.events_member import events_member_api +from chalicelib.api.events_rush import events_rush_api from chalicelib.api.accountability import accountability_api from chalicelib.api.monitoring import monitoring_api @@ -37,7 +38,8 @@ app.register_blueprint(applicants_api) app.register_blueprint(insights_api) app.register_blueprint(members_api) -app.register_blueprint(events_api) +app.register_blueprint(events_member_api) +app.register_blueprint(events_rush_api) app.register_blueprint(accountability_api) app.register_blueprint(monitoring_api) diff --git a/chalicelib/api/accountability.py b/chalicelib/api/accountability.py index 464c148..e82c988 100644 --- a/chalicelib/api/accountability.py +++ b/chalicelib/api/accountability.py @@ -1,12 +1,13 @@ from chalice import Blueprint from chalicelib.decorators import auth from chalicelib.services.AccountabilityService import accountability_service +from chalicelib.models.roles import Roles accountability_api = Blueprint(__name__) @accountability_api.route("/accountability", methods=["GET"], cors=True) -@auth(accountability_api, roles=["admin"]) +@auth(accountability_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_accountability(): if accountability_api.current_request.query_params: page = int(accountability_api.current_request.query_params.get("page", 0)) diff --git a/chalicelib/api/applicants.py b/chalicelib/api/applicants.py index 7542162..18b3926 100644 --- a/chalicelib/api/applicants.py +++ b/chalicelib/api/applicants.py @@ -1,10 +1,8 @@ from chalice import Blueprint from chalicelib.services.ApplicantService import applicant_service -from chalicelib.handlers.error_handler import handle_exceptions from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError applicants_api = Blueprint(__name__) diff --git a/chalicelib/api/events.py b/chalicelib/api/events.py deleted file mode 100644 index 90af958..0000000 --- a/chalicelib/api/events.py +++ /dev/null @@ -1,117 +0,0 @@ -from chalice import Blueprint -from chalicelib.decorators import auth -from chalicelib.services.EventService import event_service - -events_api = Blueprint(__name__) - - -@events_api.route("/timeframes", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_timeframe(): - data = events_api.current_request.json_body - return event_service.create_timeframe(data) - - -@events_api.route("/timeframes", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_all_timeframes(): - return event_service.get_all_timeframes() - - -@events_api.route("/timeframes/{timeframe_id}", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_timeframe(timeframe_id: str): - return event_service.get_timeframe(timeframe_id) - - -@events_api.route("/timeframes/{timeframe_id}", methods=["DELETE"], cors=True) -@auth(events_api, roles=["admin"]) -def delete_timeframe(timeframe_id: str): - return event_service.delete_timeframe(timeframe_id) - - -@events_api.route("/timeframes/{timeframe_id}/events", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_event(timeframe_id: str): - data = events_api.current_request.json_body - return event_service.create_event(timeframe_id, data) - - -@events_api.route("/events/{event_id}", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_event(event_id: str): - return event_service.get_event(event_id) - - -@events_api.route("/timeframes/{timeframe_id}/sheets", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_timeframe_sheets(timeframe_id: str): - return event_service.get_timeframe_sheets(timeframe_id) - - -@events_api.route("/events/{event_id}/checkin", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def checkin(event_id: str): - data = events_api.current_request.json_body - return event_service.checkin(event_id, data) - - -@events_api.route("/events/{event_id}", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def update_event(event_id: str): - pass - - -@events_api.route("/events/{event_id}", methods=["DELETE"], cors=True) -@auth(events_api, roles=["admin"]) -def delete_event(event_id: str): - return event_service.delete(event_id) - - -@events_api.route("/events/rush", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_rush_events(): - return event_service.get_rush_categories_and_events() - - -@events_api.route("/events/rush/{event_id}", methods=["GET"], cors=True) -def get_rush_event(event_id): - return event_service.get_rush_event(event_id) - - -@events_api.route("/events/rush/category", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_rush_category(): - data = events_api.current_request.json_body - return event_service.create_rush_category(data) - - -@events_api.route("/events/rush", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_rush_event(): - data = events_api.current_request.json_body - return event_service.create_rush_event(data) - - -@events_api.route("/events/rush", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def modify_rush_event(): - data = events_api.current_request.json_body - return event_service.modify_rush_event(data) - -@events_api.route("/events/rush/settings", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def modify_rush_settings(): - data = events_api.current_request.json_body - return event_service.modify_rush_settings(data) - - -@events_api.route("/events/rush/checkin/{event_id}", methods=["POST"], cors=True) -def checkin_rush(event_id): - data = events_api.current_request.json_body - return event_service.checkin_rush(event_id, data) - - -@events_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) -def delete_rush_event(event_id): - return event_service.delete_rush_event(event_id) \ No newline at end of file diff --git a/chalicelib/api/events_member.py b/chalicelib/api/events_member.py new file mode 100644 index 0000000..cba9fad --- /dev/null +++ b/chalicelib/api/events_member.py @@ -0,0 +1,69 @@ +from chalice import Blueprint +from chalicelib.decorators import auth +from chalicelib.services.EventsMemberService import events_member_service +from chalicelib.models.roles import Roles + +events_member_api = Blueprint(__name__) + + +@events_member_api.route("/timeframes", methods=["POST"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN]) +def create_timeframe(): + data = events_member_api.current_request.json_body + return events_member_service.create_timeframe(data) + + +@events_member_api.route("/timeframes", methods=["GET"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_all_timeframes(): + return events_member_service.get_all_timeframes() + + +@events_member_api.route("/timeframes/{timeframe_id}", methods=["GET"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_timeframe(timeframe_id: str): + return events_member_service.get_timeframe(timeframe_id) + + +@events_member_api.route("/timeframes/{timeframe_id}", methods=["DELETE"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN]) +def delete_timeframe(timeframe_id: str): + return events_member_service.delete_timeframe(timeframe_id) + + +@events_member_api.route("/timeframes/{timeframe_id}/events", methods=["POST"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN]) +def create_event(timeframe_id: str): + data = events_member_api.current_request.json_body + return events_member_service.create_event(timeframe_id, data) + + +@events_member_api.route("/events/{event_id}", methods=["GET"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_event(event_id: str): + return events_member_service.get_event(event_id) + + +@events_member_api.route("/timeframes/{timeframe_id}/sheets", methods=["GET"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_timeframe_sheets(timeframe_id: str): + return events_member_service.get_timeframe_sheets(timeframe_id) + + +@events_member_api.route("/events/{event_id}/checkin", methods=["POST"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def checkin(event_id: str): + data = events_member_api.current_request.json_body + return events_member_service.checkin(event_id, data) + + +@events_member_api.route("/events/{event_id}", methods=["PATCH"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN]) +def update_event(event_id: str): + pass + + +@events_member_api.route("/events/{event_id}", methods=["DELETE"], cors=True) +@auth(events_member_api, roles=[Roles.ADMIN]) +def delete_event(event_id: str): + return events_member_service.delete(event_id) \ No newline at end of file diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py new file mode 100644 index 0000000..9ae5120 --- /dev/null +++ b/chalicelib/api/events_rush.py @@ -0,0 +1,70 @@ +from chalice import Blueprint +from chalicelib.decorators import auth +from chalicelib.services.EventsRushService import events_rush_service +from chalicelib.models.roles import Roles + + +events_rush_api = Blueprint(__name__) + + +@events_rush_api.route("/events/rush", methods=["GET"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_rush_events(): + return events_rush_service.get_rush_categories_and_events() + + +@events_rush_api.route("/events/rush/{event_id}", methods=["POST"], cors=True) +def get_rush_event(event_id): + data = events_rush_api.current_request.json_body + return events_rush_service.get_rush_event(event_id=event_id, data=data) + + +@events_rush_api.route("/events/rush/category", methods=["POST"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def create_rush_category(): + data = events_rush_api.current_request.json_body + return events_rush_service.create_rush_category(data) + + +@events_rush_api.route("/events/rush", methods=["POST"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def create_rush_event(): + data = events_rush_api.current_request.json_body + return events_rush_service.create_rush_event(data) + + +@events_rush_api.route("/events/rush", methods=["PATCH"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def modify_rush_event(): + data = events_rush_api.current_request.json_body + return events_rush_service.modify_rush_event(data) + +@events_rush_api.route("/events/rush/settings", methods=["PATCH"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def modify_rush_settings(): + data = events_rush_api.current_request.json_body + return events_rush_service.modify_rush_settings(data) + + +@events_rush_api.route("/events/rush/checkin/{event_id}", methods=["POST"], cors=True) +def checkin_rush(event_id): + data = events_rush_api.current_request.json_body + return events_rush_service.checkin_rush(event_id, data) + + +@events_rush_api.route("/events/rush/default", methods=["POST"], cors=True) +def get_rush_events_default_category(): + data = events_rush_api.current_request.json_body + return events_rush_service.get_rush_events_default_category(data) + + +@events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def delete_rush_event(event_id): + return events_rush_service.delete_rush_event(event_id) + + +@events_rush_api.route("/events/rush/{category_id}/analytics", methods=["GET"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) +def get_rush_category_analytics(category_id): + return events_rush_service.get_rush_category_analytics(category_id=category_id) \ No newline at end of file diff --git a/chalicelib/api/insights.py b/chalicelib/api/insights.py index 17d3f0f..5cb376c 100644 --- a/chalicelib/api/insights.py +++ b/chalicelib/api/insights.py @@ -1,11 +1,9 @@ # TO BE COMPLETED: create api routes for analytics from chalice import Blueprint from chalicelib.services.InsightsService import insights_service -from chalicelib.handlers.error_handler import handle_exceptions from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError insights_api = Blueprint(__name__) diff --git a/chalicelib/api/listings.py b/chalicelib/api/listings.py index 7cd9c9c..482f24f 100644 --- a/chalicelib/api/listings.py +++ b/chalicelib/api/listings.py @@ -4,7 +4,6 @@ from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError listings_api = Blueprint(__name__) @@ -56,8 +55,8 @@ def toggle_visibility(id): @listings_api.route("/listings/{id}/update-field", methods=["PATCH"], cors=True) -@handle_exceptions @auth(listings_api, roles=[Roles.ADMIN, Roles.MEMBER]) +@handle_exceptions def update_listing_field_route(id): try: return listing_service.update_field_route( diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index f8b19c7..3ae8f44 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -6,8 +6,24 @@ members_api = Blueprint(__name__) +@members_api.route("/member/{user_id}", methods=["GET"], cors=True) +@auth(members_api, roles=[Roles.ADMIN, Roles.MEMBER]) +def get_member(user_id): + member = member_service.get_by_id(user_id) + return member if member else {} + + +@members_api.route("/member/{user_id}", methods=["PUT"], cors=True) +@auth(members_api, roles=[Roles.MEMBER, Roles.ADMIN]) +def update_member(user_id): + data = members_api.current_request.json_body + return member_service.update( + user_id=user_id, data=data, headers=members_api.current_request.headers + ) + + @members_api.route("/members", methods=["GET"], cors=True) -@auth(members_api, roles=["admin", "member"]) +@auth(members_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_all_members(): """Fetches all members who have access to WhyPhi.""" return member_service.get_all() @@ -17,6 +33,7 @@ def get_all_members(): @auth(members_api, roles=[]) def onboard_member(user_id): data = members_api.current_request.json_body + # TODO: If isNewUser is False, reject onboarding data["isNewUser"] = False if member_service.onboard(user_id, data): @@ -25,7 +42,7 @@ def onboard_member(user_id): "message": "User updated successfully.", } else: - { "status": False} + return {"status": False} @members_api.route("/members", methods=["POST"], cors=True) @@ -34,14 +51,22 @@ def create_member(): data = members_api.current_request.json_body return member_service.create(data) + @members_api.route("/members", methods=["DELETE"], cors=True) @auth(members_api, roles=[Roles.ADMIN]) def delete_members(): data = members_api.current_request.json_body return member_service.delete(data) + @members_api.route("/members/{user_id}/roles", methods=["PATCH"], cors=True) @auth(members_api, roles=[Roles.ADMIN]) def update_member_roles(user_id): data = members_api.current_request.json_body - return member_service.update_roles(user_id, data["roles"]) \ No newline at end of file + return member_service.update_roles(user_id, data["roles"]) + + +@members_api.route("/members/family-tree", methods=["GET"], cors=True) +@auth(members_api, roles=[Roles.MEMBER]) +def get_family_tree(): + return member_service.get_family_tree() diff --git a/chalicelib/modules/mongo.py b/chalicelib/modules/mongo.py index a3783cb..1bbc465 100644 --- a/chalicelib/modules/mongo.py +++ b/chalicelib/modules/mongo.py @@ -10,10 +10,6 @@ class MongoModule: def __init__(self, use_mock=False): """Establishes connection to MongoDB server""" - self.use_mock = use_mock - if use_mock: - self.mongo_client = mongomock.MongoClient() - return self.is_prod = os.environ.get("ENV") == "prod" self.ssm_client = boto3.client("ssm") @@ -25,20 +21,21 @@ def __init__(self, use_mock=False): )["Parameter"]["Value"] self.uri = f"mongodb+srv://{self.user}:{self.password}@cluster0.9gtht.mongodb.net/?retryWrites=true&w=majority" - self.mongo_client = MongoClient(self.uri) + # if use_mock is true -> use monogmock to execute tests with fake db + self.mongo_client = mongomock.MongoClient() if use_mock else MongoClient(self.uri) def add_env_suffix(func): - def wrapper(self, collection_name: str, *args, **kwargs): + def wrapper(self, collection: str, *args, **kwargs): # users collection is dependent on vault so suffix should not be appended - if collection_name == "users": - return func(self, collection_name, *args, **kwargs) + if collection == "users": + return func(self, collection, *args, **kwargs) if self.is_prod: - collection_name += "-prod" + collection += "-prod" else: - collection_name += "-dev" + collection += "-dev" - return func(self, collection_name, *args, **kwargs) + return func(self, collection, *args, **kwargs) return wrapper @@ -108,14 +105,21 @@ def find_one_document(self, collection: str, query: dict): raise @add_env_suffix - def get_all_data_from_collection(self, collection: str): - """Fetches all data from the specified collection.""" - if collection is None: - raise ValueError("The 'collection' parameter cannot be None") + def get_data_from_collection(self, collection: str, filter: dict = {}): + """ + Fetches all data from the specified collection. + + Args: + collection (str): The name of the collection to update the document in. + filter (dict, optional): Dictionary of filters to be applied to collection query. + + Returns: + list: List of documents in the collection that match filter. + """ try: # Use the specified collection to fetch data - cursor = self.mongo_client.vault[collection].find() + cursor = self.mongo_client.vault[collection].find(filter) data = list(cursor) # Convert the cursor to a list if data is None: diff --git a/chalicelib/s3.py b/chalicelib/s3.py index b1d09b6..49fd510 100644 --- a/chalicelib/s3.py +++ b/chalicelib/s3.py @@ -1,11 +1,8 @@ import boto3 import os -import json -from datetime import datetime from chalicelib.utils import decode_base64 - class S3Client: def __init__(self): self.bucket_name = "whyphi-zap" @@ -13,30 +10,55 @@ def __init__(self): self.s3 = boto3.client("s3") def upload_binary_data(self, path: str, data: str) -> str: - """Uploads resume to S3 Bucket and returns path""" + """Uploads object to S3 Bucket and returns path""" # Set path if self.is_prod: path = f"prod/{path}" else: path = f"dev/{path}" - + # Split parts of base64 data - parts = data.split(',') - metadata, base64_data = parts[0], parts[1] + parts = data.split(",") + metadata, base64_data = parts[0], parts[1] # Extract content type from metadata - content_type = metadata.split(';')[0][5:] # Remove "data:" prefix + content_type = metadata.split(";")[0][5:] # Remove "data:" prefix binary_data = decode_base64(base64_data) - + # Upload binary data as object with content type set self.s3.put_object( - Bucket=self.bucket_name, Key=path, Body=binary_data, ContentType=content_type + Bucket=self.bucket_name, + Key=path, + Body=binary_data, + ContentType=content_type, ) # Retrieve endpoint of object s3_endpoint = f"https://{self.bucket_name}.s3.amazonaws.com/" object_url = s3_endpoint + path - + return object_url -s3 = S3Client() \ No newline at end of file + def delete_binary_data(self, object_id: str) -> str: + """Deletes object from s3 and returns response + Args: + object_id (str): The key (path) of the object to delete from the S3 bucket. + e.g. dev/image/rush/66988908fd70b2c44bf2305d/199bb28f-b54c-48a3-9b94-1c95eab61f7d/infosession2.png + + Returns: + str: A message indicating the result of the deletion operation. + + Documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_object.html + """ + if self.is_prod: + path = f"prod/{object_id}" + else: + path = f"dev/{object_id}" + + # delete binary data given bucket_name and key + response = self.s3.delete_object(Bucket=self.bucket_name, Key=path) + + return response + + +s3 = S3Client() diff --git a/chalicelib/services/AccountabilityService.py b/chalicelib/services/AccountabilityService.py index b6f56c6..8478da0 100644 --- a/chalicelib/services/AccountabilityService.py +++ b/chalicelib/services/AccountabilityService.py @@ -1,4 +1,3 @@ -from chalice import NotFoundError, BadRequestError from chalicelib.modules.google_sheets import GoogleSheetsModule diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py deleted file mode 100644 index b3701ca..0000000 --- a/chalicelib/services/EventService.py +++ /dev/null @@ -1,457 +0,0 @@ -from chalicelib.modules.mongo import mongo_module -from chalice import NotFoundError, BadRequestError, UnauthorizedError -import json -from bson import ObjectId -import datetime -from chalicelib.modules.google_sheets import GoogleSheetsModule -from chalicelib.modules.ses import ses, SesDestination -from typing import Optional - - -class EventService: - class BSONEncoder(json.JSONEncoder): - """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" - - def default(self, o): - if isinstance(o, datetime.datetime): - return o.isoformat() - elif isinstance(o, ObjectId): - return str(o) - return super().default(o) - - def __init__(self): - self.collection_prefix = "events-" - - def create_timeframe(self, timeframe_data: dict): - timeframe_data["dateCreated"] = datetime.datetime.now() - mongo_module.insert_document( - f"{self.collection_prefix}timeframe", timeframe_data - ) - - def get_timeframe(self, timeframe_id: str): - timeframe = mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - return json.dumps(timeframe, cls=self.BSONEncoder) - - def get_all_timeframes(self): - """Retrieve all timeframes from the database.""" - timeframes = mongo_module.get_all_data_from_collection( - f"{self.collection_prefix}timeframe" - ) - - return json.dumps(timeframes, cls=self.BSONEncoder) - - def delete_timeframe(self, timeframe_id: str): - # Check if timeframe exists and if it doesn't return errors - timeframe = mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - if timeframe is None: - raise NotFoundError(f"Timeframe with ID {timeframe_id} does not exist.") - - # If timeframe exists, get the eventIds (children) - event_ids = [str(event["_id"]) for event in timeframe["events"]] - - # Delete all the events in the timeframe - for event_id in event_ids: - mongo_module.delete_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - # If timeframe exists, delete the timeframe document - mongo_module.delete_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - def create_event(self, timeframe_id: str, event_data: dict): - event_data["dateCreated"] = datetime.datetime.now() - event_data["timeframeId"] = timeframe_id - event_data["usersAttended"] = [] - - # Get Google Spreadsheet ID from timeframe - timeframe_doc = mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - spreadsheet_id = timeframe_doc["spreadsheetId"] - - # Add event name to Google Sheets - gs = GoogleSheetsModule() - col = gs.find_next_available_col(spreadsheet_id, event_data["sheetTab"]) - gs.add_event(spreadsheet_id, event_data["sheetTab"], event_data["name"], col) - - # Append next available col value to event data - event_data["spreadsheetCol"] = col - - # Insert the event in event collection - event_id = mongo_module.insert_document( - f"{self.collection_prefix}event", event_data - ) - - event_data["eventId"] = str(event_id) - - # Insert child event in timeframe collection - mongo_module.update_document( - f"{self.collection_prefix}timeframe", - timeframe_id, - {"$push": {"events": event_data}}, - ) - - return json.dumps(event_data, cls=self.BSONEncoder) - - def get_event(self, event_id: str): - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - return json.dumps(event, cls=self.BSONEncoder) - - def checkin(self, event_id: str, user: dict) -> dict: - """Checks in a user to an event. - - Arguments: - event_id {str} -- ID of the event to check into. - user {dict} -- Dictionary containing user ID and name. - - Returns: - dict -- Dictionary containing status and message. - """ - user_id, user_email = user["id"], user["email"] - member = mongo_module.get_document_by_id(f"users", user_id) - if member is None: - raise NotFoundError(f"User with ID {user_id} does not exist.") - - user_name = member["name"] - - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - if any(d["userId"] == user_id for d in event["usersAttended"]): - raise BadRequestError(f"{user_name} has already checked in.") - - checkin_data = { - "userId": user_id, - "name": user_name, - "dateCheckedIn": datetime.datetime.now(), - } - - # Get timeframe document to get Google Sheets info - timeframe = mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", event["timeframeId"] - ) - - # Get Google Sheets information - ss_id = timeframe["spreadsheetId"] - - # Initialize Google Sheets Module - gs = GoogleSheetsModule() - - # Find row in Google Sheets that matches user's email - row_num = gs.find_matching_email( - spreadsheet_id=ss_id, - sheet_name=event["sheetTab"], - col="C", - email_to_match=user_email, - ) - - if row_num == -1: - return { - "status": False, - "message": f"{user_name} was not found in the sheet.", - } - - # Update Google Sheets cell with a "1" if user has checked in - gs.update_row( - spreadsheet_id=ss_id, - sheet_name=event["sheetTab"], - col=event["spreadsheetCol"], - row=row_num + 1, - data=[["1"]], - ) - - # Update event collection with checkin data - mongo_module.update_document( - f"{self.collection_prefix}event", - event_id, - {"$push": {"usersAttended": checkin_data}}, - ) - - # Send email to user that has checked in - email_content = f""" - Hi {user_name},

- - Thank you for checking in to {event["name"]}! This is a confirmation that you have checked in.

- - Regards,
- PCT Tech Team

- - ** Please note: Do not reply to this email. This email is sent from an unattended mailbox. Replies will not be read. - """ - - ses_destination = SesDestination(tos=[user_email]) - ses.send_email( - source="checkin-bot@why-phi.com", - destination=ses_destination, - subject=f"Check-in Confirmation: {event['name']}", - text=email_content, - html=email_content, - ) - - return { - "status": True, - "message": f"{user_name} has successfully been checked in.", - } - - def delete(self, event_id: str): - # Check if event exists and if it doesn't return errors - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - if event is None: - raise NotFoundError(f"Event with ID {event_id} does not exist.") - - # If event exists, get the timeframeId (parent) - timeframe_id = event["timeframeId"] - - # Remove event from timeframe - mongo_module.update_document( - f"{self.collection_prefix}timeframe", - timeframe_id, - {"$pull": {"events": {"_id": ObjectId(event_id)}}}, - ) - - # Delete the event document - mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) - - def get_timeframe_sheets(self, timeframe_id: str): - timeframe = mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - if "spreadsheetId" not in timeframe or timeframe["spreadsheetId"] == "": - return [] - - gs = GoogleSheetsModule() - sheets = gs.get_sheets(timeframe["spreadsheetId"], include_properties=False) - return [sheet["title"] for sheet in sheets] - - def get_rush_categories_and_events(self): - rush_categories = mongo_module.get_all_data_from_collection( - f"{self.collection_prefix}rush" - ) - - return json.dumps(rush_categories, cls=self.BSONEncoder) - - def create_rush_category(self, data: dict): - data["dateCreated"] = datetime.datetime.now() - data["events"] = [] - mongo_module.insert_document(f"{self.collection_prefix}rush", data) - return - - def create_rush_event(self, data: dict): - data["dateCreated"] = datetime.datetime.now() - data["lastModified"] = data["dateCreated"] - - data_copy = data.copy() - data_copy.pop("categoryId", None) - - # Add event to its own collection - data["attendees"] = [] - data["numAttendees"] = 0 - event_id = mongo_module.insert_document( - f"{self.collection_prefix}rush-event", data - ) - - data_copy["eventId"] = str(event_id) - - # Add event to rush category - mongo_module.update_document( - f"{self.collection_prefix}rush", - data["categoryId"], - {"$push": {"events": data_copy}}, - ) - - return - - def modify_rush_event(self, data: dict): - - try: - data["lastModified"] = datetime.datetime.now() - - eventId = data["eventId"] - - # Check if event exists in the rush-event collection - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", eventId - ) - - if not event: - raise Exception("Event does not exist.") - - event_category_id = event["categoryId"] - - # Merge the existing event data with the new data - updated_event = {**event, **data} - - # categoryId not needed on rush collection array elements - updated_event.pop("categoryId") - - # Define array update query and filters - update_query = { - "$set": { - "events.$[eventElem]": updated_event - } - } - - array_filters = [ - {"eventElem.eventId": eventId} - ] - - # Modify the event in its category (rush collection) - mongo_module.update_document( - f"{self.collection_prefix}rush", - event_category_id, - update_query, - array_filters=array_filters - ) - - # Modify actual event document (rush-event collection) - mongo_module.update_document( - f"{self.collection_prefix}rush-event", - eventId, - {"$set": data}, - ) - return - - - except Exception as e: - print("error is ", e) - raise BadRequestError(e) - - def modify_rush_settings(self, data: dict): - """ - Updates defaultRushCategory from the rush collection - - Parameters - ---------- - data: dict - contains default_rush_category_id ID of the rush category to be default - - Raises - ------ - BadRequestError - If default_rush_category_id is not in the rush collection - """ - default_rush_category_id = data["defaultRushCategoryId"] - - collection = f"{self.collection_prefix}rush" - - # Set all defaultRushCategory fields to false - mongo_module.update_many_documents( - collection, - {}, - {"$set": {"defaultRushCategory": False}} - ) - - # if default_rush_category_id is "" --> reset defaultRushCategory - if not default_rush_category_id: - return - - # Update the specified document to set its defaultRushCategory to true - result = mongo_module.update_document_by_id( - collection, - default_rush_category_id, - {"defaultRushCategory": True} - ) - - if not result: - raise ValueError(f"Document with ID {default_rush_category_id} was not found or could not be updated.") - - return - - def get_rush_event(self, event_id: str, hide_attendees: bool = True): - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", event_id - ) - - if hide_attendees: - event.pop("attendees", None) - event.pop("numAttendees", None) - - event.pop("code") - - return json.dumps(event, cls=self.BSONEncoder) - - def checkin_rush(self, event_id: str, user_data: dict): - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", event_id - ) - - code = user_data["code"] - user_data.pop("code") - - if code != event["code"]: - raise UnauthorizedError("Invalid code.") - - if any(d["email"] == user_data["email"] for d in event["attendees"]): - raise BadRequestError("User has already checked in.") - - user_data["checkinTime"] = datetime.datetime.now() - event["attendees"].append(user_data) - event["numAttendees"] += 1 - - mongo_module.update_document( - f"{self.collection_prefix}rush-event", - event_id, - {"$set": event}, - ) - - return - - def delete_rush_event(self, event_id: str): - """ - Deletes an rush event from the rush-event collection - - Parameters - ---------- - event_id : str - ID of the event to be deleted - - Raises - ------ - BadRequestError - If the event does not exist in the rush-event collection - """ - try: - # Check if event exists in the rush-event collection - event = mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", event_id - ) - - if not event: - raise Exception("Event does not exist.") - - event_category_id = event["categoryId"] - - # Delete the event from its category - mongo_module.update_document( - f"{self.collection_prefix}rush", - event_category_id, - {"$pull": {"events": {"eventId": event_id}}}, - ) - - # Delete event data from the rush-event collection - mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", event_id - ) - return - - except Exception as e: - raise BadRequestError(e) - - -event_service = EventService() diff --git a/chalicelib/services/EventsMemberService.py b/chalicelib/services/EventsMemberService.py new file mode 100644 index 0000000..e70d43e --- /dev/null +++ b/chalicelib/services/EventsMemberService.py @@ -0,0 +1,248 @@ +from chalicelib.modules.mongo import mongo_module +from chalice import NotFoundError, BadRequestError +import json +from bson import ObjectId +import datetime +from chalicelib.modules.google_sheets import GoogleSheetsModule +from chalicelib.modules.ses import ses, SesDestination + + +class EventsMemberService: + class BSONEncoder(json.JSONEncoder): + """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" + + def default(self, o): + if isinstance(o, datetime.datetime): + return o.isoformat() + elif isinstance(o, ObjectId): + return str(o) + return super().default(o) + + def __init__(self, mongo_module=mongo_module): + self.mongo_module = mongo_module + self.collection_prefix = "events-" + + def create_timeframe(self, timeframe_data: dict): + timeframe_data["dateCreated"] = datetime.datetime.now() + self.mongo_module.insert_document( + collection=f"{self.collection_prefix}timeframe", data=timeframe_data + ) + return {"msg": True} + + def get_timeframe(self, timeframe_id: str): + timeframe = self.mongo_module.get_document_by_id( + collection=f"{self.collection_prefix}timeframe", document_id=timeframe_id + ) + + return json.dumps(timeframe, cls=self.BSONEncoder) + + def get_all_timeframes(self): + """Retrieve all timeframes from the database.""" + timeframes = self.mongo_module.get_data_from_collection( + f"{self.collection_prefix}timeframe" + ) + + return json.dumps(timeframes, cls=self.BSONEncoder) + + def delete_timeframe(self, timeframe_id: str): + # Check if timeframe exists and if it doesn't return errors + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + if timeframe is None: + raise NotFoundError(f"Timeframe with ID {timeframe_id} does not exist.") + + # If timeframe exists, get the eventIds (children) + event_ids = [str(event["_id"]) for event in timeframe["events"]] + + # Delete all the events in the timeframe + for event_id in event_ids: + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + # If timeframe exists, delete the timeframe document + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + return {"statusCode": 200} + + def create_event(self, timeframe_id: str, event_data: dict): + event_data["dateCreated"] = datetime.datetime.now() + event_data["timeframeId"] = timeframe_id + event_data["usersAttended"] = [] + + # Get Google Spreadsheet ID from timeframe + timeframe_doc = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + spreadsheet_id = timeframe_doc["spreadsheetId"] + + # Add event name to Google Sheets + gs = GoogleSheetsModule() + col = gs.find_next_available_col(spreadsheet_id, event_data["sheetTab"]) + gs.add_event(spreadsheet_id, event_data["sheetTab"], event_data["name"], col) + + # Append next available col value to event data + event_data["spreadsheetCol"] = col + + # Insert the event in event collection + event_id = self.mongo_module.insert_document( + f"{self.collection_prefix}event", event_data + ) + + event_data["eventId"] = str(event_id) + + # Insert child event in timeframe collection + self.mongo_module.update_document( + f"{self.collection_prefix}timeframe", + timeframe_id, + {"$push": {"events": event_data}}, + ) + + return json.dumps(event_data, cls=self.BSONEncoder) + + def get_event(self, event_id: str): + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + return json.dumps(event, cls=self.BSONEncoder) + + def checkin(self, event_id: str, user: dict) -> dict: + """Checks in a user to an event. + + Arguments: + event_id {str} -- ID of the event to check into. + user {dict} -- Dictionary containing user ID and name. + + Returns: + dict -- Dictionary containing status and message. + """ + user_id, user_email = user["id"], user["email"] + member = self.mongo_module.get_document_by_id("users", user_id) + if member is None: + raise NotFoundError(f"User with ID {user_id} does not exist.") + + user_name = member["name"] + + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + if any(d["userId"] == user_id for d in event["usersAttended"]): + raise BadRequestError(f"{user_name} has already checked in.") + + checkin_data = { + "userId": user_id, + "name": user_name, + "dateCheckedIn": datetime.datetime.now(), + } + + # Get timeframe document to get Google Sheets info + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", event["timeframeId"] + ) + + # Get Google Sheets information + ss_id = timeframe["spreadsheetId"] + + # Initialize Google Sheets Module + gs = GoogleSheetsModule() + + # Find row in Google Sheets that matches user's email + row_num = gs.find_matching_email( + spreadsheet_id=ss_id, + sheet_name=event["sheetTab"], + col="C", + email_to_match=user_email, + ) + + if row_num == -1: + return { + "status": False, + "message": f"{user_name} was not found in the sheet.", + } + + # Update Google Sheets cell with a "1" if user has checked in + gs.update_row( + spreadsheet_id=ss_id, + sheet_name=event["sheetTab"], + col=event["spreadsheetCol"], + row=row_num + 1, + data=[["1"]], + ) + + # Update event collection with checkin data + self.mongo_module.update_document( + f"{self.collection_prefix}event", + event_id, + {"$push": {"usersAttended": checkin_data}}, + ) + + # Send email to user that has checked in + email_content = f""" + Hi {user_name},

+ + Thank you for checking in to {event["name"]}! This is a confirmation that you have checked in.

+ + Regards,
+ PCT Tech Team

+ + ** Please note: Do not reply to this email. This email is sent from an unattended mailbox. Replies will not be read. + """ + + ses_destination = SesDestination(tos=[user_email]) + ses.send_email( + source="checkin-bot@why-phi.com", + destination=ses_destination, + subject=f"Check-in Confirmation: {event['name']}", + text=email_content, + html=email_content, + ) + + return { + "status": True, + "message": f"{user_name} has successfully been checked in.", + } + + def delete(self, event_id: str): + # Check if event exists and if it doesn't return errors + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + if event is None: + raise NotFoundError(f"Event with ID {event_id} does not exist.") + + # If event exists, get the timeframeId (parent) + timeframe_id = event["timeframeId"] + + # Remove event from timeframe + self.mongo_module.update_document( + f"{self.collection_prefix}timeframe", + timeframe_id, + {"$pull": {"events": {"_id": ObjectId(event_id)}}}, + ) + + # Delete the event document + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + def get_timeframe_sheets(self, timeframe_id: str): + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + if "spreadsheetId" not in timeframe or timeframe["spreadsheetId"] == "": + return [] + + gs = GoogleSheetsModule() + sheets = gs.get_sheets(timeframe["spreadsheetId"], include_properties=False) + return [sheet["title"] for sheet in sheets] + + +events_member_service = EventsMemberService() diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py new file mode 100644 index 0000000..f7545f7 --- /dev/null +++ b/chalicelib/services/EventsRushService.py @@ -0,0 +1,377 @@ +from chalicelib.modules.mongo import mongo_module +from chalice import BadRequestError, UnauthorizedError +import json +from bson import ObjectId +import datetime +from chalicelib.s3 import s3 + +class EventsRushService: + class BSONEncoder(json.JSONEncoder): + """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" + + def default(self, o): + if isinstance(o, datetime.datetime): + return o.isoformat() + elif isinstance(o, ObjectId): + return str(o) + return super().default(o) + + def __init__(self, mongo_module=mongo_module): + self.mongo_module = mongo_module + self.collection_prefix = "events-" + + def get_rush_categories_and_events(self): + rush_categories = self.mongo_module.get_data_from_collection( + collection=f"{self.collection_prefix}rush" + ) + + return json.dumps(rush_categories, cls=self.BSONEncoder) + + def create_rush_category(self, data: dict): + data["dateCreated"] = datetime.datetime.now() + data["events"] = [] + self.mongo_module.insert_document(f"{self.collection_prefix}rush", data) + return + + def create_rush_event(self, data: dict): + event_id = ObjectId() + data["dateCreated"] = datetime.datetime.now() + data["lastModified"] = data["dateCreated"] + data["_id"] = event_id + data["attendees"] = [] + data["numAttendees"] = 0 + + # upload eventCoverImage to s3 bucket (convert everything to png files for now... can adjust later) + image_path = f"image/rush/{data['categoryId']}/{event_id}.png" + image_url = s3.upload_binary_data(image_path, data["eventCoverImage"]) + + # add image_url to data object (this also replaces the original base64 image url) + data["eventCoverImage"] = image_url + + # Add event to its own collection + self.mongo_module.insert_document( + f"{self.collection_prefix}rush-event", data + ) + + # Add event to rush category + self.mongo_module.update_document( + f"{self.collection_prefix}rush", + data["categoryId"], + {"$push": { "events": data }}, + ) + + return + + def modify_rush_event(self, data: dict): + + try: + event_id = data["_id"] + event_oid = ObjectId(event_id) + + data["lastModified"] = datetime.datetime.now() + data["_id"] = event_oid + + # Check if event exists in the rush-event collection + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush-event", event_id + ) + + if not event: + raise Exception("Event does not exist.") + + event_category_id = event["categoryId"] + + # if eventCoverImage contains https://whyphi-zap.s3.amazonaws.com, no need to update anything, otherwise update s3 + if "https://whyphi-zap.s3.amazonaws.com" not in data["eventCoverImage"]: + + # get image path + image_path = f"image/rush/{event_category_id}/{event_id}.png" + + # remove previous eventCoverImage from s3 bucket + s3.delete_binary_data(object_id=image_path) + + # upload eventCoverImage to s3 bucket + image_url = s3.upload_binary_data(path=image_path, data=data["eventCoverImage"]) + + # add image_url to data object (this also replaces the original base64 image url) + data["eventCoverImage"] = image_url + + # Merge data with event (from client + mongo) --> NOTE: event must be unpacked first so + # that data overrides the matching keys + data = { **event, **data } + + # Define array update query and filters + update_query = { + "$set": { + "events.$[eventElem]": data + } + } + + array_filters = [ + {"eventElem._id": event_oid} + ] + + # Modify the event in its category (rush collection) + update_category_result = self.mongo_module.update_document( + collection=f"{self.collection_prefix}rush", + document_id=event_category_id, + query=update_query, + array_filters=array_filters + ) + + if not update_category_result: + raise Exception("Error updating rush-event-category.") + + # cannot include _id on original document (immutable) + data.pop("_id") + + # Modify actual event document (rush-event collection) + updated_event_result = self.mongo_module.update_document( + f"{self.collection_prefix}rush-event", + event_id, + {"$set": data}, + ) + + if not updated_event_result: + raise Exception("Error updating rush-event-category.") + + return + + except Exception as e: + print("error is ", e) + raise BadRequestError(e) + + def modify_rush_settings(self, data: dict): + """ + Updates defaultRushCategory from the rush collection + + Parameters + ---------- + data: dict + contains default_rush_category_id ID of the rush category to be default + + Raises + ------ + BadRequestError + If default_rush_category_id is not in the rush collection + """ + default_rush_category_id = data["defaultRushCategoryId"] + + collection = f"{self.collection_prefix}rush" + + # Set all defaultRushCategory fields to false + self.mongo_module.update_many_documents( + collection, + {}, + {"$set": {"defaultRushCategory": False}} + ) + + # if default_rush_category_id is "" --> reset defaultRushCategory + if not default_rush_category_id: + return + + # Update the specified document to set its defaultRushCategory to true + result = self.mongo_module.update_document_by_id( + collection, + default_rush_category_id, + {"defaultRushCategory": True} + ) + + if not result: + raise ValueError(f"Document with ID {default_rush_category_id} was not found or could not be updated.") + + return + + def get_rush_event(self, event_id: str, data: dict): + hide_attendees = data.get("hideAttendees", True) + hide_code = data.get("hideCode", True) + + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush-event", event_id + ) + + if hide_attendees: + event.pop("attendeesId", None) + + if hide_code: + event.pop("code") + + return json.dumps(event, cls=self.BSONEncoder) + + def checkin_rush(self, event_id: str, user_data: dict): + event_oid = ObjectId(event_id) + + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush-event", event_id + ) + + code = user_data["code"] + user_data.pop("code") + + # Parse the timestamp string to a datetime object + deadline = datetime.datetime.strptime(event["deadline"], "%Y-%m-%dT%H:%M:%S.%fZ") + + if datetime.datetime.now() > deadline: + raise UnauthorizedError("Event deadline has passed.") + + if code != event["code"]: + raise UnauthorizedError("Invalid code.") + + if any(d["email"] == user_data["email"] for d in event["attendees"]): + raise BadRequestError("User has already checked in.") + + user_data["checkinTime"] = datetime.datetime.now() + event["attendees"].append(user_data) + event["numAttendees"] += 1 + + # STEP 1: update events-rush-event collection + mongo_module.update_document( + f"{self.collection_prefix}rush-event", + event_id, + {"$set": event}, + ) + + # Define array update query and filters + update_query = { + "$set": { + "events.$[eventElem]": event + } + } + + array_filters = [ + {"eventElem._id": event_oid} + ] + + # STEP 2: Modify the event in its category (rush collection) + self.mongo_module.update_document( + collection=f"{self.collection_prefix}rush", + document_id=event["categoryId"], + query=update_query, + array_filters=array_filters + ) + + return + + def get_rush_events_default_category(self, data: dict): + rush_categories = self.mongo_module.get_data_from_collection( + collection=f"{self.collection_prefix}rush", + filter={"defaultRushCategory": True} + ) + + if len(rush_categories) == 0: + return [] + + rush_category = rush_categories[0] + + # remove code from every rush event + for event in rush_category["events"]: + event.pop("code", None) + + # check if user attended event (boolean) + checkedIn = any(attendee["email"] == data["email"] for attendee in event["attendees"]) + event["checkedIn"] = checkedIn + + # Sort events by the date field + try: + rush_category["events"].sort( + key=lambda e: datetime.datetime.strptime(e["date"], "%Y-%m-%dT%H:%M:%S.%fZ"), + reverse=True + ) + except ValueError as ve: + # Handle the case where the date format might be different or invalid + print(f"Date format error: {ve}") + + return json.dumps(rush_category, cls=self.BSONEncoder) + + def delete_rush_event(self, event_id: str): + """ + Deletes an rush event from the rush-event collection + + Parameters + ---------- + event_id : str + ID of the event to be deleted + + Raises + ------ + BadRequestError + If the event does not exist in the rush-event collection + """ + try: + # Get event_id as ObjectId + event_oid = ObjectId(event_id) + + # Check if event exists in the rush-event collection + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush-event", event_oid + ) + + if not event: + raise Exception("Event does not exist.") + + event_category_id = event["categoryId"] + + # Get eventCoverImage path + image_path = f"image/rush/{event_category_id}/{event_id}.png" + + # remove previous eventCoverImage from s3 bucket + s3.delete_binary_data(object_id=image_path) + + # upload eventCoverImage to s3 bucket + s3.delete_binary_data(object_id=image_path) + + # Delete the event from its category + self.mongo_module.update_document( + collection=f"{self.collection_prefix}rush", + document_id=event_category_id, + query={"$pull": {"events": {"_id": event_oid}}}, + ) + + # Delete event data from the rush-event collection + self.mongo_module.delete_document_by_id( + collection=f"{self.collection_prefix}rush-event", + document_id=event_oid + ) + + return + + except Exception as e: + raise BadRequestError(e) + + def get_rush_category_analytics(self, category_id: str): + category = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush", category_id + ) + + # attendees : dict of all users (user: { name, email, eventsAttended: list of objects }) + attendees = {} + + # events: list of objects (event: { name, eventId }) + events = [] + + for event in category["events"]: + new_event = { + "eventId": event["_id"], + "eventName": event["name"] + } + + # accumulate list of events + events.append(new_event) + + # accumulate attendance + for attendee in event["attendees"]: + email = attendee["email"] + if email in attendees: + attendees[email]["eventsAttended"].append(new_event) + else: + attendees[email] = { **attendee, "eventsAttended": [new_event] } + + result = { + "categoryName": category["name"], + "attendees": attendees, + "events": events, + } + + return json.dumps(result, cls=self.BSONEncoder) + +events_rush_service = EventsRushService() \ No newline at end of file diff --git a/chalicelib/services/InsightsService.py b/chalicelib/services/InsightsService.py index d5d2466..71219a0 100644 --- a/chalicelib/services/InsightsService.py +++ b/chalicelib/services/InsightsService.py @@ -1,12 +1,13 @@ # TO BE COMPLETED: create service to perform analytics (used in API...) from chalicelib.db import db + class InsightsService: def __init__(self): pass def get_insights_from_listing(self, id: str): - ''' driver function of insights (returns both `dashboard` and `distribution`) ''' + """driver function of insights (returns both `dashboard` and `distribution`)""" # fetch applicants from `get_applicants` endpoint in `db.py` data = db.get_applicants(table_name="zap-applications", listing_id=id) @@ -43,7 +44,11 @@ def _get_dashboard_insights(data): applicant["major"] = applicant["major"].title() applicant["minor"] = applicant["minor"].title() - gpa, grad_year, major = applicant["gpa"], applicant["gradYear"], applicant["major"] + gpa, grad_year, major = ( + applicant["gpa"], + applicant["gradYear"], + applicant["major"], + ) # attempt conversions (if fail, then skip) try: @@ -62,14 +67,14 @@ def _get_dashboard_insights(data): except ValueError: print("skipping gradYear: ", grad_year) pass - + # parse majors (if non-empty) if major: if major in majors: majors[major] += 1 else: majors[major] = 1 - + if count_gpa: avg_gpa /= count_gpa else: @@ -78,33 +83,33 @@ def _get_dashboard_insights(data): # calculate most common major/gradYear # Check if majors dictionary is not empty if majors: - common_major, count_common_major = max(majors.items(), key=lambda x: x[1]) + common_major, _ = max(majors.items(), key=lambda x: x[1]) else: # Handle the case when majors dictionary is empty - common_major, count_common_major = "N/A", 0 + common_major, _ = "N/A", 0 # Check if grad_years dictionary is not empty if grad_years: - common_grad_year, count_common_grad_year = max(grad_years.items(), key=lambda x: x[1]) + common_grad_year, _ = max(grad_years.items(), key=lambda x: x[1]) else: # Handle the case when grad_years dictionary is empty - common_grad_year, count_common_grad_year = "N/A", 0 - + common_grad_year, _ = "N/A", 0 dashboard = { "applicantCount": num_applicants, "avgGpa": round(avg_gpa, 1) if avg_gpa != "N/A" else avg_gpa, "commonMajor": common_major.title(), # "countCommonMajor": count_common_major, # TO-DO: maybe do something with common major counts - "commonGradYear": int(common_grad_year) if common_grad_year != 'N/A' else common_grad_year, + "commonGradYear": int(common_grad_year) + if common_grad_year != "N/A" + else common_grad_year, # "avgResponseLength": 0 # TO-DO: maybe implement parsing for response lengths } return dashboard - def _get_pie_chart_insights(data): - ''' helper function for pie charts (should be function, not method within InsightsService) ''' + """helper function for pie charts (should be function, not method within InsightsService)""" # initialize return object # value (list) structure : [ {name: string, value: int, applicants: Applicant[]}, ... , ... ] @@ -117,12 +122,20 @@ def _get_pie_chart_insights(data): "linkedin": [], "website": [], } - + # list of fields we want to consider - fields = ["colleges", "gpa", "gradYear", "major", "minor", "linkedin", "website"] + fields = [ + "colleges", + "gpa", + "gradYear", + "major", + "minor", + "linkedin", + "website", + ] def findInsightsObject(metric, metric_val): - ''' helper to the helper lol -> checks for previously added metric_name ''' + """helper to the helper lol -> checks for previously added metric_name""" # check if college exists in `distribution["colleges"]` found_object = None @@ -130,26 +143,27 @@ def findInsightsObject(metric, metric_val): if distribution_object["name"] == metric_val: found_object = distribution_object break - + return found_object for applicant in data: # iterate over applicant dictionary for metric, val in applicant.items(): - # case 1: ignore irrelevant metrics if metric not in fields: continue - + # case 2: metric is a url if metric in ["linkedin", "website"]: - val = 'N/A' if (not val or val == 'N/A') else 'hasURL' - + val = "N/A" if (not val or val == "N/A") else "hasURL" + # case 3: handle other metrics with mepty val (attempt to handle some edge cases) # TO-DO: update Form.tsx in frontend to prevent bad inputs - elif metric in ['minor', 'gpa'] and (not val or val.lower() in ['na', 'n/a', 'n a', 'n / a']): + elif metric in ["minor", "gpa"] and ( + not val or val.lower() in ["na", "n/a", "n a", "n / a"] + ): # general case - val = 'N/A' - + val = "N/A" + # case 4: colleges -> iterate over colleges object elif metric == "colleges": for college, status in val.items(): @@ -159,20 +173,24 @@ def findInsightsObject(metric, metric_val): # check if college exists in `distribution["colleges"]` found_college = findInsightsObject(metric, college) - + if found_college: found_college["value"] += 1 found_college["applicants"] += [applicant] else: - newCollege = {"name": college, "value": 1, "applicants": [applicant]} + newCollege = { + "name": college, + "value": 1, + "applicants": [applicant], + } distribution[metric] += [newCollege] # skip to next metric - continue - + continue + # handle remaining fields found_object = findInsightsObject(metric, val) - + if found_object: found_object["value"] += 1 found_object["applicants"] += [applicant] diff --git a/chalicelib/services/ListingService.py b/chalicelib/services/ListingService.py index fca8378..64d7f79 100644 --- a/chalicelib/services/ListingService.py +++ b/chalicelib/services/ListingService.py @@ -122,11 +122,11 @@ def delete(self, id: str): else: raise NotFoundError("Listing not found") - except NotFoundError as e: + except NotFoundError: # app.log.error(f"An error occurred: {str(e)}") return {"statusCode": 404, "message": "Listing not found"} - except Exception as e: + except Exception: # app.log.error(f"An error occurred: {str(e)}") return {"statusCode": 500, "message": "Internal Server Error"} diff --git a/chalicelib/services/MemberService.py b/chalicelib/services/MemberService.py index 92c70e6..91297e3 100644 --- a/chalicelib/services/MemberService.py +++ b/chalicelib/services/MemberService.py @@ -1,8 +1,11 @@ from chalicelib.modules.mongo import mongo_module -from chalice import ConflictError, NotFoundError +from chalice import ConflictError, NotFoundError, UnauthorizedError -import json from bson import ObjectId +from collections import defaultdict +import json +import jwt +import boto3 class MemberService: @@ -40,7 +43,7 @@ def create(self, data): "success": True, "message": "User created successfully", } - + def delete(self, data: list[str]) -> dict: """ Deletes user documents based on the provided IDs. @@ -70,15 +73,45 @@ def delete(self, data: list[str]) -> dict: "success": True, "message": "Documents deleted successfully", } - + + def get_by_id(self, user_id: str): + data = mongo_module.get_document_by_id(self.collection, user_id) + return json.dumps(data, cls=self.BSONEncoder) def get_all(self): - data = mongo_module.get_all_data_from_collection(self.collection) + data = mongo_module.get_data_from_collection(self.collection) return json.dumps(data, cls=self.BSONEncoder) def onboard(self, document_id=str, data=dict) -> bool: return mongo_module.update_document_by_id(self.collection, document_id, data) + def update(self, user_id: str, data: dict, headers: dict) -> bool: + ssm_client = boto3.client("ssm") + auth_header = headers.get("Authorization", None) + + if not auth_header: + raise UnauthorizedError("Authorization header is missing.") + + _, token = auth_header.split(" ", 1) if " " in auth_header else (None, None) + + if not token: + raise UnauthorizedError("Token is missing.") + + auth_secret = ssm_client.get_parameter( + Name="/Zap/AUTH_SECRET", WithDecryption=True + )["Parameter"]["Value"] + decoded = jwt.decode(token, auth_secret, algorithms=["HS256"]) + + if user_id != decoded["_id"]: + raise UnauthorizedError( + "User {user_id} is not authorized to update this user." + ) + + # NOTE: Performing an update on the path '_id' would modify the immutable field '_id' + data.pop("_id", None) + + return mongo_module.update_document_by_id(self.collection, user_id, data) + def update_roles(self, document_id=str, roles=list) -> bool: return mongo_module.update_document( self.collection, @@ -86,5 +119,19 @@ def update_roles(self, document_id=str, roles=list) -> bool: [{"$set": {"roles": roles}}], ) + def get_family_tree(self): + data = mongo_module.get_data_from_collection(self.collection) + + # Group by family + family_groups = defaultdict(list) + + for member in data: + if "big" not in member or member["big"] == "": + continue + member["_id"] = str(member["_id"]) + family_groups[member["family"]].append(member) + + return family_groups + member_service = MemberService() diff --git a/chalicelib/utils.py b/chalicelib/utils.py index 9dfab87..5926eef 100644 --- a/chalicelib/utils.py +++ b/chalicelib/utils.py @@ -1,5 +1,4 @@ import base64 -import imghdr def decode_base64(base64_data): @@ -7,15 +6,16 @@ def decode_base64(base64_data): binary_data = base64.b64decode(base64_data) return binary_data + def get_file_extension_from_base64(base64_data): - parts = base64_data.split(',') + parts = base64_data.split(",") if len(parts) != 2: return None # Invalid data URI format metadata = parts[0] # Extract content type from metadata. - content_type = metadata.split(';')[0][5:] # Remove "data:" prefix + content_type = metadata.split(";")[0][5:] # Remove "data:" prefix # Map content type to file extension. extension_map = { @@ -26,6 +26,6 @@ def get_file_extension_from_base64(base64_data): } # Use the mapping to get the extension (default to 'dat' if not found). - extension = extension_map.get(content_type, 'dat') + extension = extension_map.get(content_type, "dat") - return extension \ No newline at end of file + return extension diff --git a/tests/api/test_applicants.py b/tests/api/test_applicants.py index 02b22c8..65cd4cc 100644 --- a/tests/api/test_applicants.py +++ b/tests/api/test_applicants.py @@ -41,10 +41,9 @@ def test_get_all_applicants(): with patch( "chalicelib.services.ApplicantService.applicant_service.get_all" ) as mock_get_all: - mock_get_all.return_value = TEST_APPLICANTS response = client.http.get( - f"/applicants", + "/applicants", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -56,7 +55,6 @@ def test_get_all_applicants(): def test_get_all_applicants_from_listing(): # Create a Chalice test client with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: # Assuming the decoded token has the required role mock_decode.return_value = {"role": "admin"} @@ -65,7 +63,7 @@ def test_get_all_applicants_from_listing(): ) as mock_get_all_from_listing: mock_get_all_from_listing.return_value = TEST_APPLICANTS response = client.http.get( - f"/applicants/test_listing_id", + "/applicants/test_listing_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) diff --git a/tests/api/test_events.py b/tests/api/test_events.py deleted file mode 100644 index c6a1b58..0000000 --- a/tests/api/test_events.py +++ /dev/null @@ -1,326 +0,0 @@ -from chalice.test import Client -from unittest.mock import patch -import json - -from app import app - -with open("tests/fixtures/events/general/sample_timeframes.json") as f: - SAMPLE_TIMEFRAMES = json.load(f) - -with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: - SAMPLE_TIMEFRAME_SHEETS = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_events.json") as f: - SAMPLE_RUSH_EVENTS = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_event.json") as f: - SAMPLE_RUSH_EVENT = json.load(f) - -def test_create_timeframe(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_timeframe" - ) as mock_create_timeframe: - mock_create_timeframe.return_value = {"msg": True} - response = client.http.post( - f"/timeframes", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - - -def test_get_all_timeframes(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_all_timeframes", - ) as mock_get_all_timeframes: - mock_get_all_timeframes.return_value = SAMPLE_TIMEFRAMES - response = client.http.get( - f"/timeframes", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES - -def test_get_timeframe(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe", - ) as mock_get_timeframe: - mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] - response = client.http.get( - f"/timeframes/test_timeframe_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES[0] - -def test_delete_timeframe(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete_timeframe", - ) as mock_delete: - mock_delete.return_value = {"status": True} - response = client.http.delete( - f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} - - -def test_create_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_event" - ) as mock_create_event: - mock_create_event.return_value = {"msg": True} - response = client.http.post( - f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}/events", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_get_event(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe", - ) as mock_get_timeframe: - mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] - response = client.http.get( - f"/timeframes/test_timeframe_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES[0] - -def test_get_timeframe_sheets(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe_sheets", - ) as mock_get_timeframe_sheets: - mock_get_timeframe_sheets.return_value = SAMPLE_TIMEFRAME_SHEETS - response = client.http.get( - f"/timeframes/test_timeframe_id/sheets", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAME_SHEETS - -def test_checkin(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.checkin" - ) as mock_checkin: - mock_checkin.return_value = {"msg": True} - response = client.http.post( - f"/events/test_event_id/checkin", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_delete_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete", - ) as mock_delete: - mock_delete.return_value = {"status": True} - response = client.http.delete( - f"/events/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} - -def test_get_rush_events(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_rush_categories_and_events", - ) as mock_get_rush_categories_and_events: - mock_get_rush_categories_and_events.return_value = SAMPLE_RUSH_EVENTS - response = client.http.get( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_RUSH_EVENTS - -def test_get_rush_event(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_rush_event", - ) as mock_get_rush_event: - mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT - response = client.http.get( - f"/events/rush/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_RUSH_EVENT - -def test_create_rush_category(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_rush_category" - ) as mock_create_rush_category: - mock_create_rush_category.return_value = {"msg": True} - response = client.http.post( - f"/events/rush/category", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_create_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_rush_event" - ) as mock_create_rush_event: - mock_create_rush_event.return_value = {"msg": True} - response = client.http.post( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_modify_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.modify_rush_event" - ) as mock_modify_rush_event: - mock_modify_rush_event.return_value = {"msg": True} - response = client.http.patch( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_modify_rush_settings(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.modify_rush_settings" - ) as mock_modify_rush_settings: - mock_modify_rush_settings.return_value = {"msg": True} - response = client.http.patch( - f"/events/rush/settings", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_checkin_rush(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.checkin_rush" - ) as mock_checkin_rush: - mock_checkin_rush.return_value = {"msg": True} - response = client.http.post( - f"/events/rush/checkin/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_delete_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete_rush_event", - ) as mock_delete_rush_event: - mock_delete_rush_event.return_value = {"status": True} - response = client.http.delete( - f"/events/rush/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} \ No newline at end of file diff --git a/tests/api/test_events_members.py b/tests/api/test_events_members.py new file mode 100644 index 0000000..90ac3a2 --- /dev/null +++ b/tests/api/test_events_members.py @@ -0,0 +1,185 @@ +from chalice.test import Client +from unittest.mock import patch +import json + +from app import app + +with open("tests/fixtures/events/general/sample_timeframes.json") as f: + SAMPLE_TIMEFRAMES = json.load(f) + +with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: + SAMPLE_TIMEFRAME_SHEETS = json.load(f) + + +def test_create_timeframe(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.create_timeframe" + ) as mock_create_timeframe: + mock_create_timeframe.return_value = {"msg": True} + response = client.http.post( + "/timeframes", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_get_all_timeframes(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_all_timeframes", + ) as mock_get_all_timeframes: + mock_get_all_timeframes.return_value = SAMPLE_TIMEFRAMES + response = client.http.get( + "/timeframes", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES + + +def test_get_timeframe(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe", + ) as mock_get_timeframe: + mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] + response = client.http.get( + "/timeframes/test_timeframe_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES[0] + + +def test_delete_timeframe(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.delete_timeframe", + ) as mock_delete: + mock_delete.return_value = {"status": True} + response = client.http.delete( + f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"status": True} + + +def test_create_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.create_event" + ) as mock_create_event: + mock_create_event.return_value = {"msg": True} + response = client.http.post( + f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}/events", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_get_event(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe", + ) as mock_get_timeframe: + mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] + response = client.http.get( + "/timeframes/test_timeframe_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES[0] + + +def test_get_timeframe_sheets(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe_sheets", + ) as mock_get_timeframe_sheets: + mock_get_timeframe_sheets.return_value = SAMPLE_TIMEFRAME_SHEETS + response = client.http.get( + "/timeframes/test_timeframe_id/sheets", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAME_SHEETS + + +def test_checkin(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.checkin" + ) as mock_checkin: + mock_checkin.return_value = {"msg": True} + response = client.http.post( + "/events/test_event_id/checkin", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_delete_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.delete", + ) as mock_delete: + mock_delete.return_value = {"status": True} + response = client.http.delete( + "/events/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"status": True} diff --git a/tests/api/test_events_rush.py b/tests/api/test_events_rush.py new file mode 100644 index 0000000..914e22c --- /dev/null +++ b/tests/api/test_events_rush.py @@ -0,0 +1,162 @@ +from chalice.test import Client +from unittest.mock import patch +import json + +from app import app + +with open("tests/fixtures/events/general/sample_rush_events.json") as f: + SAMPLE_RUSH_EVENTS = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_event.json") as f: + SAMPLE_RUSH_EVENT = json.load(f) + + +def test_get_rush_events(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.get_rush_categories_and_events", + ) as mock_get_rush_categories_and_events: + mock_get_rush_categories_and_events.return_value = SAMPLE_RUSH_EVENTS + response = client.http.get( + "/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_RUSH_EVENTS + + +def test_get_rush_event(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.get_rush_event", + ) as mock_get_rush_event: + mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT + response = client.http.post( + "/events/rush/test_event_id", + body={ "hideCode": False, "hideAttendees": False }, + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_RUSH_EVENT + + +def test_create_rush_category(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.create_rush_category" + ) as mock_create_rush_category: + mock_create_rush_category.return_value = {"msg": True} + response = client.http.post( + "/events/rush/category", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_create_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.create_rush_event" + ) as mock_create_rush_event: + mock_create_rush_event.return_value = {"msg": True} + response = client.http.post( + "/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_modify_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.modify_rush_event" + ) as mock_modify_rush_event: + mock_modify_rush_event.return_value = {"msg": True} + response = client.http.patch( + "/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_modify_rush_settings(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.modify_rush_settings" + ) as mock_modify_rush_settings: + mock_modify_rush_settings.return_value = {"msg": True} + response = client.http.patch( + "/events/rush/settings", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_checkin_rush(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.checkin_rush" + ) as mock_checkin_rush: + mock_checkin_rush.return_value = {"msg": True} + response = client.http.post( + "/events/rush/checkin/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_delete_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.delete_rush_event", + ) as mock_delete_rush_event: + mock_delete_rush_event.return_value = {"status": True} + response = client.http.delete( + "/events/rush/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"status": True} diff --git a/tests/api/test_insights.py b/tests/api/test_insights.py index ccec7b3..2bc7069 100644 --- a/tests/api/test_insights.py +++ b/tests/api/test_insights.py @@ -26,7 +26,7 @@ def test_get_insights_from_listing(): SAMPLE_DISTRIBUTION, ] response = client.http.get( - f"/insights/listing/test_listing_id", + "/insights/listing/test_listing_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) diff --git a/tests/api/test_listings.py b/tests/api/test_listings.py index b3728a6..6ad636a 100644 --- a/tests/api/test_listings.py +++ b/tests/api/test_listings.py @@ -41,7 +41,7 @@ def test_create_listing(): ) as mock_create: mock_create.return_value = {"msg": True} response = client.http.post( - f"/create", + "/create", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -77,7 +77,7 @@ def test_get_all_listings(): ) as mock_get_all: mock_get_all.return_value = TEST_LISTINGS response = client.http.get( - f"/listings", + "/listings", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -162,7 +162,7 @@ def test_update_listing_field_route_not_found(): headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) - assert response.json_body == None + assert response.json_body is None def test_update_listing_field_route_bad_request(): @@ -182,7 +182,7 @@ def test_update_listing_field_route_bad_request(): # body, status_code = response.json_body - assert response.json_body == None + assert response.json_body is None def test_update_listing_field_route_exception(): @@ -200,4 +200,4 @@ def test_update_listing_field_route_exception(): headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) - assert response.json_body == None + assert response.json_body is None diff --git a/tests/api/test_members.py b/tests/api/test_members.py new file mode 100644 index 0000000..6e264bb --- /dev/null +++ b/tests/api/test_members.py @@ -0,0 +1,256 @@ +from chalice.test import Client +from unittest.mock import patch +from chalice.config import Config +from chalice.local import LocalGateway + +from app import app +import json + + +lg = LocalGateway(app, Config()) + + +TEST_MEMBER_DATA = [ + { + "_id": "12a34bc678df27ead9388708", + "name": "Name Name", + "email": "whyphi@bu.edu", + "class": "Lambda", + "college": "CAS", + "family": "Poseidon", + "graduationYear": "2026", + "isEboard": "no", + "major": "Computer Science", + "minor": "", + "isNewUser": False, + "team": "technology", + "roles": ["admin", "eboard", "member"], + "big": "Name Name", + }, + { + "_id": "12a34bc678df27ead9388709", + "name": "Name Name", + "email": "whyphi1@bu.edu", + "class": "Lambda", + "college": "QST", + "family": "Atlas", + "graduationYear": "2027", + "isEboard": "no", + "major": "Business Administration", + "minor": "", + "isNewUser": True, + "team": "operations", + "roles": ["member"], + "big": "Name Name", + }, +] + + +def test_get_member(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_by_id" + ) as mock_get: + mock_get.return_value = TEST_MEMBER_DATA[0] + response = client.http.get( + f"/member/{TEST_MEMBER_DATA[0]['_id']}", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == TEST_MEMBER_DATA[0] + + +def test_get_member_non_existent(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_by_id" + ) as mock_get: + mock_get.return_value = {} + response = client.http.get( + "/member/123", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == {} + + +def test_update_member(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.update" + ) as mock_update: + # Make copy of TEST_MEMBER_DATA[0] + update_member_data = TEST_MEMBER_DATA[0].copy() + update_member_data["name"] = "New Name" + mock_update.return_value = update_member_data + + response = client.http.put( + f"/member/{TEST_MEMBER_DATA[0]['_id']}", + body=update_member_data, + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == update_member_data + + +def test_get_all_members(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_all" + ) as mock_get_all: + mock_get_all.return_value = TEST_MEMBER_DATA + response = client.http.get( + "/members", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == TEST_MEMBER_DATA + + +def test_onboard_member(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.onboard" + ) as mock_onboard: + mock_onboard.return_value = True + + # Utilize local gateway for passing in body + response = lg.handle_request( + method="POST", + path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(TEST_MEMBER_DATA[1]), + ) + + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == { + "status": True, + "message": "User updated successfully.", + } + + +def test_onboard_member_fail_on_mongo(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.onboard" + ) as mock_onboard: + mock_onboard.return_value = False + response = lg.handle_request( + method="POST", + path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(TEST_MEMBER_DATA[1]), + ) + print(response) + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == { + "status": False, + } + + +def test_create_member(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.create" + ) as mock_create: + mock_create.return_value = { + "success": True, + "message": "User created successfully", + } + response = client.http.post( + "/members", + body=json.dumps(TEST_MEMBER_DATA[0]), + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == { + "success": True, + "message": "User created successfully", + } + + +def test_delete_members(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.delete" + ) as mock_delete: + mock_delete.return_value = { + "success": True, + "message": "Documents deleted successfully", + } + response = client.http.delete( + "/members", + body=json.dumps(TEST_MEMBER_DATA), + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == { + "success": True, + "message": "Documents deleted successfully", + } + + +def test_update_member_roles(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.update_roles" + ) as mock_update: + update_member_data = TEST_MEMBER_DATA[0].copy() + update_member_data["roles"] = ["admin"] + mock_update.return_value = update_member_data + + response = lg.handle_request( + method="PATCH", + path=f"/members/{TEST_MEMBER_DATA[0]['_id']}/roles", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(update_member_data), + ) + + print(response) + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == update_member_data diff --git a/tests/services/test_applicant_service.py b/tests/services/test_applicant_service.py index 66f1f18..41f29ee 100644 --- a/tests/services/test_applicant_service.py +++ b/tests/services/test_applicant_service.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from chalicelib.services.ApplicantService import ApplicantService @@ -33,13 +33,12 @@ def test_get_all_applicants(service): mock_db.get_all.return_value = sample_data result = applicant_service.get_all() - mock_db.get_all.assert_called_once_with( - table_name="zap-applications" - ) + mock_db.get_all.assert_called_once_with(table_name="zap-applications") assert result == sample_data assert len(result) == 2 + def test_get_all_applicants_from_listing(service): applicant_service, mock_db = service @@ -52,9 +51,8 @@ def test_get_all_applicants_from_listing(service): result = applicant_service.get_all_from_listing(listing_id) mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) assert result == sample_data - assert len(result) == 2 \ No newline at end of file + assert len(result) == 2 diff --git a/tests/services/test_events_member_service.py b/tests/services/test_events_member_service.py new file mode 100644 index 0000000..989fc7c --- /dev/null +++ b/tests/services/test_events_member_service.py @@ -0,0 +1,112 @@ +import pytest +from unittest.mock import patch +from chalicelib.services.EventsMemberService import EventsMemberService +import datetime +import json + +with open("tests/fixtures/events/general/sample_timeframes.json") as f: + SAMPLE_TIMEFRAMES: list = json.load(f) + +with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: + SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_events.json") as f: + SAMPLE_RUSH_EVENTS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_event.json") as f: + SAMPLE_RUSH_EVENT = json.load(f) + + +@pytest.fixture +def mock_mongo_module(): + with patch( + "chalicelib.modules.mongo.MongoModule", autospec=True + ) as MockMongoModule: + mock_instance = MockMongoModule(use_mock=True) + yield mock_instance + + +@pytest.fixture +def event_service(mock_mongo_module): + return EventsMemberService(mock_mongo_module) + + +def test_insert_document(event_service, mock_mongo_module): + CREATE_TIMEFRAME = { + "name": "testTimeframeName", + "spreadsheetId": "testSpreadsheetId", + } + date_created = datetime.datetime.now() + + with patch("chalicelib.services.EventsMemberService.datetime") as mock_datetime: + mock_datetime.datetime.now.return_value = date_created + result = event_service.create_timeframe(CREATE_TIMEFRAME) + CREATE_TIMEFRAME["date_created"] = date_created + mock_mongo_module.insert_document.assert_called_once_with( + collection="events-timeframe", data=CREATE_TIMEFRAME + ) + + assert result == {"msg": True} + + +def test_get_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] + timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] + + result = event_service.get_timeframe(timeframe_id=timeframe_id) + mock_mongo_module.get_document_by_id.assert_called_once_with( + collection="events-timeframe", document_id=timeframe_id + ) + + assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) + + +def test_get_all_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_data_from_collection.return_value = SAMPLE_TIMEFRAMES + + result = event_service.get_all_timeframes() + mock_mongo_module.get_data_from_collection.assert_called_once_with( + collection="events-timeframe" + ) + + assert result == json.dumps(SAMPLE_TIMEFRAMES) + + +def test_delete_timeframe(event_service, mock_mongo_module): + result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) + assert result["statusCode"] == 200 + + +# TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) +# def test_create_event(event_service, mock_mongo_module): +# CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } +# date_created = datetime.datetime.now() +# timeframe_id = "timeframeId" + +# TODO: test_events_member + +# def get_event(self, event_id: str): + +# def checkin(self, event_id: str, user: dict) -> dict: + +# def delete(self, event_id: str): + +# def get_timeframe_sheets(self, timeframe_id: str): + +# TODO: test_events_rush + +# def get_rush_categories_and_events(self): + +# def create_rush_category(self, data: dict): + +# def create_rush_event(self, data: dict): + +# def modify_rush_event(self, data: dict): + +# def modify_rush_settings(self, data: dict): + +# def get_rush_event(self, event_id: str, hide_attendees: bool = True): + +# def checkin_rush(self, event_id: str, user_data: dict): + +# def delete_rush_event(self, event_id: str): diff --git a/tests/services/test_insights_service.py b/tests/services/test_insights_service.py index d4953e0..3e4e4a6 100644 --- a/tests/services/test_insights_service.py +++ b/tests/services/test_insights_service.py @@ -1,37 +1,37 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from chalicelib.services.InsightsService import InsightsService import copy import json # Load JSON data from a file (insights test - general) -with open('tests/fixtures/insights/general/sample_applicants.json') as f: +with open("tests/fixtures/insights/general/sample_applicants.json") as f: SAMPLE_APPLICANTS = json.load(f) -with open('tests/fixtures/insights/general/sample_dashboard.json') as f: +with open("tests/fixtures/insights/general/sample_dashboard.json") as f: SAMPLE_DASHBOARD = json.load(f) -with open('tests/fixtures/insights/general/sample_distribution.json') as f: +with open("tests/fixtures/insights/general/sample_distribution.json") as f: SAMPLE_DISTRIBUTION = json.load(f) # Load JSON data from a file (insights test - no applicants) -with open('tests/fixtures/insights/noApplicants/sample_applicants.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_applicants.json") as f: SAMPLE_APPLICANTS_NO_APP = json.load(f) -with open('tests/fixtures/insights/noApplicants/sample_dashboard.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_dashboard.json") as f: SAMPLE_DASHBOARD_NO_APP = json.load(f) -with open('tests/fixtures/insights/noApplicants/sample_distribution.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_distribution.json") as f: SAMPLE_DISTRIBUTION_NO_APP = json.load(f) # Load JSON data from a file (insights test - applicant with no gpa) -with open('tests/fixtures/insights/noGPAs/sample_applicants.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_applicants.json") as f: SAMPLE_APPLICANTS_NO_GPA = json.load(f) -with open('tests/fixtures/insights/noGPAs/sample_dashboard.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_dashboard.json") as f: SAMPLE_DASHBOARD_NO_GPA = json.load(f) -with open('tests/fixtures/insights/noGPAs/sample_distribution.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_distribution.json") as f: SAMPLE_DISTRIBUTION_NO_GPA = json.load(f) @@ -51,8 +51,7 @@ def test_get_insights(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -78,8 +77,7 @@ def test_get_insights_no_applicants(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -105,8 +103,7 @@ def test_get_insights_no_gpas(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -119,4 +116,4 @@ def test_get_insights_no_gpas(service): assert len(result) == 2 assert result_dash == json.dumps(SAMPLE_DASHBOARD_NO_GPA, sort_keys=True) - assert result_dist == json.dumps(SAMPLE_DISTRIBUTION_NO_GPA, sort_keys=True) \ No newline at end of file + assert result_dist == json.dumps(SAMPLE_DISTRIBUTION_NO_GPA, sort_keys=True) diff --git a/tests/services/test_listing_service.py b/tests/services/test_listing_service.py index 1362cbc..a608dfd 100644 --- a/tests/services/test_listing_service.py +++ b/tests/services/test_listing_service.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch, Mock from chalicelib.services.ListingService import ListingService from chalice import NotFoundError from pydantic import ValidationError @@ -151,7 +151,9 @@ def test_toggle_visibility_exception(service): mock_db.toggle_visibility.side_effect = Exception("Error") - result = json.loads(listing_service.toggle_visibility(SAMPLE_LISTINGS[0]["listingId"])) + result = json.loads( + listing_service.toggle_visibility(SAMPLE_LISTINGS[0]["listingId"]) + ) assert result["statusCode"] == 500 @@ -172,10 +174,12 @@ def test_update_field_route(service): mock_db.update_listing_field.return_value = mock_updated_listing - result = json.loads(listing_service.update_field_route( - SAMPLE_LISTINGS[0]["listingId"], - {"field": "title", "value": "new test title"}, - )) + result = json.loads( + listing_service.update_field_route( + SAMPLE_LISTINGS[0]["listingId"], + {"field": "title", "value": "new test title"}, + ) + ) assert result["statusCode"] == 200 assert result["updated_listing"] == mock_updated_listing @@ -227,9 +231,6 @@ def test_update_field_updated_listing_not_found(service): assert str(exc_info.value) == "Listing not found" -from unittest.mock import Mock - - def test_update_field_validation_error(service): listing_service, _ = service diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 5b352c5..c52f0c5 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,6 +1,6 @@ -from unittest.mock import patch from chalicelib.decorators import add_env_suffix + def test_add_env_suffix_dev(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -17,6 +17,7 @@ def mocked_function(self, table_name: str, *args, **kwargs): # Check if the suffix is added correctly assert result == "test-table-dev" + def test_add_env_suffix_prod(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -31,4 +32,4 @@ def mocked_function(self, table_name: str, *args, **kwargs): result = decorated_function(instance_mock, "test-table", env=True) # Check if the suffix is added correctly - assert result == "test-table-prod" \ No newline at end of file + assert result == "test-table-prod" diff --git a/tests/test_dynamodb.py b/tests/test_dynamodb.py index da22c36..b77a3c1 100644 --- a/tests/test_dynamodb.py +++ b/tests/test_dynamodb.py @@ -101,10 +101,8 @@ def test_delete_item(db): db.put_data("test-table", SAMPLE_DATA[0]) response = db.delete_item("test-table", {"id": 123}) - assert response == True + assert response # TODO: Test case should fail, but isn't # response = db.delete_item("test-table", {"id": 124}) # assert response == False - -