From 6db4b344ffa5871fc80de0240a6b84c4f9e6c544 Mon Sep 17 00:00:00 2001 From: Michael-u21546551 Date: Mon, 22 Jul 2024 17:48:22 +0200 Subject: [PATCH 1/4] feat: adding caching for images --- occupi-backend/pkg/cache/cache.go | 53 +++++++++++++++++++++ occupi-backend/pkg/database/database.go | 28 +++++++++++ occupi-backend/pkg/handlers/api_handlers.go | 9 ++++ occupi-backend/tests/database_test.go | 4 +- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/occupi-backend/pkg/cache/cache.go b/occupi-backend/pkg/cache/cache.go index 486cbe9d..7e77414a 100644 --- a/occupi-backend/pkg/cache/cache.go +++ b/occupi-backend/pkg/cache/cache.go @@ -116,3 +116,56 @@ func DeleteOTP(appsession *models.AppSession, email string, otp string) { return } } + +func GetImage(appsession *models.AppSession, id string) (models.Image, error) { + if appsession.Cache == nil { + return models.Image{}, errors.New("cache not found") + } + + //unmarshal the image from the cache + var image models.Image + imageData, err := appsession.Cache.Get(id) + + if err != nil { + logrus.Error("key does not exist: ", err) + return models.Image{}, err + } + + if err := bson.Unmarshal(imageData, &image); err != nil { + logrus.Error("Failed to unmarshall", err) + return models.Image{}, err + } + + return image, nil +} + +func SetImage(appsession *models.AppSession, id string, image models.Image) { + if appsession.Cache == nil { + return + } + + // marshal the image + imageData, err := bson.Marshal(image) + if err != nil { + logrus.Error("failed to marshall", err) + return + } + + // set the image in the cache + if err := appsession.Cache.Set(id, imageData); err != nil { + logrus.Error("failed to set user in cache", err) + return + } +} + +func DeleteImage(appsession *models.AppSession, id string) { + if appsession.Cache == nil { + return + } + + // delete the image from the cache + if err := appsession.Cache.Delete(id); err != nil { + logrus.Error("failed to delete image from cache", err) + return + } +} diff --git a/occupi-backend/pkg/database/database.go b/occupi-backend/pkg/database/database.go index 7f5b711a..d43ceffd 100644 --- a/occupi-backend/pkg/database/database.go +++ b/occupi-backend/pkg/database/database.go @@ -1295,6 +1295,19 @@ func GetImageData(ctx *gin.Context, appsession *models.AppSession, imageID strin return models.Image{}, errors.New("database is nil") } + if imageData, err := cache.GetImage(appsession, imageID); err == nil { + resolutions := map[string][]byte{ + constants.ThumbnailRes: imageData.Thumbnail, + constants.LowRes: imageData.ImageLowRes, + constants.MidRes: imageData.ImageMidRes, + constants.HighRes: imageData.ImageHighRes, + } + + if data, ok := resolutions[quality]; ok && len(data) > 0 { + return imageData, nil + } + } + collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Images") id, err := primitive.ObjectIDFromHex(imageID) @@ -1317,6 +1330,21 @@ func GetImageData(ctx *gin.Context, appsession *models.AppSession, imageID strin return models.Image{}, err } + if imageData, err := cache.GetImage(appsession, imageID); err == nil { + switch quality { + case constants.ThumbnailRes: + imageData.Thumbnail = image.Thumbnail + case constants.LowRes: + imageData.ImageLowRes = image.ImageLowRes + case constants.MidRes: + imageData.ImageMidRes = image.ImageMidRes + case constants.HighRes: + imageData.ImageHighRes = image.ImageHighRes + } + + cache.SetImage(appsession, imageID, imageData) + } + return image, nil } diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index db7e8e56..d9e7e137 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -837,10 +837,19 @@ func DownloadProfileImage(ctx *gin.Context, appsession *models.AppSession) { switch request.Quality { case constants.ThumbnailRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.Thumbnail) + + go database.GetImageData(ctx, appsession, id, constants.LowRes) + go database.GetImageData(ctx, appsession, id, constants.MidRes) + go database.GetImageData(ctx, appsession, id, constants.HighRes) case constants.LowRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageLowRes) + + go database.GetImageData(ctx, appsession, id, constants.MidRes) + go database.GetImageData(ctx, appsession, id, constants.HighRes) case constants.MidRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageMidRes) + + go database.GetImageData(ctx, appsession, id, constants.HighRes) case constants.HighRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageHighRes) default: diff --git a/occupi-backend/tests/database_test.go b/occupi-backend/tests/database_test.go index 9622819b..9ec8e3ef 100644 --- a/occupi-backend/tests/database_test.go +++ b/occupi-backend/tests/database_test.go @@ -1901,7 +1901,7 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { mt.Run("Location exists and is valid", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ {Key: "email", Value: email}, - {Key: "locations", Value: bson.A{ + {Key: "knownLocations", Value: bson.A{ bson.D{ {Key: "city", Value: "Cape Town"}, {Key: "region", Value: "Western Cape"}, @@ -1925,7 +1925,7 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { mt.Run("Location exists but ip address unkwown", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ {Key: "email", Value: email}, - {Key: "locations", Value: bson.A{ + {Key: "knownLocations", Value: bson.A{ bson.D{ {Key: "city", Value: "Durban"}, {Key: "region", Value: "KwaZulu-Natal"}, From 2a56a134f1b0c4c653334f5126b94e45e5f8ea9a Mon Sep 17 00:00:00 2001 From: Michael-u21546551 Date: Fri, 26 Jul 2024 21:42:12 +0200 Subject: [PATCH 2/4] chore: Update UpdateUserPassword function to use AppSession in database_test --- frontend/occupi-mobile4/.gitignore | 4 +- occupi-backend/pkg/cache/cache.go | 74 +++++- occupi-backend/pkg/cache/cache_keys_gen.go | 17 ++ occupi-backend/pkg/database/database.go | 143 +++++++---- occupi-backend/pkg/handlers/api_handlers.go | 2 +- occupi-backend/pkg/handlers/auth_handlers.go | 2 +- .../tests/cache_methods_unit_test.go | 16 +- occupi-backend/tests/cache_test.go | 229 +++++++++--------- occupi-backend/tests/database_test.go | 220 +++++++++-------- 9 files changed, 411 insertions(+), 296 deletions(-) create mode 100644 occupi-backend/pkg/cache/cache_keys_gen.go diff --git a/frontend/occupi-mobile4/.gitignore b/frontend/occupi-mobile4/.gitignore index a7f1abbd..4c599a9e 100644 --- a/frontend/occupi-mobile4/.gitignore +++ b/frontend/occupi-mobile4/.gitignore @@ -24,4 +24,6 @@ google-services.json expo-env.d.ts # @end expo-cli -.env \ No newline at end of file +.env + +coverage/ \ No newline at end of file diff --git a/occupi-backend/pkg/cache/cache.go b/occupi-backend/pkg/cache/cache.go index 7e77414a..6e86232b 100644 --- a/occupi-backend/pkg/cache/cache.go +++ b/occupi-backend/pkg/cache/cache.go @@ -15,7 +15,7 @@ func GetUser(appsession *models.AppSession, email string) (models.User, error) { // unmarshal the user from the cache var user models.User - userData, err := appsession.Cache.Get(email) + userData, err := appsession.Cache.Get(UserKey(email)) if err != nil { logrus.Error("key does not exist: ", err) @@ -43,7 +43,7 @@ func SetUser(appsession *models.AppSession, user models.User) { } // set the user in the cache - if err := appsession.Cache.Set(user.Email, userData); err != nil { + if err := appsession.Cache.Set(UserKey(user.Email), userData); err != nil { logrus.Error("failed to set user in cache", err) return } @@ -55,7 +55,7 @@ func DeleteUser(appsession *models.AppSession, email string) { } // delete the user from the cache - if err := appsession.Cache.Delete(email); err != nil { + if err := appsession.Cache.Delete(UserKey(email)); err != nil { logrus.Error("failed to delete user from cache", err) return } @@ -68,8 +68,7 @@ func GetOTP(appsession *models.AppSession, email string, otp string) (models.OTP // unmarshal the otp from the cache var otpData models.OTP - otpKey := email + otp - otpDataBytes, err := appsession.Cache.Get(otpKey) + otpDataBytes, err := appsession.Cache.Get(OTPKey(email, otp)) if err != nil { logrus.Error("key does not exist: ", err) @@ -90,7 +89,6 @@ func SetOTP(appsession *models.AppSession, otpData models.OTP) { } // marshal the otp - otpKey := otpData.Email + otpData.OTP otpDataBytes, err := bson.Marshal(otpData) if err != nil { logrus.Error("failed to marshall", err) @@ -98,7 +96,7 @@ func SetOTP(appsession *models.AppSession, otpData models.OTP) { } // set the otp in the cache - if err := appsession.Cache.Set(otpKey, otpDataBytes); err != nil { + if err := appsession.Cache.Set(OTPKey(otpData.Email, otpData.OTP), otpDataBytes); err != nil { logrus.Error("failed to set otp in cache", err) return } @@ -110,13 +108,65 @@ func DeleteOTP(appsession *models.AppSession, email string, otp string) { } // delete the otp from the cache - otpKey := email + otp - if err := appsession.Cache.Delete(otpKey); err != nil { + if err := appsession.Cache.Delete(OTPKey(email, otp)); err != nil { logrus.Error("failed to delete otp from cache", err) return } } +func SetBooking(appsession *models.AppSession, booking models.Booking) { + if appsession.Cache == nil { + return + } + + // marshal the booking + bookingData, err := bson.Marshal(booking) + if err != nil { + logrus.Error("failed to marshall", err) + return + } + + // set the booking in the cache + if err := appsession.Cache.Set(RoomBookingKey(booking.OccupiID), bookingData); err != nil { + logrus.Error("failed to set booking in cache", err) + return + } +} + +func GetBooking(appsession *models.AppSession, bookingID string) (models.Booking, error) { + if appsession.Cache == nil { + return models.Booking{}, errors.New("cache not found") + } + + // unmarshal the booking from the cache + var booking models.Booking + bookingData, err := appsession.Cache.Get(RoomBookingKey(bookingID)) + + if err != nil { + logrus.Error("key does not exist: ", err) + return models.Booking{}, err + } + + if err := bson.Unmarshal(bookingData, &booking); err != nil { + logrus.Error("failed to unmarshall", err) + return models.Booking{}, err + } + + return booking, nil +} + +func DeleteBooking(appsession *models.AppSession, bookingID string) { + if appsession.Cache == nil { + return + } + + // delete the booking from the cache + if err := appsession.Cache.Delete(RoomBookingKey(bookingID)); err != nil { + logrus.Error("failed to delete booking from cache", err) + return + } +} + func GetImage(appsession *models.AppSession, id string) (models.Image, error) { if appsession.Cache == nil { return models.Image{}, errors.New("cache not found") @@ -124,7 +174,7 @@ func GetImage(appsession *models.AppSession, id string) (models.Image, error) { //unmarshal the image from the cache var image models.Image - imageData, err := appsession.Cache.Get(id) + imageData, err := appsession.Cache.Get(ImageKey(id)) if err != nil { logrus.Error("key does not exist: ", err) @@ -152,7 +202,7 @@ func SetImage(appsession *models.AppSession, id string, image models.Image) { } // set the image in the cache - if err := appsession.Cache.Set(id, imageData); err != nil { + if err := appsession.Cache.Set(ImageKey(id), imageData); err != nil { logrus.Error("failed to set user in cache", err) return } @@ -164,7 +214,7 @@ func DeleteImage(appsession *models.AppSession, id string) { } // delete the image from the cache - if err := appsession.Cache.Delete(id); err != nil { + if err := appsession.Cache.Delete(ImageKey(id)); err != nil { logrus.Error("failed to delete image from cache", err) return } diff --git a/occupi-backend/pkg/cache/cache_keys_gen.go b/occupi-backend/pkg/cache/cache_keys_gen.go new file mode 100644 index 00000000..7a22b2ab --- /dev/null +++ b/occupi-backend/pkg/cache/cache_keys_gen.go @@ -0,0 +1,17 @@ +package cache + +func UserKey(email string) string { + return "Users:" + email +} + +func OTPKey(email, otp string) string { + return "OTPs:" + email + ":" + otp +} + +func RoomBookingKey(roomID string) string { + return "RoomBookings:" + roomID +} + +func ImageKey(imageID string) string { + return "Images:" + imageID +} diff --git a/occupi-backend/pkg/database/database.go b/occupi-backend/pkg/database/database.go index d43ceffd..1a113ca4 100644 --- a/occupi-backend/pkg/database/database.go +++ b/occupi-backend/pkg/database/database.go @@ -58,6 +58,11 @@ func GetAllData(ctx *gin.Context, appsession *models.AppSession) []bson.M { // attempts to save booking in database func SaveBooking(ctx *gin.Context, appsession *models.AppSession, booking models.Booking) (bool, error) { + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return false, errors.New("database is nil") + } // Save the booking to the database collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("RoomBooking") _, err := collection.InsertOne(ctx, booking) @@ -65,11 +70,20 @@ func SaveBooking(ctx *gin.Context, appsession *models.AppSession, booking models logrus.Error(err) return false, err } + + cache.SetBooking(appsession, booking) + return true, nil } // Confirms the user check-in by checking certain criteria func ConfirmCheckIn(ctx *gin.Context, appsession *models.AppSession, checkIn models.CheckIn) (bool, error) { + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return false, errors.New("database is nil") + } + // Save the check-in to the database collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("RoomBooking") @@ -79,29 +93,19 @@ func ConfirmCheckIn(ctx *gin.Context, appsession *models.AppSession, checkIn mod "creator": checkIn.Creator, } - // Find the booking - var booking models.Booking - err := collection.FindOne(context.TODO(), filter).Decode(&booking) + update := bson.M{"$set": bson.M{"checkedIn": true}} + + _, err := collection.UpdateOne(ctx, filter, update) if err != nil { - if err == mongo.ErrNoDocuments { - logrus.Error("Booking not found") - return false, errors.New("booking not found") - } - logrus.Error("Failed to find booking:", err) + logrus.Error("Failed to update booking:", err) return false, err } - update := bson.M{ - "$set": bson.M{"checkedIn": true}, + if booking, err := cache.GetBooking(appsession, checkIn.BookingID); err == nil { + booking.CheckedIn = true + cache.SetBooking(appsession, booking) } - opts := options.FindOneAndUpdate().SetReturnDocument(options.After) - var updatedBooking models.Booking - err = collection.FindOneAndUpdate(context.TODO(), filter, update, opts).Decode(&updatedBooking) - if err != nil { - logrus.Error("Failed to update booking:", err) - return false, err - } return true, nil } @@ -136,6 +140,17 @@ func EmailExists(ctx *gin.Context, appsession *models.AppSession, email string) // checks if booking exists in database func BookingExists(ctx *gin.Context, appsession *models.AppSession, id string) bool { + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return false + } + + // Check if the booking exists in the cache if cache is not nil + if _, err := cache.GetBooking(appsession, id); err == nil { + return true + } + // Check if the booking exists in the database collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("RoomBooking") @@ -146,6 +161,10 @@ func BookingExists(ctx *gin.Context, appsession *models.AppSession, id string) b logrus.Error(err) return false } + + // Add the booking to the cache if cache is not nil + cache.SetBooking(appsession, existingbooking) + return true } @@ -167,7 +186,26 @@ func AddUser(ctx *gin.Context, appsession *models.AppSession, user models.Regist NextVerificationDate: time.Now(), // this will be updated once the email is verified TwoFAEnabled: false, KnownLocations: []models.Location{}, - ExpoPushToken: user.ExpoPushToken, + Details: models.Details{ + ImageID: "", + Name: "", + DOB: time.Now(), + Gender: "", + Pronouns: "", + }, + Notifications: models.Notifications{ + Invites: true, + BookingReminder: true, + }, + Security: models.Security{ + MFA: false, + Biometrics: false, + ForceLogout: false, + }, + Status: "", + Position: "", + DepartmentNo: "", + ExpoPushToken: user.ExpoPushToken, } // Save the user to the database collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Users") @@ -266,18 +304,6 @@ func DeleteOTP(ctx *gin.Context, appsession *models.AppSession, email string, ot return true, nil } -// GetResetOTP retrieves the OTP for the given email and OTP from the database -func GetResetOTP(ctx context.Context, db *mongo.Client, email, otp string) (*models.OTP, error) { - collection := db.Database(configs.GetMongoDBName()).Collection("OTPs") - var resetOTP models.OTP - filter := bson.M{"email": email, "otp": otp} - err := collection.FindOne(ctx, filter).Decode(&resetOTP) - if err != nil { - return nil, err - } - return &resetOTP, nil -} - // verifies a user in the database func VerifyUser(ctx *gin.Context, appsession *models.AppSession, email string, ipAddress string) (bool, error) { // check if database is nil @@ -459,6 +485,11 @@ func UpdateVerificationStatusTo(ctx *gin.Context, appsession *models.AppSession, // Confirms if a booking has been cancelled func ConfirmCancellation(ctx *gin.Context, appsession *models.AppSession, id string, email string) (bool, error) { + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return false, errors.New("database is nil") + } // Save the check-in to the database collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("RoomBooking") @@ -467,30 +498,26 @@ func ConfirmCancellation(ctx *gin.Context, appsession *models.AppSession, id str "_id": id, "creator": email} - // Find the booking - var localBooking models.Booking - err := collection.FindOne(context.TODO(), filter).Decode(&localBooking) - if err != nil { - if err == mongo.ErrNoDocuments { - logrus.Error("Email not associated with the room") - return false, errors.New("email not associated with the room") - } - logrus.Error("Failed to find booking:", err) - return false, err - } - // Delete the booking - _, err = collection.DeleteOne(context.TODO(), filter) + _, err := collection.DeleteOne(ctx, filter) if err != nil { logrus.Error("Failed to cancel booking:", err) return false, err } + + // delete booking from cache if cache is not nil + cache.DeleteBooking(appsession, id) + return true, nil } // Get user information func GetUserDetails(ctx *gin.Context, appsession *models.AppSession, email string) (models.UserDetailsRequest, error) { - collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Users") + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return models.UserDetailsRequest{}, errors.New("database is nil") + } // check if user is in cache if userData, err := cache.GetUser(appsession, email); err == nil { @@ -506,6 +533,8 @@ func GetUserDetails(ctx *gin.Context, appsession *models.AppSession, email strin }, nil } + collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Users") + filter := bson.M{"email": email} var user models.User err := collection.FindOne(ctx, filter).Decode(&user) @@ -637,7 +666,7 @@ func CheckIfUserIsAdmin(ctx *gin.Context, appsession *models.AppSession, email s return user.Role == constants.Admin, nil } -// AddResetToken adds a reset token to the database +// AddResetToken adds a reset token to the database **Deprecated - Cannot confirm if this is still in use** func AddResetToken(ctx context.Context, db *mongo.Client, email string, resetToken string, expirationTime time.Time) (bool, error) { collection := db.Database(configs.GetMongoDBName()).Collection("ResetTokens") resetTokenStruct := models.ResetToken{ @@ -653,7 +682,7 @@ func AddResetToken(ctx context.Context, db *mongo.Client, email string, resetTok return true, nil } -// retrieves the email associated with a reset token +// retrieves the email associated with a reset token **Deprecated - Cannot confirm if this is still in use** func GetEmailByResetToken(ctx context.Context, db *mongo.Client, resetToken string) (string, error) { collection := db.Database(configs.GetMongoDBName()).Collection("ResetTokens") filter := bson.M{"token": resetToken} @@ -666,7 +695,7 @@ func GetEmailByResetToken(ctx context.Context, db *mongo.Client, resetToken stri return resetTokenStruct.Email, nil } -// CheckResetToken function +// CheckResetToken function **Deprecated - Cannot confirm if this is still in use** func CheckResetToken(ctx *gin.Context, db *mongo.Client, email string, token string) (bool, error) { // Access the "ResetTokens" collection within the configs.GetMongoDBName() database. collection := db.Database(configs.GetMongoDBName()).Collection("ResetTokens") @@ -696,15 +725,15 @@ func CheckResetToken(ctx *gin.Context, db *mongo.Client, email string, token str } // UpdateUserPassword, which updates the password in the database set by the user -func UpdateUserPassword(ctx *gin.Context, db *mongo.Client, email string, password string) (bool, error) { +func UpdateUserPassword(ctx *gin.Context, appsession *models.AppSession, email string, password string) (bool, error) { // Check if the database is nil - if db == nil { + if appsession.DB == nil { logrus.Error("Database is nil") return false, errors.New("database is nil") } // Update the password in the database - collection := db.Database(configs.GetMongoDBName()).Collection("Users") + collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Users") filter := bson.M{"email": email} update := bson.M{"$set": bson.M{"password": password}} _, err := collection.UpdateOne(ctx, filter, update) @@ -714,11 +743,15 @@ func UpdateUserPassword(ctx *gin.Context, db *mongo.Client, email string, passwo } // Update users password in cache if cache is not nil + if userData, err := cache.GetUser(appsession, email); err == nil { + userData.Password = password + cache.SetUser(appsession, userData) + } return true, nil } -// ClearRestToekn, removes the reset token from the database +// ClearRestToekn, removes the reset token from the database **Deprecated - Cannot confirm if this is still in use** func ClearResetToken(ctx *gin.Context, db *mongo.Client, email string, token string) (bool, error) { // Delete the token from the database collection := db.Database(configs.GetMongoDBName()).Collection("ResetTokens") @@ -731,7 +764,7 @@ func ClearResetToken(ctx *gin.Context, db *mongo.Client, email string, token str return true, nil } -// ValidateResetToken +// ValidateResetToken validates the reset token **Deprecated - Cannot confirm if this is still in use** func ValidateResetToken(ctx context.Context, db *mongo.Client, email, token string) (bool, string, error) { // Find the reset token document var resetToken models.ResetToken @@ -1285,6 +1318,9 @@ func UploadImageData(ctx *gin.Context, appsession *models.AppSession, image mode return "", err } + // add image to cache + cache.SetImage(appsession, id.InsertedID.(primitive.ObjectID).Hex(), image) + return id.InsertedID.(primitive.ObjectID).Hex(), nil } @@ -1364,6 +1400,9 @@ func DeleteImageData(ctx *gin.Context, appsession *models.AppSession, imageID st return err } + // delete image from cache + cache.DeleteImage(appsession, imageID) + return nil } diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index d9e7e137..fbd05f64 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -153,7 +153,7 @@ func BookRoom(ctx *gin.Context, appsession *models.AppSession) { return } - ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully booked!", booking.ID)) + ctx.JSON(http.StatusOK, utils.SuccessResponse(http.StatusOK, "Successfully booked!", booking.RoomID)) } func CancelBooking(ctx *gin.Context, appsession *models.AppSession) { diff --git a/occupi-backend/pkg/handlers/auth_handlers.go b/occupi-backend/pkg/handlers/auth_handlers.go index 6ef2c44d..0477ed0e 100644 --- a/occupi-backend/pkg/handlers/auth_handlers.go +++ b/occupi-backend/pkg/handlers/auth_handlers.go @@ -425,7 +425,7 @@ func ResetPassword(ctx *gin.Context, appsession *models.AppSession, role string, } // Update password in database - success, err := database.UpdateUserPassword(ctx, appsession.DB, resetRequest.Email, password) + success, err := database.UpdateUserPassword(ctx, appsession, resetRequest.Email, password) if err != nil || !success { ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse( http.StatusInternalServerError, diff --git a/occupi-backend/tests/cache_methods_unit_test.go b/occupi-backend/tests/cache_methods_unit_test.go index 58e20e79..1db72db5 100644 --- a/occupi-backend/tests/cache_methods_unit_test.go +++ b/occupi-backend/tests/cache_methods_unit_test.go @@ -52,7 +52,7 @@ func TestGetUser(t *testing.T) { appsession = &models.AppSession{ Cache: configs.CreateCache(), } - err := appsession.Cache.Set(email, userData) + err := appsession.Cache.Set(cache.UserKey(email), userData) assert.NoError(t, err) } else { @@ -109,7 +109,7 @@ func TestSetUser(t *testing.T) { if tt.name != "cache is nil" { // check if user was set in cache - userData, err := appsession.Cache.Get(email) + userData, err := appsession.Cache.Get(cache.UserKey(email)) assert.NoError(t, err) // unmarshal the user from the cache @@ -156,7 +156,7 @@ func TestDeleteUser(t *testing.T) { // add user to cache userData, _ := bson.Marshal(user) - err := appsession.Cache.Set(email, userData) + err := appsession.Cache.Set(cache.UserKey(email), userData) assert.NoError(t, err) } else { @@ -168,7 +168,7 @@ func TestDeleteUser(t *testing.T) { if tt.name != "cache is nil" { // check if user was deleted in cache - userData, err := appsession.Cache.Get(email) + userData, err := appsession.Cache.Get(cache.UserKey(email)) assert.NotNil(t, err) assert.Nil(t, userData) } @@ -217,7 +217,7 @@ func TestGetOTP(t *testing.T) { appsession = &models.AppSession{ Cache: configs.CreateCache(), } - err := appsession.Cache.Set(email+otpv, otpData) + err := appsession.Cache.Set(cache.OTPKey(email, otpv), otpData) assert.NoError(t, err) } else { @@ -277,7 +277,7 @@ func TestSetOTP(t *testing.T) { if tt.name != "cache is nil" { // check if otp was set in cache - otpData, err := appsession.Cache.Get(email + otpv) + otpData, err := appsession.Cache.Get(cache.OTPKey(email, otpv)) assert.NoError(t, err) // unmarshal the otp from the cache @@ -325,7 +325,7 @@ func TestDeleteOTPF(t *testing.T) { // add otp to cache otpData, _ := bson.Marshal(otp) - err := appsession.Cache.Set(email+otpv, otpData) + err := appsession.Cache.Set(cache.OTPKey(email, otpv), otpData) assert.NoError(t, err) } else { @@ -336,7 +336,7 @@ func TestDeleteOTPF(t *testing.T) { if tt.name != "cache is nil" { // check if otp was deleted in cache - otpData, err := appsession.Cache.Get(email + otpv) + otpData, err := appsession.Cache.Get(cache.OTPKey(email, otpv)) assert.NotNil(t, err) assert.Nil(t, otpData) } diff --git a/occupi-backend/tests/cache_test.go b/occupi-backend/tests/cache_test.go index c9896bbf..d1c67b86 100644 --- a/occupi-backend/tests/cache_test.go +++ b/occupi-backend/tests/cache_test.go @@ -10,6 +10,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "github.com/COS301-SE-2024/occupi/occupi-backend/configs" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/cache" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" @@ -18,9 +19,9 @@ import ( func TestEmailExistsPerformance(t *testing.T) { email := "TestEmailExistsPerformance@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -28,12 +29,12 @@ func TestEmailExistsPerformance(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appsessionWithCache := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } - // Create a new AppSession without the cache + // Create a new AppSession without the Cache appsessionWithoutCache := &models.AppSession{ DB: db, Cache: nil, @@ -49,31 +50,31 @@ func TestEmailExistsPerformance(t *testing.T) { t.Fatalf("Failed to insert test email into database: %v", err) } - // Test performance with cache + // Test performance with Cache startTime := time.Now() for i := 0; i < 1000; i++ { database.EmailExists(ctx, appsessionWithCache, email) } durationWithCache := time.Since(startTime) - // Test performance without cache + // Test performance without Cache startTime = time.Now() for i := 0; i < 1000; i++ { database.EmailExists(ctx, appsessionWithoutCache, email) } durationWithoutCache := time.Since(startTime) - // Assert that the cache improves the speed + // Assert that the Cache improves the speed if durationWithoutCache <= durationWithCache { - t.Errorf("Cache did not improve performance: duration with cache %v, duration without cache %v", durationWithCache, durationWithoutCache) + t.Errorf("Cache did not improve performance: duration with Cache %v, duration without Cache %v", durationWithCache, durationWithoutCache) } } func TestEmailExists_WithCache(t *testing.T) { email := "TestEmailExists_WithCache@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -81,10 +82,10 @@ func TestEmailExists_WithCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -104,17 +105,17 @@ func TestEmailExists_WithCache(t *testing.T) { // Verify the response assert.True(t, exists) - // Verify the user is in the cache - cachedUser, err := cache.Get(email) + // Verify the user is in the Cache + cachedUser, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, cachedUser) } func TestAddUser_WithCache(t *testing.T) { - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -122,10 +123,10 @@ func TestAddUser_WithCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } user := models.RegisterUser{ @@ -138,24 +139,24 @@ func TestAddUser_WithCache(t *testing.T) { assert.True(t, success) assert.Nil(t, err) - // Verify the user is in the cache - cachedUser, err := cache.Get(user.Email) + // Verify the user is in the Cache + cachedUser, err := Cache.Get(cache.UserKey(user.Email)) assert.Nil(t, err) assert.NotNil(t, cachedUser) - // sleep for 2 * cache expiry time to ensure the cache expires + // sleep for 2 * Cache expiry time to ensure the Cache expires time.Sleep(time.Duration(configs.GetCacheEviction()) * 2 * time.Second) - // Verify the user is not in the cache - cachedUser, err = cache.Get(user.Email) + // Verify the user is not in the Cache + cachedUser, err = Cache.Get(cache.UserKey(user.Email)) assert.NotNil(t, err) assert.Nil(t, cachedUser) } func TestAddOTP_WithCache(t *testing.T) { - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -163,10 +164,10 @@ func TestAddOTP_WithCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } email := "test_withcache@example.com" @@ -176,16 +177,16 @@ func TestAddOTP_WithCache(t *testing.T) { assert.True(t, success) assert.Nil(t, err) - // Verify the otp is in the cache - cachedUser, err := cache.Get(email + otp) + // Verify the otp is in the Cache + cachedUser, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, cachedUser) - // sleep for 2 * cache expiry time to ensure the cache expires + // sleep for 2 * Cache expiry time to ensure the Cache expires time.Sleep(time.Duration(configs.GetCacheEviction()) * 2 * time.Second) - // Verify the user is not in the cache - cachedUser, err = cache.Get(email + otp) + // Verify the user is not in the Cache + cachedUser, err = Cache.Get(cache.OTPKey(email, otp)) assert.NotNil(t, err) assert.Nil(t, cachedUser) } @@ -194,9 +195,9 @@ func TestOTPExistsPerformance(t *testing.T) { email := "TestOTPExistsPerformance@example.com" otp := "123456" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -204,12 +205,12 @@ func TestOTPExistsPerformance(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appsessionWithCache := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } - // Create a new AppSession without the cache + // Create a new AppSession without the Cache appsessionWithoutCache := &models.AppSession{ DB: db, Cache: nil, @@ -227,32 +228,32 @@ func TestOTPExistsPerformance(t *testing.T) { t.Fatalf("Failed to insert test otp into database: %v", err) } - // Test performance with cache + // Test performance with Cache startTime := time.Now() for i := 0; i < 1000; i++ { database.OTPExists(ctx, appsessionWithCache, email, otp) } durationWithCache := time.Since(startTime) - // Test performance without cache + // Test performance without Cache startTime = time.Now() for i := 0; i < 1000; i++ { database.OTPExists(ctx, appsessionWithoutCache, email, otp) } durationWithoutCache := time.Since(startTime) - // Assert that the cache improves the speed + // Assert that the Cache improves the speed if durationWithoutCache <= durationWithCache { - t.Errorf("Cache did not improve performance: duration with cache %v, duration without cache %v", durationWithCache, durationWithoutCache) + t.Errorf("Cache did not improve performance: duration with Cache %v, duration without Cache %v", durationWithCache, durationWithoutCache) } } func TestOTPExists_WithCache(t *testing.T) { email := "TestOTPExists_WithCache@example.com" otp := "123456" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -260,10 +261,10 @@ func TestOTPExists_WithCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -278,8 +279,8 @@ func TestOTPExists_WithCache(t *testing.T) { t.Fatalf("Failed to insert test otp into database: %v", err) } - // Verify the otp is not in the cache before calling the function - nocachedOTP, err := cache.Get(email + otp) + // Verify the otp is not in the Cache before calling the function + nocachedOTP, err := Cache.Get(cache.OTPKey(email, otp)) assert.NotNil(t, err) assert.Nil(t, nocachedOTP) @@ -291,8 +292,8 @@ func TestOTPExists_WithCache(t *testing.T) { assert.True(t, exists) assert.Nil(t, err) - // Verify the user is in the cache - cachedUser, err := cache.Get(email + otp) + // Verify the otp is in the Cache + cachedUser, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, cachedUser) @@ -301,9 +302,9 @@ func TestOTPExists_WithCache(t *testing.T) { func TestDeleteOTP_withCache(t *testing.T) { email := "TestDeleteOTP_withCache@example.com" otp := "123456" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -311,10 +312,10 @@ func TestDeleteOTP_withCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -329,17 +330,17 @@ func TestDeleteOTP_withCache(t *testing.T) { t.Fatalf("Failed to insert test otp into database: %v", err) } - // add otp to cache + // add otp to Cache if otpData, err := bson.Marshal(otpStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email+otp, otpData); err != nil { + if err := Cache.Set(cache.OTPKey(email, otp), otpData); err != nil { t.Fatal(err) } } - // Verify the otp is in the cache before calling the function - nocachedOTP, err := cache.Get(email + otp) + // Verify the otp is in the Cache before calling the function + nocachedOTP, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, nocachedOTP) @@ -351,8 +352,8 @@ func TestDeleteOTP_withCache(t *testing.T) { assert.True(t, success) assert.Nil(t, err) - // Verify the otp is not in the cache - cachedUser, err := cache.Get(email + otp) + // Verify the otp is not in the Cache + cachedUser, err := Cache.Get(cache.OTPKey(email, otp)) assert.NotNil(t, err) assert.Nil(t, cachedUser) @@ -361,9 +362,9 @@ func TestDeleteOTP_withCache(t *testing.T) { func TestGetPasswordPerformance(t *testing.T) { email := "TestGetPasswordPerformance@example.com" password := "password" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -371,12 +372,12 @@ func TestGetPasswordPerformance(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appsessionWithCache := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } - // Create a new AppSession without the cache + // Create a new AppSession without the Cache appsessionWithoutCache := &models.AppSession{ DB: db, Cache: nil, @@ -394,32 +395,32 @@ func TestGetPasswordPerformance(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Test performance with cache + // Test performance with Cache startTime := time.Now() for i := 0; i < 1000; i++ { database.GetPassword(ctx, appsessionWithCache, email) } durationWithCache := time.Since(startTime) - // Test performance without cache + // Test performance without Cache startTime = time.Now() for i := 0; i < 1000; i++ { database.GetPassword(ctx, appsessionWithoutCache, email) } durationWithoutCache := time.Since(startTime) - // Assert that the cache improves the speed + // Assert that the Cache improves the speed if durationWithoutCache <= durationWithCache { - t.Errorf("Cache did not improve performance: duration with cache %v, duration without cache %v", durationWithCache, durationWithoutCache) + t.Errorf("Cache did not improve performance: duration with Cache %v, duration without Cache %v", durationWithCache, durationWithoutCache) } } func TestGetPassword_withCache(t *testing.T) { email := "TestGetPassword_withCache@example.com" password := "password" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -427,10 +428,10 @@ func TestGetPassword_withCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -445,8 +446,8 @@ func TestGetPassword_withCache(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Verify the user is not in the cache before calling the function - nocachedUser, err := cache.Get(email) + // Verify the user is not in the Cache before calling the function + nocachedUser, err := Cache.Get(cache.UserKey(email)) assert.NotNil(t, err) assert.Nil(t, nocachedUser) @@ -458,8 +459,8 @@ func TestGetPassword_withCache(t *testing.T) { assert.Equal(t, password, passwordv) assert.Nil(t, err) - // Verify the user is in the cache - cachedUser, err := cache.Get(email) + // Verify the user is in the Cache + cachedUser, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, cachedUser) @@ -467,9 +468,9 @@ func TestGetPassword_withCache(t *testing.T) { func TestCheckIfUserIsAdminPerformance(t *testing.T) { email := "TestCheckIfUserIsAdminPerformance@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -477,12 +478,12 @@ func TestCheckIfUserIsAdminPerformance(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appsessionWithCache := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } - // Create a new AppSession without the cache + // Create a new AppSession without the Cache appsessionWithoutCache := &models.AppSession{ DB: db, Cache: nil, @@ -500,7 +501,7 @@ func TestCheckIfUserIsAdminPerformance(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Test performance with cache + // Test performance with Cache startTime := time.Now() for i := 0; i < 1000; i++ { database.CheckIfUserIsAdmin(ctx, appsessionWithCache, email) @@ -508,7 +509,7 @@ func TestCheckIfUserIsAdminPerformance(t *testing.T) { durationWithCache := time.Since(startTime) - // Test performance without cache + // Test performance without Cache startTime = time.Now() for i := 0; i < 1000; i++ { database.CheckIfUserIsAdmin(ctx, appsessionWithoutCache, email) @@ -516,18 +517,18 @@ func TestCheckIfUserIsAdminPerformance(t *testing.T) { durationWithoutCache := time.Since(startTime) - // Assert that the cache improves the speed + // Assert that the Cache improves the speed if durationWithoutCache <= durationWithCache { - t.Errorf("Cache did not improve performance: duration with cache %v, duration without cache %v", durationWithCache, durationWithoutCache) + t.Errorf("Cache did not improve performance: duration with Cache %v, duration without Cache %v", durationWithCache, durationWithoutCache) } } func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { email1 := "TestCheckIfUserIsAdmin_WithCache1@example.com" email2 := "TestCheckIfUserIsAdmin_WithCache2@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -535,10 +536,10 @@ func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -563,13 +564,13 @@ func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Verify the user is not in the cache before calling the function - nocachedUser1, err := cache.Get(email1) + // Verify the user is not in the Cache before calling the function + nocachedUser1, err := Cache.Get(cache.UserKey(email1)) assert.NotNil(t, err) assert.Nil(t, nocachedUser1) - nocachedUser2, err := cache.Get(email2) + nocachedUser2, err := Cache.Get(cache.UserKey(email2)) assert.NotNil(t, err) assert.Nil(t, nocachedUser2) @@ -581,8 +582,8 @@ func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { assert.True(t, isAdmin1) assert.Nil(t, err) - // Verify the user is in the cache - cachedUser1, err := cache.Get(email1) + // Verify the user is in the Cache + cachedUser1, err := Cache.Get(cache.UserKey(email1)) assert.Nil(t, err) assert.NotNil(t, cachedUser1) @@ -594,8 +595,8 @@ func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { assert.False(t, isAdmin2) assert.Nil(t, err) - // Verify the user is in the cache - cachedUser2, err := cache.Get(email2) + // Verify the user is in the Cache + cachedUser2, err := Cache.Get(cache.UserKey(email2)) assert.Nil(t, err) assert.NotNil(t, cachedUser2) @@ -603,9 +604,9 @@ func TestCheckIfUserIsAdmin_WithCache(t *testing.T) { func TestCheckIfUserIsLoggingInFromKnownLocationPerformance(t *testing.T) { email := "TestCheckIfUserIsLoggingInFromKnownLocationPerformance@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -613,12 +614,12 @@ func TestCheckIfUserIsLoggingInFromKnownLocationPerformance(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appsessionWithCache := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } - // Create a new AppSession without the cache + // Create a new AppSession without the Cache appsessionWithoutCache := &models.AppSession{ DB: db, Cache: nil, @@ -642,7 +643,7 @@ func TestCheckIfUserIsLoggingInFromKnownLocationPerformance(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Test performance with cache + // Test performance with Cache startTime := time.Now() for i := 0; i < 1000; i++ { database.CheckIfUserIsLoggingInFromKnownLocation(ctx, appsessionWithCache, email, "8.8.8.8") @@ -650,7 +651,7 @@ func TestCheckIfUserIsLoggingInFromKnownLocationPerformance(t *testing.T) { durationWithCache := time.Since(startTime) - // Test performance without cache + // Test performance without Cache startTime = time.Now() for i := 0; i < 1000; i++ { database.CheckIfUserIsLoggingInFromKnownLocation(ctx, appsessionWithoutCache, email, "8.8.8.8") @@ -658,17 +659,17 @@ func TestCheckIfUserIsLoggingInFromKnownLocationPerformance(t *testing.T) { durationWithoutCache := time.Since(startTime) - // Assert that the cache improves the speed + // Assert that the Cache improves the speed if durationWithoutCache <= durationWithCache { - t.Errorf("Cache did not improve performance: duration with cache %v, duration without cache %v", durationWithCache, durationWithoutCache) + t.Errorf("Cache did not improve performance: duration with Cache %v, duration without Cache %v", durationWithCache, durationWithoutCache) } } func TestCheckIfUserIsLoggingInFromKnownLocation_withCache(t *testing.T) { email := "TestCheckIfUserIsLoggingInFromKnownLocation_withCache@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) - cache := configs.CreateCache() + Cache := configs.CreateCache() // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. w := httptest.NewRecorder() @@ -676,10 +677,10 @@ func TestCheckIfUserIsLoggingInFromKnownLocation_withCache(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, - Cache: cache, + Cache: Cache, } // Mock the DB response @@ -700,8 +701,8 @@ func TestCheckIfUserIsLoggingInFromKnownLocation_withCache(t *testing.T) { t.Fatalf("Failed to insert test user into database: %v", err) } - // Verify the user is not in the cache before calling the function - nocachedUser, err := cache.Get(email) + // Verify the user is not in the Cache before calling the function + nocachedUser, err := Cache.Get(cache.UserKey(email)) assert.NotNil(t, err) assert.Nil(t, nocachedUser) @@ -714,8 +715,8 @@ func TestCheckIfUserIsLoggingInFromKnownLocation_withCache(t *testing.T) { assert.Nil(t, err) assert.Nil(t, info) - // Verify the user is in the cache - cachedUser, err := cache.Get(email) + // Verify the user is in the Cache + cachedUser, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, cachedUser) diff --git a/occupi-backend/tests/database_test.go b/occupi-backend/tests/database_test.go index 9ec8e3ef..88d1e55a 100644 --- a/occupi-backend/tests/database_test.go +++ b/occupi-backend/tests/database_test.go @@ -22,6 +22,7 @@ import ( "github.com/COS301-SE-2024/occupi/occupi-backend/configs" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/authenticator" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/cache" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database" "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" @@ -175,16 +176,16 @@ func TestEmailExists(t *testing.T) { assert.True(t, exists) }) - mt.Run("Email exists adding to cache", func(mt *mtest.T) { + mt.Run("Email exists adding to Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ {Key: "email", Value: email}, })) - cache := configs.CreateCache() + Cache := configs.CreateCache() appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -193,8 +194,8 @@ func TestEmailExists(t *testing.T) { // Validate the result assert.True(t, exists) - // Check if the email exists in the cache - email, err := cache.Get(email) + // Check if the email exists in the Cache + email, err := Cache.Get(cache.UserKey(email)) assert.NoError(t, err) assert.NotNil(t, email) }) @@ -278,14 +279,14 @@ func TestAddUser(t *testing.T) { assert.True(t, success) }) - mt.Run("Add user successfully to cache", func(mt *mtest.T) { + mt.Run("Add user successfully to Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -295,8 +296,8 @@ func TestAddUser(t *testing.T) { assert.NoError(t, err) assert.True(t, success) - // Verify the user was added to the cache - user, err := cache.Get(user.Email) + // Verify the user was added to the Cache + user, err := Cache.Get(cache.UserKey(user.Email)) assert.Nil(t, err) assert.NotNil(t, user) @@ -372,10 +373,10 @@ func TestOTPExists(t *testing.T) { assert.True(t, exists) }) - mt.Run("OTP exists and is valid in cache", func(mt *mtest.T) { + mt.Run("OTP exists and is valid in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() otpStruct := models.OTP{ Email: email, @@ -383,24 +384,24 @@ func TestOTPExists(t *testing.T) { ExpireWhen: validOTP, } - // add otp to cache + // add otp to Cache if otpData, err := bson.Marshal(otpStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email+otp, otpData); err != nil { + if err := Cache.Set(cache.OTPKey(email, otp), otpData); err != nil { t.Fatal(err) } } - // Assert that the otp is in the cache - otpA, err := cache.Get(email + otp) + // Assert that the otp is in the Cache + otpA, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, otpA) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -429,10 +430,10 @@ func TestOTPExists(t *testing.T) { assert.False(t, exists) }) - mt.Run("OTP exists but is expired in cache", func(mt *mtest.T) { + mt.Run("OTP exists but is expired in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() otpStruct := models.OTP{ Email: email, @@ -440,24 +441,24 @@ func TestOTPExists(t *testing.T) { ExpireWhen: expiredOTP, } - // add otp to cache + // add otp to Cache if otpData, err := bson.Marshal(otpStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email+otp, otpData); err != nil { + if err := Cache.Set(cache.OTPKey(email, otp), otpData); err != nil { t.Fatal(err) } } - // Assert that the otp is in the cache - otpA, err := cache.Get(email + otp) + // Assert that the otp is in the Cache + otpA, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, otpA) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -548,14 +549,14 @@ func TestAddOTP(t *testing.T) { // Verify the inserted document }) - mt.Run("Add OTP successfully to cache", func(mt *mtest.T) { + mt.Run("Add OTP successfully to Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -565,8 +566,8 @@ func TestAddOTP(t *testing.T) { assert.NoError(t, err) assert.True(t, success) - // Verify the otp was added to the cache - otp, err := cache.Get(email + otp) + // Verify the otp was added to the Cache + otp, err := Cache.Get(cache.OTPKey(email, otp)) assert.Nil(t, err) assert.NotNil(t, otp) @@ -707,18 +708,18 @@ func TestVerifyUser(t *testing.T) { // Verify the update }) - mt.Run("Verify user successfully and user is not in cache", func(mt *mtest.T) { + mt.Run("Verify user successfully and user is not in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".OTPS", mtest.FirstBatch, bson.D{ {Key: "email", Value: email}, {Key: "isVerified", Value: false}, {Key: "nextVerificationDate", Value: time.Now().Add(-1 * time.Hour)}, })) - cache := configs.CreateCache() + Cache := configs.CreateCache() appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -731,14 +732,14 @@ func TestVerifyUser(t *testing.T) { // Verify the update }) - mt.Run("Verify user successfully and user is in cache", func(mt *mtest.T) { + mt.Run("Verify user successfully and user is in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".OTPS", mtest.FirstBatch, bson.D{ {Key: "email", Value: email}, {Key: "isVerified", Value: false}, {Key: "nextVerificationDate", Value: time.Now().Add(-1 * time.Hour)}, })) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, @@ -746,24 +747,24 @@ func TestVerifyUser(t *testing.T) { NextVerificationDate: time.Now().Add(-1 * time.Hour), } - // add user to cache + // add user to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the user is in the cache - userA, err := cache.Get(email) + // Assert that the user is in the Cache + userA, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, userA) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -773,8 +774,8 @@ func TestVerifyUser(t *testing.T) { assert.NoError(t, err) assert.True(t, success) - // Verify the update in cache - user, err := cache.Get(email) + // Verify the update in Cache + user, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) @@ -860,34 +861,34 @@ func TestGetPassword(t *testing.T) { assert.Equal(t, password, pass) }) - mt.Run("Get password successfully from cache", func(mt *mtest.T) { + mt.Run("Get password successfully from Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, Password: password, } - // Add password to cache + // Add password to Cache if passData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, passData); err != nil { + if err := Cache.Set(cache.UserKey(email), passData); err != nil { t.Fatal(err) } } - // Assert that the password is in the cache - pass, err := cache.Get(email) + // Assert that the password is in the Cache + pass, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, pass) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -1046,34 +1047,34 @@ func TestUpdateVerificationStatusTo(t *testing.T) { // Verify the update }) - mt.Run("Update verification status successfully in cache to true", func(mt *mtest.T) { + mt.Run("Update verification status successfully in Cache to true", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, IsVerified: false, } - // Add password to cache + // Add password to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the password is in the cache - user, err := cache.Get(email) + // Assert that the password is in the Cache + user, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } success, err := database.UpdateVerificationStatusTo(ctx, appsession, email, true) @@ -1082,8 +1083,8 @@ func TestUpdateVerificationStatusTo(t *testing.T) { assert.NoError(t, err) assert.True(t, success) - // Verify the update in cache - user, err = cache.Get(email) + // Verify the update in Cache + user, err = Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) @@ -1097,34 +1098,34 @@ func TestUpdateVerificationStatusTo(t *testing.T) { assert.True(t, userB.IsVerified) }) - mt.Run("Update verification status successfully in cache to false", func(mt *mtest.T) { + mt.Run("Update verification status successfully in Cache to false", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, IsVerified: true, } - // Add password to cache + // Add password to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the password is in the cache - user, err := cache.Get(email) + // Assert that the password is in the Cache + user, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } success, err := database.UpdateVerificationStatusTo(ctx, appsession, email, false) @@ -1133,8 +1134,8 @@ func TestUpdateVerificationStatusTo(t *testing.T) { assert.NoError(t, err) assert.True(t, success) - // Verify the update in cache - user, err = cache.Get(email) + // Verify the update in Cache + user, err = Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) @@ -1214,34 +1215,34 @@ func TestCheckIfUserIsAdmin(t *testing.T) { assert.True(t, isAdmin) }) - mt.Run("User is admin in cache", func(mt *mtest.T) { + mt.Run("User is admin in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, Role: constants.Admin, } - // Add user to cache + // Add user to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the user is in the cache - user, err := cache.Get(email) + // Assert that the user is in the Cache + user, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -1269,34 +1270,34 @@ func TestCheckIfUserIsAdmin(t *testing.T) { assert.False(t, isAdmin) }) - mt.Run("User is not admin in cache", func(mt *mtest.T) { + mt.Run("User is not admin in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, Role: constants.Basic, } - // Add user to cache + // Add user to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the user is in the cache - user, err := cache.Get(email) + // Assert that the user is in the Cache + user, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -1306,8 +1307,8 @@ func TestCheckIfUserIsAdmin(t *testing.T) { assert.NoError(t, err) assert.False(t, isAdmin) - // Verify the user was not updated in the cache - user, err = cache.Get(email) + // Verify the user was not updated in the Cache + user, err = Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, user) @@ -1397,7 +1398,6 @@ func TestGetEmailByResetToken(t *testing.T) { } // Test CheckResetToken - func TestCheckResetToken(t *testing.T) { mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) @@ -1464,7 +1464,10 @@ func TestUpdateUserPassword(t *testing.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - success, err := database.UpdateUserPassword(ctx, mt.Client, email, newPassword) + appsession := &models.AppSession{ + DB: mt.Client, + } + success, err := database.UpdateUserPassword(ctx, appsession, email, newPassword) assert.NoError(t, err) assert.True(t, success) @@ -1479,7 +1482,10 @@ func TestUpdateUserPassword(t *testing.T) { Message: "update error", })) - success, err := database.UpdateUserPassword(ctx, mt.Client, email, newPassword) + appsession := &models.AppSession{ + DB: mt.Client, + } + success, err := database.UpdateUserPassword(ctx, appsession, email, newPassword) assert.Error(t, err) assert.False(t, success) @@ -1605,7 +1611,7 @@ func TestFilterUsersWithProjection(t *testing.T) { func TestFilterUsersWithProjectionSuccess(t *testing.T) { email := "TestFilterUsersWithProjectionSuccess@example.com" - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. @@ -1614,7 +1620,7 @@ func TestFilterUsersWithProjectionSuccess(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, } @@ -1681,7 +1687,7 @@ func TestFilterUsersWithProjectionSuccess(t *testing.T) { } func TestFilterUsersWithProjectionAndSortAscSuccess(t *testing.T) { - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. @@ -1690,7 +1696,7 @@ func TestFilterUsersWithProjectionAndSortAscSuccess(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, } @@ -1764,7 +1770,7 @@ func TestFilterUsersWithProjectionAndSortAscSuccess(t *testing.T) { } func TestFilterUsersWithProjectionAndSortDescSuccess(t *testing.T) { - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. @@ -1773,7 +1779,7 @@ func TestFilterUsersWithProjectionAndSortDescSuccess(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, } @@ -1949,10 +1955,10 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { assert.Equal(t, "South Africa", info.Country) }) - mt.Run("Location exists and is valid in cache", func(mt *mtest.T) { + mt.Run("Location exists and is valid in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, @@ -1965,24 +1971,24 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { }, } - // add userstruct to cache + // add userstruct to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the user is in the cache - userA, err := cache.Get(email) + // Assert that the user is in the Cache + userA, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, userA) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -1994,10 +2000,10 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { assert.Nil(t, info) }) - mt.Run("Location exists but does not match what is in cache", func(mt *mtest.T) { + mt.Run("Location exists but does not match what is in Cache", func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateSuccessResponse()) - cache := configs.CreateCache() + Cache := configs.CreateCache() userStruct := models.User{ Email: email, @@ -2010,24 +2016,24 @@ func TestCheckIfUserIsLoggingInFromKnownLocation(t *testing.T) { }, } - // add userstruct to cache + // add userstruct to Cache if userData, err := bson.Marshal(userStruct); err != nil { t.Fatal(err) } else { - if err := cache.Set(email, userData); err != nil { + if err := Cache.Set(cache.UserKey(email), userData); err != nil { t.Fatal(err) } } - // Assert that the user is in the cache - userA, err := cache.Get(email) + // Assert that the user is in the Cache + userA, err := Cache.Get(cache.UserKey(email)) assert.Nil(t, err) assert.NotNil(t, userA) appsession := &models.AppSession{ DB: mt.Client, - Cache: cache, + Cache: Cache, } // Call the function under test @@ -2076,7 +2082,7 @@ func TestGetUsersPushTokens(t *testing.T) { }, }, } - // Create database connection and cache + // Create database connection and Cache db := configs.ConnectToDatabase(constants.AdminDBAccessOption) // Create a new ResponseRecorder (which satisfies http.ResponseWriter) to record the response. @@ -2085,7 +2091,7 @@ func TestGetUsersPushTokens(t *testing.T) { // Create a response writer and context ctx, _ := gin.CreateTestContext(w) - // Create a new AppSession with the cache + // Create a new AppSession with the Cache appSession := &models.AppSession{ DB: db, } From 26e3bf169a1916651b3617403aea1e6948d2f660 Mon Sep 17 00:00:00 2001 From: Michael-u21546551 Date: Sat, 27 Jul 2024 11:24:46 +0200 Subject: [PATCH 3/4] test: 471 passing tests with increased codecov --- .../tests/cache_methods_unit_test.go | 409 +++++++++++++++++- occupi-backend/tests/middleware_test.go | 4 +- 2 files changed, 409 insertions(+), 4 deletions(-) diff --git a/occupi-backend/tests/cache_methods_unit_test.go b/occupi-backend/tests/cache_methods_unit_test.go index 1db72db5..c49c7cf3 100644 --- a/occupi-backend/tests/cache_methods_unit_test.go +++ b/occupi-backend/tests/cache_methods_unit_test.go @@ -12,6 +12,43 @@ import ( "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" ) +func TestUserKey(t *testing.T) { + email := "test@example.com" + expected := "Users:test@example.com" + result := cache.UserKey(email) + if result != expected { + t.Errorf("UserKey(%s) = %s; want %s", email, result, expected) + } +} + +func TestOTPKey(t *testing.T) { + email := "test@example.com" + otp := "123456" + expected := "OTPs:test@example.com:123456" + result := cache.OTPKey(email, otp) + if result != expected { + t.Errorf("OTPKey(%s, %s) = %s; want %s", email, otp, result, expected) + } +} + +func TestRoomBookingKey(t *testing.T) { + roomID := "room123" + expected := "RoomBookings:room123" + result := cache.RoomBookingKey(roomID) + if result != expected { + t.Errorf("RoomBookingKey(%s) = %s; want %s", roomID, result, expected) + } +} + +func TestImageKey(t *testing.T) { + imageID := "image123" + expected := "Images:image123" + result := cache.ImageKey(imageID) + if result != expected { + t.Errorf("ImageKey(%s) = %s; want %s", imageID, result, expected) + } +} + func TestGetUser(t *testing.T) { email := "test@example.com" user := models.User{Email: email} @@ -143,6 +180,11 @@ func TestDeleteUser(t *testing.T) { email: email, expectedUser: models.User{}, }, + { + name: "cache key does not exist", + email: "doesnotexist@example.com", + expectedUser: models.User{}, + }, } for _, tt := range tests { @@ -165,13 +207,20 @@ func TestDeleteUser(t *testing.T) { cache.DeleteUser(appsession, tt.email) - if tt.name != "cache is nil" { + if tt.name != "cache is nil" && tt.name != "cache key does not exist" { // check if user was deleted in cache userData, err := appsession.Cache.Get(cache.UserKey(email)) assert.NotNil(t, err) assert.Nil(t, userData) } + + if tt.name == "cache key does not exist" { + // check if user was not deleted in cache + userData, err := appsession.Cache.Get(cache.UserKey(email)) + assert.NoError(t, err) + assert.NotNil(t, userData) + } }) } } @@ -312,6 +361,13 @@ func TestDeleteOTPF(t *testing.T) { OTP: otpv, }, }, + { + name: "cache key does not exist", + expectedOTP: models.OTP{ + Email: "doesnotexist@example.com", + OTP: "012345", + }, + }, } for _, tt := range tests { @@ -334,12 +390,361 @@ func TestDeleteOTPF(t *testing.T) { cache.DeleteOTP(appsession, tt.expectedOTP.Email, tt.expectedOTP.OTP) - if tt.name != "cache is nil" { + if tt.name != "cache is nil" && tt.name != "cache key does not exist" { // check if otp was deleted in cache otpData, err := appsession.Cache.Get(cache.OTPKey(email, otpv)) assert.NotNil(t, err) assert.Nil(t, otpData) } + + if tt.name == "cache key does not exist" { + // check if otp was not deleted in cache + otpData, err := appsession.Cache.Get(cache.OTPKey(email, otpv)) + assert.NoError(t, err) + assert.NotNil(t, otpData) + } + }) + } +} + +func TestSetBooking(t *testing.T) { + booking := models.Booking{OccupiID: "booking123"} + + tests := []struct { + name string + booking models.Booking + expectedBooking models.Booking + }{ + { + name: "cache is nil", + booking: booking, + expectedBooking: models.Booking{}, + }, + { + name: "successful set booking in cache", + booking: booking, + expectedBooking: booking, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + } else { + appsession = &models.AppSession{} + } + + cache.SetBooking(appsession, tt.booking) + + if tt.name != "cache is nil" { + // check if booking was set in cache + bookingData, err := appsession.Cache.Get(cache.RoomBookingKey(tt.booking.OccupiID)) + assert.NoError(t, err) + + // unmarshal the booking from the cache + var booking models.Booking + if err := bson.Unmarshal(bookingData, &booking); err != nil { + t.Error("failed to unmarshall", err) + } + + assert.Equal(t, tt.expectedBooking, booking) + } + }) + } +} + +func TestGetBooking(t *testing.T) { + booking := models.Booking{OccupiID: "booking123"} + bookingData, _ := bson.Marshal(booking) + + tests := []struct { + name string + bookingID string + expectedBook models.Booking + expectedErr error + }{ + { + name: "cache is nil", + bookingID: "booking123", + expectedBook: models.Booking{}, + expectedErr: errors.New("cache not found"), + }, + { + name: "cache key does not exist", + bookingID: "booking1234", + expectedBook: models.Booking{}, + expectedErr: errors.New("Entry not found"), + }, + { + name: "successful get booking from cache", + bookingID: "booking123", + expectedBook: booking, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + // add booking to cache + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + err := appsession.Cache.Set(cache.RoomBookingKey(booking.OccupiID), bookingData) + + assert.NoError(t, err) + } else { + appsession = &models.AppSession{} + } + + result, err := cache.GetBooking(appsession, tt.bookingID) + + assert.Equal(t, tt.expectedBook, result) + if tt.expectedErr != nil { + assert.EqualError(t, err, tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDeleteBooking(t *testing.T) { + booking := models.Booking{OccupiID: "booking123"} + + tests := []struct { + name string + bookingID string + expectedBook models.Booking + }{ + { + name: "cache is nil", + bookingID: "booking123", + expectedBook: models.Booking{}, + }, + { + name: "successful delete booking in cache", + bookingID: "booking123", + expectedBook: models.Booking{}, + }, + { + name: "cache key does not exist", + bookingID: "booking1234", + expectedBook: models.Booking{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + + // add booking to cache + bookingData, _ := bson.Marshal(booking) + err := appsession.Cache.Set(cache.RoomBookingKey(booking.OccupiID), bookingData) + + assert.NoError(t, err) + } else { + appsession = &models.AppSession{} + } + + cache.DeleteBooking(appsession, tt.bookingID) + + if tt.name != "cache is nil" && tt.name != "cache key does not exist" { + // check if booking was deleted in cache + bookingData, err := appsession.Cache.Get(cache.RoomBookingKey(booking.OccupiID)) + assert.NotNil(t, err) + assert.Nil(t, bookingData) + } + + if tt.name == "cache key does not exist" { + // check if booking was not deleted in cache + bookingData, err := appsession.Cache.Get(cache.RoomBookingKey(booking.OccupiID)) + assert.NoError(t, err) + assert.NotNil(t, bookingData) + } + }) + } +} + +func TestSetImage(t *testing.T) { + image := models.Image{ID: "image123"} + + tests := []struct { + name string + image models.Image + expectedImage models.Image + }{ + { + name: "cache is nil", + image: image, + expectedImage: models.Image{}, + }, + { + name: "successful set image in cache", + image: image, + expectedImage: image, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + } else { + appsession = &models.AppSession{} + } + + cache.SetImage(appsession, tt.image.ID, tt.image) + + if tt.name != "cache is nil" { + // check if image was set in cache + imageData, err := appsession.Cache.Get(cache.ImageKey(tt.image.ID)) + assert.NoError(t, err) + + // unmarshal the image from the cache + var image models.Image + if err := bson.Unmarshal(imageData, &image); err != nil { + t.Error("failed to unmarshall", err) + } + + assert.Equal(t, tt.expectedImage, image) + } + }) + } +} + +func TestGetImage(t *testing.T) { + image := models.Image{ID: "image123"} + imageData, _ := bson.Marshal(image) + + tests := []struct { + name string + imageID string + expectedImg models.Image + expectedErr error + }{ + { + name: "cache is nil", + imageID: "image123", + expectedImg: models.Image{}, + expectedErr: errors.New("cache not found"), + }, + { + name: "cache key does not exist", + imageID: "image1234", + expectedImg: models.Image{}, + expectedErr: errors.New("Entry not found"), + }, + { + name: "successful get image from cache", + imageID: "image123", + expectedImg: image, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + // add image to cache + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + err := appsession.Cache.Set(cache.ImageKey(image.ID), imageData) + + assert.NoError(t, err) + } else { + appsession = &models.AppSession{} + } + + result, err := cache.GetImage(appsession, tt.imageID) + + assert.Equal(t, tt.expectedImg, result) + if tt.expectedErr != nil { + assert.EqualError(t, err, tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDeleteImage(t *testing.T) { + image := models.Image{ID: "image123"} + + tests := []struct { + name string + imageID string + expectedImg models.Image + }{ + { + name: "cache is nil", + imageID: "image123", + expectedImg: models.Image{}, + }, + { + name: "successful delete image in cache", + imageID: "image123", + expectedImg: models.Image{}, + }, + { + name: "cache key does not exist", + imageID: "image1234", + expectedImg: models.Image{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var appsession *models.AppSession + + if tt.name != "cache is nil" { + appsession = &models.AppSession{ + Cache: configs.CreateCache(), + } + + // add image to cache + imageData, _ := bson.Marshal(image) + err := appsession.Cache.Set(cache.ImageKey(image.ID), imageData) + + assert.NoError(t, err) + } else { + appsession = &models.AppSession{} + } + + cache.DeleteImage(appsession, tt.imageID) + + if tt.name != "cache is nil" && tt.name != "cache key does not exist" { + // check if image was deleted in cache + imageData, err := appsession.Cache.Get(cache.ImageKey(image.ID)) + assert.NotNil(t, err) + assert.Nil(t, imageData) + } + + if tt.name == "cache key does not exist" { + // check if image was not deleted in cache + imageData, err := appsession.Cache.Get(cache.ImageKey(image.ID)) + assert.NoError(t, err) + assert.NotNil(t, imageData) + } }) } } diff --git a/occupi-backend/tests/middleware_test.go b/occupi-backend/tests/middleware_test.go index 651d43be..ec067ac9 100644 --- a/occupi-backend/tests/middleware_test.go +++ b/occupi-backend/tests/middleware_test.go @@ -847,12 +847,12 @@ func TestAttachOTPRateLimitMiddleware(t *testing.T) { clientIP: "192.168.0.1", waitDuration: 4 * time.Second, expectedCode: http.StatusOK, - expectedBody: `OTP request successfu`, + expectedBody: "OTP request successful", }, { description: "request after a couple seconds should succeed", clientIP: "192.168.0.1", - waitDuration: 5 * time.Second, + waitDuration: 4 * time.Second, expectedCode: http.StatusOK, expectedBody: "OTP request successful", }, From 3b7717960b7c7eac5dacfa81ea533602454ff974 Mon Sep 17 00:00:00 2001 From: Michael-u21546551 Date: Sat, 27 Jul 2024 11:34:11 +0200 Subject: [PATCH 4/4] lint: fixing some linting issues --- occupi-backend/pkg/cache/cache.go | 2 +- occupi-backend/pkg/handlers/api_handlers.go | 9 +-- occupi-backend/pkg/handlers/api_helpers.go | 77 +++++++++++++++++++++ occupi-backend/pkg/utils/utils.go | 2 +- 4 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 occupi-backend/pkg/handlers/api_helpers.go diff --git a/occupi-backend/pkg/cache/cache.go b/occupi-backend/pkg/cache/cache.go index 6e86232b..bc407577 100644 --- a/occupi-backend/pkg/cache/cache.go +++ b/occupi-backend/pkg/cache/cache.go @@ -172,7 +172,7 @@ func GetImage(appsession *models.AppSession, id string) (models.Image, error) { return models.Image{}, errors.New("cache not found") } - //unmarshal the image from the cache + // unmarshal the image from the cache var image models.Image imageData, err := appsession.Cache.Get(ImageKey(id)) diff --git a/occupi-backend/pkg/handlers/api_handlers.go b/occupi-backend/pkg/handlers/api_handlers.go index fbd05f64..125bbe85 100644 --- a/occupi-backend/pkg/handlers/api_handlers.go +++ b/occupi-backend/pkg/handlers/api_handlers.go @@ -838,18 +838,15 @@ func DownloadProfileImage(ctx *gin.Context, appsession *models.AppSession) { case constants.ThumbnailRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.Thumbnail) - go database.GetImageData(ctx, appsession, id, constants.LowRes) - go database.GetImageData(ctx, appsession, id, constants.MidRes) - go database.GetImageData(ctx, appsession, id, constants.HighRes) + go PreloadAllImageResolutions(ctx, appsession, id) case constants.LowRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageLowRes) - go database.GetImageData(ctx, appsession, id, constants.MidRes) - go database.GetImageData(ctx, appsession, id, constants.HighRes) + go PreloadMidAndHighResolutions(ctx, appsession, id) case constants.MidRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageMidRes) - go database.GetImageData(ctx, appsession, id, constants.HighRes) + go PreloadHighResolution(ctx, appsession, id) case constants.HighRes: ctx.Data(http.StatusOK, "application/octet-stream", imageData.ImageHighRes) default: diff --git a/occupi-backend/pkg/handlers/api_helpers.go b/occupi-backend/pkg/handlers/api_helpers.go new file mode 100644 index 00000000..af110000 --- /dev/null +++ b/occupi-backend/pkg/handlers/api_helpers.go @@ -0,0 +1,77 @@ +package handlers + +import ( + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/constants" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/database" + "github.com/COS301-SE-2024/occupi/occupi-backend/pkg/models" + "github.com/gin-gonic/gin" +) + +func PreloadAllImageResolutions(ctx *gin.Context, appsession *models.AppSession, id string) { + // ***IMPORTANT NOTE ABOUT ERROR HANDLING IN THIS FUNCTION*** + // if an error occurs, its not a big deal + // this mainly serves as an attempt to cache the image + // so that it can be served faster when the user requests it + // if we cannot, we can just serve the image from the database + // the user will just have to wait a bit longer + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.LowRes) + if err != nil { + return + } + }() + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.MidRes) + if err != nil { + return + } + }() + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.HighRes) + if err != nil { + return + } + }() +} + +func PreloadMidAndHighResolutions(ctx *gin.Context, appsession *models.AppSession, id string) { + // ***IMPORTANT NOTE ABOUT ERROR HANDLING IN THIS FUNCTION*** + // if an error occurs, its not a big deal + // this mainly serves as an attempt to cache the image + // so that it can be served faster when the user requests it + // if we cannot, we can just serve the image from the database + // the user will just have to wait a bit longer + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.MidRes) + if err != nil { + return + } + }() + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.HighRes) + if err != nil { + return + } + }() +} + +func PreloadHighResolution(ctx *gin.Context, appsession *models.AppSession, id string) { + // ***IMPORTANT NOTE ABOUT ERROR HANDLING IN THIS FUNCTION*** + // if an error occurs, its not a big deal + // this mainly serves as an attempt to cache the image + // so that it can be served faster when the user requests it + // if we cannot, we can just serve the image from the database + // the user will just have to wait a bit longer + + go func() { + _, err := database.GetImageData(ctx, appsession, id, constants.HighRes) + if err != nil { + return + } + }() +} diff --git a/occupi-backend/pkg/utils/utils.go b/occupi-backend/pkg/utils/utils.go index 5a36785d..f9b9a3c9 100644 --- a/occupi-backend/pkg/utils/utils.go +++ b/occupi-backend/pkg/utils/utils.go @@ -458,7 +458,7 @@ func ConvertToStringArray(input interface{}) []string { } func ConvertTokensToStringArray(tokens []primitive.M, key string) ([]string, error) { - var stringArray []string + stringArray := []string{} for _, token := range tokens { // Ensure the map contains the key