user_handler_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. package api_test
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "net/http/httptest"
  6. "reflect"
  7. "strings"
  8. "testing"
  9. "time"
  10. "github.com/go-chi/chi"
  11. "github.com/porter-dev/porter/internal/config"
  12. "github.com/porter-dev/porter/internal/models"
  13. "github.com/porter-dev/porter/internal/repository"
  14. "github.com/porter-dev/porter/internal/repository/test"
  15. "github.com/porter-dev/porter/server/api"
  16. "github.com/porter-dev/porter/server/router"
  17. lr "github.com/porter-dev/porter/internal/logger"
  18. vr "github.com/porter-dev/porter/internal/validator"
  19. )
  20. func initApi(canQuery bool) (*api.App, *repository.Repository) {
  21. appConf := config.Conf{
  22. Debug: true,
  23. Server: config.ServerConf{
  24. Port: 8080,
  25. TimeoutRead: time.Second * 5,
  26. TimeoutWrite: time.Second * 10,
  27. TimeoutIdle: time.Second * 15,
  28. },
  29. // unimportant
  30. Db: config.DBConf{},
  31. }
  32. logger := lr.NewConsole(appConf.Debug)
  33. validator := vr.New()
  34. repo := test.NewRepository(canQuery)
  35. return api.New(logger, repo, validator), repo
  36. }
  37. func testUserRequest(t *testing.T, c userTest) {
  38. // create a mock API
  39. api, repo := initApi(c.canQuery)
  40. r := router.New(api)
  41. if c.init != nil {
  42. c.init(repo)
  43. }
  44. req, err := http.NewRequest(
  45. c.method,
  46. c.endpoint,
  47. strings.NewReader(c.body),
  48. )
  49. if err != nil {
  50. t.Fatal(err)
  51. }
  52. rr := httptest.NewRecorder()
  53. r.ServeHTTP(rr, req)
  54. // first, check that the status matches
  55. if status := rr.Code; status != c.expStatus {
  56. t.Errorf("%s, handler returned wrong status code: got %v want %v",
  57. c.msg, status, c.expStatus)
  58. }
  59. // if there's a validator, call it
  60. for _, validate := range c.validators {
  61. validate(rr, c, r, t)
  62. }
  63. }
  64. type userTest struct {
  65. init func(repo *repository.Repository)
  66. msg,
  67. method,
  68. endpoint,
  69. body string
  70. expStatus int
  71. expBody string
  72. canQuery bool
  73. validators []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T)
  74. }
  75. var createUserTests = []userTest{
  76. userTest{
  77. msg: "Create user",
  78. method: "POST",
  79. endpoint: "/api/users",
  80. body: `{
  81. "email": "belanger@getporter.dev",
  82. "password": "hello"
  83. }`,
  84. expStatus: http.StatusCreated,
  85. expBody: "",
  86. canQuery: true,
  87. },
  88. userTest{
  89. msg: "Create user invalid email",
  90. method: "POST",
  91. endpoint: "/api/users",
  92. body: `{
  93. "email": "notanemail",
  94. "password": "hello"
  95. }`,
  96. expStatus: http.StatusUnprocessableEntity,
  97. expBody: `{"code":601,"errors":["email validation failed"]}`,
  98. canQuery: true,
  99. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  100. BasicBodyValidator,
  101. },
  102. },
  103. userTest{
  104. msg: "Create user missing field",
  105. method: "POST",
  106. endpoint: "/api/users",
  107. body: `{
  108. "password": "hello"
  109. }`,
  110. expStatus: http.StatusUnprocessableEntity,
  111. expBody: `{"code":601,"errors":["required validation failed"]}`,
  112. canQuery: true,
  113. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  114. BasicBodyValidator,
  115. },
  116. },
  117. userTest{
  118. msg: "Create user db connection down",
  119. method: "POST",
  120. endpoint: "/api/users",
  121. body: `{
  122. "email": "belanger@getporter.dev",
  123. "password": "hello"
  124. }`,
  125. expStatus: http.StatusInternalServerError,
  126. expBody: `{"code":500,"errors":["could not read from database"]}`,
  127. canQuery: false,
  128. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  129. BasicBodyValidator,
  130. },
  131. },
  132. userTest{
  133. init: func(repo *repository.Repository) {
  134. repo.User.CreateUser(&models.User{
  135. Email: "belanger@getporter.dev",
  136. Password: "hello",
  137. })
  138. },
  139. msg: "Create user same email",
  140. method: "POST",
  141. endpoint: "/api/users",
  142. body: `{
  143. "email": "belanger@getporter.dev",
  144. "password": "hello"
  145. }`,
  146. expStatus: http.StatusUnprocessableEntity,
  147. expBody: `{"code":601,"errors":["email already taken"]}`,
  148. canQuery: true,
  149. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  150. BasicBodyValidator,
  151. },
  152. },
  153. userTest{
  154. msg: "Create user invalid field type",
  155. method: "POST",
  156. endpoint: "/api/users",
  157. body: `{
  158. "email": "belanger@getporter.dev",
  159. "password": 0
  160. }`,
  161. expStatus: http.StatusBadRequest,
  162. expBody: `{"code":600,"errors":["could not process request"]}`,
  163. canQuery: true,
  164. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  165. BasicBodyValidator,
  166. },
  167. },
  168. }
  169. func TestHandleCreateUser(t *testing.T) {
  170. for _, c := range createUserTests {
  171. testUserRequest(t, c)
  172. }
  173. }
  174. var readUserTests = []userTest{
  175. userTest{
  176. init: func(repo *repository.Repository) {
  177. repo.User.CreateUser(&models.User{
  178. Email: "belanger@getporter.dev",
  179. Password: "hello",
  180. Clusters: []models.ClusterConfig{
  181. models.ClusterConfig{
  182. Name: "cluster-test",
  183. Server: "https://localhost",
  184. Context: "context-test",
  185. User: "test-admin",
  186. },
  187. },
  188. RawKubeConfig: []byte("apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin"),
  189. })
  190. },
  191. msg: "Read user successful",
  192. method: "GET",
  193. endpoint: "/api/users/1",
  194. body: "",
  195. expStatus: http.StatusOK,
  196. expBody: `{"id":1,"email":"belanger@getporter.dev","clusters":[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin"}`,
  197. canQuery: true,
  198. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  199. UserModelBodyValidator,
  200. },
  201. },
  202. userTest{
  203. init: func(repo *repository.Repository) {
  204. repo.User.CreateUser(&models.User{
  205. Email: "belanger@getporter.dev",
  206. Password: "hello",
  207. })
  208. },
  209. msg: "Read user bad id field",
  210. method: "GET",
  211. endpoint: "/api/users/aldkfjas",
  212. body: "",
  213. expStatus: http.StatusBadRequest,
  214. expBody: `{"code":600,"errors":["could not process request"]}`,
  215. canQuery: true,
  216. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  217. BasicBodyValidator,
  218. },
  219. },
  220. userTest{
  221. init: func(repo *repository.Repository) {
  222. repo.User.CreateUser(&models.User{
  223. Email: "belanger@getporter.dev",
  224. Password: "hello",
  225. })
  226. },
  227. msg: "Read user not found",
  228. method: "GET",
  229. endpoint: "/api/users/2",
  230. body: "",
  231. expStatus: http.StatusNotFound,
  232. expBody: `{"code":602,"errors":["could not find requested object"]}`,
  233. canQuery: true,
  234. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  235. BasicBodyValidator,
  236. },
  237. },
  238. }
  239. func TestHandleReadUser(t *testing.T) {
  240. for _, c := range readUserTests {
  241. testUserRequest(t, c)
  242. }
  243. }
  244. var readUserClustersTests = []userTest{
  245. userTest{
  246. init: func(repo *repository.Repository) {
  247. repo.User.CreateUser(&models.User{
  248. Email: "belanger@getporter.dev",
  249. Password: "hello",
  250. Clusters: []models.ClusterConfig{
  251. models.ClusterConfig{
  252. Name: "cluster-test",
  253. Server: "https://localhost",
  254. Context: "context-test",
  255. User: "test-admin",
  256. },
  257. },
  258. RawKubeConfig: []byte("apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin"),
  259. })
  260. },
  261. msg: "Read user successful",
  262. method: "GET",
  263. endpoint: "/api/users/1/clusters",
  264. body: "",
  265. expStatus: http.StatusOK,
  266. expBody: `[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}]`,
  267. canQuery: true,
  268. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  269. ClusterBodyValidator,
  270. },
  271. },
  272. }
  273. func TestHandleReadUserClusters(t *testing.T) {
  274. for _, c := range readUserClustersTests {
  275. testUserRequest(t, c)
  276. }
  277. }
  278. var readUserClustersAllTests = []userTest{
  279. userTest{
  280. init: func(repo *repository.Repository) {
  281. repo.User.CreateUser(&models.User{
  282. Email: "belanger@getporter.dev",
  283. Password: "hello",
  284. Clusters: []models.ClusterConfig{},
  285. RawKubeConfig: []byte("apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin"),
  286. })
  287. },
  288. msg: "Read user successful",
  289. method: "GET",
  290. endpoint: "/api/users/1/clusters/all",
  291. body: "",
  292. expStatus: http.StatusOK,
  293. expBody: `[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}]`,
  294. canQuery: true,
  295. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  296. ClusterBodyValidator,
  297. },
  298. },
  299. userTest{
  300. init: func(repo *repository.Repository) {
  301. repo.User.CreateUser(&models.User{
  302. Email: "belanger@getporter.dev",
  303. Password: "hello",
  304. Clusters: []models.ClusterConfig{},
  305. RawKubeConfig: []byte("apiVersion: \xc5\n"),
  306. })
  307. },
  308. msg: "Read user with invalid utf-8 \xc5 in kubeconfig",
  309. method: "GET",
  310. endpoint: "/api/users/1/clusters/all",
  311. body: "",
  312. expStatus: http.StatusBadRequest,
  313. expBody: `{"code":600,"errors":["could not process request"]}`,
  314. canQuery: true,
  315. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  316. BasicBodyValidator,
  317. },
  318. },
  319. }
  320. func TestHandleReadUserClustersAll(t *testing.T) {
  321. for _, c := range readUserClustersAllTests {
  322. testUserRequest(t, c)
  323. }
  324. }
  325. var updateUserTests = []userTest{
  326. userTest{
  327. init: func(repo *repository.Repository) {
  328. repo.User.CreateUser(&models.User{
  329. Email: "belanger@getporter.dev",
  330. Password: "hello",
  331. })
  332. },
  333. msg: "Update user successful",
  334. method: "PUT",
  335. endpoint: "/api/users/1",
  336. body: `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin", "allowedClusters":[]}`,
  337. expStatus: http.StatusNoContent,
  338. expBody: "",
  339. canQuery: true,
  340. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  341. func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T) {
  342. req, err := http.NewRequest(
  343. "GET",
  344. "/api/users/1",
  345. strings.NewReader(""),
  346. )
  347. if err != nil {
  348. t.Fatal(err)
  349. }
  350. rr2 := httptest.NewRecorder()
  351. r.ServeHTTP(rr2, req)
  352. gotBody := &models.UserExternal{}
  353. expBody := &models.UserExternal{}
  354. json.Unmarshal(rr2.Body.Bytes(), gotBody)
  355. json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","clusters":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin"}`), expBody)
  356. if !reflect.DeepEqual(gotBody, expBody) {
  357. t.Errorf("%s, handler returned wrong body: got %v want %v",
  358. "validator failed", gotBody, expBody)
  359. }
  360. },
  361. },
  362. },
  363. userTest{
  364. init: func(repo *repository.Repository) {
  365. repo.User.CreateUser(&models.User{
  366. Email: "belanger@getporter.dev",
  367. Password: "hello",
  368. })
  369. },
  370. msg: "Update user invalid id",
  371. method: "PUT",
  372. endpoint: "/api/users/alsdfjk",
  373. body: `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin", "allowedClusters":[]}`,
  374. expStatus: http.StatusBadRequest,
  375. expBody: `{"code":600,"errors":["could not process request"]}`,
  376. canQuery: true,
  377. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  378. BasicBodyValidator,
  379. },
  380. },
  381. userTest{
  382. init: func(repo *repository.Repository) {
  383. repo.User.CreateUser(&models.User{
  384. Email: "belanger@getporter.dev",
  385. Password: "hello",
  386. })
  387. },
  388. msg: "Update user bad kubeconfig",
  389. method: "PUT",
  390. endpoint: "/api/users/1",
  391. body: `{"rawKubeConfig":"notvalidyaml", "allowedClusters":[]}`,
  392. expStatus: http.StatusBadRequest,
  393. expBody: `{"code":600,"errors":["could not process request"]}`,
  394. canQuery: true,
  395. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  396. BasicBodyValidator,
  397. },
  398. },
  399. userTest{
  400. init: func(repo *repository.Repository) {
  401. repo.User.CreateUser(&models.User{
  402. Email: "belanger@getporter.dev",
  403. Password: "hello",
  404. })
  405. },
  406. msg: "Update user db connection down",
  407. method: "PUT",
  408. endpoint: "/api/users/1",
  409. body: `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: default\nclusters:\n- cluster:\n server: https://localhost\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\nusers:\n- name: test-admin", "allowedClusters":[]}`,
  410. expStatus: http.StatusInternalServerError,
  411. expBody: `{"code":500,"errors":["could not write to database"]}`,
  412. canQuery: false,
  413. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  414. BasicBodyValidator,
  415. },
  416. },
  417. }
  418. func TestHandleUpdateUser(t *testing.T) {
  419. for _, c := range updateUserTests {
  420. testUserRequest(t, c)
  421. }
  422. }
  423. var deleteUserTests = []userTest{
  424. userTest{
  425. init: func(repo *repository.Repository) {
  426. repo.User.CreateUser(&models.User{
  427. Email: "belanger@getporter.dev",
  428. Password: "hello",
  429. })
  430. },
  431. msg: "Delete user successful",
  432. method: "DELETE",
  433. endpoint: "/api/users/1",
  434. body: `{"password":"hello"}`,
  435. expStatus: http.StatusNoContent,
  436. expBody: "",
  437. canQuery: true,
  438. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  439. func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T) {
  440. req, err := http.NewRequest(
  441. "GET",
  442. "/api/users/1",
  443. strings.NewReader(""),
  444. )
  445. if err != nil {
  446. t.Fatal(err)
  447. }
  448. rr2 := httptest.NewRecorder()
  449. r.ServeHTTP(rr2, req)
  450. gotBody := &models.UserExternal{}
  451. expBody := &models.UserExternal{}
  452. if status := rr2.Code; status != 404 {
  453. t.Errorf("DELETE user validation, handler returned wrong status code: got %v want %v",
  454. status, 404)
  455. }
  456. json.Unmarshal(rr2.Body.Bytes(), gotBody)
  457. json.Unmarshal([]byte(`{"code":602,"errors":["could not find requested object"]}`), expBody)
  458. if !reflect.DeepEqual(gotBody, expBody) {
  459. t.Errorf("%s, handler returned wrong body: got %v want %v",
  460. "validator failed", gotBody, expBody)
  461. }
  462. },
  463. },
  464. },
  465. userTest{
  466. init: func(repo *repository.Repository) {
  467. repo.User.CreateUser(&models.User{
  468. Email: "belanger@getporter.dev",
  469. Password: "hello",
  470. })
  471. },
  472. msg: "Delete user invalid id",
  473. method: "DELETE",
  474. endpoint: "/api/users/aldkjf",
  475. body: `{"password":"hello"}`,
  476. expStatus: http.StatusBadRequest,
  477. expBody: `{"code":600,"errors":["could not process request"]}`,
  478. canQuery: true,
  479. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  480. BasicBodyValidator,
  481. },
  482. },
  483. userTest{
  484. init: func(repo *repository.Repository) {
  485. repo.User.CreateUser(&models.User{
  486. Email: "belanger@getporter.dev",
  487. Password: "hello",
  488. })
  489. },
  490. msg: "Delete user missing password",
  491. method: "DELETE",
  492. endpoint: "/api/users/1",
  493. body: `{}`,
  494. expStatus: http.StatusUnprocessableEntity,
  495. expBody: `{"code":601,"errors":["required validation failed"]}`,
  496. canQuery: true,
  497. validators: []func(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T){
  498. BasicBodyValidator,
  499. },
  500. },
  501. }
  502. func TestHandleDeleteUser(t *testing.T) {
  503. for _, c := range deleteUserTests {
  504. testUserRequest(t, c)
  505. }
  506. }
  507. func BasicBodyValidator(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T) {
  508. if body := rr.Body.String(); body != c.expBody {
  509. t.Errorf("%s, handler returned wrong body: got %v want %v",
  510. c.msg, body, c.expBody)
  511. }
  512. }
  513. func UserModelBodyValidator(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T) {
  514. gotBody := &models.UserExternal{}
  515. expBody := &models.UserExternal{}
  516. json.Unmarshal(rr.Body.Bytes(), gotBody)
  517. json.Unmarshal([]byte(c.expBody), expBody)
  518. if !reflect.DeepEqual(gotBody, expBody) {
  519. t.Errorf("%s, handler returned wrong body: got %v want %v",
  520. c.msg, gotBody, expBody)
  521. }
  522. }
  523. func ClusterBodyValidator(rr *httptest.ResponseRecorder, c userTest, r *chi.Mux, t *testing.T) {
  524. // if status is expected to be 200, parse the body for UserExternal
  525. gotBody := &[]models.ClusterConfigExternal{}
  526. expBody := &[]models.ClusterConfigExternal{}
  527. json.Unmarshal(rr.Body.Bytes(), gotBody)
  528. json.Unmarshal([]byte(c.expBody), expBody)
  529. if !reflect.DeepEqual(gotBody, expBody) {
  530. t.Errorf("%s, handler returned wrong body: got %v want %v",
  531. c.msg, gotBody, expBody)
  532. }
  533. }