Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into rhoai-2.16
Browse files Browse the repository at this point in the history
  • Loading branch information
dchourasia committed Nov 1, 2024
2 parents 83468db + acac42c commit 264ecfc
Show file tree
Hide file tree
Showing 40 changed files with 958 additions and 503 deletions.
6 changes: 3 additions & 3 deletions backend/src/routes/api/connection-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { V1ConfigMap } from '@kubernetes/client-node';
import { FastifyReply, FastifyRequest } from 'fastify';
import { KubeFastifyInstance, RecursivePartial } from '../../../types';
import { secureAdminRoute } from '../../../utils/route-security';
import { secureAdminRoute, secureRoute } from '../../../utils/route-security';
import {
getConnectionType,
listConnectionTypes,
Expand All @@ -14,7 +14,7 @@ import {
module.exports = async (fastify: KubeFastifyInstance) => {
fastify.get(
'/',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) =>
secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) =>
listConnectionTypes(fastify)
.then((res) => res)
.catch((res) => {
Expand All @@ -25,7 +25,7 @@ module.exports = async (fastify: KubeFastifyInstance) => {

fastify.get(
'/:name',
secureAdminRoute(fastify)(
secureRoute(fastify)(
async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) =>
getConnectionType(fastify, request.params.name)
.then((res) => res)
Expand Down
268 changes: 52 additions & 216 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"axios": "^1.6.4",
"classnames": "^2.2.6",
"compare-versions": "^3.6.0",
"cypress-plugin-steps": "^1.1.1",
"dompurify": "^2.2.6",
"google-protobuf": "^3.11.2",
"grpc-web": "^1.2.1",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/__mocks__/mockInferenceServiceK8sResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export const mockInferenceServiceK8sResource = ({
key: secretName,
path,
},
args: [''],
env: [],
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# testProjectCreation.cy.ts Test Data #
dsProjectName: "Cypress Test Project 1"
dsProjectDescription: "Cypress Test project 1 description."
dsOCProjectName: "cypress-test-project-1"
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ class CreateConnectionTypePage {
findDuplicateConnectionTypeButton() {
return cy.findByTestId('duplicate-connection-type');
}

findCompatibleModelServingTypesAlert() {
return cy.findByTestId('compatible-model-serving-types-alert');
}

findModelServingCompatibleTypeDropdown() {
return cy.findByTestId('select-model-serving-compatible-type');
}
}

class CategorySection extends Contextual<HTMLElement> {
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,22 @@ class ProjectDetails {
return this.findKserveModelsTable().findByTestId(`metrics-link-${name}`);
}

verifyProjectName(project: string) {
return cy.get('[data-testid="app-page-title"]').should('contain.text', project);
}

verifyProjectDescription(description: string) {
return cy.findByText(description);
}

findActions() {
return cy.findByTestId('project-actions');
}

findDeleteProjectButton() {
return cy.findByTestId('delete-project-action').find('button');
}

getKserveTableRow(name: string) {
return new KserveTableRow(() =>
this.findKserveModelsTable()
Expand Down
1 change: 1 addition & 0 deletions frontend/src/__tests__/cypress/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'cypress-mochawesome-reporter/register';
import './commands';
import { asProjectAdminUser } from '~/__tests__/cypress/cypress/utils/mockUsers';
import { addCommands as webSocketsAddCommands } from './websockets';
import 'cypress-plugin-steps';

chai.use(chaiSubset);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import yaml from 'js-yaml';
import { ADMIN_USER } from '~/__tests__/cypress/cypress/utils/e2eUsers';
import {
projectListPage,
createProjectModal,
projectDetails,
} from '~/__tests__/cypress/cypress/pages/projects';
import {
verifyOpenShiftProjectExists,
deleteOpenShiftProject,
} from '~/__tests__/cypress/cypress/utils/oc_commands/project';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import type { DataScienceProjectData } from '~/__tests__/cypress/cypress/types';

describe('Verify Data Science Project - Creation and Deletion', () => {
let testData: DataScienceProjectData;

// Setup: Load test data and ensure clean state
before(() => {
return cy
.fixture('e2e/dataScienceProjects/dataScienceProject.yaml', 'utf8')
.then((yamlContent: string) => {
testData = yaml.load(yamlContent) as DataScienceProjectData;
const projectName = testData.dsOCProjectName;

if (!projectName) {
throw new Error('Project name is undefined or empty');
}

return verifyOpenShiftProjectExists(projectName);
})
.then((exists: boolean) => {
const projectName = testData.dsOCProjectName;
// Clean up existing project if it exists
if (exists) {
cy.log(`Project ${projectName} exists. Deleting before test.`);
return deleteOpenShiftProject(projectName);
}
cy.log(`Project ${projectName} does not exist. Proceeding with test.`);
// Return a resolved promise to ensure a value is always returned
return cy.wrap(null);
});
});

it('Create and Delete a Data Science Project in RHOAI', () => {
// Authentication and navigation
cy.step('Log into the application');
cy.visitWithLogin('/', ADMIN_USER);
projectListPage.navigate();

// Initiate project creation
cy.step('Open Create Data Science Project modal');
createProjectModal.shouldBeOpen(false);
projectListPage.findCreateProjectButton().click();

// Input project details
cy.step('Enter valid project information');
createProjectModal.k8sNameDescription.findDisplayNameInput().type(testData.dsProjectName);
createProjectModal.k8sNameDescription
.findDescriptionInput()
.type(testData.dsProjectDescription);

// Submit project creation
cy.step('Save the project');
createProjectModal.findSubmitButton().click();

// Verify project creation
cy.step(`Verify that the project ${testData.dsProjectName} has been created`);
cy.url().should('include', `/projects/${testData.dsOCProjectName}`);
projectDetails.verifyProjectName(testData.dsProjectName);
projectDetails.verifyProjectDescription(testData.dsProjectDescription);

// Initiate project deletion
cy.step('Deleting the project - clicking actions');
projectDetails.findActions().click();
projectDetails.findDeleteProjectAction().click();

// Confirm project deletion
cy.step('Entering project details for deletion');
deleteModal.shouldBeOpen();
deleteModal.findInput().type(testData.dsProjectName);
deleteModal.findSubmitButton().should('be.enabled').click();

// Verify project deletion
cy.step(`Verify that the project ${testData.dsProjectName} has been deleted`);
projectListPage.filterProjectByName(testData.dsProjectName);
projectListPage.findEmptyResults();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,31 @@ describe('create', () => {
disableConnectionTypes: false,
}),
);
cy.interceptOdh('GET /api/connection-types', [
mockConnectionTypeConfigMap({
displayName: 'URI - v1',
name: 'uri-v1',
fields: [
{
type: 'uri',
name: 'URI field test',
envVar: 'URI',
required: true,
properties: {},
},
],
}),
]);
});

it('Display base page', () => {
it('Can create connection type', () => {
const categorySection = createConnectionTypePage.getCategorySection();
createConnectionTypePage.visitCreatePage();

createConnectionTypePage.findConnectionTypeName().should('exist');
createConnectionTypePage.findConnectionTypeDesc().should('exist');
createConnectionTypePage.findConnectionTypeEnableCheckbox().should('exist');
createConnectionTypePage.findConnectionTypePreviewToggle().should('exist');
});

it('Allows create button with valid name and category', () => {
const categorySection = createConnectionTypePage.getCategorySection();
createConnectionTypePage.visitCreatePage();

createConnectionTypePage.findConnectionTypeName().should('have.value', '');
createConnectionTypePage.findSubmitButton().should('be.disabled');
Expand All @@ -40,6 +51,28 @@ describe('create', () => {
categorySection.findCategoryTable();
categorySection.findMultiGroupSelectButton('Object-storage');
createConnectionTypePage.findSubmitButton().should('be.enabled');

categorySection.findMultiGroupInput().type('Database');
categorySection.findMultiGroupSelectButton('Database');

categorySection.findMultiGroupInput().type('New category');

categorySection.findMultiGroupSelectButton('Option');
categorySection.findChipItem('New category').should('exist');
categorySection.findMultiGroupInput().type('{esc}');

createConnectionTypePage
.findModelServingCompatibleTypeDropdown()
.findDropdownItem('URI')
.click();
createConnectionTypePage.findCompatibleModelServingTypesAlert().should('exist');
createConnectionTypePage.getFieldsTableRow(0).findSectionHeading().should('exist');
createConnectionTypePage
.getFieldsTableRow(1)
.findName()
.should('contain.text', 'URI field test');

createConnectionTypePage.findSubmitButton().should('be.enabled');
});

it('Shows creation error message when creation fails', () => {
Expand All @@ -60,28 +93,6 @@ describe('create', () => {

createConnectionTypePage.findFooterError().should('contain.text', 'returned error message');
});

it('Selects category or creates new category', () => {
createConnectionTypePage.visitCreatePage();

const categorySection = createConnectionTypePage.getCategorySection();

categorySection.findCategoryTable();
categorySection.findMultiGroupSelectButton('Object-storage');

categorySection.findChipItem('Object storage').should('exist');
categorySection.clearMultiChipItem();

categorySection.findMultiGroupSelectButton('Object-storage');

categorySection.findMultiGroupInput().type('Database');
categorySection.findMultiGroupSelectButton('Database');

categorySection.findMultiGroupInput().type('New category');

categorySection.findMultiGroupSelectButton('Option');
categorySection.findChipItem('New category').should('exist');
});
});

describe('duplicate', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ describe('Model version details', () => {
cy.wait('@updateModelFormat').then((interception) => {
expect(interception.request.body).to.deep.equal({
modelFormatName: 'UpdatedFormat',
artifactType: 'model-artifact',
});
});
});
Expand All @@ -439,6 +440,7 @@ describe('Model version details', () => {
cy.wait('@updateModelVersion').then((interception) => {
expect(interception.request.body).to.deep.equal({
modelFormatVersion: '2.0.0',
artifactType: 'model-artifact',
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,12 @@ describe('Model Serving Global', () => {
modelFormat: { name: 'onnx', version: '1' },
runtime: 'test-model',
storage: { key: 'test-secret', path: 'test-model/' },
args: [''],
env: [],
},
},
},
});
} satisfies InferenceServiceKind);
});

// Actaul request
Expand Down Expand Up @@ -471,10 +473,12 @@ describe('Model Serving Global', () => {
modelFormat: { name: 'onnx', version: '1' },
runtime: 'test-model',
storage: { key: 'test-secret', path: 'test-model/test-model/' },
args: [''],
env: [],
},
},
},
});
} satisfies InferenceServiceKind);
});

cy.findByText('Error creating model server');
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/__tests__/cypress/cypress/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ export type TestConfig = {
S3: AWSS3Buckets;
};

export type DataScienceProjectData = {
dsProjectName: string;
dsProjectDescription: string;
dsOCProjectName: string;
};

export type NimServingResponse = {
body: {
body: ConfigMapKind | SecretKind;
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/__tests__/cypress/cypress/utils/errorHandling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Handles the result of an OpenShift (oc) command execution.
* @param result The result of the command execution.
* @throws Error if the command fails with an unexpected exit code.
*/
export function handleOCCommandResult(result: Cypress.Exec): void {
if (result.code !== 0 && result.code !== 1) {
// When some resources e.g. projects don't exist
cy.log(`ERROR: Command execution failed
stdout: ${result.stdout}
stderr: ${result.stderr}`);
throw new Error(`Command failed with code ${result.code}`);
}
}
Loading

0 comments on commit 264ecfc

Please sign in to comment.