2
0

user_handler_test.go 14 KB

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