|
@@ -0,0 +1,373 @@
|
|
|
+package handler
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "database/sql"
|
|
|
+ "errors"
|
|
|
+ "net/http/httptest"
|
|
|
+ "strconv"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/brianvoe/gofakeit/v6"
|
|
|
+ "github.com/dmitriygnatenko/internal/interfaces"
|
|
|
+ "github.com/dmitriygnatenko/internal/models"
|
|
|
+ repositoryMocks "github.com/dmitriygnatenko/internal/repositories/mocks"
|
|
|
+ sp "github.com/dmitriygnatenko/internal/service_provider"
|
|
|
+ cacheMocks "github.com/dmitriygnatenko/internal/services/cache/mocks"
|
|
|
+ "github.com/dmitriygnatenko/internal/services/handler/test"
|
|
|
+ "github.com/gofiber/fiber/v2"
|
|
|
+ "github.com/gojuno/minimock/v3"
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
+)
|
|
|
+
|
|
|
+func Test_ArticleHandler(t *testing.T) {
|
|
|
+ type cacheMockFunc func(mc *minimock.Controller) interfaces.ICache
|
|
|
+ type tagMockFunc func(mc *minimock.Controller) interfaces.ITagRepository
|
|
|
+ type articleMockFunc func(mc *minimock.Controller) interfaces.IArticleRepository
|
|
|
+
|
|
|
+ type req struct {
|
|
|
+ method string
|
|
|
+ route string
|
|
|
+ }
|
|
|
+
|
|
|
+ var (
|
|
|
+ mc = minimock.NewController(t)
|
|
|
+ articleID = gofakeit.Number(1, 100)
|
|
|
+ date = gofakeit.Date()
|
|
|
+ publishTime = date.Format("2006-01-02 15:04:05")
|
|
|
+ internalErr = errors.New(gofakeit.Phrase())
|
|
|
+
|
|
|
+ article = models.Article{
|
|
|
+ ID: articleID,
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ Text: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ IsActive: true,
|
|
|
+ }
|
|
|
+
|
|
|
+ notActiveArticle = models.Article{
|
|
|
+ ID: articleID,
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ Text: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ }
|
|
|
+
|
|
|
+ tags = []models.Tag{
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ Tag: gofakeit.Word(),
|
|
|
+ URL: gofakeit.Word(),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ Tag: gofakeit.Word(),
|
|
|
+ URL: gofakeit.Word(),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ previewArticles = []models.ArticlePreview{
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ID: gofakeit.Number(1, 100),
|
|
|
+ URL: gofakeit.URL(),
|
|
|
+ Title: gofakeit.Phrase(),
|
|
|
+ PublishTime: publishTime,
|
|
|
+ PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
|
|
|
+ Image: sql.NullString{Valid: true, String: gofakeit.URL()},
|
|
|
+ },
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ req req
|
|
|
+ res int
|
|
|
+ err error
|
|
|
+ cacheMock cacheMockFunc
|
|
|
+ tagMock tagMockFunc
|
|
|
+ articleMock articleMockFunc
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "positive case",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusOK,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+ mock.SetMock.Return()
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+ mock.GetAllUsedMock.Return(tags, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(&article, nil)
|
|
|
+
|
|
|
+ mock.GetAllPreviewMock.Return(previewArticles, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - article not found",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusNotFound,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(nil, sql.ErrNoRows)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - article repository error",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusInternalServerError,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(nil, internalErr)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - article not active",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusNotFound,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(¬ActiveArticle, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - article mapper error",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusInternalServerError,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(&models.Article{IsActive: true}, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - tags repository error",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusInternalServerError,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+ mock.GetAllUsedMock.Return(nil, internalErr)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(&article, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - articles repository error",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusInternalServerError,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+ mock.GetAllUsedMock.Return(tags, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(&article, nil)
|
|
|
+
|
|
|
+ mock.GetAllPreviewMock.Return(nil, internalErr)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "negative case - articles mapper error",
|
|
|
+ req: req{
|
|
|
+ method: fiber.MethodGet,
|
|
|
+ route: "/article/" + strconv.Itoa(articleID),
|
|
|
+ },
|
|
|
+ res: fiber.StatusInternalServerError,
|
|
|
+ err: nil,
|
|
|
+ cacheMock: func(mc *minimock.Controller) interfaces.ICache {
|
|
|
+ mock := cacheMocks.NewICacheMock(mc)
|
|
|
+ mock.GetMock.Return(nil, false)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
|
|
|
+ mock := repositoryMocks.NewITagRepositoryMock(mc)
|
|
|
+ mock.GetAllUsedMock.Return(tags, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
|
|
|
+ mock := repositoryMocks.NewIArticleRepositoryMock(mc)
|
|
|
+
|
|
|
+ mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
|
|
|
+ assert.Equal(mc, strconv.Itoa(articleID), url)
|
|
|
+ }).Return(&article, nil)
|
|
|
+
|
|
|
+ mock.GetAllPreviewMock.Return([]models.ArticlePreview{{}}, nil)
|
|
|
+
|
|
|
+ return mock
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ fiberApp := fiber.New(test.GetFiberConfig())
|
|
|
+ fiberReq := httptest.NewRequest(tt.req.method, tt.req.route, nil)
|
|
|
+ serviceProvider := sp.InitMock(tt.cacheMock(mc), tt.tagMock(mc), tt.articleMock(mc))
|
|
|
+
|
|
|
+ fiberApp.Get("/article/:article", ArticleHandler(serviceProvider))
|
|
|
+
|
|
|
+ fiberRes, fiberErr := fiberApp.Test(fiberReq)
|
|
|
+ assert.Equal(t, tt.res, fiberRes.StatusCode)
|
|
|
+ assert.Equal(t, tt.err, fiberErr)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|