From 45707b4f27a18b5925badc3b1e26573779611a6e Mon Sep 17 00:00:00 2001 From: Ashique Ansari Date: Wed, 24 Apr 2024 16:38:26 +0530 Subject: [PATCH] Create cypress test for Notebook server tab --- .../src/__mocks__/mockNotebookK8sResource.ts | 6 +- .../e2e/applications/NotebookServer.cy.ts | 96 +++++++++++++++++++ .../cypress/cypress/pages/notebookServer.ts | 41 ++++++++ .../cypress/cypress/support/commands/odh.ts | 38 ++++++++ .../screens/server/NotebookServer.tsx | 2 +- .../screens/server/StartServerModal.tsx | 1 + .../screens/server/StopServerModal.tsx | 1 + 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 frontend/src/__tests__/cypress/cypress/e2e/applications/NotebookServer.cy.ts create mode 100644 frontend/src/__tests__/cypress/cypress/pages/notebookServer.ts diff --git a/frontend/src/__mocks__/mockNotebookK8sResource.ts b/frontend/src/__mocks__/mockNotebookK8sResource.ts index 35a2c3df32..405512e062 100644 --- a/frontend/src/__mocks__/mockNotebookK8sResource.ts +++ b/frontend/src/__mocks__/mockNotebookK8sResource.ts @@ -13,6 +13,7 @@ type MockResourceConfigType = { description?: string; envFromName?: string; resources?: ContainerResources; + image?: string; opts?: RecursivePartial; uid?: string; }; @@ -25,6 +26,7 @@ export const mockNotebookK8sResource = ({ user = 'test-user', description = '', resources = DEFAULT_NOTEBOOK_SIZES[0].resources, + image = 'test-imagestream:1.2', opts = {}, uid = genUID('notebook'), }: MockResourceConfigType): NotebookKind => @@ -90,7 +92,7 @@ export const mockNotebookK8sResource = ({ { name: 'JUPYTER_IMAGE', value: - 'image-registry.openshift-image-registry.svc:5000/redhat-ods-applications/s2i-minimal-notebook:py3.8-v1', + 'image-registry.openshift-image-registry.svc:5000/opendatahub/code-server-notebook:2023.2', }, ], envFrom: [ @@ -100,7 +102,7 @@ export const mockNotebookK8sResource = ({ }, }, ], - image: 'test-imagestream:1.2', + image, imagePullPolicy: 'Always', livenessProbe: { failureThreshold: 3, diff --git a/frontend/src/__tests__/cypress/cypress/e2e/applications/NotebookServer.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/applications/NotebookServer.cy.ts new file mode 100644 index 0000000000..8a66b47dbb --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/e2e/applications/NotebookServer.cy.ts @@ -0,0 +1,96 @@ +import { mockRoleBindingK8sResource } from '~/__mocks__/mockRoleBindingK8sResource'; +import { mockK8sResourceList, mockNotebookK8sResource } from '~/__mocks__'; +import { RoleBindingSubject } from '~/types'; +import { mockAllowedUsers } from '~/__mocks__/mockAllowedUsers'; +import { mockNotebookImageInfo } from '~/__mocks__/mockNotebookImageInfo'; +import { mockStartNotebookData } from '~/__mocks__/mockStartNotebookData'; +import { notebookServer } from '~/__tests__/cypress/cypress/pages/notebookServer'; +import { asProductAdminUser, asProjectEditUser } from '~/__tests__/cypress/cypress/utils/users'; +import { notebookController } from '~/__tests__/cypress/cypress/pages/administration'; + +const groupSubjects: RoleBindingSubject[] = [ + { + kind: 'Group', + apiGroup: 'rbac.authorization.k8s.io', + name: 'group-1', + }, +]; +const initIntercepts = () => { + cy.interceptOdh('POST /api/notebooks', mockStartNotebookData({})).as('startNotebookServer'); + cy.interceptOdh( + 'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers', + mockK8sResourceList([ + mockRoleBindingK8sResource({ + name: 'group-1', + subjects: groupSubjects, + roleRefName: 'edit', + }), + ]), + ); + cy.interceptOdh('GET /api/images/:type', { path: { type: 'jupyter' } }, mockNotebookImageInfo()); + cy.interceptOdh('GET /api/status/openshift-ai-notebooks/allowedUsers', mockAllowedUsers({})); +}; + +it('Administartion tab should not be accessible for non-project admins', () => { + initIntercepts(); + asProjectEditUser(); + notebookServer.visit(); + notebookController.findAdministrationTab().should('not.exist'); + notebookController.findSpawnerTab().should('not.exist'); + notebookController.findAppTitle().should('contain', 'Start a notebook server'); +}); + +describe('NotebookServer', () => { + beforeEach(() => { + initIntercepts(); + asProductAdminUser(); + }); + + it('should start notebook server', () => { + notebookServer.visit(); + notebookServer.findStartServerButton().should('be.visible'); + notebookServer.findStartServerButton().click(); + notebookServer.findEventlog().click(); + + cy.wait('@startNotebookServer').then((interception) => { + expect(interception.request.body).to.eql({ + notebookSizeName: 'XSmall', + imageName: 'code-server-notebook', + imageTagName: '2023.2', + acceleratorProfile: { acceleratorProfiles: [], count: 0, useExisting: false }, + envVars: { configMap: {}, secrets: {} }, + state: 'started', + }); + }); + }); + + it('should stop notebook server', () => { + cy.interceptOdh( + 'GET /api/notebooks/openshift-ai-notebooks/:username/status', + { path: { username: 'jupyter-nb-test-2duser' } }, + { + notebook: mockNotebookK8sResource({ image: 'code-server-notebook:2023.2' }), + isRunning: true, + }, + ); + cy.interceptOdh('PATCH /api/notebooks', mockStartNotebookData({})).as('stopNotebookServer'); + notebookServer.visit(); + notebookServer.findStopServerButton().should('be.visible'); + notebookServer.findStopServerButton().click(); + + cy.interceptOdh( + 'GET /api/notebooks/openshift-ai-notebooks/:username/status', + { path: { username: 'jupyter-nb-test-2duser' } }, + { + notebook: mockNotebookK8sResource({ image: 'code-server-notebook:2023.2' }), + isRunning: false, + }, + ); + notebookServer.findStopNotebookServerButton().should('be.visible'); + notebookServer.findStopNotebookServerButton().click(); + + cy.wait('@stopNotebookServer').then((interception) => { + expect(interception.request.body).to.eql({ state: 'stopped', username: 'test-user' }); + }); + }); +}); diff --git a/frontend/src/__tests__/cypress/cypress/pages/notebookServer.ts b/frontend/src/__tests__/cypress/cypress/pages/notebookServer.ts new file mode 100644 index 0000000000..dc51256c99 --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/pages/notebookServer.ts @@ -0,0 +1,41 @@ +class NotebookServer { + visit() { + cy.visit('/notebookController/spawner'); + this.wait(); + } + + private wait() { + this.findAppTitle(); + cy.testA11y(); + } + + findAppTitle() { + return cy.findByTestId('app-page-title'); + } + + findAdministrationTab() { + return cy.findByTestId('admin-tab'); + } + + findSpawnerTab() { + return cy.findByTestId('spawner-tab'); + } + + findStartServerButton() { + return cy.findByTestId('start-server-button'); + } + + findEventlog() { + return cy.findByTestId('expand-logs').findByRole('button'); + } + + findStopServerButton() { + return cy.findByTestId('stop-nb-button'); + } + + findStopNotebookServerButton() { + return cy.findByTestId('stop-nb-server-button'); + } +} + +export const notebookServer = new NotebookServer(); diff --git a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts index 30d06ce43d..526efde305 100644 --- a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts +++ b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts @@ -10,7 +10,10 @@ import type { RoleBindingKind, ServingRuntimeKind, TemplateKind, + NotebookKind, } from '~/k8sTypes'; + +import { StartNotebookData } from '~/pages/projects/types'; import { AllowedUser } from '~/pages/notebookController/screens/admin/types'; import { GroupsConfig } from '~/pages/groupSettings/groupTypes'; import type { StatusResponse } from '~/redux/types'; @@ -105,6 +108,11 @@ declare global { response: OdhResponse, ): Cypress.Chainable; + interceptOdh( + type: 'GET /api/status/openshift-ai-notebooks/allowedUsers', + response: OdhResponse, + ): Cypress.Chainable; + interceptOdh( type: 'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers', response: OdhResponse>, @@ -209,6 +217,23 @@ declare global { response: OdhResponse, ): Cypress.Chainable; + interceptOdh( + type: 'POST /api/notebooks', + response: OdhResponse, + ): Cypress.Chainable; + + interceptOdh( + type: 'PATCH /api/notebooks', + response: OdhResponse, + ): Cypress.Chainable; + + interceptOdh( + type: 'GET /api/notebooks/openshift-ai-notebooks/:username/status', + options: { + path: { username: string }; + }, + response: OdhResponse, + ): Cypress.Chainable; interceptOdh( type: 'POST /api/prometheus/pvc', response: OdhResponse<{ code: number; response: PrometheusQueryResponse }>, @@ -294,6 +319,19 @@ declare global { type: 'GET /api/service/modelregistry/modelregistry-sample/api/model_registry/v1alpha3/registered_models/1/versions', response: OdhResponse, ): Cypress.Chainable; + + interceptOdh( + type: 'GET /api/rolebindings/opendatahub/openshift-ai-notebooks-image-pullers', + response: OdhResponse>, + ): Cypress.Chainable; + + interceptOdh( + type: 'GET /api/notebooks/openshift-ai-notebooks/:username/status', + options: { + path: { username: string }; + }, + response: OdhResponse<{ notebook: NotebookKind; isRunning: boolean }>, + ): Cypress.Chainable; } } } diff --git a/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx b/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx index d45e552d37..1be1ee9111 100644 --- a/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx +++ b/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx @@ -72,7 +72,7 @@ const NotebookServer: React.FC = () => { setNotebooksToStop([notebook])}> - diff --git a/frontend/src/pages/notebookController/screens/server/StartServerModal.tsx b/frontend/src/pages/notebookController/screens/server/StartServerModal.tsx index b0e317d038..08c85be5e3 100644 --- a/frontend/src/pages/notebookController/screens/server/StartServerModal.tsx +++ b/frontend/src/pages/notebookController/screens/server/StartServerModal.tsx @@ -211,6 +211,7 @@ const StartServerModal: React.FC = ({ open, spawnInProgre const renderLogs = () => ( setLogsExpanded(isExpanded)} isExpanded={logsExpanded} diff --git a/frontend/src/pages/notebookController/screens/server/StopServerModal.tsx b/frontend/src/pages/notebookController/screens/server/StopServerModal.tsx index 3114096c01..b8f38d16c9 100644 --- a/frontend/src/pages/notebookController/screens/server/StopServerModal.tsx +++ b/frontend/src/pages/notebookController/screens/server/StopServerModal.tsx @@ -60,6 +60,7 @@ const StopServerModal: React.FC = ({ notebooksToStop, onNo const modalActions = [