project_handler_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. package api_test
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "strings"
  6. "testing"
  7. "github.com/porter-dev/porter/internal/kubernetes/fixtures"
  8. "github.com/porter-dev/porter/internal/models/integrations"
  9. "gorm.io/gorm"
  10. "github.com/go-test/deep"
  11. "github.com/porter-dev/porter/internal/forms"
  12. "github.com/porter-dev/porter/internal/models"
  13. )
  14. // ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
  15. type projTest 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 *projTest, tester *tester, t *testing.T)
  25. }
  26. func testProjRequests(t *testing.T, tests []*projTest, 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 createProjectTests = []*projTest{
  61. &projTest{
  62. initializers: []func(t *tester){
  63. initUserDefault,
  64. },
  65. msg: "Create project",
  66. method: "POST",
  67. endpoint: "/api/projects",
  68. body: `{
  69. "name": "project-test"
  70. }`,
  71. expStatus: http.StatusCreated,
  72. expBody: `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
  73. useCookie: true,
  74. validators: []func(c *projTest, tester *tester, t *testing.T){
  75. projectModelBodyValidator,
  76. },
  77. },
  78. }
  79. func TestHandleCreateProject(t *testing.T) {
  80. testProjRequests(t, createProjectTests, true)
  81. }
  82. var readProjectTests = []*projTest{
  83. &projTest{
  84. initializers: []func(t *tester){
  85. initUserDefault,
  86. initProject,
  87. },
  88. msg: "Read project",
  89. method: "GET",
  90. endpoint: "/api/projects/1",
  91. body: ``,
  92. expStatus: http.StatusOK,
  93. expBody: `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
  94. useCookie: true,
  95. validators: []func(c *projTest, tester *tester, t *testing.T){
  96. projectModelBodyValidator,
  97. },
  98. },
  99. }
  100. func TestHandleReadProject(t *testing.T) {
  101. testProjRequests(t, readProjectTests, true)
  102. }
  103. var readProjectClusterTest = []*projTest{
  104. &projTest{
  105. initializers: []func(t *tester){
  106. initUserDefault,
  107. initProject,
  108. initProjectClusterDefault,
  109. },
  110. msg: "Read project cluster",
  111. method: "GET",
  112. endpoint: "/api/projects/1/clusters/1",
  113. body: ``,
  114. expStatus: http.StatusOK,
  115. expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10"}`,
  116. useCookie: true,
  117. validators: []func(c *projTest, tester *tester, t *testing.T){
  118. projectClusterBodyValidator,
  119. },
  120. },
  121. }
  122. func TestHandleReadProjectSA(t *testing.T) {
  123. testProjRequests(t, readProjectClusterTest, true)
  124. }
  125. var listProjectClustersTest = []*projTest{
  126. &projTest{
  127. initializers: []func(t *tester){
  128. initUserDefault,
  129. initProject,
  130. initProjectClusterDefault,
  131. },
  132. msg: "List project clusters",
  133. method: "GET",
  134. endpoint: "/api/projects/1/clusters",
  135. body: ``,
  136. expStatus: http.StatusOK,
  137. expBody: `[{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10"}]`,
  138. useCookie: true,
  139. validators: []func(c *projTest, tester *tester, t *testing.T){
  140. projectClustersBodyValidator,
  141. },
  142. },
  143. }
  144. func TestHandleListProjectClusters(t *testing.T) {
  145. testProjRequests(t, listProjectClustersTest, true)
  146. }
  147. var createProjectClusterCandidatesTests = []*projTest{
  148. &projTest{
  149. initializers: []func(t *tester){
  150. initUserDefault,
  151. initProject,
  152. },
  153. msg: "Create project cluster candidate w/ no actions -- should create SA by default",
  154. method: "POST",
  155. endpoint: "/api/projects/1/clusters/candidates",
  156. body: `{"kubeconfig":"` + OIDCAuthWithDataForJSON + `"}`,
  157. expStatus: http.StatusCreated,
  158. expBody: `[{"id":1,"resolvers":[],"created_cluster_id":1,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  159. useCookie: true,
  160. validators: []func(c *projTest, tester *tester, t *testing.T){
  161. projectClusterCandidateBodyValidator,
  162. // check that Cluster was created by default
  163. func(c *projTest, tester *tester, t *testing.T) {
  164. clusters, err := tester.repo.Cluster.ListClustersByProjectID(1)
  165. if err != nil {
  166. t.Fatalf("%v\n", err)
  167. }
  168. if len(clusters) != 1 {
  169. t.Fatal("Expected cluster to be created by default, but does not exist\n")
  170. }
  171. gotCluster := clusters[0]
  172. gotCluster.Model = gorm.Model{}
  173. expCluster := &models.Cluster{
  174. AuthMechanism: models.OIDC,
  175. ProjectID: 1,
  176. Name: "cluster-test",
  177. Server: "https://10.10.10.10",
  178. OIDCIntegrationID: 1,
  179. TokenCache: integrations.TokenCache{},
  180. CertificateAuthorityData: []byte("-----BEGIN CER"),
  181. }
  182. if diff := deep.Equal(gotCluster, expCluster); diff != nil {
  183. t.Errorf("handler returned wrong body:\n")
  184. t.Error(diff)
  185. }
  186. },
  187. },
  188. },
  189. &projTest{
  190. initializers: []func(t *tester){
  191. initUserDefault,
  192. initProject,
  193. },
  194. msg: "Create project SA candidate",
  195. method: "POST",
  196. endpoint: "/api/projects/1/clusters/candidates",
  197. body: `{"kubeconfig":"` + OIDCAuthWithoutDataForJSON + `"}`,
  198. expStatus: http.StatusCreated,
  199. expBody: `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  200. useCookie: true,
  201. validators: []func(c *projTest, tester *tester, t *testing.T){
  202. projectClusterCandidateBodyValidator,
  203. },
  204. },
  205. }
  206. func TestHandleCreateProjectClusterCandidate(t *testing.T) {
  207. testProjRequests(t, createProjectClusterCandidatesTests, true)
  208. }
  209. var listProjectClusterCandidatesTests = []*projTest{
  210. &projTest{
  211. initializers: []func(t *tester){
  212. initUserDefault,
  213. initProject,
  214. initProjectClusterCandidate,
  215. },
  216. msg: "List project cluster candidates",
  217. method: "GET",
  218. endpoint: "/api/projects/1/clusters/candidates",
  219. body: ``,
  220. expStatus: http.StatusOK,
  221. expBody: `[{"id":1,"resolvers":[{"name":"upload-oidc-idp-issuer-ca-data","data":{"filename":"/fake/path/to/ca.pem"},"docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"created_cluster_id":0,"project_id":1,"context_name":"context-test","name":"cluster-test","server":"https://10.10.10.10"}]`,
  222. useCookie: true,
  223. validators: []func(c *projTest, tester *tester, t *testing.T){
  224. projectClusterCandidateBodyValidator,
  225. },
  226. },
  227. }
  228. func TestHandleListProjectClusterCandidates(t *testing.T) {
  229. testProjRequests(t, listProjectClusterCandidatesTests, true)
  230. }
  231. var resolveProjectClusterCandidatesTests = []*projTest{
  232. &projTest{
  233. initializers: []func(t *tester){
  234. initUserDefault,
  235. initProject,
  236. initProjectClusterCandidate,
  237. },
  238. msg: "Resolve project cluster candidate",
  239. method: "POST",
  240. endpoint: "/api/projects/1/clusters/candidates/1/resolve",
  241. body: `{"oidc_idp_issuer_ca_data": "LS0tLS1CRUdJTiBDRVJ="}`,
  242. expStatus: http.StatusCreated,
  243. expBody: `{"id":1,"project_id":1,"name":"cluster-test","server":"https://10.10.10.10"}`,
  244. useCookie: true,
  245. validators: []func(c *projTest, tester *tester, t *testing.T){
  246. projectClusterBodyValidator,
  247. },
  248. },
  249. }
  250. func TestHandleResolveProjectClusterCandidate(t *testing.T) {
  251. testProjRequests(t, resolveProjectClusterCandidatesTests, true)
  252. }
  253. var deleteProjectTests = []*projTest{
  254. &projTest{
  255. initializers: []func(t *tester){
  256. initUserDefault,
  257. initProject,
  258. },
  259. msg: "Delete project",
  260. method: "DELETE",
  261. endpoint: "/api/projects/1",
  262. body: ``,
  263. expStatus: http.StatusOK,
  264. expBody: `{"id":1,"name":"project-test","roles":[{"id":0,"kind":"admin","user_id":1,"project_id":1}]}`,
  265. useCookie: true,
  266. validators: []func(c *projTest, tester *tester, t *testing.T){
  267. projectModelBodyValidator,
  268. },
  269. },
  270. }
  271. func TestHandleDeleteProject(t *testing.T) {
  272. testProjRequests(t, deleteProjectTests, true)
  273. }
  274. // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
  275. func initProject(tester *tester) {
  276. user, _ := tester.repo.User.ReadUserByEmail("belanger@getporter.dev")
  277. // handle write to the database
  278. projModel, _ := tester.repo.Project.CreateProject(&models.Project{
  279. Name: "project-test",
  280. })
  281. // create a new Role with the user as the owner
  282. tester.repo.Project.CreateProjectRole(projModel, &models.Role{
  283. UserID: user.ID,
  284. ProjectID: projModel.ID,
  285. Kind: models.RoleAdmin,
  286. })
  287. }
  288. func initProjectClusterCandidate(tester *tester) {
  289. proj, _ := tester.repo.Project.ReadProject(1)
  290. form := &forms.CreateClusterCandidatesForm{
  291. ProjectID: proj.ID,
  292. Kubeconfig: fixtures.OIDCAuthWithoutData,
  293. }
  294. // convert the form to a ServiceAccountCandidate
  295. ccs, _ := form.ToClusterCandidates(false)
  296. for _, cc := range ccs {
  297. tester.repo.Cluster.CreateClusterCandidate(cc)
  298. }
  299. }
  300. func initProjectClusterDefault(tester *tester) {
  301. proj, _ := tester.repo.Project.ReadProject(1)
  302. form := &forms.CreateClusterCandidatesForm{
  303. ProjectID: proj.ID,
  304. Kubeconfig: fixtures.OIDCAuthWithData,
  305. }
  306. // convert the form to a ServiceAccountCandidate
  307. ccs, _ := form.ToClusterCandidates(false)
  308. for _, cc := range ccs {
  309. tester.repo.Cluster.CreateClusterCandidate(cc)
  310. }
  311. clusterForm := forms.ResolveClusterForm{
  312. Resolver: &models.ClusterResolverAll{},
  313. ClusterCandidateID: 1,
  314. ProjectID: 1,
  315. UserID: 1,
  316. }
  317. clusterForm.ResolveIntegration(*tester.repo)
  318. clusterForm.ResolveCluster(*tester.repo)
  319. }
  320. func projectBasicBodyValidator(c *projTest, tester *tester, t *testing.T) {
  321. if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
  322. t.Errorf("%s, handler returned wrong body: got %v want %v",
  323. c.msg, body, c.expBody)
  324. }
  325. }
  326. func projectModelBodyValidator(c *projTest, tester *tester, t *testing.T) {
  327. gotBody := &models.ProjectExternal{}
  328. expBody := &models.ProjectExternal{}
  329. json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
  330. json.Unmarshal([]byte(c.expBody), expBody)
  331. if diff := deep.Equal(gotBody, expBody); diff != nil {
  332. t.Errorf("handler returned wrong body:\n")
  333. t.Error(diff)
  334. }
  335. }
  336. func projectClusterCandidateBodyValidator(c *projTest, tester *tester, t *testing.T) {
  337. gotBody := make([]*models.ClusterCandidateExternal, 0)
  338. expBody := make([]*models.ClusterCandidateExternal, 0)
  339. json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
  340. json.Unmarshal([]byte(c.expBody), &expBody)
  341. if diff := deep.Equal(gotBody, expBody); diff != nil {
  342. t.Errorf("handler returned wrong body:\n")
  343. t.Error(diff)
  344. }
  345. }
  346. func projectClusterBodyValidator(c *projTest, tester *tester, t *testing.T) {
  347. gotBody := &models.ClusterExternal{}
  348. expBody := &models.ClusterExternal{}
  349. json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
  350. json.Unmarshal([]byte(c.expBody), expBody)
  351. if diff := deep.Equal(gotBody, expBody); diff != nil {
  352. t.Errorf("handler returned wrong body:\n")
  353. t.Error(diff)
  354. }
  355. }
  356. func projectClustersBodyValidator(c *projTest, tester *tester, t *testing.T) {
  357. gotBody := make([]*models.ClusterExternal, 0)
  358. expBody := make([]*models.ClusterExternal, 0)
  359. json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
  360. json.Unmarshal([]byte(c.expBody), &expBody)
  361. if diff := deep.Equal(gotBody, expBody); diff != nil {
  362. t.Errorf("handler returned wrong body:\n")
  363. t.Error(diff)
  364. }
  365. }
  366. const OIDCAuthWithDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n server: https://10.10.10.10\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n user:\n auth-provider:\n config:\n client-id: porter-api\n id-token: token\n idp-issuer-url: https://10.10.10.10\n idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: oidc`
  367. const OIDCAuthWithoutDataForJSON string = `apiVersion: v1\nclusters:\n- cluster:\n server: https://10.10.10.10\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=\n name: cluster-test\ncontexts:\n- context:\n cluster: cluster-test\n user: test-admin\n name: context-test\ncurrent-context: context-test\nkind: Config\npreferences: {}\nusers:\n- name: test-admin\n user:\n auth-provider:\n config:\n client-id: porter-api\n id-token: token\n idp-issuer-url: https://10.10.10.10\n idp-certificate-authority: /fake/path/to/ca.pem\n name: oidc`
  368. const OIDCAuthWithoutData string = `
  369. apiVersion: v1
  370. clusters:
  371. - cluster:
  372. server: https://10.10.10.10
  373. certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  374. name: cluster-test
  375. contexts:
  376. - context:
  377. cluster: cluster-test
  378. user: test-admin
  379. name: context-test
  380. current-context: context-test
  381. kind: Config
  382. preferences: {}
  383. users:
  384. - name: test-admin
  385. user:
  386. auth-provider:
  387. config:
  388. client-id: porter-api
  389. id-token: token
  390. idp-issuer-url: https://10.10.10.10
  391. idp-certificate-authority: /fake/path/to/ca.pem
  392. name: oidc
  393. `
  394. const OIDCAuthWithData string = `
  395. apiVersion: v1
  396. clusters:
  397. - cluster:
  398. server: https://10.10.10.10
  399. certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  400. name: cluster-test
  401. contexts:
  402. - context:
  403. cluster: cluster-test
  404. user: test-admin
  405. name: context-test
  406. current-context: context-test
  407. kind: Config
  408. preferences: {}
  409. users:
  410. - name: test-admin
  411. user:
  412. auth-provider:
  413. config:
  414. client-id: porter-api
  415. id-token: token
  416. idp-issuer-url: https://10.10.10.10
  417. idp-certificate-authority-data: LS0tLS1CRUdJTiBDRVJ=
  418. name: oidc
  419. `