Keine Beschreibung

main_test.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. package main
  2. import (
  3. "github.com/shuffle/shuffle-shared"
  4. "bytes"
  5. "context"
  6. "log"
  7. "net/http"
  8. "net/http/httptest"
  9. "reflect"
  10. "runtime"
  11. "testing"
  12. "time"
  13. "cloud.google.com/go/datastore"
  14. "cloud.google.com/go/storage"
  15. "google.golang.org/api/option"
  16. "google.golang.org/grpc"
  17. )
  18. type endpoint struct {
  19. handler http.HandlerFunc
  20. path string
  21. method string
  22. body []byte
  23. }
  24. func init() {
  25. ctx := context.Background()
  26. dbclient, err := datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy()))
  27. if err != nil {
  28. log.Fatalf("[DEBUG] Database client error during init: %s", err)
  29. }
  30. _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", true, "elasticsearch", false, 0)
  31. log.Printf("INIT")
  32. }
  33. // TestTestAuthenticationRequired tests that the handlers in the `handlers`
  34. // variable returns 401 Unauthorized when called without credentials.
  35. func TestAuthenticationRequired(t *testing.T) {
  36. handlers := []endpoint{
  37. {handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
  38. {handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
  39. {handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
  40. {handler: shuffle.HandleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
  41. // handleRegister generates nil pointer exception. Not necessary for this anyway.
  42. //{handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
  43. {handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
  44. {handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"},
  45. {handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"},
  46. {handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"},
  47. {handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"},
  48. {handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"},
  49. {handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"},
  50. {handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"},
  51. {handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"},
  52. // handleWorkflowQueue generates nil pointer exception
  53. //{handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"},
  54. // handleGetStreamResults generates nil pointer exception
  55. //{handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"},
  56. {handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"},
  57. {handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"},
  58. {handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"},
  59. {handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"},
  60. {handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"},
  61. {handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"},
  62. {handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"},
  63. {handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"},
  64. //{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"},
  65. {handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"},
  66. {handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"},
  67. {handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"},
  68. {handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"},
  69. {handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"},
  70. {handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"},
  71. {handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"},
  72. {handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"},
  73. {handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"},
  74. {handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"},
  75. {handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"},
  76. {handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"},
  77. {handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"},
  78. {handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"},
  79. {handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"},
  80. // createOutlookSub generates nil pointer exception
  81. {handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"},
  82. // handleDeleteOutlookSub generates nil pointer exception
  83. {handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"},
  84. {handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"},
  85. {handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"},
  86. {handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"},
  87. {handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"},
  88. {handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"},
  89. {handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"},
  90. {handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"},
  91. {handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"},
  92. {handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"},
  93. //{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"},
  94. {handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"},
  95. {handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"},
  96. {handler: shuffle.EchoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
  97. {handler: shuffle.EchoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
  98. {handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"},
  99. {handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"},
  100. //{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"},
  101. {handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"},
  102. {handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"},
  103. {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST", body: []byte("hi")},
  104. }
  105. var err error
  106. ctx := context.Background()
  107. // Most handlers requires database access in order to not crash or cause
  108. // nil pointer issues.
  109. // To start a local database instance, run:
  110. // docker-compose up database
  111. // To let the tests know about the database, run:
  112. // DATASTORE_EMULATOR_HOST=0.0.0.0:8000 go test
  113. dbclient, err = datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy()))
  114. if err != nil {
  115. t.Fatal(err)
  116. }
  117. dummyBody := bytes.NewBufferString("dummy")
  118. for _, e := range handlers {
  119. log.Printf("Endpoint: %#v", e.path)
  120. req, err := http.NewRequest(e.method, e.path, dummyBody)
  121. if err != nil {
  122. t.Fatal(err)
  123. }
  124. rr := httptest.NewRecorder()
  125. handler := http.HandlerFunc(e.handler)
  126. timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`)
  127. timeoutHandler.ServeHTTP(rr, req)
  128. funcName := getFunctionNameFromFunction(e.handler)
  129. if status := rr.Code; status != http.StatusUnauthorized {
  130. t.Errorf("%s handler returned wrong status code: got %v want %v",
  131. funcName, status, http.StatusUnauthorized)
  132. }
  133. }
  134. }
  135. func TestAuthenticationNotRequired(t *testing.T) {
  136. // All of these return 200 OK when user not logged in
  137. handlers := []endpoint{
  138. {handler: checkAdminLogin, path: "/api/v1/users/checkusers", method: "GET"},
  139. {handler: shuffle.HandleLogout, path: "/api/v1/users/logout", method: "POST"},
  140. {handler: shuffle.GetDocList, path: "/api/v1/docs", method: "GET"},
  141. {handler: shuffle.GetDocs, path: "/api/v1/docs/123", method: "GET"},
  142. {handler: healthCheckHandler, path: "/api/v1/_ah/health"},
  143. }
  144. for _, e := range handlers {
  145. log.Printf("Endpoint: %#v", e.path)
  146. req, err := http.NewRequest(e.method, e.path, nil)
  147. if err != nil {
  148. t.Fatal(err)
  149. }
  150. rr := httptest.NewRecorder()
  151. handler := http.HandlerFunc(e.handler)
  152. timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`)
  153. timeoutHandler.ServeHTTP(rr, req)
  154. funcName := getFunctionNameFromFunction(e.handler)
  155. if status := rr.Code; status != http.StatusOK {
  156. t.Errorf("%s handler returned wrong status code: got %v want %v",
  157. funcName, status, http.StatusOK)
  158. }
  159. }
  160. }
  161. // TestCors tests that all endpoints returns the same CORS headers when hit
  162. // with an OPTIONS type request.
  163. // It feels very fragile to test headers like this, especially for the
  164. // "Access-Control-Allow-Origin", but this test should be helpful while
  165. // refactoring the CORS logic into a middleware, and that's the reason this
  166. // test exists right now. It might change after the refactor because our
  167. // requirements might change after the refactor.
  168. func TestCors(t *testing.T) {
  169. handlers := []endpoint{
  170. {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
  171. {handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
  172. {handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
  173. {handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
  174. // handleRegister generates nil pointer exception
  175. {handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
  176. {handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
  177. {handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"},
  178. {handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"},
  179. {handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"},
  180. {handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"},
  181. // handlePasswordChange generates nil pointer exception
  182. {handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"},
  183. {handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"},
  184. {handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"},
  185. {handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"},
  186. // handleWorkflowQueue generates nil pointer exception
  187. {handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"},
  188. // handleGetStreamResults generates nil pointer exception
  189. {handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"},
  190. {handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"},
  191. {handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"},
  192. {handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"},
  193. {handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"},
  194. {handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"},
  195. {handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"},
  196. {handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"},
  197. {handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"},
  198. //{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"},
  199. {handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"},
  200. {handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"},
  201. {handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"},
  202. {handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"},
  203. {handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"},
  204. {handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"},
  205. {handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"},
  206. {handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"},
  207. {handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"},
  208. {handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"},
  209. {handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"},
  210. {handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"},
  211. {handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"},
  212. {handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"},
  213. {handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"},
  214. // createOutlookSub generates nil pointer exception
  215. {handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"},
  216. // handleDeleteOutlookSub generates nil pointer exception
  217. {handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"},
  218. {handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"},
  219. {handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"},
  220. {handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"},
  221. {handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"},
  222. {handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"},
  223. {handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"},
  224. {handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"},
  225. {handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"},
  226. {handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"},
  227. //{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"},
  228. {handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"},
  229. {handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"},
  230. {handler: echoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
  231. {handler: echoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
  232. {handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"},
  233. {handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"},
  234. //{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"},
  235. {handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"},
  236. {handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"},
  237. {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST"},
  238. }
  239. //r := initHandlers(context.TODO())
  240. initHandlers()
  241. outerLoop:
  242. for _, e := range handlers {
  243. log.Printf("Endpoint: %#v", e.path)
  244. req, err := http.NewRequest("OPTIONS", e.path, nil)
  245. req.Header.Add("Origin", "http://localhost:3000")
  246. req.Header.Add("Access-Control-Request-Method", "POST")
  247. req.Header.Add("Access-Control-Request-Headers", "Content-Type, Accept, X-Requested-With, remember-me")
  248. // OPTIONS /resource/foo
  249. // Access-Control-Request-Method: DELETE
  250. // Access-Control-Request-Headers: origin, x-requested-with
  251. // Origin: https://foo.bar.org
  252. if err != nil {
  253. t.Errorf("Failure in OPTIONS setup: %s", err)
  254. continue
  255. }
  256. rr := httptest.NewRecorder()
  257. //timeoutHandler := http.TimeoutHandler(r, 2*time.Second, `Request Timeout`)
  258. //timeoutHandler.ServeHTTP(rr, req)
  259. funcName := getFunctionNameFromFunction(e.handler)
  260. if status := rr.Code; status != http.StatusOK {
  261. t.Errorf("%s handler returned wrong status code: got %v want %v",
  262. funcName, status, http.StatusOK)
  263. continue
  264. }
  265. want := map[string]string{
  266. "Vary": "Origin",
  267. "Access-Control-Allow-Headers": "Content-Type, Accept, X-Requested-With, Remember-Me",
  268. "Access-Control-Allow-Methods": "POST",
  269. "Access-Control-Allow-Credentials": "true",
  270. "Access-Control-Allow-Origin": "http://localhost:3000",
  271. }
  272. // Remember to use canonical header name if accessing the headers array
  273. // directly:
  274. // v := r.Header[textproto.CanonicalMIMEHeaderKey("foo")]
  275. // When using Header().Get(h), h will automatically be converted to canonical format.
  276. for key, value := range want {
  277. got := rr.Header().Get(key)
  278. if got != value {
  279. t.Errorf("%s handler returned wrong value for '%s' header: got '%v' want '%v'",
  280. funcName, key, got, value)
  281. continue outerLoop
  282. }
  283. }
  284. }
  285. }
  286. func getFunctionNameFromFunction(f interface{}) string {
  287. return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
  288. }