Skip to content

Commit

Permalink
Test articles controller
Browse files Browse the repository at this point in the history
  • Loading branch information
System-Glitch committed May 13, 2024
1 parent 763cf14 commit 9f2a74a
Show file tree
Hide file tree
Showing 2 changed files with 323 additions and 8 deletions.
11 changes: 3 additions & 8 deletions http/controller/article/article.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ func (ctrl *Controller) Index(response *goyave.Response, request *goyave.Request

func (ctrl *Controller) Show(response *goyave.Response, request *goyave.Request) {
user, err := ctrl.ArticleService.GetBySlug(request.Context(), request.RouteParams["slug"])
if err != nil {
response.Error(err)
if response.WriteDBError(err) {
return
}
response.JSON(http.StatusOK, user)
Expand Down Expand Up @@ -92,9 +91,7 @@ func (ctrl *Controller) Update(response *goyave.Response, request *goyave.Reques
updateDTO := typeutil.MustConvert[*dto.UpdateArticle](request.Data)

err = ctrl.ArticleService.Update(request.Context(), uint(id), updateDTO)
if response.WriteDBError(err) {
return
}
response.WriteDBError(err)
}

func (ctrl *Controller) Delete(response *goyave.Response, request *goyave.Request) {
Expand All @@ -105,7 +102,5 @@ func (ctrl *Controller) Delete(response *goyave.Response, request *goyave.Reques
}

err = ctrl.ArticleService.Delete(request.Context(), uint(id))
if response.WriteDBError(err) {
return
}
response.WriteDBError(err)
}
320 changes: 320 additions & 0 deletions http/controller/article/article_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
package article

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/go-goyave/goyave-blog-example/database/seed"
"github.com/go-goyave/goyave-blog-example/dto"
"github.com/go-goyave/goyave-blog-example/service"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
"goyave.dev/filter"
"goyave.dev/goyave/v5"
"goyave.dev/goyave/v5/auth"
"goyave.dev/goyave/v5/database"
"goyave.dev/goyave/v5/middleware/parse"
"goyave.dev/goyave/v5/util/testutil"
"goyave.dev/goyave/v5/util/typeutil"
)

type updateArticleDTO struct {
Title string `json:"title"`
Contents string `json:"contents"`
}

type ServiceMock struct {
paginator *database.PaginatorDTO[*dto.Article]
article *dto.Article
err error

createCallback func(*dto.CreateArticle)
updateCallback func(*dto.UpdateArticle)

isOwner bool
}

func (s *ServiceMock) Index(_ context.Context, _ *filter.Request) (*database.PaginatorDTO[*dto.Article], error) {
return s.paginator, s.err
}

func (s *ServiceMock) GetBySlug(_ context.Context, slug string) (*dto.Article, error) {
if s.article.Slug == slug {
return s.article, s.err
}
return nil, gorm.ErrRecordNotFound
}

func (s *ServiceMock) Create(_ context.Context, createDTO *dto.CreateArticle) error {
s.createCallback(createDTO)
return s.err
}

func (s *ServiceMock) Update(_ context.Context, _ uint, updateDTO *dto.UpdateArticle) error {
s.updateCallback(updateDTO)
return s.err
}

func (s *ServiceMock) Delete(_ context.Context, _ uint) error {
return s.err
}

func (s *ServiceMock) IsOwner(_ context.Context, _ uint, _ uint) (bool, error) {
return s.isOwner, nil
}

func (s *ServiceMock) Name() string {
return service.Article
}

const mockAuthUserMeta = "mock:authuser"

type mockAuthMiddleware struct {
goyave.Component
}

func (m *mockAuthMiddleware) Handle(next goyave.Handler) goyave.Handler {
return func(response *goyave.Response, request *goyave.Request) {
request.User, _ = request.Route.LookupMeta(mockAuthUserMeta)
requireAuth, _ := request.Route.LookupMeta(auth.MetaAuth)
if requireAuth.(bool) && request.User == nil {
response.Status(http.StatusUnauthorized)
return
}
next(response, request)
}
}

func generatePaginator() *database.PaginatorDTO[*dto.Article] {
records := database.NewFactory(seed.ArticleGenerator).Generate(3)
return &database.PaginatorDTO[*dto.Article]{
Records: typeutil.MustConvert[[]*dto.Article](records),
MaxPage: 1,
Total: 3,
PageSize: 10,
CurrentPage: 1,
}
}

func setupArticleTest(t *testing.T, service *ServiceMock) *testutil.TestServer {
server := testutil.NewTestServer(t, "config.test.json")
// server.Logger = slog.New(slog.NewHandler(true, io.Discard))
server.RegisterService(service)
server.RegisterRoutes(func(_ *goyave.Server, r *goyave.Router) {
r.GlobalMiddleware(&parse.Middleware{})
r.Controller(NewController())
})
return server
}

func TestArticle(t *testing.T) {
t.Run("Index", func(t *testing.T) {
service := &ServiceMock{
paginator: generatePaginator(),
}
server := setupArticleTest(t, service)
request := httptest.NewRequest(http.MethodGet, "/articles", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusOK, response.StatusCode)
paginator, err := testutil.ReadJSONBody[*database.PaginatorDTO[*dto.Article]](response.Body)
assert.NoError(t, err)
assert.NoError(t, response.Body.Close())

assert.Equal(t, service.paginator, paginator)

t.Run("error", func(t *testing.T) {
service.err = fmt.Errorf("test error")
request := httptest.NewRequest(http.MethodGet, "/articles", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusInternalServerError, response.StatusCode)
assert.NoError(t, response.Body.Close())
})
})

t.Run("Show", func(t *testing.T) {
service := &ServiceMock{
article: typeutil.MustConvert[*dto.Article](seed.ArticleGenerator()),
}
server := setupArticleTest(t, service)
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/articles/%s", service.article.Slug), nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusOK, response.StatusCode)
paginator, err := testutil.ReadJSONBody[*dto.Article](response.Body)
assert.NoError(t, err)
assert.NoError(t, response.Body.Close())

assert.Equal(t, service.article, paginator)

t.Run("not_found", func(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/articles/incorrect-slug", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusNotFound, response.StatusCode)
assert.NoError(t, response.Body.Close())
})
})

t.Run("Create", func(t *testing.T) {
service := &ServiceMock{}
server := setupArticleTest(t, service)
user := &dto.InternalUser{
User: dto.User{ID: 1},
}
server.Router().GlobalMiddleware(&mockAuthMiddleware{}).SetMeta(mockAuthUserMeta, user)

requestBody := &dto.CreateArticle{
Title: "article title",
Contents: "article contents",
}

request := httptest.NewRequest(http.MethodPost, "/articles", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")

service.createCallback = func(createDTO *dto.CreateArticle) {
expected := typeutil.Copy(&dto.CreateArticle{AuthorID: user.ID}, createDTO)
assert.Equal(t, expected, createDTO)
}

response := server.TestRequest(request)
assert.Equal(t, http.StatusCreated, response.StatusCode)
assert.NoError(t, response.Body.Close())

t.Run("error", func(t *testing.T) {
service.err = fmt.Errorf("test error")
request := httptest.NewRequest(http.MethodPost, "/articles", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusInternalServerError, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("require_auth", func(t *testing.T) {
server.Router().RemoveMeta(mockAuthUserMeta)
request := httptest.NewRequest(http.MethodPost, "/articles", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
assert.NoError(t, response.Body.Close())
})
})

t.Run("Update", func(t *testing.T) {
service := &ServiceMock{}
server := setupArticleTest(t, service)
user := &dto.InternalUser{
User: dto.User{ID: 1},
}
server.Router().GlobalMiddleware(&mockAuthMiddleware{}).SetMeta(mockAuthUserMeta, user)

requestBody := &updateArticleDTO{
Title: "article title",
Contents: "article contents",
}

request := httptest.NewRequest(http.MethodPatch, "/articles/1", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")

service.isOwner = true
service.updateCallback = func(updateDTO *dto.UpdateArticle) {
assert.Equal(t, requestBody.Title, updateDTO.Title.Val)
assert.Equal(t, requestBody.Contents, updateDTO.Contents.Val)
}

response := server.TestRequest(request)
assert.Equal(t, http.StatusNoContent, response.StatusCode)
assert.NoError(t, response.Body.Close())

t.Run("invalid_id", func(t *testing.T) {
request := httptest.NewRequest(http.MethodPatch, "/articles/999999999999999999999999999999999999", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusNotFound, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("not_found", func(t *testing.T) {
service.err = gorm.ErrRecordNotFound
request := httptest.NewRequest(http.MethodPatch, "/articles/1", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusNotFound, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("error", func(t *testing.T) {
service.err = fmt.Errorf("test error")
request := httptest.NewRequest(http.MethodPatch, "/articles/1", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusInternalServerError, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("not_owner", func(t *testing.T) {
service.err = nil
service.isOwner = false
request := httptest.NewRequest(http.MethodPatch, "/articles/1", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("require_auth", func(t *testing.T) {
server.Router().RemoveMeta(mockAuthUserMeta)
request := httptest.NewRequest(http.MethodPatch, "/articles/1", testutil.ToJSON(requestBody))
request.Header.Set("Content-Type", "application/json")
response := server.TestRequest(request)
assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
assert.NoError(t, response.Body.Close())
})
})

t.Run("Delete", func(t *testing.T) {
service := &ServiceMock{}
server := setupArticleTest(t, service)
user := &dto.InternalUser{
User: dto.User{ID: 1},
}
server.Router().GlobalMiddleware(&mockAuthMiddleware{}).SetMeta(mockAuthUserMeta, user)
service.isOwner = true
request := httptest.NewRequest(http.MethodDelete, "/articles/1", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusNoContent, response.StatusCode)
assert.NoError(t, response.Body.Close())

t.Run("invalid_id", func(t *testing.T) {
request := httptest.NewRequest(http.MethodDelete, "/articles/999999999999999999999999999999999999", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusNotFound, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("not_found", func(t *testing.T) {
service.err = gorm.ErrRecordNotFound
request := httptest.NewRequest(http.MethodDelete, "/articles/2", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusNotFound, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("not_owner", func(t *testing.T) {
service.err = nil
service.isOwner = false
request := httptest.NewRequest(http.MethodDelete, "/articles/1", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusForbidden, response.StatusCode)
assert.NoError(t, response.Body.Close())
})

t.Run("require_auth", func(t *testing.T) {
server.Router().RemoveMeta(mockAuthUserMeta)
request := httptest.NewRequest(http.MethodDelete, "/articles/1", nil)
response := server.TestRequest(request)
assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
assert.NoError(t, response.Body.Close())
})
})
}

0 comments on commit 9f2a74a

Please sign in to comment.