From 2716580834e6711b1191454fd50585f68d5ca253 Mon Sep 17 00:00:00 2001 From: Anton Zadvorny Date: Fri, 12 Jan 2024 12:20:15 +0300 Subject: [PATCH] Refactor repository --- repository.go | 10 +-- repository_test.go | 195 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 repository_test.go diff --git a/repository.go b/repository.go index da13544..b043772 100644 --- a/repository.go +++ b/repository.go @@ -7,7 +7,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" - driver "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -28,11 +28,11 @@ type RepositoryInterface[T Document] interface { } type Repository[T Document] struct { - collection *driver.Collection - indexes []driver.IndexModel + collection *mongo.Collection + indexes []mongo.IndexModel } -func NewRepository[T Document](collection *driver.Collection, indexes []driver.IndexModel) *Repository[T] { +func NewRepository[T Document](collection *mongo.Collection, indexes []mongo.IndexModel) *Repository[T] { return &Repository[T]{collection: collection, indexes: withTimestampIndexes(indexes)} } @@ -112,6 +112,6 @@ func (s *Repository[T]) EnsureIndexes(ctx context.Context, createIndexes bool) ( return ensureIndexes(ctx, s.collection, s.indexes, createIndexes) } -func repositoryError(collection *driver.Collection, op string, err error) error { +func repositoryError(collection *mongo.Collection, op string, err error) error { return fmt.Errorf("%s.%s: %v", collection.Name(), op, err) } diff --git a/repository_test.go b/repository_test.go new file mode 100644 index 0000000..9121c10 --- /dev/null +++ b/repository_test.go @@ -0,0 +1,195 @@ +package leaf + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/integration/mtest" +) + +type tModel struct { + Name string `bson:"name"` + Base `bson:",inline"` +} + +func tModelToBSON(model *tModel) bson.D { + raw, _ := bson.Marshal(model) + + var doc bson.D + bson.Unmarshal(raw, &doc) + + return doc +} + +func tModelsToBSON(models []*tModel) []bson.D { + docs := make([]bson.D, len(models)) + for i := range models { + docs[i] = tModelToBSON(models[i]) + } + + return docs +} + +func TestRepository_Find(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("returns data on success", func(mt *mtest.T) { + data := []*tModel{{Name: "test1"}, {Name: "test2"}} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch, tModelsToBSON(data)...)) + result, err := repo.Find(ctx, bson.M{}) + assert.Nil(mt, err) + assert.Equal(mt, data, result) + }) +} + +func TestRepository_FindOne(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("returns data on success", func(mt *mtest.T) { + data := &tModel{Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses((mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch, tModelToBSON(data)))) + result, err := repo.FindOne(ctx, bson.M{"name": "test"}) + assert.Nil(t, err) + assert.Equal(t, data, result) + }) + + mt.Run("returns error on failure", func(mt *mtest.T) { + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses((mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch))) + _, err := repo.FindOne(ctx, bson.M{"name": "test"}) + assert.NotNil(t, err) + }) +} + +func TestRepository_FindByID(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("returns data on success", func(mt *mtest.T) { + data := &tModel{Base: Base{ObjectID: primitive.NewObjectID()}, Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch, tModelToBSON(data))) + result, err := repo.FindByID(ctx, data.ID().Hex()) + assert.Nil(t, err) + assert.Equal(t, data, result) + }) + + mt.Run("returns error on failure", func(mt *mtest.T) { + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses(mtest.CreateCursorResponse(0, "test.foo", mtest.FirstBatch)) + _, err := repo.FindByID(ctx, "invalid") + assert.NotNil(t, err) + }) +} + +func TestRepository_Create(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("creates a new document successfully", func(mt *mtest.T) { + data := &tModel{Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses(mtest.CreateSuccessResponse()) + result, err := repo.Create(ctx, data) + assert.Nil(t, err) + assert.NotEmpty(t, result.ObjectID) + assert.WithinDuration(t, time.Now(), result.CreatedAt, time.Second) + assert.WithinDuration(t, time.Now(), result.UpdatedAt, time.Second) + }) + + mt.Run("returns error on failure", func(mt *mtest.T) { + data := &tModel{Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + errResponse := mtest.CreateWriteErrorsResponse(mtest.WriteError{Index: 0, Code: 11000, Message: "duplicate key error"}) + mt.AddMockResponses(errResponse) + _, err := repo.Create(ctx, data) + assert.NotNil(t, err) + }) +} + +func TestRepository_UpdateOne(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("updates a document successfully", func(mt *mtest.T) { + data := &tModel{Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + successResponse := mtest.CreateSuccessResponse( + bson.E{Key: "n", Value: 1}, + bson.E{Key: "nModified", Value: 1}, + ) + + mt.AddMockResponses(successResponse) + result, err := repo.UpdateOne(ctx, bson.M{"name": "test"}, data) + assert.Nil(t, err) + assert.Equal(t, data, result) + assert.WithinDuration(t, time.Now(), result.UpdatedAt, time.Second) + }) + + mt.Run("returns error on failure", func(mt *mtest.T) { + data := &tModel{Name: "test"} + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + errResponse := mtest.CreateWriteErrorsResponse(mtest.WriteError{Index: 0, Code: 11000, Message: "duplicate key error"}) + mt.AddMockResponses(errResponse) + _, err := repo.UpdateOne(ctx, bson.M{"name": "test"}, data) + assert.NotNil(t, err) + }) +} + +func TestRepository_DeleteOne(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("deletes a document successfully", func(mt *mtest.T) { + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + successResponse := mtest.CreateSuccessResponse( + bson.E{Key: "n", Value: 1}, + ) + + mt.AddMockResponses(successResponse) + err := repo.DeleteOne(ctx, bson.M{"name": "test"}) + assert.Nil(t, err) + }) + + mt.Run("returns error on failure", func(mt *mtest.T) { + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + errResponse := mtest.CreateWriteErrorsResponse(mtest.WriteError{Index: 0, Code: 11000, Message: "delete error"}) + mt.AddMockResponses(errResponse) + err := repo.DeleteOne(ctx, bson.M{"name": "test"}) + assert.NotNil(t, err) + }) +} + +func TestRepository_CountDocuments(t *testing.T) { + mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) + ctx := context.Background() + + mt.Run("returns count on success", func(mt *mtest.T) { + repo := NewRepository[*tModel](mt.Coll, []mongo.IndexModel{}) + + mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.foo", mtest.FirstBatch, bson.D{bson.E{Key: "n", Value: 2}})) + count, err := repo.CountDocuments(ctx, bson.M{"name": "test"}) + assert.Nil(t, err) + assert.Equal(t, 2, count) + }) +}