Skip to content

Commit

Permalink
added cluster storage cypress test
Browse files Browse the repository at this point in the history
  • Loading branch information
pnaik1 committed Apr 2, 2024
1 parent f07dd13 commit ed6d6e0
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 5 deletions.
8 changes: 6 additions & 2 deletions frontend/src/__mocks__/mockPVCK8sResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,30 @@ type MockResourceConfigType = {
name?: string;
namespace?: string;
storage?: string;
displayName?: string;
uid?: string;
};

export const mockPVCK8sResource = ({
name = 'test-storage',
namespace = 'test-project',
storage = '5Gi',
displayName = 'Test Storage',
uid = genUID('pvc'),
}: MockResourceConfigType): PersistentVolumeClaimKind => ({
kind: 'PersistentVolumeClaim',
apiVersion: 'v1',
metadata: {
annotations: {
'openshift.io/description': '',
'openshift.io/display-name': 'Test Storage',
'openshift.io/display-name': displayName,
},
name,
namespace,
labels: {
[KnownLabels.DASHBOARD_RESOURCE]: 'true',
},
uid: genUID('pvc'),
uid,
},
spec: {
accessModes: ['ReadWriteOnce'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import {
mockDashboardConfig,
mockDscStatus,
mockK8sResourceList,
mockNotebookK8sResource,
mockProjectK8sResource,
mockStatus,
} from '~/__mocks__';
import { mockClusterSettings } from '~/__mocks__/mockClusterSettings';
import { mockPVCK8sResource } from '~/__mocks__/mockPVCK8sResource';
import { mockPodK8sResource } from '~/__mocks__/mockPodK8sResource';
import {
clusterStorage,
addClusterStorageModal,
updateClusterStorageModal,
} from '~/__tests__/cypress/cypress/pages/clusterStorage';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { be } from '~/__tests__/cypress/cypress/utils/should';

type HandlersProps = {
isEmpty?: boolean;
};

const initInterceptors = ({ isEmpty = false }: HandlersProps) => {
cy.intercept('/api/status', mockStatus());
cy.intercept('/api/config', mockDashboardConfig({}));
cy.intercept('/api/cluster-settings', mockClusterSettings({}));
cy.intercept('/api/dsc/status', mockDscStatus({}));
cy.intercept(
{ pathname: '/api/k8s/api/v1/namespaces/test-project/pods' },
mockK8sResourceList([mockPodK8sResource({})]),
);
cy.intercept(
{ pathname: '/api/k8s/apis/project.openshift.io/v1/projects' },
mockK8sResourceList([mockProjectK8sResource({})]),
);
cy.intercept(
{ pathname: '/api/k8s/apis/project.openshift.io/v1/projects/test-project' },
mockProjectK8sResource({}),
);
cy.intercept({ pathname: '/api/prometheus/pvc' }, mockPVCK8sResource({}));
cy.intercept(
{
method: 'GET',
pathname:
'/api/k8s/apis/opendatahub.io/v1alpha/namespaces/opendatahub/odhdashboardconfigs/odh-dashboard-config',
},
mockDashboardConfig({}),
);
cy.intercept(
{ pathname: '/api/k8s/api/v1/namespaces/test-project/persistentvolumeclaims' },
mockK8sResourceList(
isEmpty
? []
: [
mockPVCK8sResource({ uid: 'test-id' }),
mockPVCK8sResource({ displayName: 'Another Cluster Storage' }),
],
),
);
cy.intercept(
{
pathname: '/api/k8s/apis/kubeflow.org/v1/namespaces/test-project/notebooks',
},
mockK8sResourceList([mockNotebookK8sResource({})]),
);
};

describe('ClusterStorage', () => {
it('Empty state', () => {
initInterceptors({ isEmpty: true });
clusterStorage.visit('test-project');
clusterStorage.findEmptyState().should('exist');
clusterStorage.findCreateButton().should('be.enabled');
clusterStorage.findCreateButton().click();
});

it('Add cluster storage', () => {
initInterceptors({ isEmpty: true });
clusterStorage.visit('test-project');
clusterStorage.findCreateButton().click();
addClusterStorageModal.findNameInput().fill('test-storage');
addClusterStorageModal.findSubmitButton().should('be.enabled');
addClusterStorageModal.findDescriptionInput().fill('description');
addClusterStorageModal.findPVSizeMinusButton().click();
addClusterStorageModal.findPVSizeInput().should('have.value', '19');
addClusterStorageModal.findPVSizePlusButton().click();
addClusterStorageModal.findPVSizeInput().should('have.value', '20');
addClusterStorageModal.selectPVSize('Mi');

//connect workbench
addClusterStorageModal
.findWorkbenchConnectionSelect()
.findSelectOption('Test Notebook')
.click();
addClusterStorageModal.findMountField().fill('data');
addClusterStorageModal.findWorkbenchRestartAlert().should('exist');

cy.intercept(
{
method: 'PATCH',
pathname: '/api/k8s/apis/kubeflow.org/v1/namespaces/test-project/notebooks/test-notebook',
},
mockNotebookK8sResource({}),
).as('addClusterStorage');

cy.intercept(
{ pathname: '/api/k8s/api/v1/namespaces/test-project/persistentvolumeclaims' },
mockK8sResourceList([mockPVCK8sResource({})]),
);

addClusterStorageModal.findSubmitButton().click();
cy.wait('@addClusterStorage').then((interception) => {
expect(interception.request.url).to.include('?dryRun=All');
expect(interception.request.body).to.eql([
{ op: 'add', path: '/spec/template/spec/volumes/-', value: { persistentVolumeClaim: {} } },
{
op: 'add',
path: '/spec/template/spec/containers/0/volumeMounts/-',
value: { mountPath: '/opt/app-root/src/data' },
},
]);
});

cy.wait('@addClusterStorage').then((interception) => {
expect(interception.request.url).not.to.include('?dryRun=All');
});

cy.get('@addClusterStorage.all').then((interceptions) => {
expect(interceptions).to.have.length(2);
});
});

it('list accelerator profiles and Table sorting', () => {
initInterceptors({});
clusterStorage.visit('test-project');
const clusterStorageRow = clusterStorage.getClusterStorageRow('Test Storage');
clusterStorageRow.shouldHaveStorageTypeValue('Persistent storage');
clusterStorageRow.findConnectedWorkbenches().should('have.text', 'No connections');
clusterStorageRow.toggleExpandableContent();
clusterStorageRow.shouldHaveStorageSize('Max 5Gi');

//sort by Name
clusterStorage.findClusterStorageTableHeaderButton('Name').click();
clusterStorage.findClusterStorageTableHeaderButton('Name').should(be.sortAscending);
clusterStorage.findClusterStorageTableHeaderButton('Name').click();
clusterStorage.findClusterStorageTableHeaderButton('Name').should(be.sortDescending);
});

it('Edit cluster storage', () => {
initInterceptors({});
clusterStorage.visit('test-project');
const clusterStorageRow = clusterStorage.getClusterStorageRow('Test Storage');
clusterStorageRow.findKebabAction('Edit storage').click();
updateClusterStorageModal.findNameInput().should('have.value', 'Test Storage');
updateClusterStorageModal.findPVSizeInput().should('have.value', '5');
updateClusterStorageModal.shouldHavePVSizeSelectValue('Gi');
updateClusterStorageModal.findPersistentStorageWarning().should('exist');
updateClusterStorageModal.findSubmitButton().should('be.enabled');
updateClusterStorageModal.findNameInput().fill('test-updated');

cy.intercept(
{
method: 'PUT',
pathname: '/api/k8s/api/v1/namespaces/test-project/persistentvolumeclaims/test-storage',
},
mockPVCK8sResource({}),
).as('editClusterStorage');

updateClusterStorageModal.findSubmitButton().click();
cy.wait('@editClusterStorage').then((interception) => {
expect(interception.request.url).to.include('?dryRun=All');
expect(interception.request.body).to.containSubset({
metadata: {
annotations: {
'openshift.io/description': '',
'openshift.io/display-name': 'test-updated',
},
name: 'test-storage',
namespace: 'test-project',
},
spec: {
resources: { requests: { storage: '5Gi' } },
},
status: { phase: 'Pending', accessModes: ['ReadWriteOnce'], capacity: { storage: '5Gi' } },
});
});

cy.wait('@editClusterStorage').then((interception) => {
expect(interception.request.url).not.to.include('?dryRun=All');
});

cy.get('@editClusterStorage.all').then((interceptions) => {
expect(interceptions).to.have.length(2);
});
});

it('Delete cluster storage', () => {
initInterceptors({});
clusterStorage.visit('test-project');
const clusterStorageRow = clusterStorage.getClusterStorageRow('Test Storage');
clusterStorageRow.findKebabAction('Delete storage').click();
deleteModal.findInput().fill('Test Storage');

cy.intercept(
{
method: 'DELETE',
pathname: '/api/k8s/api/v1/namespaces/test-project/persistentvolumeclaims/test-storage',
},
{ kind: 'Status', apiVersion: 'v1', metadata: {}, status: 'Success' },
).as('deleteClusterStorage');

deleteModal.findSubmitButton().click();
cy.wait('@deleteClusterStorage');
});
});
126 changes: 126 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/clusterStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Modal } from './components/Modal';
import { TableRow } from './components/table';

class ClusterStorageRow extends TableRow {
shouldHaveStorageTypeValue(name: string) {
this.find().find(`[data-label=Type]`).contains(name).should('exist');
return this;
}

findConnectedWorkbenches() {
return this.find().find('[data-label="Connected workbenches"]');
}

toggleExpandableContent() {
this.find().findByRole('button', { name: 'Details' }).click();
}

shouldHaveStorageSize(name: string) {
this.find().siblings().find('[data-label=Size]').contains(name).should('exist');
return this;
}
}

class ClusterStorageModal extends Modal {
constructor(private edit = false) {
super(edit ? 'Update cluster storage' : 'Add cluster storage');
}

findWorkbenchConnectionSelect() {
return this.find()
.findByTestId('connect-existing-workbench-group')
.findByRole('button', { name: 'Options menu' });
}

findMountField() {
return this.find().findByTestId('mount-path-folder-value');
}

findWorkbenchRestartAlert() {
return this.find().findByTestId('notebook-restart-alert');
}

findNameInput() {
return this.find().findByTestId('create-new-storage-name');
}

findDescriptionInput() {
return this.find().findByTestId('create-new-storage-description');
}

findSubmitButton() {
return this.find().findByTestId('modal-submit-button');
}

private findPVSizeSelectButton() {
return cy.findByTestId('value-unit-select');
}

selectPVSize(name: string) {
this.findPVSizeSelectButton().click();
cy.findByRole('menuitem', { name }).click();
}

shouldHavePVSizeSelectValue(name: string) {
this.findPVSizeSelectButton().findByRole('button', { name }).should('exist');
return this;
}

private findPVSizeField() {
return this.find().findByTestId('create-new-storage-size');
}

findPVSizeMinusButton() {
return this.findPVSizeField().findByRole('button', { name: 'Minus' });
}

findPersistentStorageWarning() {
return this.find().findByTestId('persistent-storage-warning');
}

findPVSizeInput() {
return this.findPVSizeField().find('input');
}

findPVSizePlusButton() {
return this.findPVSizeField().findByRole('button', { name: 'Plus' });
}
}

class ClusterStorage {
visit(projectName: string) {
cy.visitWithLogin(`/projects/${projectName}?section=cluster-storages`);
this.wait();
}

private wait() {
cy.findByTestId('app-page-title');
cy.testA11y();
}

findEmptyState() {
return cy.findByTestId('empty-state-title');
}

private findClusterStorageTable() {
return cy.findByTestId('storage-table');
}

findClusterStorageTableHeaderButton(name: string) {
return this.findClusterStorageTable().find('thead').findByRole('button', { name });
}

getClusterStorageRow(name: string) {
return new ClusterStorageRow(() =>
this.findClusterStorageTable().find(`[data-label=Name]`).contains(name).parents('tr'),
);
}

findCreateButton() {
return cy.findByTestId('cluster-storage-button');
}
}

export const clusterStorage = new ClusterStorage();
export const addClusterStorageModal = new ClusterStorageModal();
export const updateClusterStorageModal = new ClusterStorageModal(true);
1 change: 1 addition & 0 deletions frontend/src/components/ValueUnitField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ValueUnitField: React.FC<ValueUnitFieldProps> = ({
</SplitItem>
<SplitItem>
<Dropdown
data-testid="value-unit-select"
menuAppendTo="parent"
toggle={
<DropdownToggle id="toggle-basic" onToggle={() => setOpen(!open)}>
Expand Down
Loading

0 comments on commit ed6d6e0

Please sign in to comment.