user_handler_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. package api_test
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/http/httptest"
  6. "reflect"
  7. "strings"
  8. "testing"
  9. "github.com/porter-dev/porter/internal/models"
  10. )
  11. // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
  12. type userTest struct {
  13. initializers []func(t *tester)
  14. msg string
  15. method string
  16. endpoint string
  17. body string
  18. expStatus int
  19. expBody string
  20. useCookie bool
  21. validators []func(c *userTest, tester *tester, t *testing.T)
  22. }
  23. func testUserRequests(t *testing.T, tests []*userTest, canQuery bool) {
  24. for _, c := range tests {
  25. // create a new tester
  26. tester := newTester(canQuery)
  27. // if there's an initializer, call it
  28. for _, init := range c.initializers {
  29. init(tester)
  30. }
  31. req, err := http.NewRequest(
  32. c.method,
  33. c.endpoint,
  34. strings.NewReader(c.body),
  35. )
  36. tester.req = req
  37. if c.useCookie {
  38. req.AddCookie(tester.cookie)
  39. }
  40. if err != nil {
  41. t.Fatal(err)
  42. }
  43. tester.execute()
  44. rr := tester.rr
  45. // first, check that the status matches
  46. if status := rr.Code; status != c.expStatus {
  47. t.Errorf("%s, handler returned wrong status code: got %v want %v",
  48. c.msg, status, c.expStatus)
  49. }
  50. // if there's a validator, call it
  51. for _, validate := range c.validators {
  52. validate(c, tester, t)
  53. }
  54. }
  55. }
  56. // ------------------------- TEST FIXTURES AND FUNCTIONS ------------------------- //
  57. var authCheckTests = []*userTest{
  58. &userTest{
  59. initializers: []func(tester *tester){
  60. initUserDefault,
  61. },
  62. msg: "Auth check successful. User is logged in.",
  63. method: "GET",
  64. endpoint: "/api/auth/check",
  65. expStatus: http.StatusOK,
  66. body: "",
  67. expBody: `{"id":1,"email":"belanger@getporter.dev"}`,
  68. useCookie: true,
  69. validators: []func(c *userTest, tester *tester, t *testing.T){
  70. userBasicBodyValidator,
  71. },
  72. },
  73. &userTest{
  74. initializers: []func(tester *tester){
  75. initUserDefault,
  76. },
  77. msg: "Auth check failure. User is not logged in.",
  78. method: "GET",
  79. endpoint: "/api/auth/check",
  80. body: "",
  81. expStatus: http.StatusForbidden,
  82. expBody: http.StatusText(http.StatusForbidden) + "\n",
  83. validators: []func(c *userTest, tester *tester, t *testing.T){
  84. userBasicBodyValidator,
  85. },
  86. },
  87. }
  88. func TestHandleAuthCheck(t *testing.T) {
  89. testUserRequests(t, authCheckTests, true)
  90. }
  91. var createUserTests = []*userTest{
  92. &userTest{
  93. msg: "Create user",
  94. method: "POST",
  95. endpoint: "/api/users",
  96. body: `{
  97. "email": "belanger@getporter.dev",
  98. "password": "hello"
  99. }`,
  100. expStatus: http.StatusCreated,
  101. expBody: `{"id":1,"email":"belanger@getporter.dev"}`,
  102. validators: []func(c *userTest, tester *tester, t *testing.T){
  103. userModelBodyValidator,
  104. },
  105. },
  106. &userTest{
  107. msg: "Create user invalid email",
  108. method: "POST",
  109. endpoint: "/api/users",
  110. body: `{
  111. "email": "notanemail",
  112. "password": "hello"
  113. }`,
  114. expStatus: http.StatusUnprocessableEntity,
  115. expBody: `{"code":601,"errors":["email validation failed"]}`,
  116. validators: []func(c *userTest, tester *tester, t *testing.T){
  117. userBasicBodyValidator,
  118. },
  119. },
  120. &userTest{
  121. msg: "Create user missing field",
  122. method: "POST",
  123. endpoint: "/api/users",
  124. body: `{
  125. "password": "hello"
  126. }`,
  127. expStatus: http.StatusUnprocessableEntity,
  128. expBody: `{"code":601,"errors":["required validation failed"]}`,
  129. validators: []func(c *userTest, tester *tester, t *testing.T){
  130. userBasicBodyValidator,
  131. },
  132. },
  133. &userTest{
  134. initializers: []func(tester *tester){
  135. initUserDefault,
  136. },
  137. msg: "Create user same email",
  138. method: "POST",
  139. endpoint: "/api/users",
  140. body: `{
  141. "email": "belanger@getporter.dev",
  142. "password": "hello"
  143. }`,
  144. expStatus: http.StatusUnprocessableEntity,
  145. expBody: `{"code":601,"errors":["email already taken"]}`,
  146. validators: []func(c *userTest, tester *tester, t *testing.T){
  147. userBasicBodyValidator,
  148. },
  149. },
  150. &userTest{
  151. msg: "Create user invalid field type",
  152. method: "POST",
  153. endpoint: "/api/users",
  154. body: `{
  155. "email": "belanger@getporter.dev",
  156. "password": 0
  157. }`,
  158. expStatus: http.StatusBadRequest,
  159. expBody: `{"code":600,"errors":["could not process request"]}`,
  160. validators: []func(c *userTest, tester *tester, t *testing.T){
  161. userBasicBodyValidator,
  162. },
  163. },
  164. }
  165. func TestHandleCreateUser(t *testing.T) {
  166. testUserRequests(t, createUserTests, true)
  167. }
  168. var createUserTestsWriteFail = []*userTest{
  169. &userTest{
  170. msg: "Create user db connection down",
  171. method: "POST",
  172. endpoint: "/api/users",
  173. body: `{
  174. "email": "belanger@getporter.dev",
  175. "password": "hello"
  176. }`,
  177. expStatus: http.StatusInternalServerError,
  178. expBody: `{"code":500,"errors":["could not read from database"]}`,
  179. validators: []func(c *userTest, tester *tester, t *testing.T){
  180. userBasicBodyValidator,
  181. },
  182. },
  183. }
  184. func TestHandleCreateUserWriteFail(t *testing.T) {
  185. testUserRequests(t, createUserTestsWriteFail, false)
  186. }
  187. var loginUserTests = []*userTest{
  188. &userTest{
  189. initializers: []func(tester *tester){
  190. initUserDefault,
  191. },
  192. msg: "Login user successful",
  193. method: "POST",
  194. endpoint: "/api/login",
  195. body: `{
  196. "email": "belanger@getporter.dev",
  197. "password": "hello"
  198. }`,
  199. expStatus: http.StatusOK,
  200. expBody: `{"id":1,"email":"belanger@getporter.dev"}`,
  201. validators: []func(c *userTest, tester *tester, t *testing.T){
  202. userBasicBodyValidator,
  203. },
  204. },
  205. &userTest{
  206. initializers: []func(tester *tester){
  207. initUserDefault,
  208. },
  209. msg: "Login user already logged in",
  210. method: "POST",
  211. endpoint: "/api/login",
  212. body: `{
  213. "email": "belanger@getporter.dev",
  214. "password": "hello"
  215. }`,
  216. expStatus: http.StatusOK,
  217. expBody: `{"id":1,"email":"belanger@getporter.dev"}`,
  218. useCookie: true,
  219. validators: []func(c *userTest, tester *tester, t *testing.T){
  220. userBasicBodyValidator,
  221. },
  222. },
  223. &userTest{
  224. msg: "Login user unregistered email",
  225. method: "POST",
  226. endpoint: "/api/login",
  227. body: `{
  228. "email": "belanger@getporter.dev",
  229. "password": "hello"
  230. }`,
  231. expStatus: http.StatusUnauthorized,
  232. expBody: `{"code":401,"errors":["email not registered"]}`,
  233. validators: []func(c *userTest, tester *tester, t *testing.T){
  234. userBasicBodyValidator,
  235. },
  236. },
  237. &userTest{
  238. initializers: []func(tester *tester){
  239. initUserDefault,
  240. },
  241. msg: "Login user incorrect password",
  242. method: "POST",
  243. endpoint: "/api/login",
  244. body: `{
  245. "email": "belanger@getporter.dev",
  246. "password": "notthepassword"
  247. }`,
  248. expStatus: http.StatusUnauthorized,
  249. expBody: `{"code":401,"errors":["incorrect password"]}`,
  250. useCookie: true,
  251. validators: []func(c *userTest, tester *tester, t *testing.T){
  252. userBasicBodyValidator,
  253. },
  254. },
  255. }
  256. func TestHandleLoginUser(t *testing.T) {
  257. testUserRequests(t, loginUserTests, true)
  258. }
  259. var logoutUserTests = []*userTest{
  260. &userTest{
  261. initializers: []func(tester *tester){
  262. initUserDefault,
  263. },
  264. msg: "Logout user successful",
  265. method: "POST",
  266. endpoint: "/api/logout",
  267. body: `{
  268. "email": "belanger@getporter.dev",
  269. "password": "hello"
  270. }`,
  271. expStatus: http.StatusOK,
  272. expBody: ``,
  273. useCookie: true,
  274. validators: []func(c *userTest, tester *tester, t *testing.T){
  275. func(c *userTest, tester *tester, t *testing.T) {
  276. req, err := http.NewRequest(
  277. "GET",
  278. "/api/users/1",
  279. strings.NewReader(""),
  280. )
  281. req.AddCookie(tester.cookie)
  282. if err != nil {
  283. t.Fatal(err)
  284. }
  285. rr2 := httptest.NewRecorder()
  286. tester.router.ServeHTTP(rr2, req)
  287. if status := rr2.Code; status != http.StatusForbidden {
  288. t.Errorf("%s, handler returned wrong status: got %v want %v",
  289. "validator failed", status, http.StatusForbidden)
  290. }
  291. },
  292. },
  293. },
  294. }
  295. func TestHandleLogoutUser(t *testing.T) {
  296. testUserRequests(t, logoutUserTests, true)
  297. }
  298. var readUserTests = []*userTest{
  299. &userTest{
  300. initializers: []func(tester *tester){
  301. initUserDefault,
  302. },
  303. msg: "Read user successful",
  304. method: "GET",
  305. endpoint: "/api/users/1",
  306. body: "",
  307. expStatus: http.StatusOK,
  308. expBody: `{"id":1,"email":"belanger@getporter.dev"}`,
  309. useCookie: true,
  310. validators: []func(c *userTest, tester *tester, t *testing.T){
  311. userModelBodyValidator,
  312. },
  313. },
  314. &userTest{
  315. initializers: []func(tester *tester){
  316. initUserDefault,
  317. },
  318. msg: "Read user unauthorized",
  319. method: "GET",
  320. endpoint: "/api/users/2",
  321. body: "",
  322. expStatus: http.StatusForbidden,
  323. expBody: http.StatusText(http.StatusForbidden) + "\n",
  324. validators: []func(c *userTest, tester *tester, t *testing.T){
  325. userBasicBodyValidator,
  326. },
  327. },
  328. }
  329. func TestHandleReadUser(t *testing.T) {
  330. testUserRequests(t, readUserTests, true)
  331. }
  332. var deleteUserTests = []*userTest{
  333. &userTest{
  334. initializers: []func(tester *tester){
  335. initUserDefault,
  336. },
  337. msg: "Delete user successful",
  338. method: "DELETE",
  339. endpoint: "/api/users/1",
  340. body: `{"password":"hello"}`,
  341. expStatus: http.StatusNoContent,
  342. expBody: "",
  343. useCookie: true,
  344. validators: []func(c *userTest, tester *tester, t *testing.T){
  345. func(c *userTest, tester *tester, t *testing.T) {
  346. req, err := http.NewRequest(
  347. "GET",
  348. "/api/users/1",
  349. strings.NewReader(""),
  350. )
  351. req.AddCookie(tester.cookie)
  352. if err != nil {
  353. t.Fatal(err)
  354. }
  355. rr2 := httptest.NewRecorder()
  356. tester.router.ServeHTTP(rr2, req)
  357. gotBody := &models.UserExternal{}
  358. expBody := &models.UserExternal{}
  359. if status := rr2.Code; status != 404 {
  360. t.Errorf("DELETE user validation, handler returned wrong status code: got %v want %v",
  361. status, 404)
  362. }
  363. json.Unmarshal(rr2.Body.Bytes(), gotBody)
  364. json.Unmarshal([]byte(`{"code":602,"errors":["could not find requested object"]}`), expBody)
  365. if !reflect.DeepEqual(gotBody, expBody) {
  366. t.Errorf("%s, handler returned wrong body: got %v want %v",
  367. "validator failed", gotBody, expBody)
  368. }
  369. },
  370. },
  371. },
  372. &userTest{
  373. initializers: []func(tester *tester){
  374. initUserDefault,
  375. },
  376. msg: "Delete user invalid id",
  377. method: "DELETE",
  378. endpoint: "/api/users/aldkjf",
  379. body: `{"password":"hello"}`,
  380. expStatus: http.StatusForbidden,
  381. expBody: http.StatusText(http.StatusForbidden) + "\n",
  382. validators: []func(c *userTest, tester *tester, t *testing.T){
  383. userBasicBodyValidator,
  384. },
  385. },
  386. &userTest{
  387. initializers: []func(tester *tester){
  388. initUserDefault,
  389. },
  390. msg: "Delete user missing password",
  391. method: "DELETE",
  392. endpoint: "/api/users/1",
  393. body: `{}`,
  394. expStatus: http.StatusUnprocessableEntity,
  395. expBody: `{"code":601,"errors":["required validation failed"]}`,
  396. useCookie: true,
  397. validators: []func(c *userTest, tester *tester, t *testing.T){
  398. userBasicBodyValidator,
  399. },
  400. },
  401. }
  402. func TestHandleDeleteUser(t *testing.T) {
  403. testUserRequests(t, deleteUserTests, true)
  404. }
  405. // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
  406. func initUserDefault(tester *tester) {
  407. tester.createUserSession("belanger@getporter.dev", "hello")
  408. }
  409. func userBasicBodyValidator(c *userTest, tester *tester, t *testing.T) {
  410. if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
  411. t.Errorf("%s, handler returned wrong body: got %v want %v",
  412. c.msg, body, c.expBody)
  413. }
  414. }
  415. func userModelBodyValidator(c *userTest, tester *tester, t *testing.T) {
  416. gotBody := &models.UserExternal{}
  417. expBody := &models.UserExternal{}
  418. json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
  419. json.Unmarshal([]byte(c.expBody), expBody)
  420. if !reflect.DeepEqual(gotBody, expBody) {
  421. t.Errorf("%s, handler returned wrong body: got %v want %v",
  422. c.msg, gotBody, expBody)
  423. }
  424. }
  425. func userContextBodyValidator(c *userTest, tester *tester, t *testing.T) {
  426. gotBody := &[]models.Context{}
  427. expBody := &[]models.Context{}
  428. json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
  429. json.Unmarshal([]byte(c.expBody), expBody)
  430. if !reflect.DeepEqual(gotBody, expBody) {
  431. t.Errorf("%s, handler returned wrong body: got %v want %v",
  432. c.msg, gotBody, expBody)
  433. }
  434. }