diff --git a/occupi-backend/pkg/database/database.go b/occupi-backend/pkg/database/database.go index 6c881fe5..7e4dc429 100644 --- a/occupi-backend/pkg/database/database.go +++ b/occupi-backend/pkg/database/database.go @@ -1290,6 +1290,27 @@ func AddImageIDToRoom(ctx *gin.Context, appsession *models.AppSession, roomID, i return nil } +func DeleteImageIDFromRoom(ctx *gin.Context, appsession *models.AppSession, roomID, imageID string) error { + // check if database is nil + if appsession.DB == nil { + logrus.Error("Database is nil") + return errors.New("database is nil") + } + + collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Rooms") + + filter := bson.M{"roomId": roomID} + update := bson.M{"$pull": bson.M{"roomImageIds": imageID}} + + _, err := collection.UpdateOne(ctx, filter, update) + if err != nil { + logrus.Error(err) + return err + } + + return nil +} + func CheckIfUserHasMFAEnabled(ctx *gin.Context, appsession *models.AppSession, email string) (bool, error) { // check if database is nil if appsession.DB == nil { @@ -1477,24 +1498,3 @@ func GetAvailableSlots(ctx *gin.Context, appsession *models.AppSession, request return slots, nil } - -func DeleteImageIDFromRoom(ctx *gin.Context, appsession *models.AppSession, roomID, imageID string) error { - // check if database is nil - if appsession.DB == nil { - logrus.Error("Database is nil") - return errors.New("database is nil") - } - - collection := appsession.DB.Database(configs.GetMongoDBName()).Collection("Rooms") - - filter := bson.M{"roomId": roomID} - update := bson.M{"$pull": bson.M{"roomImageIds": imageID}} - - _, err := collection.UpdateOne(ctx, filter, update) - if err != nil { - logrus.Error(err) - return err - } - - return nil -} diff --git a/occupi-backend/tests/database_test.go b/occupi-backend/tests/database_test.go index 532d9ce9..f07999c7 100644 --- a/occupi-backend/tests/database_test.go +++ b/occupi-backend/tests/database_test.go @@ -14,6 +14,7 @@ import ( "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/go-redis/redismock/v9" + "github.com/go-webauthn/webauthn/webauthn" "github.com/ipinfo/go/v2/ipinfo" "github.com/stretchr/testify/assert" @@ -6780,3 +6781,764 @@ func TestUpdateNotificationSettings(t *testing.T) { assert.Error(t, err) }) } + +func TestAddImageIDToRoom(t *testing.T) { + // Set Gin mode to match your configuration + gin.SetMode(configs.GetGinRunMode()) + + // Create a new mtest instance + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + + mt.Run("database is nil", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: nil, + } + + err := database.AddImageIDToRoom(ctx, appsession, "room1", "image1") + assert.EqualError(t, err, "database is nil") + }) + + mt.Run("update image ID successful", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + // Mock the UpdateOne operation as successful + mt.AddMockResponses(mtest.CreateSuccessResponse()) + + err := database.AddImageIDToRoom(ctx, appsession, "room1", "image1") + assert.NoError(t, err) + }) + + mt.Run("update image ID failure", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + // Mock the UpdateOne operation to return an error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 11000, + Message: "update error", + })) + + err := database.AddImageIDToRoom(ctx, appsession, "room1", "image1") + assert.EqualError(t, err, "update error") + }) +} + +func TestDeleteImageIDFromRoom(t *testing.T) { + // Set Gin mode to match your configuration + gin.SetMode(configs.GetGinRunMode()) + + // Create a new mtest instance + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + + mt.Run("database is nil", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: nil, + } + + err := database.DeleteImageIDFromRoom(ctx, appsession, "room1", "image1") + assert.EqualError(t, err, "database is nil") + }) + + mt.Run("delete image ID successful", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + // Mock the UpdateOne operation as successful + mt.AddMockResponses(mtest.CreateSuccessResponse()) + + err := database.DeleteImageIDFromRoom(ctx, appsession, "room1", "image1") + assert.NoError(t, err) + }) + + mt.Run("delete image ID failure", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + // Mock the UpdateOne operation to return an error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 11000, + Message: "delete error", + })) + + err := database.DeleteImageIDFromRoom(ctx, appsession, "room1", "image1") + assert.EqualError(t, err, "delete error") + }) +} + +func TestAddRoom(t *testing.T) { + // Set Gin mode to match your configuration + gin.SetMode(configs.GetGinRunMode()) + + // Create a new mtest instance + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + + ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) + + mt.Run("database is nil", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: nil, + } + + rroom := models.RequestRoom{ + RoomID: "room1", + RoomNo: "101", + FloorNo: "1", + MinOccupancy: 1, + MaxOccupancy: 4, + Description: "Test Room", + RoomName: "Test Room Name", + } + + _, err := database.AddRoom(ctx, appsession, rroom) + assert.EqualError(t, err, "database is nil") + }) + + mt.Run("room already exists", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + rroom := models.RequestRoom{ + RoomID: "room1", + RoomNo: "101", + FloorNo: "1", + MinOccupancy: 1, + MaxOccupancy: 4, + Description: "Test Room", + RoomName: "Test Room Name", + } + + // Mock the FindOne operation to return a matching room + mt.AddMockResponses(mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Rooms", mtest.FirstBatch, bson.D{ + {Key: "roomId", Value: rroom.RoomID}, + {Key: "roomNo", Value: rroom.RoomNo}, + })) + + _, err := database.AddRoom(ctx, appsession, rroom) + assert.EqualError(t, err, "room already exists") + }) + + mt.Run("add room successful", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + rroom := models.RequestRoom{ + RoomID: "room2", + RoomNo: "102", + FloorNo: "1", + MinOccupancy: 1, + MaxOccupancy: 4, + Description: "Another Test Room", + RoomName: "Another Test Room Name", + } + + // Mock the FindOne operation to return no matching room (room does not exist) + mt.AddMockResponses(mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".Rooms", mtest.FirstBatch)) + + // Mock the InsertOne operation as successful + mt.AddMockResponses(mtest.CreateSuccessResponse()) + + id, err := database.AddRoom(ctx, appsession, rroom) + assert.NoError(t, err) + assert.NotEmpty(t, id) + }) + + mt.Run("add room failure", func(mt *mtest.T) { + appsession := &models.AppSession{ + DB: mt.Client, + } + + rroom := models.RequestRoom{ + RoomID: "room3", + RoomNo: "103", + FloorNo: "1", + MinOccupancy: 1, + MaxOccupancy: 4, + Description: "Third Test Room", + RoomName: "Third Test Room Name", + } + + // Mock the FindOne operation to return no matching room (room does not exist) + mt.AddMockResponses(mtest.CreateCursorResponse(0, configs.GetMongoDBName()+".Rooms", mtest.FirstBatch)) + + // Mock the InsertOne operation to return an error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 11000, + Message: "duplicate key error", + })) + + _, err := database.AddRoom(ctx, appsession, rroom) + assert.EqualError(t, err, "duplicate key error") + }) +} + +func TestCheckIfUserHasMFAenabled(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()) + + mt.Run("Nil database", func(mt *mtest.T) { + // Call the function under test + appsession := &models.AppSession{} + res, err := database.CheckIfUserHasMFAEnabled(ctx, appsession, "test@example.com") + + // Validate the result + assert.False(t, res) + assert.Error(t, err) + }) + + mt.Run("Check from database", func(mt *mtest.T) { + firstBatch := mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ + {Key: "email", Value: "test@example.com"}, + {Key: "security", Value: bson.D{ + {Key: "mfa", Value: true}, + }, + }, + }) + + mt.AddMockResponses(firstBatch) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res, err := database.CheckIfUserHasMFAEnabled(ctx, appSession, "test@example.com") + + // Validate the result + assert.NoError(t, err) + assert.True(t, res) + }) + + mt.Run("Check from cache", func(mt *mtest.T) { + Cache, mock := redismock.NewClientMock() + + user := models.User{ + Email: "test@example.com", + Security: models.Security{ + MFA: true, + }, + } + + // add user to Cache + userData, err := bson.Marshal(user) + + assert.Nil(t, err) + + // Mock the Set operation + mock.ExpectSet(cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(userData)) + + // set the user in the Cache + res := Cache.Set(context.Background(), cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second) + + assert.Nil(t, res.Err()) + + // Call the function under test + appSession := &models.AppSession{ + DB: mt.Client, + Cache: Cache, + } + + // mock expect get + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + // Call the function under test + res1, err := database.CheckIfUserHasMFAEnabled(ctx, appSession, "test@example.com") + + // Validate the result + assert.NoError(t, err) + + // Ensure all expectations are met + assert.NoError(t, mock.ExpectationsWereMet()) + + assert.True(t, res1) + }) + + mt.Run("Find returns an error", func(mt *mtest.T) { + // Add a mock response that simulates a find error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 1, + Message: "find error", + })) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res, err := database.CheckIfUserHasMFAEnabled(ctx, appSession, "test@example.com") + + // Validate the result + assert.Error(t, err) + assert.False(t, res) + assert.Contains(t, err.Error(), "find error") + }) +} + +func TestGetUserCredentials(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()) + + mt.Run("Nil database", func(mt *mtest.T) { + // Call the function under test + appsession := &models.AppSession{} + res, err := database.GetUserCredentials(ctx, appsession, "test@example.com") + + // Validate the result + assert.Empty(t, res) + assert.Error(t, err) + }) + + mt.Run("Get user credentials from database", func(mt *mtest.T) { + firstBatch := mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ + {Key: "email", Value: "test@example.com"}, + {Key: "security", Value: bson.D{ + {Key: "credentials", Value: bson.D{ + //stored as bytes[] + {Key: "id", Value: primitive.Binary{Data: []byte("testID")}}, + {Key: "publicKey", Value: primitive.Binary{Data: []byte("testPublicKey")}}, + {Key: "attestationType", Value: "testAttestationType"}, + }, + }, + }, + }, + }) + + mt.AddMockResponses(firstBatch) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res, err := database.GetUserCredentials(ctx, appSession, "test@example.com") + + // Validate the result + assert.NoError(t, err) + assert.NotEmpty(t, res) + }) + + mt.Run("Get user credentials from cache", func(mt *mtest.T) { + Cache, mock := redismock.NewClientMock() + + user := models.User{ + Email: "test@example.com", + Security: models.Security{ + Credentials: webauthn.Credential{ + ID: []byte("testID"), + PublicKey: []byte("testPublicKey"), + AttestationType: "testAttestationType", + }, + }, + } + + // add user to Cache + userData, err := bson.Marshal(user) + + assert.Nil(t, err) + + // Mock the Set operation + mock.ExpectSet(cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(userData)) + + // set the user in the Cache + res := Cache.Set(context.Background(), cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second) + + assert.Nil(t, res.Err()) + + // Call the function under test + appSession := &models.AppSession{ + DB: mt.Client, + Cache: Cache, + } + + // mock expect get + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + // Call the function under test + res1, err := database.GetUserCredentials(ctx, appSession, "test@example.com") + + // Validate the result + assert.NoError(t, err) + + // Ensure all expectations are met + assert.NoError(t, mock.ExpectationsWereMet()) + + assert.NotEmpty(t, res1) + }) + + mt.Run("Find returns an error", func(mt *mtest.T) { + // Add a mock response that simulates a find error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 1, + Message: "find error", + })) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res, err := database.GetUserCredentials(ctx, appSession, "test@example.com") + + // Validate the result + assert.Error(t, err) + assert.Empty(t, res) + + assert.Contains(t, err.Error(), "find error") + + }) +} + +func TestAddUserCredentials(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()) + + mt.Run("Nil database", func(mt *mtest.T) { + // Call the function under test + appsession := &models.AppSession{} + err := database.AddUserCredential(ctx, appsession, "test@example.com", &webauthn.Credential{}) + + // Validate the result + assert.Error(t, err) + }) + + mt.Run("Add user credentials successfully", func(mt *mtest.T) { + // Mock the UpdateOne operation as successful + mt.AddMockResponses(mtest.CreateSuccessResponse()) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + err := database.AddUserCredential(ctx, appSession, "test@example.com", &webauthn.Credential{ + ID: []byte("testID"), + PublicKey: []byte("testPublicKey"), + AttestationType: "testAttestationType", + }) + + // Validate the result + assert.NoError(t, err) + }) + + mt.Run("Add user credentials successfully in cache", func(mt *mtest.T) { + // Mock the UpdateOne operation as successful + mt.AddMockResponses(mtest.CreateSuccessResponse()) + + Cache, mock := redismock.NewClientMock() + + user := models.User{ + Email: "test@example.com", + Security: models.Security{ + Credentials: webauthn.Credential{ + ID: []byte("testID"), + PublicKey: []byte("testPublicKey"), + AttestationType: "testAttestationType", + }, + }, + } + + // add user to Cache + userData, err := bson.Marshal(user) + + assert.Nil(t, err) + + // Mock the Set operation + mock.ExpectSet(cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(userData)) + + // set the user in the Cache + res := Cache.Set(context.Background(), cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second) + + assert.Nil(t, res.Err()) + + // call the function under test + appSession := &models.AppSession{ + DB: mt.Client, + Cache: Cache, + } + + // Validate the result + assert.NoError(t, err) + + // updated user + updatedUser := models.User{ + Email: "test@example.com", + Security: models.Security{ + Credentials: webauthn.Credential{ + ID: []byte("newtestID"), + PublicKey: []byte("newtestPublicKey"), + AttestationType: "newtestAttestationType", + }, + }, + } + + // marshal updated user + updatedUserData, err := bson.Marshal(updatedUser) + + assert.Nil(t, err) + + // mock expect get and set + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + mock.ExpectSet(cache.UserKey(user.Email), updatedUserData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(updatedUserData)) + + err = database.AddUserCredential(ctx, appSession, "test@example.com", &webauthn.Credential{ + ID: []byte("newtestID"), + PublicKey: []byte("newtestPublicKey"), + AttestationType: "newtestAttestationType", + }) + + // Validate the result + assert.NoError(t, err) + + // mock expect get + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + // Assert that the user is in the Cache + res1 := Cache.Get(context.Background(), cache.UserKey(user.Email)) + + userA, err := res1.Bytes() + + assert.Nil(t, err) + + // unmarshal user + var userB models.User + + if err := bson.Unmarshal(userA, &userB); err != nil { + t.Fatal(err) + } + + // Ensure all expectations are met + assert.NoError(t, mock.ExpectationsWereMet()) + }) + + mt.Run("Add user credentials failure", func(mt *mtest.T) { + // Mock the UpdateOne operation to return an error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 11000, + Message: "update error", + })) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + err := database.AddUserCredential(ctx, appSession, "test@example.com", &webauthn.Credential{ + ID: []byte("testID"), + PublicKey: []byte("testPublicKey"), + AttestationType: "testAttestationType", + }) + + // Validate the result + assert.EqualError(t, err, "update error") + }) +} + +func TestIsIPWithinRange(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()) + + mt.Run("Nil database", func(mt *mtest.T) { + // Call the function under test + appsession := &models.AppSession{} + res := database.IsIPWithinRange(ctx, appsession, "test@example.com", &ipinfo.Core{Location: "37.7749,-122.4194"}) + + // Validate the result + assert.False(t, res) + }) + + mt.Run("IP within range", func(mt *mtest.T) { + // insert user with location for firstbatch + firstBatch := mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ + {Key: "email", Value: "test@example.com"}, + // knownLocations is an array of locations + {Key: "knownLocations", Value: bson.A{ + bson.D{ + {Key: "city", Value: "CityA"}, + {Key: "region", Value: "RegionA"}, + {Key: "country", Value: "CountryA"}, + {Key: "location", Value: "37.7749,-122.4194"}, // San Francisco + }, + }, + }, + }) + + mt.AddMockResponses(firstBatch) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res := database.IsIPWithinRange(ctx, appSession, "test@example.com", &ipinfo.Core{Location: "34.0522,-118.2437"}) // Los Angeles + + // Validate the result + assert.True(t, res) + }) + + mt.Run("IP not within range", func(mt *mtest.T) { + // insert user with location for firstbatch + firstBatch := mtest.CreateCursorResponse(1, configs.GetMongoDBName()+".Users", mtest.FirstBatch, bson.D{ + {Key: "email", Value: "test@example.com"}, + // knownLocations is an array of locations + {Key: "knownLocations", Value: bson.A{ + bson.D{ + {Key: "city", Value: "CityA"}, + {Key: "region", Value: "RegionA"}, + {Key: "country", Value: "CountryA"}, + {Key: "location", Value: "37.7749,-122.4194"}, // San Francisco + }, + }, + }, + }) + + mt.AddMockResponses(firstBatch) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res := database.IsIPWithinRange(ctx, appSession, "test@example.com", &ipinfo.Core{Location: "40.7128,-74.0060"}) // New York + + // Validate the result + assert.False(t, res) + }) + + mt.Run("IP in range in cache", func(mt *mtest.T) { + Cache, mock := redismock.NewClientMock() + + user := models.User{ + Email: "test@example.com", + KnownLocations: []models.Location{ + { + City: "CityA", + Region: "RegionA", + Country: "CountryA", + Location: "37.7749,-122.4194", + }, + }, + } + + // add user to Cache + userData, err := bson.Marshal(user) + + assert.Nil(t, err) + + // Mock the Set operation + mock.ExpectSet(cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(userData)) + + // set the user in the Cache + res := Cache.Set(context.Background(), cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second) + + assert.Nil(t, res.Err()) + + // Call the function under test + appSession := &models.AppSession{ + DB: mt.Client, + Cache: Cache, + } + + // mock expect get + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + // Call the function under test + res1 := database.IsIPWithinRange(ctx, appSession, "test@example.com", &ipinfo.Core{Location: "34.0522,-118.2437"}) // Los Angeles + assert.True(t, res1) + + // Ensure all expectations are met + assert.NoError(t, mock.ExpectationsWereMet()) + }) + + mt.Run("IP not in range in cache", func(mt *mtest.T) { + Cache, mock := redismock.NewClientMock() + + user := models.User{ + Email: "test@example.com", + KnownLocations: []models.Location{ + { + City: "CityA", + Region: "RegionA", + Country: "CountryA", + Location: "37.7749,-122.4194", + }, + }, + } + + // add user to Cache + userData, err := bson.Marshal(user) + + assert.Nil(t, err) + + // Mock the Set operation + mock.ExpectSet(cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second).SetVal(string(userData)) + + // set the user in the Cache + res := Cache.Set(context.Background(), cache.UserKey(user.Email), userData, time.Duration(configs.GetCacheEviction())*time.Second) + + assert.Nil(t, res.Err()) + + // Call the function under test + appSession := &models.AppSession{ + DB: mt.Client, + Cache: Cache, + } + + // mock expect get + mock.ExpectGet(cache.UserKey(user.Email)).SetVal(string(userData)) + + // Call the function under test + res1 := database.IsIPWithinRange(ctx, appSession, "test@example.com", &ipinfo.Core{Location: "40.7128,-74.0060"}) // New York + assert.False(t, res1) + + // Ensure all expectations are met + assert.NoError(t, mock.ExpectationsWereMet()) + }) + + mt.Run("Find returns an error", func(mt *mtest.T) { + // Add a mock response that simulates a find error + mt.AddMockResponses(mtest.CreateCommandErrorResponse(mtest.CommandError{ + Code: 1, + Message: "find error", + })) + + // Initialize the app session with the mock client + appSession := &models.AppSession{ + DB: mt.Client, + } + + // Call the function under test + res := database.IsIPWithinRange(ctx, appSession, "test@example.com", &ipinfo.Core{Location: "37.7749,-122.4194"}) + + // Validate the result + assert.False(t, res) + }) +}