From 4ccfc9a03f03b375ccf50e21c007b830f6738d52 Mon Sep 17 00:00:00 2001 From: Zhaoxinxin <107842350+Liam-Zhao@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:13:14 +0800 Subject: [PATCH] feat: add create cluster e2e test (#323) --- cypress/e2e/404.cy.ts | 3 +- cypress/e2e/clusters.cy.ts | 64 +-- cypress/e2e/create-cluster.cy.ts | 465 ++++++++++++++++++ cypress/e2e/signin.cy.ts | 50 +- cypress/e2e/signup.cy.ts | 51 +- .../fixtures/api/clusters/create-cluster.json | 314 ++++++++++++ ...luster-search.json => search-cluster.json} | 0 src/components/clusters/index.tsx | 18 +- src/components/clusters/new.tsx | 17 +- 9 files changed, 893 insertions(+), 89 deletions(-) create mode 100644 cypress/e2e/create-cluster.cy.ts create mode 100644 cypress/fixtures/api/clusters/create-cluster.json rename cypress/fixtures/api/clusters/{cluster-search.json => search-cluster.json} (100%) diff --git a/cypress/e2e/404.cy.ts b/cypress/e2e/404.cy.ts index 7664a87..5aa20e8 100644 --- a/cypress/e2e/404.cy.ts +++ b/cypress/e2e/404.cy.ts @@ -40,7 +40,8 @@ describe('404', () => { cy.visit('/root'); cy.get('.MuiTypography-h4').should('have.text', 'Something gone wrong!'); cy.get('.MuiButtonBase-root').click(); - // Then I see that the current page is the clusters + + // Then I see that the current page is the clusters. cy.url().should('include', '/clusters'); }); }); diff --git a/cypress/e2e/clusters.cy.ts b/cypress/e2e/clusters.cy.ts index e2570ab..a165a9a 100644 --- a/cypress/e2e/clusters.cy.ts +++ b/cypress/e2e/clusters.cy.ts @@ -3,7 +3,7 @@ import root from '../fixtures/api/role-root.json'; import user from '../fixtures/api/user.json'; import seedPeers from '../fixtures/api/clusters/seed-peers.json'; import schedulers from '../fixtures/api/clusters/schedulers.json'; -import clusterSearch from '../fixtures/api/clusters/cluster-search.json'; +import searchCluster from '../fixtures/api/clusters/search-cluster.json'; describe('Clusters', () => { beforeEach(() => { @@ -117,21 +117,21 @@ describe('Clusters', () => { }); it('can display clusters card', () => { - // displays a card componen + // Display the card component. cy.get('.MuiBackdrop-root > .MuiBox-root').should('exist'); - // display Default background color + // Show Default background color. cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > #isDefault') .should('be.visible') .and('contain', 'Default') .and('have.css', 'background-color', 'rgb(46, 143, 121)'); - // display cluster name + // Show cluster name. cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') .should('be.visible') .and('contain', 'cluster-1'); - // display cluster description + // Show cluster description. cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .css-m4gmz7 > .MuiTypography-root') .should('be.visible') .and( @@ -139,12 +139,12 @@ describe('Clusters', () => { 'Cluster-1 is a high-performance computing cluster located in China, specifically in Hangzhou and Beijing data centers.', ); - // display Non-Default cluster + // Show Non-Default cluster. cy.get(':nth-child(8) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') .should('be.visible') .and('contain', 'cluster-2'); - // display Non-Default background color + // Show Non-Default background color. cy.get(':nth-child(8) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > #isDefault') .should('be.visible') .and('contain', 'Non-Default') @@ -239,7 +239,8 @@ describe('Clusters', () => { it('cluster card should present an empty status', () => { cy.get('#clusters').should('not.exist'); - // shouldn't render pagination buttons + + // Shouldn't render pagination buttons. cy.get('#clusterPagination > .MuiPagination-ul').should('not.exist'); }); }); @@ -248,10 +249,10 @@ describe('Clusters', () => { it('pagination updates results and page number', () => { cy.get('.Mui-selected').invoke('text').should('eq', 'Cluster1'); - //total number of pages + // Check number of pagination. cy.get('#clusterPagination > .MuiPagination-ul').children().should('have.length', 4); - // show cluster name + // Show cluster name. cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') .should('be.visible') .and('contain', 'cluster-1'); @@ -260,13 +261,13 @@ describe('Clusters', () => { it('when pagination changes, different page results are rendered', () => { cy.get('.Mui-selected').invoke('text').should('eq', 'Cluster1'); - // go to last page + // Go to last page. cy.get('.MuiPagination-ul > :nth-child(3) > .MuiButtonBase-root').click(); - // display last page cluster information + // Display last page cluster information. cy.get('.clusters_clusterListContent__UwWjF > .css-k008qs').should('be.visible').and('contain', '8'); - // display cluster information + // Display cluster information. cy.get('#isDefault') .should('be.visible') .and('contain', 'Non-Default') @@ -283,25 +284,27 @@ describe('Clusters', () => { 'Cluster-8 is a high-performance computing cluster located in China, specifically in Jiangsu data centers.', ); - // current page number + // Check the current page number. cy.get('#clusterPagination > .MuiPagination-ul .Mui-selected').should('have.text', '2'); }); it('pagination resets results and page number to first page when refresh is clicked', () => { - // Go to last page + // Go to last page. cy.get('.MuiPagination-ul > :nth-child(3) > .MuiButtonBase-root').click(); cy.get('.clusters_clusterListContent__UwWjF > .MuiTypography-h6') .should('be.visible') .and('contain', 'cluster-8'); + // Check the current page number. cy.get('#clusterPagination > .MuiPagination-ul .Mui-selected').should('have.text', '2'); - // refresh page + + // Refresh page. cy.reload().then(() => { cy.wait(2000); }); - // page numbers have been reset + // Check if the page number has been reset. cy.get('#clusterPagination > .MuiPagination-ul .Mui-selected').should('have.text', '1'); cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') @@ -338,12 +341,12 @@ describe('Clusters', () => { }); it('show error message', () => { - // show error message + // Show error message. cy.get('.css-1rr4qq7 > .MuiSnackbar-root > .MuiPaper-root > .MuiAlert-message') .should('be.visible') .and('contain', 'Failed to fetch'); - // close error message + // Close error message. cy.get('.css-1rr4qq7 > .MuiSnackbar-root > .MuiPaper-root > .MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.css-1rr4qq7 > .MuiSnackbar-root > .MuiPaper-root > .MuiAlert-message').should('not.exist'); }); @@ -392,9 +395,10 @@ describe('Clusters', () => { }); it('cluster card should present an empty status', () => { - // no clusters + // No clusters. cy.get('#clusters').should('not.exist'); - // no pagination + + // No pagination. cy.get('#clusterPagination > .MuiPagination-ul').should('not.exist'); }); }); @@ -409,20 +413,21 @@ describe('Clusters', () => { (req) => { req.reply({ statusCode: 200, - body: clusterSearch, + body: searchCluster, }); }, ); cy.get('#free-solo-demo').type('cluster-1{enter}'); cy.get('#clusterPagination > .MuiPagination-ul').should('not.exist'); - // clear search box + // Clear search box. cy.get('#free-solo-demo').clear(); - //search all clusters + + // If the search is empty, all clusters will be displayed. cy.get('#free-solo-demo').type('{enter}'); cy.get('#clusterPagination > .MuiPagination-ul').should('exist'); - // number of pagination + // Check number of pagination. cy.get('#clusterPagination > .MuiPagination-ul').children().should('have.length', 4); cy.get(':nth-child(1) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') @@ -449,9 +454,11 @@ describe('Clusters', () => { ); cy.get('#free-solo-demo').type('cluster-16{enter}'); - //no clusters card + + // No clusters card. cy.get('#clusters').should('not.exist'); - // hidden pagination + + // Pagination has been hidden. cy.get('#clusterPagination > .MuiPagination-ul').should('not.exist'); }); @@ -470,7 +477,8 @@ describe('Clusters', () => { cy.get('#free-solo-demo').clear(); cy.get('#free-solo-demo').type('cluster-1{enter}'); - // error message + + // Show error message. cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Failed to fetch'); }); }); diff --git a/cypress/e2e/create-cluster.cy.ts b/cypress/e2e/create-cluster.cy.ts new file mode 100644 index 0000000..f961691 --- /dev/null +++ b/cypress/e2e/create-cluster.cy.ts @@ -0,0 +1,465 @@ +import clusters from '../fixtures/api/clusters/clusters.json'; +import root from '../fixtures/api/role-root.json'; +import guest from '../fixtures/api/role-guest.json'; +import user from '../fixtures/api/user.json'; +import guestUser from '../fixtures/api/guest-user.json'; +import seedPeers from '../fixtures/api/clusters/seed-peers.json'; +import schedulers from '../fixtures/api/clusters/schedulers.json'; +import createClustes from '../fixtures/api/clusters/create-cluster.json'; +import _ from 'lodash'; + +describe('Create cluster', () => { + beforeEach(() => { + cy.intercept( + { + method: 'GET', + url: '/api/v1/schedulers?page=1&per_page=10000000', + }, + (req) => { + req.reply({ + statusCode: 200, + body: schedulers, + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/clusters?page=1&per_page=10000000', + }, + (req) => { + req.reply({ + statusCode: 200, + body: clusters, + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/seed-peers?page=1&per_page=10000000', + }, + (req) => { + req.reply({ + statusCode: 200, + body: seedPeers, + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/users/1', + }, + (req) => { + req.reply({ + statusCode: 200, + body: user, + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/users/1/roles', + }, + (req) => { + req.reply({ + statusCode: 200, + body: root, + }); + }, + ); + + cy.signin(); + cy.visit('/clusters/new'); + cy.viewport(1440, 1080); + }); + + it('can create cluster', () => { + cy.visit('/clusters'); + + // Show number of cluster. + cy.get( + ':nth-child(1) > .css-q5fqw0 > .clusters_clusterContentContainer__ZxKuh > .css-zm3ms > .css-70qvj9 > .MuiTypography-root', + ) + .should('be.visible') + .and('contain', '11'); + + // Show number of cluster default. + cy.get( + ':nth-child(1) > .css-q5fqw0 > .clusters_clusterContentContainer__ZxKuh > .css-zm3ms > .MuiGrid-root > .clusters_clusterBottomContentContainer__KII0M > .clusters_clusterBottomContent__k3P4u', + ) + .should('be.visible') + .and('contain', '7'); + + // Click the `ADD CLUSTER` button. + cy.get('.clusters_clusterTitle__5Lhnw > .MuiButtonBase-root').click(); + + cy.url().should('include', '/clusters/new'); + + // Add Information. + cy.get('.PrivateSwitchBase-input').click(); + cy.get('#name').type('cluster-12'); + cy.get('#description').type('Add new cluster case'); + cy.get('#location').type('China|Hang|Zhou'); + + // Add idc. + cy.get(':nth-child(2) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type('hz{enter}'); + cy.get(':nth-child(2) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type('sh{enter}'); + cy.get(':nth-child(3) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').click(); + + // Add cidrs. + cy.contains('li', '10.0.0.0/8').click(); + cy.get(':nth-child(3) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').click(); + cy.contains('li', '172.16.0.0/12').click(); + cy.get(':nth-child(3) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').click(); + cy.contains('li', '192.168.0.0/16').click(); + + // Add config. + cy.get(':nth-child(3) > .MuiInputBase-root').clear(); + cy.get(':nth-child(3) > .MuiInputBase-root').type('10'); + + cy.intercept( + { + method: 'POST', + url: '/api/v1/clusters', + }, + (req) => { + req.body = ''; + req.reply({ + statusCode: 200, + body: [], + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/clusters?page=1&per_page=10000000', + }, + (req) => { + req.reply({ + statusCode: 200, + body: createClustes, + }); + }, + ); + + // Click the `save` button. + cy.get('#save').click(); + + // Then I see that the current page is the clusters. + cy.url().should('include', '/clusters'); + + // Displays successfully added clusters. + cy.get(':nth-child(8) > .MuiPaper-root > .clusters_clusterListContent__UwWjF > .MuiTypography-h6') + .should('be.visible') + .and('contain', 'cluster-12'); + + // The number of clusters has been increased. + cy.get( + ':nth-child(1) > .css-q5fqw0 > .clusters_clusterContentContainer__ZxKuh > .css-zm3ms > .css-70qvj9 > .MuiTypography-root', + ) + .should('be.visible') + .and('contain', '12'); + + // The default number of clusters has been increased. + cy.get( + ':nth-child(1) > .css-q5fqw0 > .clusters_clusterContentContainer__ZxKuh > .css-zm3ms > .MuiGrid-root > .clusters_clusterBottomContentContainer__KII0M > .clusters_clusterBottomContent__k3P4u', + ) + .should('be.visible') + .and('contain', '8'); + }); + + it('cannot create cluster with existing cluster', () => { + cy.intercept( + { + method: 'POST', + url: '/api/v1/clusters', + }, + (req) => { + req.body = { + name: 'cluster-1', + bio: '', + scopes: { + idc: '', + location: '', + cidrs: [], + }, + scheduler_cluster_config: { + filter_parent_limit: 4, + filter_parent_range_limit: 40, + }, + seed_peer_cluster_config: { + load_limit: 300, + }, + peer_cluster_config: { + load_limit: 50, + concurrent_piece_count: 4, + }, + is_default: true, + }; + req.reply({ + statusCode: 409, + body: { message: 'Conflict' }, + }); + }, + ); + + cy.get('#name').type('cluster-1{enter}'); + + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Conflict'); + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + cy.get('.MuiPaper-root').should('not.exist'); + }); + + it('click the `CANCEL button', () => { + cy.get('#cancel').click(); + + // Then I see that the current page is the clusters. + cy.url().should('include', '/clusters'); + }); + + it('cannot create cluster without required attributes', () => { + cy.get('#save').click(); + + cy.get('#name-helper-text').should('be.visible').and('contain', 'Fill in the characters, the length is 1-40.'); + }); + + it('try to create cluster with guest user', () => { + cy.intercept( + { + method: 'GET', + url: '/api/v1/users/2', + }, + (req) => { + req.reply({ + statusCode: 200, + body: guestUser, + }); + }, + ); + cy.intercept( + { + method: 'GET', + url: '/api/v1/users/2/roles', + }, + (req) => { + req.reply({ + statusCode: 200, + body: guest, + }); + }, + ); + + cy.guestSignin(); + + cy.intercept({ method: 'POST', url: '/api/v1/clusters' }, (req) => { + (req.body = ''), + req.reply({ + statusCode: 401, + body: { message: 'permission deny' }, + }); + }); + + cy.get('#name').type('cluster-12{enter}'); + + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'permission deny'); + + cy.get('#cancel').click(); + + cy.wait(1000); + + // Then I see that the current page is the clusters! + cy.url().should('include', '/clusters'); + }); + + it('should handle API error response', () => { + cy.intercept({ method: 'POST', url: '/api/v1/clusters' }, (req) => { + (req.body = ''), + req.reply({ + forceNetworkError: true, + }); + }); + + cy.get('#name').type('cluster-12'); + cy.get('#save').click(); + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Failed to fetch'); + }); + + describe('cannot create cluster with invalid attributes', () => { + it('try to verify information', () => { + const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const name = _.times(41, () => _.sample(characters)).join(''); + const description = _.times(1001, () => _.sample(characters)).join(''); + + // Should display message name the validation error. + cy.get('#save').click(); + + // Name is a required attribute. + cy.get('#name-helper-text').should('be.visible').and('contain', 'Fill in the characters, the length is 1-40.'); + cy.get('#name').type(name); + + // Show verification error message. + cy.get('#name-helper-text').should('be.visible').and('contain', 'Fill in the characters, the length is 1-40.'); + + // Submit form when validation fails + cy.get('#save').click(); + + // Cluster creation failed, the page is still in cluster/new. + cy.url().should('include', '/clusters/new'); + cy.get('#name').clear(); + + // Enter the correct name。 + cy.get('#name').type('cluster-12'); + cy.get('#name-helper-text').should('not.exist'); + + // Should display message describing the validation error. + cy.get('#description').type(description); + + // Show verification error message. + cy.get('#description-helper-text') + .should('be.visible') + .and('contain', 'Fill in the characters, the length is 0-1000.'); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#description').clear(); + cy.get('#description').type('cluster description'); + cy.get('#name-helper-text').should('not.exist'); + }); + + it('try to verify scopes', () => { + const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const location = _.times(101, () => _.sample(characters)).join(''); + + // Name is a required attribute. + cy.get('#name').type('cluster-12'); + + // Should display location the validation error message. + cy.get('#location').type(location); + + // Show verification error message. + cy.get('#location-helper-text') + .should('be.visible') + .and('contain', 'Fill in the characters, the length is 0-100.'); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#location').clear(); + + // Verification passed. + cy.get('#location').type('Beijing'); + cy.get('#location-helper-text').should('not.exist'); + + // Should display idc the validation error message. + cy.get(':nth-child(2) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type('hz'); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#idc-helper-text').should('be.visible').and('contain', `Please press ENTER to end the IDC creation.`); + cy.get(':nth-child(2) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type('hz{enter}'); + + // Verification passed. + cy.get('#idc-helper-text').should('not.exist'); + + // Should display cidrs the validation error message. + cy.get(':nth-child(3) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type( + '192.168.40.0/24', + ); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#cidrs-helper-text').should('be.visible').and('contain', `Please press ENTER to end the CIDRs creation.`); + + cy.get(':nth-child(3) > .MuiAutocomplete-root > .MuiFormControl-root > .MuiInputBase-root').type( + '192.168.40.0/24{enter}', + ); + + // Verification passed. + cy.get('#cidrs-helper-text').should('not.exist'); + }); + + it('try to verify config', () => { + // Name is a required attribute. + cy.get('#name').type('cluster-12'); + + // Should display seed peer load limit the validation error message. + cy.get('#seedPeerLoadLimit').type('5000'); + cy.get('#seedPeerLoadLimit-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 0-5000.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#seedPeerLoadLimit').clear(); + cy.get('#seedPeerLoadLimit').type('400'); + + // Verification passed. + cy.get('#seedPeerLoadLimit-helper-text').should('not.exist'); + + // Should display peer load limit the validation error message. + cy.get('#peerLoadLimit').clear(); + cy.get('#peerLoadLimit').type('2001'); + cy.get('#peerLoadLimit-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 0-2000.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#peerLoadLimit').clear(); + cy.get('#peerLoadLimit').type('50'); + + // Verification passed. + cy.get('#peerLoadLimit-helper-text').should('not.exist'); + + // Should display number of concurrent download pieces the validation error message. + cy.get('#numberOfConcurrentDownloadPieces').clear(); + cy.get('#numberOfConcurrentDownloadPieces').type('51'); + cy.get('#numberOfConcurrentDownloadPieces-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 0-50.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#numberOfConcurrentDownloadPieces').clear(); + cy.get('#numberOfConcurrentDownloadPieces').type('10'); + + // Verification passed. + cy.get('#numberOfConcurrentDownloadPieces-helper-text').should('not.exist'); + + // Should display candidate parent limit the validation error message. + cy.get('#candidateParentLimit').clear(); + cy.get('#candidateParentLimit').type('21'); + cy.get('#candidateParentLimit-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 1-20.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#candidateParentLimit').clear(); + cy.get('#candidateParentLimit').type('5'); + cy.get('#candidateParentLimit-helper-text').should('not.exist'); + + // Should display filter parent limit the validation error message. + cy.get('#filterParentLimit').clear(); + + // Minimum validation range not reached. + cy.get('#filterParentLimit').type('9'); + cy.get('#filterParentLimit-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 10-1000.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#filterParentLimit').clear(); + + // Maximum verification range exceeded. + cy.get('#filterParentLimit').type('1001'); + cy.get('#filterParentLimit-helper-text') + .should('be.visible') + .and('contain', `Fill in the number, the length is 10-1000.`); + cy.get('#save').click(); + cy.url().should('include', '/clusters/new'); + cy.get('#filterParentLimit').clear(); + cy.get('#filterParentLimit').type('100'); + + // Verification passed. + cy.get('#filterParentLimit-helper-text').should('not.exist'); + }); + }); +}); diff --git a/cypress/e2e/signin.cy.ts b/cypress/e2e/signin.cy.ts index 672023f..f816e8c 100644 --- a/cypress/e2e/signin.cy.ts +++ b/cypress/e2e/signin.cy.ts @@ -94,16 +94,19 @@ describe('Signin', () => { cy.signin(); cy.get('form').submit(); - // Then I see that the current page is the clusters + + // Then I see that the current page is the clusters. cy.url().should('include', '/clusters'); cy.location('pathname').should('eq', '/clusters'); - //prompt message: Please change your password promptly when logging in for the first time! - cy.get('.MuiSnackbar-root > .MuiPaper-root').should('exist'); - // close prompt message - cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + // Prompt message: Please change your password promptly when logging in for the first time! + cy.get('.MuiSnackbar-root > .MuiPaper-root').should('exist'); + + // Close the prompt message. + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); - // menu exists for users + + // Menu exists for users. cy.get('[href="/users"]').should('exist'); }); @@ -111,11 +114,10 @@ describe('Signin', () => { cy.get('#account').type('root'); cy.get('#password').type('rooot1'); cy.get('form').submit(); - // show error message - cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Unauthorized'); - // close error message - cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Unauthorized'); + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); @@ -123,11 +125,10 @@ describe('Signin', () => { cy.get('#account').type('root-1'); cy.get('#password').type('dragonfly'); cy.get('form').submit(); - // show error message - cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Unauthorized'); - // close error message - cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Unauthorized'); + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); @@ -166,29 +167,34 @@ describe('Signin', () => { cy.get('#account').type('root-2'); cy.get('#password').type(`dragonfly`); - // guest user + cy.guestSignin(); cy.get('form').submit(); - // Then I see that the current page is the clusters + + // Then I see that the current page is the clusters! cy.url().should('include', '/clusters'); cy.location('pathname').should('eq', '/clusters'); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('exist'); cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); - // users menu does not exist + + // Users menu does not exist. cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); - //menu not exists for users + + // Menu not exists for users. cy.get('[href="/users"]').should('not.exist'); }); it('click the `Create an account` button', () => { cy.get('.MuiTypography-inherit > .MuiTypography-root').click(); - // Then I see that the current page is the signup + + // Then I see that the current page is the signup! cy.url().should('include', '/signup'); cy.get('.MuiTypography-inherit > .MuiTypography-root').click(); - // Then I see that the current page is the signin + + // Then I see that the current page is the signin! cy.url().should('include', '/signin'); }); @@ -216,11 +222,9 @@ describe('Signin', () => { cy.get('form').submit(); - // show error message + // Show error message. cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Failed to fetch'); - // close error message cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); - cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); diff --git a/cypress/e2e/signup.cy.ts b/cypress/e2e/signup.cy.ts index 11b2eee..7b4cfe1 100644 --- a/cypress/e2e/signup.cy.ts +++ b/cypress/e2e/signup.cy.ts @@ -27,7 +27,8 @@ describe('Signup', () => { cy.get('#email').type('root@console.com'); cy.get('#password').type('dragonfly1'); cy.get('#confirmPassword').type(`dragonfly1{enter}`); - // then I see that the current page is the signin + + // Then I see that the current page is the signin! cy.url().should('include', '/signin'); }); @@ -36,11 +37,12 @@ describe('Signup', () => { cy.get('#email').type('lucy@example.com'); cy.get('#password').type('dragonfly1'); cy.get('#confirmPassword').type(`dragonfly1{enter}`); - // show error message - cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Conflict'); - // close error message - cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Conflict'); + + // Close error message. + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); @@ -49,11 +51,10 @@ describe('Signup', () => { cy.get('#email').type('root@console.co'); cy.get('#password').type('dragonfly1'); cy.get('#confirmPassword').type(`dragonfly1{enter}`); - // show error message - cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Conflict'); - // close error message - cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); + // Show error message. + cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Conflict'); + cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); @@ -90,21 +91,21 @@ describe('Signup', () => { cy.get('#password').type('dragonfly1'); cy.get('#confirmPassword').type(`dragonfly1{enter}`); - // show error message + // Show error message. cy.get('.MuiAlert-message').should('be.visible').and('contain', 'Failed to fetch'); - // close error message cy.get('.MuiAlert-action > .MuiButtonBase-root').click(); - cy.get('.MuiSnackbar-root > .MuiPaper-root').should('not.exist'); }); it('click the `Sign in` button', () => { cy.get('.MuiTypography-inherit > .MuiTypography-root').click(); - // Then I see that the current page is the signin + + // Then I see that the current page is the signin! cy.url().should('include', '/signin'); cy.get('.MuiTypography-inherit > .MuiTypography-root').click(); - // Then I see that the current page is the signup + + // Then I see that the current page is the signup! cy.url().should('include', '/signup'); }); @@ -113,6 +114,7 @@ describe('Signup', () => { const nameLengthExceeds = _.times(11, () => _.sample('abcdefghijklmnopqrstuvwxyz')).join(''); const passsword = _.times(8, () => _.sample('abcdefghijklmnopqrstuvwxyz')).join(''); + // Should display message account the validation error. cy.get('#account').type(nameNotLongEnough); cy.get('#account-helper-text').should('be.visible').and('contain', 'Fill in the characters, the length is 3-10.'); @@ -122,20 +124,25 @@ describe('Signup', () => { cy.get('#account').clear(); cy.get('#account').type('root'); - // verification passed + + // Verification passed. cy.get('#account-helper-text').should('not.exist'); + // Should display message email the validation error. cy.get('#email').type('root'); cy.get('#email-helper-text').should('be.visible').and('contain', 'Email is invalid or already taken.'); cy.get('#email').clear(); cy.get('#email').type('root@console.com'); - // verification passed + + // Verification passed. cy.get('#email-helper-text').should('not.exist'); + // Should display message password the validation error. cy.get('#password').type(passsword); - // missing number + + // Missing number. cy.get('#password-helper-text') .should('be.visible') .and('contain', 'At least 8-16 characters, with at least 1 lowercase letter and 1 number.'); @@ -143,17 +150,21 @@ describe('Signup', () => { cy.get('#password').clear(); cy.get('#password').type('dragonfly1'); - // verification passed + + // Verification passed. cy.get('#password-helper-text').should('not.exist'); + // Should display message confirm password the validation error. cy.get('#confirmPassword').type(`dragonfly`); - // confirm password verification error when the two passwords are not the same + + // Confirm password verification error when the two passwords are not the same. cy.get('#confirmPassword-helper-text').should('be.visible').and('contain', 'Please enter the same password.'); cy.get('#confirmPassword').clear(); cy.get('#confirmPassword').type('dragonfly1'); - // verification passed + + // verification passed. cy.get('#confirmPassword-helper-text').should('not.exist'); }); }); diff --git a/cypress/fixtures/api/clusters/create-cluster.json b/cypress/fixtures/api/clusters/create-cluster.json new file mode 100644 index 0000000..b40f36b --- /dev/null +++ b/cypress/fixtures/api/clusters/create-cluster.json @@ -0,0 +1,314 @@ +[ + { + "id": 1, + "name": "cluster-1", + "bio": "Cluster-1 is a high-performance computing cluster located in China, specifically in Hangzhou and Beijing data centers.", + "scopes": { + "idc": "Hangzhou|Shanghai|Beijing", + "location": "China|Hang|Zhou", + "cidrs": ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] + }, + "scheduler_cluster_id": 1, + "seed_peer_cluster_id": 1, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 51, + "concurrent_piece_count": 4 + }, + "created_at": "2023-10-31T07:48:35Z", + "updated_at": "2023-10-31T07:48:35Z", + "is_default": true + }, + { + "id": 2, + "name": "cluster-2", + "bio": "", + "scopes": { + "idc": "", + "location": "", + "cidrs": [] + }, + "scheduler_cluster_id": 2, + "seed_peer_cluster_id": 2, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-10-31T17:48:35Z", + "updated_at": "2023-10-31T17:48:35Z", + "is_default": false + }, + { + "id": 3, + "name": "cluster-3", + "bio": "Cluster-3 is a high-performance computing cluster located in Korea, specifically in Seoul data centers.", + "scopes": { + "idc": "Korea", + "location": "Seoul|Korea", + "cidrs": ["192.168.0.0/16", "172.16.0.0/12"] + }, + "scheduler_cluster_id": 3, + "seed_peer_cluster_id": 3, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-08T11:19:36Z", + "updated_at": "2023-11-08T11:19:36Z", + "is_default": true + }, + { + "id": 4, + "name": "cluster-4", + "bio": "Cluster-4 is a high-performance computing cluster located in China, specifically in Hangzhou data centers.", + "scopes": { + "idc": "hz|dl", + "location": "China|Hang|Zhou", + "cidrs": ["10.0.0.0/8", "192.168.0.0/16"] + }, + "scheduler_cluster_id": 4, + "seed_peer_cluster_id": 4, + "scheduler_cluster_config": { + "candidate_parent_limit": 5, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 5 + }, + "created_at": "2023-11-09T18:19:36Z", + "updated_at": "2023-11-09T18:19:36Z", + "is_default": true + }, + { + "id": 5, + "name": "cluster-5", + "bio": "Cluster-5 is a high-performance computing cluster located in China, specifically in Chongqing data centers.", + "scopes": { + "idc": "cq|cd", + "location": "China|Chong|Qing", + "cidrs": ["10.0.0.0/8", "192.168.0.0/16"] + }, + "scheduler_cluster_id": 5, + "seed_peer_cluster_id": 5, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-01T10:19:36Z", + "updated_at": "2023-11-01T10:19:36Z", + "is_default": false + }, + { + "id": 6, + "name": "cluster-6", + "bio": "Cluster-6 is a high-performance computing cluster located in China, specifically in Chongdu data centers.", + "scopes": { + "idc": "cq|cd", + "location": "China|Chong|Du", + "cidrs": ["10.0.0.0/8", "192.168.0.0/16"] + }, + "scheduler_cluster_id": 6, + "seed_peer_cluster_id": 6, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-09T20:12:36Z", + "updated_at": "2023-11-09T20:12:36Z", + "is_default": true + }, + { + "id": 7, + "name": "cluster-7", + "bio": "Cluster-7 is a high-performance computing cluster located in China, specifically in Chongdu data centers.", + "scopes": { + "idc": "cd", + "location": "China|Cheng|Du", + "cidrs": ["10.0.0.0/8", "172.16.0.0/19"] + }, + "scheduler_cluster_id": 7, + "seed_peer_cluster_id": 7, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-11T10:19:36Z", + "updated_at": "2023-11-11T10:19:36Z", + "is_default": true + }, + { + "id": 8, + "name": "cluster-8", + "bio": "Cluster-8 is a high-performance computing cluster located in China, specifically in Jiangsu data centers.", + "scopes": { + "idc": "js", + "location": "China|Jiang|Su", + "cidrs": ["10.0.0.0/5", "172.16.0.0/19"] + }, + "scheduler_cluster_id": 8, + "seed_peer_cluster_id": 8, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-11T13:19:36Z", + "updated_at": "2023-11-11T13:19:36Z", + "is_default": false + }, + { + "id": 9, + "name": "cluster-9", + "bio": "Cluster-9 is a high-performance computing cluster located in China, specifically in Hangzhou data centers.", + "scopes": { + "idc": "hz|hf", + "location": "China|Hang|Zhou", + "cidrs": ["10.0.0.0/8", "192.168.0.0/16"] + }, + "scheduler_cluster_id": 9, + "seed_peer_cluster_id": 9, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-11T10:29:36Z", + "updated_at": "2023-11-11T10:29:36Z", + "is_default": true + }, + { + "id": 10, + "name": "cluster-10", + "bio": "Cluster-10 is a high-performance computing cluster located in England, specifically in London data centers.", + "scopes": { + "idc": "London", + "location": "London|England", + "cidrs": ["192.168.255.255"] + }, + "scheduler_cluster_id": 10, + "seed_peer_cluster_id": 10, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-15T05:19:36Z", + "updated_at": "2023-11-15T05:19:36Z", + "is_default": true + }, + { + "id": 11, + "name": "cluster-11", + "bio": "Cluster-10 is a high-performance computing cluster located in France, specifically in Paris data centers.", + "scopes": { + "idc": "Paris", + "location": "Paris|France", + "cidrs": ["192.168.0.0", "10.0.0.0"] + }, + "scheduler_cluster_id": 11, + "seed_peer_cluster_id": 11, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 4 + }, + "created_at": "2023-11-01T10:19:36Z", + "updated_at": "2023-11-01T10:19:36Z", + "is_default": false + }, + { + "id": 12, + "name": "cluster-12", + "bio": "Add new cluster case", + "scopes": { + "idc": "hz|sh", + "location": "China|Hang|Zhou", + "cidrs": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] + }, + "scheduler_cluster_id": 12, + "seed_peer_cluster_id": 12, + "scheduler_cluster_config": { + "candidate_parent_limit": 4, + "filter_parent_limit": 40 + }, + "seed_peer_cluster_config": { + "load_limit": 300 + }, + "peer_cluster_config": { + "load_limit": 50, + "concurrent_piece_count": 10 + }, + "created_at": "2023-11-21T06:49:41Z", + "updated_at": "2023-11-21T06:49:41Z", + "is_default": true + } +] diff --git a/cypress/fixtures/api/clusters/cluster-search.json b/cypress/fixtures/api/clusters/search-cluster.json similarity index 100% rename from cypress/fixtures/api/clusters/cluster-search.json rename to cypress/fixtures/api/clusters/search-cluster.json diff --git a/src/components/clusters/index.tsx b/src/components/clusters/index.tsx index 27cccb8..bdca907 100644 --- a/src/components/clusters/index.tsx +++ b/src/components/clusters/index.tsx @@ -62,7 +62,7 @@ export default function Clusters() { const [clusterIsLoading, setClusterIsLoading] = useState(true); const [clusterPage, setClusterPage] = useState(1); const [totalPages, setTotalPages] = useState(1); - const [clustersSearch, setClustersSearch] = useState(''); + const [searchClusters, setSearchClusters] = useState(''); const [clusterCount, setClusterCount] = useState([]); const [cluster, setCluster] = useState([]); const [scheduler, setScheduler] = useState([]); @@ -134,11 +134,11 @@ export default function Clusters() { const numberOfActiveSeedPeers = Array.isArray(seedPeer) && seedPeer?.filter((item: any) => item?.state === 'active').length; - const clusterSearch = async () => { + const searchCluster = async () => { try { setClusterIsLoading(true); - const cluster = clustersSearch - ? await getClusters({ page: 1, per_page: MAX_PAGE_SIZE, name: clustersSearch }) + const cluster = searchClusters + ? await getClusters({ page: 1, per_page: MAX_PAGE_SIZE, name: searchClusters }) : await getClusters({ page: 1, per_page: MAX_PAGE_SIZE }); setCluster(cluster); @@ -153,7 +153,7 @@ export default function Clusters() { } }; - const clusterSearchKeyDown = (event: any) => { + const searchClusterKeyDown = (event: any) => { if (event.key === 'Enter') { event.preventDefault(); const submitButton = document.getElementById('submit-button'); @@ -311,10 +311,10 @@ export default function Clusters() { color="secondary" id="free-solo-demo" freeSolo - onKeyDown={clusterSearchKeyDown} - inputValue={clustersSearch} + onKeyDown={searchClusterKeyDown} + inputValue={searchClusters} onInputChange={(_event, newInputValue) => { - setClustersSearch(newInputValue); + setSearchClusters(newInputValue); }} options={(Array.isArray(clusterCount) && clusterCount.map((option) => option?.name)) || ['']} renderInput={(params) => } @@ -325,7 +325,7 @@ export default function Clusters() { aria-label="search" id="submit-button" size="small" - onClick={clusterSearch} + onClick={searchCluster} sx={{ width: '3rem' }} > diff --git a/src/components/clusters/new.tsx b/src/components/clusters/new.tsx index ecf05fa..09416fc 100644 --- a/src/components/clusters/new.tsx +++ b/src/components/clusters/new.tsx @@ -223,7 +223,7 @@ export default function NewCluster() { const configForm = [ { formProps: { - id: 'seed peer load limit', + id: 'seedPeerLoadLimit', label: 'Seed Peer load limit', name: 'seedPeerLoadLimit', type: 'number', @@ -258,7 +258,7 @@ export default function NewCluster() { }, { formProps: { - id: 'peer load limit', + id: 'peerLoadLimit', label: 'Peer load limit', name: 'peerLoadLimit', type: 'number', @@ -295,7 +295,7 @@ export default function NewCluster() { }, { formProps: { - id: 'number of concurrent download pieces', + id: 'numberOfConcurrentDownloadPieces', label: 'Number of concurrent download pieces', name: 'numberOfConcurrentDownloadPieces', type: 'number', @@ -330,7 +330,7 @@ export default function NewCluster() { }, { formProps: { - id: 'candidate parent limit', + id: 'candidateParentLimit', label: 'Candidate parent limit', name: 'candidateParentLimit', type: 'number', @@ -365,7 +365,7 @@ export default function NewCluster() { }, { formProps: { - id: 'filter parent limit', + id: 'filterParentLimit', label: 'Filter parent limit', name: 'filterParentLimit', type: 'number', @@ -420,7 +420,7 @@ export default function NewCluster() { const cidrsText = event.currentTarget.elements.cidrs.value; if (idcText) { - setIDCHelperText('Please press ENTER to end the IDC creation'); + setIDCHelperText('Please press ENTER to end the IDC creation.'); setIDCError(true); } else { setIDCError(false); @@ -428,7 +428,7 @@ export default function NewCluster() { } if (cidrsText) { - setCIDRsHelperText('Please press ENTER to end the CIDRs creation'); + setCIDRsHelperText('Please press ENTER to end the CIDRs creation.'); setCIDRsError(true); } else { setCIDRsError(false); @@ -710,7 +710,7 @@ export default function NewCluster() { size="small" variant="outlined" loadingPosition="end" - id="cancle" + id="cancel" sx={{ '&.MuiLoadingButton-root': { color: 'var(--calcel-size-color)', @@ -743,6 +743,7 @@ export default function NewCluster() { variant="outlined" type="submit" loadingPosition="end" + id="save" sx={{ '&.MuiLoadingButton-root': { backgroundColor: 'var(--save-color)',