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) }) } }