update_thing_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. package thing
  2. import (
  3. "context"
  4. "database/sql"
  5. "net/http/httptest"
  6. "strconv"
  7. "testing"
  8. "github.com/brianvoe/gofakeit/v6"
  9. "github.com/gofiber/fiber/v2"
  10. "github.com/gojuno/minimock/v3"
  11. "github.com/stretchr/testify/assert"
  12. "git.dmitriygnatenko.ru/dima/homethings/internal/api/v1/thing/mocks"
  13. "git.dmitriygnatenko.ru/dima/homethings/internal/dto"
  14. "git.dmitriygnatenko.ru/dima/homethings/internal/helpers/test"
  15. "git.dmitriygnatenko.ru/dima/homethings/internal/models"
  16. )
  17. func TestUpdateThingHandler(t *testing.T) {
  18. t.Parallel()
  19. type req struct {
  20. method string
  21. route string
  22. contentType string
  23. body *dto.UpdateThingRequest
  24. }
  25. var (
  26. placeID = uint64(gofakeit.Number(1, 1000))
  27. thingID = uint64(gofakeit.Number(1, 1000))
  28. title = gofakeit.Phrase()
  29. description = gofakeit.Phrase()
  30. testError = gofakeit.Error()
  31. layout = "2006-01-02 15:04:05"
  32. txMockFunc = func(ctx context.Context, f func(ctx context.Context) error) error {
  33. return f(ctx)
  34. }
  35. correctReq = req{
  36. method: fiber.MethodPut,
  37. route: "/v1/things/" + strconv.FormatUint(thingID, 10),
  38. body: &dto.UpdateThingRequest{
  39. PlaceID: placeID,
  40. Title: title,
  41. Description: description,
  42. },
  43. contentType: fiber.MIMEApplicationJSON,
  44. }
  45. repoResBeforeUpdate = models.Thing{
  46. ID: thingID,
  47. Title: gofakeit.Phrase(),
  48. Description: gofakeit.Phrase(),
  49. CreatedAt: gofakeit.Date(),
  50. UpdatedAt: gofakeit.Date(),
  51. }
  52. placeThingRepoResBeforeUpdate = models.PlaceThing{
  53. PlaceID: uint64(gofakeit.Number(1, 1000)),
  54. ThingID: thingID,
  55. CreatedAt: gofakeit.Date(),
  56. }
  57. repoRes = models.Thing{
  58. ID: thingID,
  59. Title: title,
  60. Description: description,
  61. CreatedAt: gofakeit.Date(),
  62. UpdatedAt: gofakeit.Date(),
  63. }
  64. expectedRes = dto.ThingResponse{
  65. ID: thingID,
  66. Title: title,
  67. Description: description,
  68. CreatedAt: repoRes.CreatedAt.Format(layout),
  69. UpdatedAt: repoRes.UpdatedAt.Format(layout),
  70. }
  71. )
  72. tests := []struct {
  73. name string
  74. req req
  75. resCode int
  76. resBody interface{}
  77. tmMock func(mc *minimock.Controller) TransactionManager
  78. thingRepoMock func(mc *minimock.Controller) ThingRepository
  79. placeThingRepoMock func(mc *minimock.Controller) PlaceThingRepository
  80. }{
  81. {
  82. name: "positive case",
  83. req: correctReq,
  84. resCode: fiber.StatusOK,
  85. resBody: expectedRes,
  86. tmMock: func(mc *minimock.Controller) TransactionManager {
  87. mock := mocks.NewTransactionManagerMock(mc)
  88. mock.ReadCommittedMock.Set(txMockFunc)
  89. return mock
  90. },
  91. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  92. mock := mocks.NewThingRepositoryMock(mc)
  93. mock.UpdateMock.Inspect(func(ctx context.Context, req models.UpdateThingRequest) {
  94. assert.Equal(mc, title, req.Title)
  95. assert.Equal(mc, description, req.Description)
  96. }).Return(nil)
  97. mock.GetMock.Set(func(ctx context.Context, id uint64) (*models.Thing, error) {
  98. assert.Equal(mc, thingID, id)
  99. if mock.GetAfterCounter() == 0 {
  100. return &repoResBeforeUpdate, nil
  101. }
  102. return &repoRes, nil
  103. })
  104. return mock
  105. },
  106. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  107. mock := mocks.NewPlaceThingRepositoryMock(mc)
  108. mock.GetByThingIDMock.Inspect(func(ctx context.Context, id uint64) {
  109. assert.Equal(mc, thingID, id)
  110. }).Return(&placeThingRepoResBeforeUpdate, nil)
  111. mock.UpdatePlaceMock.Inspect(func(ctx context.Context, req models.UpdatePlaceThingRequest) {
  112. assert.Equal(mc, placeID, req.PlaceID)
  113. assert.Equal(mc, thingID, req.ThingID)
  114. }).Return(nil)
  115. return mock
  116. },
  117. },
  118. {
  119. name: "negative case - bad request",
  120. req: req{
  121. method: fiber.MethodPut,
  122. route: "/v1/things/" + gofakeit.Word(),
  123. },
  124. resCode: fiber.StatusBadRequest,
  125. tmMock: func(mc *minimock.Controller) TransactionManager {
  126. return mocks.NewTransactionManagerMock(mc)
  127. },
  128. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  129. return mocks.NewThingRepositoryMock(mc)
  130. },
  131. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  132. return mocks.NewPlaceThingRepositoryMock(mc)
  133. },
  134. },
  135. {
  136. name: "negative case - body parse error",
  137. req: req{
  138. method: fiber.MethodPut,
  139. route: "/v1/things/" + strconv.FormatUint(thingID, 10),
  140. },
  141. resCode: fiber.StatusBadRequest,
  142. tmMock: func(mc *minimock.Controller) TransactionManager {
  143. return mocks.NewTransactionManagerMock(mc)
  144. },
  145. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  146. return mocks.NewThingRepositoryMock(mc)
  147. },
  148. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  149. return mocks.NewPlaceThingRepositoryMock(mc)
  150. },
  151. },
  152. {
  153. name: "negative case - request without place_id",
  154. req: req{
  155. method: fiber.MethodPut,
  156. route: "/v1/things/" + strconv.FormatUint(thingID, 10),
  157. contentType: fiber.MIMEApplicationJSON,
  158. body: &dto.UpdateThingRequest{
  159. Title: title,
  160. },
  161. },
  162. resCode: fiber.StatusBadRequest,
  163. resBody: []*dto.ValidateErrorResponse{
  164. {
  165. Field: "UpdateThingRequest.PlaceID",
  166. Tag: "required",
  167. },
  168. },
  169. tmMock: func(mc *minimock.Controller) TransactionManager {
  170. return mocks.NewTransactionManagerMock(mc)
  171. },
  172. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  173. return mocks.NewThingRepositoryMock(mc)
  174. },
  175. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  176. return mocks.NewPlaceThingRepositoryMock(mc)
  177. },
  178. },
  179. {
  180. name: "negative case - request without title",
  181. req: req{
  182. method: fiber.MethodPut,
  183. route: "/v1/things/" + strconv.FormatUint(thingID, 10),
  184. contentType: fiber.MIMEApplicationJSON,
  185. body: &dto.UpdateThingRequest{
  186. PlaceID: placeID,
  187. },
  188. },
  189. resCode: fiber.StatusBadRequest,
  190. resBody: []*dto.ValidateErrorResponse{
  191. {
  192. Field: "UpdateThingRequest.Title",
  193. Tag: "required",
  194. },
  195. },
  196. tmMock: func(mc *minimock.Controller) TransactionManager {
  197. return mocks.NewTransactionManagerMock(mc)
  198. },
  199. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  200. return mocks.NewThingRepositoryMock(mc)
  201. },
  202. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  203. return mocks.NewPlaceThingRepositoryMock(mc)
  204. },
  205. },
  206. {
  207. name: "negative case - repository error (get thing)",
  208. req: correctReq,
  209. resCode: fiber.StatusInternalServerError,
  210. tmMock: func(mc *minimock.Controller) TransactionManager {
  211. mock := mocks.NewTransactionManagerMock(mc)
  212. mock.ReadCommittedMock.Set(txMockFunc)
  213. return mock
  214. },
  215. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  216. mock := mocks.NewThingRepositoryMock(mc)
  217. mock.GetMock.Inspect(func(ctx context.Context, id uint64) {
  218. assert.Equal(mc, thingID, id)
  219. }).Return(nil, sql.ErrNoRows)
  220. return mock
  221. },
  222. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  223. return mocks.NewPlaceThingRepositoryMock(mc)
  224. },
  225. },
  226. {
  227. name: "negative case - repository error (get place thing)",
  228. req: correctReq,
  229. resCode: fiber.StatusInternalServerError,
  230. tmMock: func(mc *minimock.Controller) TransactionManager {
  231. mock := mocks.NewTransactionManagerMock(mc)
  232. mock.ReadCommittedMock.Set(txMockFunc)
  233. return mock
  234. },
  235. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  236. mock := mocks.NewThingRepositoryMock(mc)
  237. mock.GetMock.Inspect(func(ctx context.Context, id uint64) {
  238. assert.Equal(mc, thingID, id)
  239. }).Return(&repoResBeforeUpdate, nil)
  240. return mock
  241. },
  242. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  243. mock := mocks.NewPlaceThingRepositoryMock(mc)
  244. mock.GetByThingIDMock.Inspect(func(ctx context.Context, id uint64) {
  245. assert.Equal(mc, thingID, id)
  246. }).Return(nil, sql.ErrNoRows)
  247. return mock
  248. },
  249. },
  250. {
  251. name: "negative case - repository error (update thing)",
  252. req: correctReq,
  253. resCode: fiber.StatusInternalServerError,
  254. tmMock: func(mc *minimock.Controller) TransactionManager {
  255. mock := mocks.NewTransactionManagerMock(mc)
  256. mock.ReadCommittedMock.Set(txMockFunc)
  257. return mock
  258. },
  259. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  260. mock := mocks.NewThingRepositoryMock(mc)
  261. mock.GetMock.Inspect(func(ctx context.Context, id uint64) {
  262. assert.Equal(mc, thingID, id)
  263. }).Return(&repoResBeforeUpdate, nil)
  264. mock.UpdateMock.Inspect(func(ctx context.Context, req models.UpdateThingRequest) {
  265. assert.Equal(mc, title, req.Title)
  266. assert.Equal(mc, description, req.Description)
  267. }).Return(testError)
  268. return mock
  269. },
  270. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  271. mock := mocks.NewPlaceThingRepositoryMock(mc)
  272. mock.GetByThingIDMock.Inspect(func(ctx context.Context, id uint64) {
  273. assert.Equal(mc, thingID, id)
  274. }).Return(&placeThingRepoResBeforeUpdate, nil)
  275. return mock
  276. },
  277. },
  278. {
  279. name: "negative case - repository error (update place)",
  280. req: correctReq,
  281. resCode: fiber.StatusInternalServerError,
  282. tmMock: func(mc *minimock.Controller) TransactionManager {
  283. mock := mocks.NewTransactionManagerMock(mc)
  284. mock.ReadCommittedMock.Set(txMockFunc)
  285. return mock
  286. },
  287. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  288. mock := mocks.NewThingRepositoryMock(mc)
  289. mock.GetMock.Inspect(func(ctx context.Context, id uint64) {
  290. assert.Equal(mc, thingID, id)
  291. }).Return(&repoResBeforeUpdate, nil)
  292. mock.UpdateMock.Inspect(func(ctx context.Context, req models.UpdateThingRequest) {
  293. assert.Equal(mc, title, req.Title)
  294. assert.Equal(mc, description, req.Description)
  295. }).Return(nil)
  296. return mock
  297. },
  298. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  299. mock := mocks.NewPlaceThingRepositoryMock(mc)
  300. mock.GetByThingIDMock.Inspect(func(ctx context.Context, id uint64) {
  301. assert.Equal(mc, thingID, id)
  302. }).Return(&placeThingRepoResBeforeUpdate, nil)
  303. mock.UpdatePlaceMock.Inspect(func(ctx context.Context, req models.UpdatePlaceThingRequest) {
  304. assert.Equal(mc, placeID, req.PlaceID)
  305. assert.Equal(mc, thingID, req.ThingID)
  306. }).Return(testError)
  307. return mock
  308. },
  309. },
  310. {
  311. name: "negative case - repository error (get thing)",
  312. req: correctReq,
  313. resCode: fiber.StatusInternalServerError,
  314. tmMock: func(mc *minimock.Controller) TransactionManager {
  315. mock := mocks.NewTransactionManagerMock(mc)
  316. mock.ReadCommittedMock.Set(txMockFunc)
  317. return mock
  318. },
  319. thingRepoMock: func(mc *minimock.Controller) ThingRepository {
  320. mock := mocks.NewThingRepositoryMock(mc)
  321. mock.GetMock.Set(func(ctx context.Context, thingID uint64) (*models.Thing, error) {
  322. if mock.GetAfterCounter() == 0 {
  323. return &repoResBeforeUpdate, nil
  324. }
  325. return nil, sql.ErrNoRows
  326. })
  327. mock.UpdateMock.Inspect(func(ctx context.Context, req models.UpdateThingRequest) {
  328. assert.Equal(mc, title, req.Title)
  329. assert.Equal(mc, description, req.Description)
  330. }).Return(nil)
  331. return mock
  332. },
  333. placeThingRepoMock: func(mc *minimock.Controller) PlaceThingRepository {
  334. mock := mocks.NewPlaceThingRepositoryMock(mc)
  335. mock.GetByThingIDMock.Inspect(func(ctx context.Context, id uint64) {
  336. assert.Equal(mc, thingID, id)
  337. }).Return(&placeThingRepoResBeforeUpdate, nil)
  338. mock.UpdatePlaceMock.Inspect(func(ctx context.Context, req models.UpdatePlaceThingRequest) {
  339. assert.Equal(mc, placeID, req.PlaceID)
  340. assert.Equal(mc, thingID, req.ThingID)
  341. }).Return(nil)
  342. return mock
  343. },
  344. },
  345. }
  346. for _, tt := range tests {
  347. t.Run(tt.name, func(t *testing.T) {
  348. t.Parallel()
  349. mc := minimock.NewController(t)
  350. fiberApp := fiber.New()
  351. fiberApp.Put("/v1/things/:thingId", UpdateThingHandler(
  352. tt.tmMock(mc),
  353. tt.thingRepoMock(mc),
  354. tt.placeThingRepoMock(mc),
  355. ))
  356. fiberReq := httptest.NewRequest(tt.req.method, tt.req.route, test.ConvertDataToIOReader(tt.req.body))
  357. fiberReq.Header.Add(fiber.HeaderContentType, tt.req.contentType)
  358. fiberRes, _ := fiberApp.Test(fiberReq, test.TestTimeout)
  359. assert.Equal(t, tt.resCode, fiberRes.StatusCode)
  360. if tt.resBody != nil {
  361. assert.Equal(t, test.MarshalResponse(tt.resBody), test.ConvertBodyToString(fiberRes.Body))
  362. }
  363. })
  364. }
  365. }