article_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package handler
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "net/http/httptest"
  7. "strconv"
  8. "testing"
  9. "github.com/brianvoe/gofakeit/v6"
  10. "github.com/dmitriygnatenko/internal/interfaces"
  11. "github.com/dmitriygnatenko/internal/models"
  12. repositoryMocks "github.com/dmitriygnatenko/internal/repositories/mocks"
  13. sp "github.com/dmitriygnatenko/internal/service_provider"
  14. cacheMocks "github.com/dmitriygnatenko/internal/services/cache/mocks"
  15. "github.com/dmitriygnatenko/internal/services/handler/test"
  16. "github.com/gofiber/fiber/v2"
  17. "github.com/gojuno/minimock/v3"
  18. "github.com/stretchr/testify/assert"
  19. )
  20. func Test_ArticleHandler(t *testing.T) {
  21. type cacheMockFunc func(mc *minimock.Controller) interfaces.ICache
  22. type tagMockFunc func(mc *minimock.Controller) interfaces.ITagRepository
  23. type articleMockFunc func(mc *minimock.Controller) interfaces.IArticleRepository
  24. type req struct {
  25. method string
  26. route string
  27. }
  28. var (
  29. mc = minimock.NewController(t)
  30. articleID = gofakeit.Number(1, 100)
  31. date = gofakeit.Date()
  32. publishTime = date.Format("2006-01-02 15:04:05")
  33. internalErr = errors.New(gofakeit.Phrase())
  34. article = models.Article{
  35. ID: articleID,
  36. URL: gofakeit.URL(),
  37. Title: gofakeit.Phrase(),
  38. Text: gofakeit.Phrase(),
  39. PublishTime: publishTime,
  40. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  41. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  42. IsActive: true,
  43. }
  44. notActiveArticle = models.Article{
  45. ID: articleID,
  46. URL: gofakeit.URL(),
  47. Title: gofakeit.Phrase(),
  48. Text: gofakeit.Phrase(),
  49. PublishTime: publishTime,
  50. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  51. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  52. }
  53. tags = []models.Tag{
  54. {
  55. ID: gofakeit.Number(1, 100),
  56. Tag: gofakeit.Word(),
  57. URL: gofakeit.Word(),
  58. },
  59. {
  60. ID: gofakeit.Number(1, 100),
  61. Tag: gofakeit.Word(),
  62. URL: gofakeit.Word(),
  63. },
  64. }
  65. previewArticles = []models.ArticlePreview{
  66. {
  67. ID: gofakeit.Number(1, 100),
  68. URL: gofakeit.URL(),
  69. Title: gofakeit.Phrase(),
  70. PublishTime: publishTime,
  71. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  72. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  73. },
  74. {
  75. ID: gofakeit.Number(1, 100),
  76. URL: gofakeit.URL(),
  77. Title: gofakeit.Phrase(),
  78. PublishTime: publishTime,
  79. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  80. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  81. },
  82. {
  83. ID: gofakeit.Number(1, 100),
  84. URL: gofakeit.URL(),
  85. Title: gofakeit.Phrase(),
  86. PublishTime: publishTime,
  87. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  88. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  89. },
  90. {
  91. ID: gofakeit.Number(1, 100),
  92. URL: gofakeit.URL(),
  93. Title: gofakeit.Phrase(),
  94. PublishTime: publishTime,
  95. PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
  96. Image: sql.NullString{Valid: true, String: gofakeit.URL()},
  97. },
  98. }
  99. )
  100. tests := []struct {
  101. name string
  102. req req
  103. res int
  104. err error
  105. cacheMock cacheMockFunc
  106. tagMock tagMockFunc
  107. articleMock articleMockFunc
  108. }{
  109. {
  110. name: "positive case",
  111. req: req{
  112. method: fiber.MethodGet,
  113. route: "/article/" + strconv.Itoa(articleID),
  114. },
  115. res: fiber.StatusOK,
  116. err: nil,
  117. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  118. mock := cacheMocks.NewICacheMock(mc)
  119. mock.GetMock.Return(nil, false)
  120. mock.SetMock.Return()
  121. return mock
  122. },
  123. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  124. mock := repositoryMocks.NewITagRepositoryMock(mc)
  125. mock.GetAllUsedMock.Return(tags, nil)
  126. return mock
  127. },
  128. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  129. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  130. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  131. assert.Equal(mc, strconv.Itoa(articleID), url)
  132. }).Return(&article, nil)
  133. mock.GetAllPreviewMock.Return(previewArticles, nil)
  134. return mock
  135. },
  136. },
  137. {
  138. name: "negative case - article not found",
  139. req: req{
  140. method: fiber.MethodGet,
  141. route: "/article/" + strconv.Itoa(articleID),
  142. },
  143. res: fiber.StatusNotFound,
  144. err: nil,
  145. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  146. mock := cacheMocks.NewICacheMock(mc)
  147. mock.GetMock.Return(nil, false)
  148. return mock
  149. },
  150. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  151. mock := repositoryMocks.NewITagRepositoryMock(mc)
  152. return mock
  153. },
  154. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  155. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  156. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  157. assert.Equal(mc, strconv.Itoa(articleID), url)
  158. }).Return(nil, sql.ErrNoRows)
  159. return mock
  160. },
  161. },
  162. {
  163. name: "negative case - article repository error",
  164. req: req{
  165. method: fiber.MethodGet,
  166. route: "/article/" + strconv.Itoa(articleID),
  167. },
  168. res: fiber.StatusInternalServerError,
  169. err: nil,
  170. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  171. mock := cacheMocks.NewICacheMock(mc)
  172. mock.GetMock.Return(nil, false)
  173. return mock
  174. },
  175. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  176. mock := repositoryMocks.NewITagRepositoryMock(mc)
  177. return mock
  178. },
  179. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  180. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  181. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  182. assert.Equal(mc, strconv.Itoa(articleID), url)
  183. }).Return(nil, internalErr)
  184. return mock
  185. },
  186. },
  187. {
  188. name: "negative case - article not active",
  189. req: req{
  190. method: fiber.MethodGet,
  191. route: "/article/" + strconv.Itoa(articleID),
  192. },
  193. res: fiber.StatusNotFound,
  194. err: nil,
  195. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  196. mock := cacheMocks.NewICacheMock(mc)
  197. mock.GetMock.Return(nil, false)
  198. return mock
  199. },
  200. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  201. mock := repositoryMocks.NewITagRepositoryMock(mc)
  202. return mock
  203. },
  204. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  205. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  206. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  207. assert.Equal(mc, strconv.Itoa(articleID), url)
  208. }).Return(&notActiveArticle, nil)
  209. return mock
  210. },
  211. },
  212. {
  213. name: "negative case - article mapper error",
  214. req: req{
  215. method: fiber.MethodGet,
  216. route: "/article/" + strconv.Itoa(articleID),
  217. },
  218. res: fiber.StatusInternalServerError,
  219. err: nil,
  220. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  221. mock := cacheMocks.NewICacheMock(mc)
  222. mock.GetMock.Return(nil, false)
  223. return mock
  224. },
  225. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  226. mock := repositoryMocks.NewITagRepositoryMock(mc)
  227. return mock
  228. },
  229. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  230. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  231. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  232. assert.Equal(mc, strconv.Itoa(articleID), url)
  233. }).Return(&models.Article{IsActive: true}, nil)
  234. return mock
  235. },
  236. },
  237. {
  238. name: "negative case - tags repository error",
  239. req: req{
  240. method: fiber.MethodGet,
  241. route: "/article/" + strconv.Itoa(articleID),
  242. },
  243. res: fiber.StatusInternalServerError,
  244. err: nil,
  245. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  246. mock := cacheMocks.NewICacheMock(mc)
  247. mock.GetMock.Return(nil, false)
  248. return mock
  249. },
  250. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  251. mock := repositoryMocks.NewITagRepositoryMock(mc)
  252. mock.GetAllUsedMock.Return(nil, internalErr)
  253. return mock
  254. },
  255. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  256. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  257. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  258. assert.Equal(mc, strconv.Itoa(articleID), url)
  259. }).Return(&article, nil)
  260. return mock
  261. },
  262. },
  263. {
  264. name: "negative case - articles repository error",
  265. req: req{
  266. method: fiber.MethodGet,
  267. route: "/article/" + strconv.Itoa(articleID),
  268. },
  269. res: fiber.StatusInternalServerError,
  270. err: nil,
  271. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  272. mock := cacheMocks.NewICacheMock(mc)
  273. mock.GetMock.Return(nil, false)
  274. return mock
  275. },
  276. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  277. mock := repositoryMocks.NewITagRepositoryMock(mc)
  278. mock.GetAllUsedMock.Return(tags, nil)
  279. return mock
  280. },
  281. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  282. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  283. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  284. assert.Equal(mc, strconv.Itoa(articleID), url)
  285. }).Return(&article, nil)
  286. mock.GetAllPreviewMock.Return(nil, internalErr)
  287. return mock
  288. },
  289. },
  290. {
  291. name: "negative case - articles mapper error",
  292. req: req{
  293. method: fiber.MethodGet,
  294. route: "/article/" + strconv.Itoa(articleID),
  295. },
  296. res: fiber.StatusInternalServerError,
  297. err: nil,
  298. cacheMock: func(mc *minimock.Controller) interfaces.ICache {
  299. mock := cacheMocks.NewICacheMock(mc)
  300. mock.GetMock.Return(nil, false)
  301. return mock
  302. },
  303. tagMock: func(mc *minimock.Controller) interfaces.ITagRepository {
  304. mock := repositoryMocks.NewITagRepositoryMock(mc)
  305. mock.GetAllUsedMock.Return(tags, nil)
  306. return mock
  307. },
  308. articleMock: func(mc *minimock.Controller) interfaces.IArticleRepository {
  309. mock := repositoryMocks.NewIArticleRepositoryMock(mc)
  310. mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
  311. assert.Equal(mc, strconv.Itoa(articleID), url)
  312. }).Return(&article, nil)
  313. mock.GetAllPreviewMock.Return([]models.ArticlePreview{{}}, nil)
  314. return mock
  315. },
  316. },
  317. }
  318. for _, tt := range tests {
  319. t.Run(tt.name, func(t *testing.T) {
  320. fiberApp := fiber.New(test.GetFiberConfig())
  321. fiberReq := httptest.NewRequest(tt.req.method, tt.req.route, nil)
  322. serviceProvider := sp.InitMock(tt.cacheMock(mc), tt.tagMock(mc), tt.articleMock(mc))
  323. fiberApp.Get("/article/:article", ArticleHandler(serviceProvider))
  324. fiberRes, fiberErr := fiberApp.Test(fiberReq)
  325. assert.Equal(t, tt.res, fiberRes.StatusCode)
  326. assert.Equal(t, tt.err, fiberErr)
  327. })
  328. }
  329. }