@@ -17,6 +17,7 @@ limitations under the License.
1717package api
1818
1919import (
20+ "bytes"
2021 "encoding/json"
2122 "fmt"
2223 "io"
@@ -195,8 +196,6 @@ var _ = Describe("WorkspaceKinds Handler", func() {
195196 })
196197 })
197198
198- // NOTE: these tests assume a specific state of the cluster, so cannot be run in parallel with other tests.
199- // therefore, we run them using the `Serial` Ginkgo decorators.
200199 Context ("with no existing WorkspaceKinds" , Serial , func () {
201200
202201 It ("should return an empty list of WorkspaceKinds" , func () {
@@ -254,4 +253,192 @@ var _ = Describe("WorkspaceKinds Handler", func() {
254253 Expect (rs .StatusCode ).To (Equal (http .StatusNotFound ), descUnexpectedHTTPStatus , rr .Body .String ())
255254 })
256255 })
256+
257+ // NOTE: these tests create and delete resources on the cluster, so cannot be run in parallel.
258+ // therefore, we run them using the `Serial` Ginkgo decorator.
259+ Context ("when creating a WorkspaceKind" , Serial , func () {
260+
261+ var newWorkspaceKindName = "wsk-create-test"
262+ var validYAML []byte
263+
264+ BeforeEach (func () {
265+ validYAML = []byte (fmt .Sprintf (`
266+ apiVersion: workspaces.kubeflow.org/v1beta1
267+ kind: WorkspaceKind
268+ metadata:
269+ name: %s
270+ spec:
271+ spawner:
272+ displayName: "Test Jupyter Environment"
273+ description: "A valid description for testing."
274+ icon:
275+ url: "https://example.com/icon.png"
276+ logo:
277+ url: "https://example.com/logo.svg"
278+ podTemplate:
279+ options:
280+ imageConfig:
281+ spawner:
282+ default: "default-image"
283+ values:
284+ - id: "default-image"
285+ name: "Jupyter Scipy"
286+ path: "kubeflownotebooks/jupyter-scipy:v1.9.0"
287+ spawner:
288+ displayName: "Jupyter with SciPy v1.9.0"
289+ spec:
290+ image: "kubeflownotebooks/jupyter-scipy:v1.9.0"
291+ ports:
292+ - id: "notebook-port"
293+ displayName: "Notebook Port"
294+ port: 8888
295+ protocol: "HTTP"
296+ podConfig:
297+ spawner:
298+ default: "default-pod-config"
299+ values:
300+ - id: "default-pod-config"
301+ name: "Default Resources"
302+ spawner:
303+ displayName: "Small CPU/RAM"
304+ resources:
305+ requests:
306+ cpu: "500m"
307+ memory: "1Gi"
308+ limits:
309+ cpu: "1"
310+ memory: "2Gi"
311+ volumeMounts:
312+ home: "/home/jovyan"
313+ ` , newWorkspaceKindName ))
314+ })
315+
316+ AfterEach (func () {
317+ By ("cleaning up the created WorkspaceKind" )
318+ wsk := & kubefloworgv1beta1.WorkspaceKind {
319+ ObjectMeta : metav1.ObjectMeta {
320+ Name : newWorkspaceKindName ,
321+ },
322+ }
323+ _ = k8sClient .Delete (ctx , wsk )
324+ })
325+
326+ It ("should succeed when creating a new WorkspaceKind with valid YAML" , func () {
327+ req , err := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (validYAML ))
328+ Expect (err ).NotTo (HaveOccurred ())
329+ req .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
330+ req .Header .Set (userIdHeader , adminUser )
331+
332+ rr := httptest .NewRecorder ()
333+ a .CreateWorkspaceKindHandler (rr , req , httprouter.Params {})
334+ rs := rr .Result ()
335+ defer rs .Body .Close ()
336+
337+ Expect (rs .StatusCode ).To (Equal (http .StatusCreated ), "Body: %s" , rr .Body .String ())
338+
339+ By ("verifying the resource was created in the cluster" )
340+ createdWsk := & kubefloworgv1beta1.WorkspaceKind {}
341+ err = k8sClient .Get (ctx , types.NamespacedName {Name : newWorkspaceKindName }, createdWsk )
342+ Expect (err ).NotTo (HaveOccurred ())
343+ })
344+
345+ It ("should fail with 400 Bad Request if the YAML is missing a required name" , func () {
346+ missingNameYAML := []byte (`
347+ apiVersion: workspaces.kubeflow.org/v1beta1
348+ kind: WorkspaceKind
349+ metadata: {}
350+ spec:
351+ spawner:
352+ displayName: "This will fail"` )
353+ req , _ := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (missingNameYAML ))
354+ req .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
355+ req .Header .Set (userIdHeader , adminUser )
356+
357+ rr := httptest .NewRecorder ()
358+ a .CreateWorkspaceKindHandler (rr , req , httprouter.Params {})
359+
360+ Expect (rr .Code ).To (Equal (http .StatusBadRequest ))
361+ Expect (rr .Body .String ()).To (ContainSubstring ("'.metadata.name' is a required field" ))
362+ })
363+
364+ It ("should return a 409 Conflict when creating a WorkspaceKind that already exists" , func () {
365+ By ("creating the resource once successfully" )
366+ req1 , _ := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (validYAML ))
367+ req1 .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
368+ req1 .Header .Set (userIdHeader , adminUser )
369+ rr1 := httptest .NewRecorder ()
370+ a .CreateWorkspaceKindHandler (rr1 , req1 , httprouter.Params {})
371+ Expect (rr1 .Code ).To (Equal (http .StatusCreated ))
372+
373+ By ("attempting to create the exact same resource a second time" )
374+ req2 , _ := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (validYAML ))
375+ req2 .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
376+ req2 .Header .Set (userIdHeader , adminUser )
377+ rr2 := httptest .NewRecorder ()
378+ a .CreateWorkspaceKindHandler (rr2 , req2 , httprouter.Params {})
379+
380+ Expect (rr2 .Code ).To (Equal (http .StatusConflict ))
381+ })
382+
383+ It ("should fail with 400 Bad Request when the YAML has the wrong kind" , func () {
384+ wrongKindYAML := []byte (`apiVersion: v1
385+ kind: Pod
386+ metadata:
387+ name: i-am-the-wrong-kind` )
388+ req , _ := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (wrongKindYAML ))
389+ req .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
390+ req .Header .Set (userIdHeader , adminUser )
391+
392+ rr := httptest .NewRecorder ()
393+ a .CreateWorkspaceKindHandler (rr , req , httprouter.Params {})
394+
395+ Expect (rr .Code ).To (Equal (http .StatusBadRequest ))
396+ Expect (rr .Body .String ()).To (ContainSubstring ("invalid kind in YAML: expected 'WorkspaceKind', got 'Pod'" ))
397+ })
398+
399+ It ("should fail when the body is not valid YAML" , func () {
400+ notYAML := []byte (`this is not yaml {` )
401+ req , _ := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (notYAML ))
402+ req .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
403+ req .Header .Set (userIdHeader , adminUser )
404+
405+ rr := httptest .NewRecorder ()
406+ a .CreateWorkspaceKindHandler (rr , req , httprouter.Params {})
407+
408+ By ("verifying the handler returns a 400 Bad Request with a valid error envelope" )
409+ Expect (rr .Code ).To (Equal (http .StatusBadRequest ))
410+ var response ErrorEnvelope
411+ err := json .Unmarshal (rr .Body .Bytes (), & response )
412+ Expect (err ).NotTo (HaveOccurred (), "The error response should be valid JSON" )
413+ Expect (response .Error .Message ).To (Equal ("request body is not a valid YAML manifest" ))
414+ })
415+ It ("should fail with 400 Bad Request for an empty YAML object" , func () {
416+ By ("defining an empty YAML object as the payload" )
417+ invalidYAML := []byte ("{}" )
418+
419+ By ("creating the HTTP request" )
420+ req , err := http .NewRequest (http .MethodPost , AllWorkspaceKindsPath , bytes .NewReader (invalidYAML ))
421+ Expect (err ).NotTo (HaveOccurred ())
422+ req .Header .Set ("Content-Type" , ContentTypeYAMLManifest )
423+ req .Header .Set (userIdHeader , adminUser )
424+
425+ By ("executing the CreateWorkspaceKindHandler" )
426+ rr := httptest .NewRecorder ()
427+ a .CreateWorkspaceKindHandler (rr , req , httprouter.Params {})
428+ rs := rr .Result ()
429+ defer rs .Body .Close ()
430+
431+ By ("verifying the handler returns a 400 Bad Request" )
432+ // First, check the status code
433+ Expect (rs .StatusCode ).To (Equal (http .StatusBadRequest ))
434+
435+ By ("verifying the error message in the response body" )
436+ // Second, read the body from the response stream
437+ body , err := io .ReadAll (rs .Body )
438+ Expect (err ).NotTo (HaveOccurred ()) // Ensure reading the body didn't cause an error
439+
440+ // Finally, assert on the content of the body
441+ Expect (string (body )).To (ContainSubstring ("invalid kind in YAML: expected 'WorkspaceKind', got ''" ))
442+ })
443+ })
257444})
0 commit comments