Skip to content

Commit

Permalink
Refactor database package to include CheckCoincidingBookings function
Browse files Browse the repository at this point in the history
  • Loading branch information
waveyboym committed Oct 21, 2024
1 parent e6a2b97 commit 751f193
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 2 deletions.
35 changes: 35 additions & 0 deletions occupi-backend/pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,41 @@ func GetAllData(ctx *gin.Context, appsession *models.AppSession) []bson.M {
return users
}

func CheckCoincidingBookings(ctx *gin.Context, appsession *models.AppSession, booking models.Booking) (bool, error) {
if appsession.DB == nil {
logrus.Error("Database is nil")
return false, errors.New("database is nil")
}

// Check if the booking exists in the database
collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("RoomBooking")

filter := bson.M{
"roomId": booking.RoomID,
"$and": []bson.M{
{"start": bson.M{"$lt": booking.End}}, // Existing booking starts before new booking ends
{"end": bson.M{"$gt": booking.Start}}, // Existing booking ends after new booking starts
},
}

var existingbooking models.Booking
err := collection.FindOne(ctx, filter).Decode(&existingbooking)
if err != nil {
logrus.Error(err)
if err == mongo.ErrNoDocuments {
return false, nil // No conflict found
}
return false, err // Other errors
}

logrus.Info("Existing booking: ", existingbooking)
logrus.Info("Room id's match: ", existingbooking.RoomID == booking.RoomID)
logrus.Info("Room id to check: ", booking.RoomID)
logrus.Info("Existing room id: ", existingbooking.RoomID)

return true, nil
}

// attempts to save booking in database
func SaveBooking(ctx *gin.Context, appsession *models.AppSession, booking models.Booking) (bool, error) {
// check if database is nil
Expand Down
13 changes: 13 additions & 0 deletions occupi-backend/pkg/handlers/api_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ func BookRoom(ctx *gin.Context, appsession *models.AppSession) {
return
}

// check if no booking has been made that coincides with the start and end time of this booking
coinciding, err := database.CheckCoincidingBookings(ctx, appsession, booking)
if err != nil {
configs.CaptureError(ctx, err)
ctx.JSON(http.StatusInternalServerError, utils.ErrorResponse(http.StatusInternalServerError, "Failed to book", constants.InternalServerErrorCode, "Failed to book", nil))
return
}

if coinciding {
ctx.JSON(http.StatusBadRequest, utils.ErrorResponse(http.StatusBadRequest, "Booking coincides with another booking", constants.BadRequestCode, "Booking coincides with another booking", nil))
return
}

// Generate a unique ID for the booking
booking.ID = primitive.NewObjectID().Hex()
booking.OccupiID = utils.GenerateBookingID()
Expand Down
166 changes: 164 additions & 2 deletions occupi-backend/tests/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1366,8 +1366,7 @@ func TestAddOTP(t *testing.T) {
mock.ExpectGet(cache.OTPKey(email, otp)).SetVal(string(otpData))

// Verify the otp was added to the Cache
res := Cache.Get(context.Background(), cache.OTPKey(email, otp))
_, err = res.Bytes()
_ = Cache.Get(context.Background(), cache.OTPKey(email, otp))

//assert.Nil(t, err)
//assert.NotNil(t, otpv)
Expand Down Expand Up @@ -11578,3 +11577,166 @@ func TestGetUsersLocations(t *testing.T) {
assert.Nil(t, results, "Expected nil results")
})
}

// TestCheckCoincidingBookings contains all test cases with sub-tests
func TestCheckCoincidingBookings(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))

// Set gin run mode
gin.SetMode(configs.GetGinRunMode())
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())

// Test cases
mt.Run("Database Is Nil", func(mt *mtest.T) {
// Database is nil case
appsessionNil := &models.AppSession{DB: nil}
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 9, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 14, 0, 0, 0, time.UTC),
}

result, err := database.CheckCoincidingBookings(ctx, appsessionNil, booking)

// Assertions
assert.Error(t, err)
assert.Equal(t, "database is nil", err.Error())
assert.False(t, result)
})

mt.Run("NoConflict", func(mt *mtest.T) {
// No overlapping booking in the database
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 13, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 14, 0, 0, 0, time.UTC),
}
mt.AddMockResponses(mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".RoomBooking", mtest.FirstBatch))

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.NoError(t, err)
assert.False(t, result)
})

mt.Run("WithConflict_FullyEnclosed", func(mt *mtest.T) {
// Test with an existing booking that fully encloses the new one (10am-12pm, new booking 9am-1pm)
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 9, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 13, 0, 0, 0, time.UTC),
}
existingBooking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 10, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 12, 0, 0, 0, time.UTC),
}
firstBatch := mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".RoomBooking", mtest.FirstBatch, bson.D{
{Key: "roomId", Value: existingBooking.RoomID},
{Key: "start", Value: existingBooking.Start},
{Key: "end", Value: existingBooking.End},
})
mt.AddMockResponses(firstBatch)

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.NoError(t, err)
assert.True(t, result)
})

mt.Run("WithConflict_NewFullyEnclosesExisting", func(mt *mtest.T) {
// Test with a new booking that fully encloses an existing one (new 9am-2pm, existing 10am-12pm)
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 9, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 14, 0, 0, 0, time.UTC),
}
existingBooking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 10, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 12, 0, 0, 0, time.UTC),
}
firstBatch := mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".RoomBooking", mtest.FirstBatch, bson.D{
{Key: "roomId", Value: existingBooking.RoomID},
{Key: "start", Value: existingBooking.Start},
{Key: "end", Value: existingBooking.End},
})
mt.AddMockResponses(firstBatch)

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.NoError(t, err)
assert.True(t, result)
})

mt.Run("PartialOverlap_StartInsideExistingEndAfter", func(mt *mtest.T) {
// Test with a booking that starts inside and ends after existing booking (existing 10am-12pm, new 11am-1pm)
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 11, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 13, 0, 0, 0, time.UTC),
}
existingBooking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 10, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 12, 0, 0, 0, time.UTC),
}
firstBatch := mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".RoomBooking", mtest.FirstBatch, bson.D{
{Key: "roomId", Value: existingBooking.RoomID},
{Key: "start", Value: existingBooking.Start},
{Key: "end", Value: existingBooking.End},
})
mt.AddMockResponses(firstBatch)

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.NoError(t, err)
assert.True(t, result)
})

mt.Run("NoConflict_NonOverlapping", func(mt *mtest.T) {
// No overlap (existing 8am-9am, new booking 9am-10am)
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 9, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 10, 0, 0, 0, time.UTC),
}
mt.AddMockResponses(mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".RoomBooking", mtest.FirstBatch))

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.NoError(t, err)
assert.False(t, result)
})

mt.Run("DatabaseError", func(mt *mtest.T) {
// Simulate database error
booking := models.Booking{
RoomID: "Room1",
Start: time.Date(2024, 10, 20, 9, 0, 0, 0, time.UTC),
End: time.Date(2024, 10, 20, 14, 0, 0, 0, time.UTC),
}

mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{
Code: 11000,
Message: "mock error",
}))

appsession := &models.AppSession{DB: mt.Client}

result, err := database.CheckCoincidingBookings(ctx, appsession, booking)

assert.Error(t, err)
assert.False(t, result)
})
}

0 comments on commit 751f193

Please sign in to comment.