From e6c2ad9a40c71b6a9299bef4b5ad67091924fe67 Mon Sep 17 00:00:00 2001 From: Matthieu Jacquot Date: Fri, 10 Aug 2018 17:30:59 +0200 Subject: [PATCH] 100% coverage on usecases comments & favorites --- uc/comments.go | 71 -------------------- uc/commentsDelete.go | 28 ++++++++ uc/commentsDelete_test.go | 93 ++++++++++++++++++++++++++ uc/commentsGet.go | 12 ++++ uc/commentsGet_test.go | 40 +++++++++++ uc/commentsPost.go | 33 +++++++++ uc/commentsPost_test.go | 93 ++++++++++++++++++++++++++ uc/favorite_test.go | 22 ------ uc/{favorite.go => favoritesUpdate.go} | 10 +-- uc/favoritesUpdate_test.go | 85 +++++++++++++++++++++++ 10 files changed, 389 insertions(+), 98 deletions(-) delete mode 100644 uc/comments.go create mode 100644 uc/commentsDelete.go create mode 100644 uc/commentsDelete_test.go create mode 100644 uc/commentsGet.go create mode 100644 uc/commentsGet_test.go create mode 100644 uc/commentsPost.go create mode 100644 uc/commentsPost_test.go delete mode 100644 uc/favorite_test.go rename uc/{favorite.go => favoritesUpdate.go} (75%) create mode 100644 uc/favoritesUpdate_test.go diff --git a/uc/comments.go b/uc/comments.go deleted file mode 100644 index bded698..0000000 --- a/uc/comments.go +++ /dev/null @@ -1,71 +0,0 @@ -package uc - -import ( - "github.com/err0r500/go-realworld-clean/domain" -) - -func (i interactor) CommentsGet(slug string) ([]domain.Comment, error) { - article, err := i.articleRW.GetBySlug(slug) - if err != nil { - return nil, err - } - - return article.Comments, nil -} - -func (i interactor) CommentsPost(username, slug, comment string) (*domain.Comment, error) { - commentPoster, err := i.userRW.GetByName(username) - if err != nil { - return nil, err - } - - article, err := i.articleRW.GetBySlug(slug) - if err != nil { - return nil, err - } - - rawComment := domain.Comment{ - Body: comment, - Author: *commentPoster, - } - - insertedComment, err := i.commentRW.Create(rawComment) - if err != nil { - return nil, err - } - - article.Comments = append(article.Comments, *insertedComment) - - if _, err := i.articleRW.Save(*article); err != nil { - return nil, err - } - - return insertedComment, nil -} - -func (i interactor) CommentsDelete(username, slug string, id int) error { - comment, err := i.commentRW.GetByID(id) - if err != nil { - return err - } - if comment.Author.Name != username { - return errWrongUser - } - - if err := i.commentRW.Delete(id); err != nil { - return err - } - - article, err := i.articleRW.GetBySlug(slug) - if err != nil { - return err - } - - article.UpdateComments(*comment, false) - - if _, err := i.articleRW.Save(*article); err != nil { - return err - } - - return nil -} diff --git a/uc/commentsDelete.go b/uc/commentsDelete.go new file mode 100644 index 0000000..89415a6 --- /dev/null +++ b/uc/commentsDelete.go @@ -0,0 +1,28 @@ +package uc + +func (i interactor) CommentsDelete(username, slug string, id int) error { + comment, err := i.commentRW.GetByID(id) + if err != nil { + return err + } + if comment.Author.Name != username { + return errWrongUser + } + + if err := i.commentRW.Delete(id); err != nil { + return err + } + + article, err := i.articleRW.GetBySlug(slug) + if err != nil { + return err + } + + article.UpdateComments(*comment, false) + + if _, err := i.articleRW.Save(*article); err != nil { + return err + } + + return nil +} diff --git a/uc/commentsDelete_test.go b/uc/commentsDelete_test.go new file mode 100644 index 0000000..0f764ab --- /dev/null +++ b/uc/commentsDelete_test.go @@ -0,0 +1,93 @@ +package uc_test + +import ( + "testing" + + "errors" + + "github.com/err0r500/go-realworld-clean/domain" + "github.com/err0r500/go-realworld-clean/implem/uc.mock" + "github.com/err0r500/go-realworld-clean/testData" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestInteractor_CommentsDelete_happycase(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + user := testData.User("rick") + article := testData.Article("jane") + comment := article.Comments[0] + articleWithoutTheComment := article + articleWithoutTheComment.Comments = []domain.Comment{} + + i := mock.NewMockedInteractor(mockCtrl) + i.CommentRW.EXPECT().GetByID(comment.ID).Return(&comment, nil) + i.CommentRW.EXPECT().Delete(comment.ID).Return(nil) + i.ArticleRW.EXPECT().GetBySlug(article.Slug).Return(&article, nil) + i.ArticleRW.EXPECT().Save(articleWithoutTheComment) + + assert.NoError(t, i.GetUCHandler().CommentsDelete(user.Name, article.Slug, comment.ID)) +} + +func TestInteractor_CommentsDelete_fails(t *testing.T) { + mutations := map[string]mock.Tester{ + "shouldPass": { + Calls: func(i *mock.Interactor) { // change nothing + }, + ShouldPass: true}, + "failed to get comment by ID": { + Calls: func(i *mock.Interactor) { + i.CommentRW.EXPECT().GetByID(gomock.Any()).Return(nil, errors.New("")) + }}, + "returned a comment belonging to another user": { + Calls: func(i *mock.Interactor) { + i.CommentRW.EXPECT().GetByID(gomock.Any()).Return(&domain.Comment{Author: domain.User{Name: "hey"}}, nil) + }}, + "failed to delete comment": { + Calls: func(i *mock.Interactor) { + i.CommentRW.EXPECT().Delete(gomock.Any()).Return(errors.New("")) + }}, + "failed to get article by slug comment": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(nil, errors.New("")) + }}, + "failed to save article": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().Save(gomock.Any()).Return(nil, errors.New("")) + }}, + } + + user := testData.User("rick") + article := testData.Article("jane") + comment := article.Comments[0] + articleWithoutTheComment := article + articleWithoutTheComment.Comments = []domain.Comment{} + + // same as the happy case but with any parameter and called any number of times (including 0) + validCalls := func(i *mock.Interactor) { + i.CommentRW.EXPECT().GetByID(gomock.Any()).Return(&comment, nil).AnyTimes() + i.CommentRW.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes() + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(&article, nil).AnyTimes() + i.ArticleRW.EXPECT().Save(gomock.Any()).AnyTimes() + + } + + for testName, mutation := range mutations { + t.Run(testName, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + i := mock.NewMockedInteractor(mockCtrl) + mutation.Calls(&i) // put the tested call first (important) + validCalls(&i) // then fill the gaps with valid calls + + err := i.GetUCHandler().CommentsDelete(user.Name, article.Slug, comment.ID) + if mutation.ShouldPass { + assert.NoError(t, err) + return + } + assert.Error(t, err) + }) + } +} diff --git a/uc/commentsGet.go b/uc/commentsGet.go new file mode 100644 index 0000000..4be7c56 --- /dev/null +++ b/uc/commentsGet.go @@ -0,0 +1,12 @@ +package uc + +import "github.com/err0r500/go-realworld-clean/domain" + +func (i interactor) CommentsGet(slug string) ([]domain.Comment, error) { + article, err := i.articleRW.GetBySlug(slug) + if err != nil { + return nil, err + } + + return article.Comments, nil +} diff --git a/uc/commentsGet_test.go b/uc/commentsGet_test.go new file mode 100644 index 0000000..2c59fa1 --- /dev/null +++ b/uc/commentsGet_test.go @@ -0,0 +1,40 @@ +package uc_test + +import ( + "testing" + + "errors" + + "github.com/err0r500/go-realworld-clean/implem/uc.mock" + "github.com/err0r500/go-realworld-clean/testData" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestInteractor_CommentsGet(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + article := testData.Article("jane") + + i := mock.NewMockedInteractor(mockCtrl) + i.ArticleRW.EXPECT().GetBySlug(article.Slug).Return(&article, nil) + + comments, err := i.GetUCHandler().CommentsGet(article.Slug) + assert.NoError(t, err) + assert.Equal(t, article.Comments, comments) +} + +func TestInteractor_CommentsGet_fails(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + article := testData.Article("jane") + + i := mock.NewMockedInteractor(mockCtrl) + i.ArticleRW.EXPECT().GetBySlug(article.Slug).Return(nil, errors.New("")) + + comments, err := i.GetUCHandler().CommentsGet(article.Slug) + assert.Error(t, err) + assert.Nil(t, comments) +} diff --git a/uc/commentsPost.go b/uc/commentsPost.go new file mode 100644 index 0000000..efe3cd9 --- /dev/null +++ b/uc/commentsPost.go @@ -0,0 +1,33 @@ +package uc + +import "github.com/err0r500/go-realworld-clean/domain" + +func (i interactor) CommentsPost(username, slug, comment string) (*domain.Comment, error) { + commentPoster, err := i.userRW.GetByName(username) + if err != nil { + return nil, err + } + + article, err := i.articleRW.GetBySlug(slug) + if err != nil { + return nil, err + } + + rawComment := domain.Comment{ + Body: comment, + Author: *commentPoster, + } + + insertedComment, err := i.commentRW.Create(rawComment) + if err != nil { + return nil, err + } + + article.Comments = append(article.Comments, *insertedComment) + + if _, err := i.articleRW.Save(*article); err != nil { + return nil, err + } + + return insertedComment, nil +} diff --git a/uc/commentsPost_test.go b/uc/commentsPost_test.go new file mode 100644 index 0000000..bf6230f --- /dev/null +++ b/uc/commentsPost_test.go @@ -0,0 +1,93 @@ +package uc_test + +import ( + "testing" + + "errors" + + "github.com/err0r500/go-realworld-clean/domain" + "github.com/err0r500/go-realworld-clean/implem/uc.mock" + "github.com/err0r500/go-realworld-clean/testData" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestInteractor_CommentsPost_happycase(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + user := testData.User("rick") + article := testData.Article("jane") + articleWithoutComment := article + articleWithoutComment.Comments = []domain.Comment{} //reset comments + comment := article.Comments[0] + + i := mock.NewMockedInteractor(mockCtrl) + i.UserRW.EXPECT().GetByName(user.Name).Return(&user, nil) + i.ArticleRW.EXPECT().GetBySlug(article.Slug).Return(&articleWithoutComment, nil) + i.CommentRW.EXPECT().Create(domain.Comment{Body: comment.Body, Author: user}).Return(&comment, nil) + i.ArticleRW.EXPECT().Save(article) + + returnedComment, err := i.GetUCHandler().CommentsPost(user.Name, article.Slug, comment.Body) + assert.NoError(t, err) + assert.Equal(t, comment, *returnedComment) +} + +func TestInteractor_CommentsPost_fails(t *testing.T) { + mutations := map[string]mock.Tester{ + "shouldPass": { + Calls: func(i *mock.Interactor) { // change nothing + }, + ShouldPass: true}, + "failed get user by name": { + Calls: func(i *mock.Interactor) { + i.UserRW.EXPECT().GetByName(gomock.Any()).Return(nil, errors.New("")) + }, + }, + "failed get by slug": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(nil, errors.New("")) + }, + }, + "failed get create comment": { + Calls: func(i *mock.Interactor) { + i.CommentRW.EXPECT().Create(gomock.Any()).Return(nil, errors.New("")) + }, + }, + "failed save article": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().Save(gomock.Any()).Return(nil, errors.New("")) + }, + }, + } + user := testData.User("rick") + article := testData.Article("jane") + articleWithoutComment := article + articleWithoutComment.Comments = []domain.Comment{} //reset comments + comment := article.Comments[0] + + // same as the happy case but with any parameter and called any number of times (including 0) + validCalls := func(i *mock.Interactor) { + i.UserRW.EXPECT().GetByName(gomock.Any()).Return(&user, nil).AnyTimes() + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(&articleWithoutComment, nil).AnyTimes() + i.CommentRW.EXPECT().Create(gomock.Any()).Return(&comment, nil).AnyTimes() + i.ArticleRW.EXPECT().Save(gomock.Any()).AnyTimes() + } + + for testName, mutation := range mutations { + t.Run(testName, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + i := mock.NewMockedInteractor(mockCtrl) + mutation.Calls(&i) // put the tested call first (important) + validCalls(&i) // then fill the gaps with valid calls + + _, err := i.GetUCHandler().CommentsPost(user.Name, article.Slug, comment.Body) + if mutation.ShouldPass { + assert.NoError(t, err) + return + } + assert.Error(t, err) + }) + } +} diff --git a/uc/favorite_test.go b/uc/favorite_test.go deleted file mode 100644 index 2e0d70f..0000000 --- a/uc/favorite_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package uc_test - -import ( - "testing" -) - -func TestInteractor_FavoritesUpdate(t *testing.T) { - //fixme : todo - //mockCtrl := gomock.NewController(t) - //defer mockCtrl.Finish() - // - //rick := testData.User("rick") - //janeArticle := testData.Article("janeArticle") - // - //i := mock.NewMockedInteractor(mockCtrl) - //i.TagsRW.EXPECT().GetAll().Return(tags, nil).Times(1) - // - //retArticle, err := i.GetUCHandler().FavoritesUpdate(rick.Name, janeArticle.Slug, true) - // - //assert.NoError(t, err) - //assert.Equal(t, tags, retArticle) -} diff --git a/uc/favorite.go b/uc/favoritesUpdate.go similarity index 75% rename from uc/favorite.go rename to uc/favoritesUpdate.go index 92321aa..955230f 100644 --- a/uc/favorite.go +++ b/uc/favoritesUpdate.go @@ -2,23 +2,23 @@ package uc import "github.com/err0r500/go-realworld-clean/domain" -// fixme : should return total favorite count and fav for current user func (i interactor) FavoritesUpdate(username, slug string, favorite bool) (*domain.User, *domain.Article, error) { - article, err := i.articleRW.GetBySlug(slug) + user, err := i.userRW.GetByName(username) if err != nil { return nil, nil, err } - user, err := i.userRW.GetByName(username) + article, err := i.articleRW.GetBySlug(slug) if err != nil { return nil, nil, err } article.UpdateFavoritedBy(*user, favorite) - if err := i.userRW.Save(*user); err != nil { + updatedArticle, err := i.articleRW.Save(*article) + if err != nil { return nil, nil, err } - return user, article, nil + return user, updatedArticle, nil } diff --git a/uc/favoritesUpdate_test.go b/uc/favoritesUpdate_test.go new file mode 100644 index 0000000..802e2ee --- /dev/null +++ b/uc/favoritesUpdate_test.go @@ -0,0 +1,85 @@ +package uc_test + +import ( + "errors" + "testing" + + "github.com/err0r500/go-realworld-clean/domain" + mock "github.com/err0r500/go-realworld-clean/implem/uc.mock" + "github.com/err0r500/go-realworld-clean/testData" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestInteractor_FavoritesUpdate(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + user := testData.User("rick") + article := testData.Article("jane") + articleWithoutFav := article + articleWithoutFav.FavoritedBy = []domain.User{} + + i := mock.NewMockedInteractor(mockCtrl) + i.ArticleRW.EXPECT().GetBySlug(article.Slug).Return(&articleWithoutFav, nil) + i.UserRW.EXPECT().GetByName(user.Name).Return(&user, nil) + i.ArticleRW.EXPECT().Save(article).Return(&article, nil) + + returnedUser, returnedArticle, err := i.GetUCHandler().FavoritesUpdate(user.Name, article.Slug, true) + assert.NoError(t, err) + assert.Equal(t, user, *returnedUser) + assert.Equal(t, article, *returnedArticle) +} + +func TestInteractor_FavoritesUpdate_fails(t *testing.T) { + mutations := map[string]mock.Tester{ + "shouldPass": { + Calls: func(i *mock.Interactor) { // change nothing + }, + ShouldPass: true}, + "failed get user by name": { + Calls: func(i *mock.Interactor) { + i.UserRW.EXPECT().GetByName(gomock.Any()).Return(nil, errors.New("")) + }, + }, + "failed get by slug": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(nil, errors.New("")) + }, + }, + "failed save article": { + Calls: func(i *mock.Interactor) { + i.ArticleRW.EXPECT().Save(gomock.Any()).Return(nil, errors.New("")) + }, + }, + } + + user := testData.User("rick") + article := testData.Article("jane") + articleWithoutFav := article + articleWithoutFav.FavoritedBy = []domain.User{} + + // same as the happy case but with any parameter and called any number of times (including 0) + validCalls := func(i *mock.Interactor) { + i.ArticleRW.EXPECT().GetBySlug(gomock.Any()).Return(&articleWithoutFav, nil).AnyTimes() + i.UserRW.EXPECT().GetByName(gomock.Any()).Return(&user, nil).AnyTimes() + i.ArticleRW.EXPECT().Save(gomock.Any()).Return(&article, nil).AnyTimes() + } + + for testName, mutation := range mutations { + t.Run(testName, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + i := mock.NewMockedInteractor(mockCtrl) + mutation.Calls(&i) // put the tested call first (important) + validCalls(&i) // then fill the gaps with valid calls + + _, _, err := i.GetUCHandler().FavoritesUpdate(user.Name, article.Slug, true) + if mutation.ShouldPass { + assert.NoError(t, err) + return + } + assert.Error(t, err) + }) + } +}