diff --git a/ui/cypress/e2e/tokens.test.ts b/ui/cypress/e2e/tokens.test.ts new file mode 100644 index 0000000000..f6448ac742 --- /dev/null +++ b/ui/cypress/e2e/tokens.test.ts @@ -0,0 +1,344 @@ +import {Organization} from '@influxdata/influx' + +describe('tokens', () => { + let authData: {description: string; status: boolean; id: string}[] + let testTokens: { + id: string + description: string + status: string + permissions: object[] + }[] + + beforeEach(() => { + cy.flush() + + authData = [] + + cy.signin().then(({body}) => { + const { + org: {id}, + } = body + cy.wrap(body.org).as('org') + + testTokens = [ + { + id: id, + description: 'token test \u0950', + status: 'active', + permissions: [ + {action: 'write', resource: {type: 'views'}}, + {action: 'write', resource: {type: 'documents'}}, + {action: 'write', resource: {type: 'dashboards'}}, + {action: 'write', resource: {type: 'buckets'}}, + ], + }, + { + id: id, + description: 'token test 02', + status: 'inactive', + permissions: [ + {action: 'write', resource: {type: 'views'}}, + {action: 'write', resource: {type: 'documents'}}, + {action: 'write', resource: {type: 'dashboards'}}, + {action: 'write', resource: {type: 'buckets'}}, + ], + }, + { + id: id, + description: 'token test 03', + status: 'inactive', + permissions: [ + {action: 'read', resource: {type: 'views'}}, + {action: 'read', resource: {type: 'documents'}}, + {action: 'read', resource: {type: 'dashboards'}}, + {action: 'read', resource: {type: 'buckets'}}, + ], + }, + ] + + //check out array.reduce for the nested calls here + cy.request('api/v2/authorizations').then(resp => { + expect(resp.body).to.exist + authData.push({ + description: resp.body.authorizations[0].description, + status: resp.body.authorizations[0].status === 'active', + id: resp.body.authorizations[0].id, + }) + + testTokens.forEach(token => { + cy.createToken( + token.id, + token.description, + token.status, + token.permissions + ).then(resp => { + expect(resp.body).to.exist + authData.push({ + description: resp.body.description, + status: resp.body.status === 'active', + id: resp.body.id, + }) + }) + }) + + cy.fixture('routes').then(({orgs}) => { + cy.visit(`${orgs}/${id}/tokens`) + }) + }) + }) + }) + + it('can list tokens', () => { + cy.getByTestID('table-row').should('have.length', 4) + cy.getByTestID('table-row').then(rows => { + authData = authData.sort((a, b) => + // eslint-disable-next-line + a.description < b.description ? -1 : a.description > b.description ? 1 : 0 + ) + + for (var i = 0; i < rows.length; i++) { + cy.getByTestID('editable-name') + .eq(i) + .children('a') + .children('span') + .should('have.text', authData[i].description) + + if (authData[i].status) { + cy.getByTestID('slide-toggle') + .eq(i) + .should('have.class', 'active') + } else { + cy.getByTestID('slide-toggle') + .eq(i) + .should('not.have.class', 'active') + } + } + }) + }) + + it('can filter tokens', () => { + //basic filter + cy.getByTestID('input-field--filter').type('test') + cy.getByTestID('table-row').should('have.length', 3) + + //clear filter + cy.getByTestID('input-field--filter').clear() + cy.getByTestID('table-row').should('have.length', 4) + + //exotic filter + cy.getByTestID('input-field--filter').type('\u0950') + cy.getByTestID('table-row').should('have.length', 1) + }) + + it('can change token activation status', () => { + //toggle on + cy.getByTestID('table-row') + .contains('token test 02') + .parents('[data-testid=table-row]') + .within(() => { + cy.getByTestID('slide-toggle') + .click() + .then(() => { + //wait for backend to sync + cy.wait(1000) + + //check for status update on backend + // @ts-ignore + cy.request( + 'api/v2/authorizations/' + + authData.find(function(item) { + return item.description === 'token test 02' + }).id + ).then(resp => { + expect(resp.body.status).equals('active') + }) + }) + }) + + cy.getByTestID('table-row') + .contains('token test 02') + .parents('[data-testid=table-row]') + .within(() => { + cy.getByTestID('slide-toggle').should('have.class', 'active') + }) + + cy.getByTestID('table-row') + .contains('token test 02') + .parents('[data-testid=table-row]') + .within(() => { + cy.getByTestID('slide-toggle') + .click() + .then(() => { + //wait for backend to sync + cy.wait(1000) + + //check for status update on backend + // @ts-ignore + cy.request( + 'api/v2/authorizations/' + + authData.find(function(item) { + return item.description === 'token test 02' + }).id + ).then(resp => { + expect(resp.body.status).equals('inactive') + }) + }) + }) + + cy.getByTestID('table-row') + .contains('token test 02') + .parents('[data-testid=table-row]') + .within(() => { + cy.getByTestID('slide-toggle').should('not.have.class', 'active') + }) + }) + + it('can delete a token', () => { + cy.getByTestID('table-row').should('have.length', 4) + + cy.getByTestID('table-row') + .contains('token test 03') + .parents('[data-testid=table-row]') + .within(() => { + cy.getByTestID('delete-button') + .click() + .then(() => { + cy.getByTestID('confirmation-button').click({force: true}) + }) + }) + + cy.getByTestID('table-row').should('have.length', 3) + + cy.getByTestID('table-row') + .contains('token test 03') + .should('not.exist') + }) + + it('can generate a read/write token', () => { + cy.getByTestID('table-row').should('have.length', 4) + + //create some extra buckets for filters + cy.get('@org').then(({id, name}) => { + cy.createBucket(id, name, 'Magna Carta').then(() => { + cy.createBucket(id, name, 'Sicilsky Bull').then(() => { + cy.createBucket(id, name, 'A la Carta') + }) + }) + }) + + // open overlay + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestIDSubStr('dropdown--item').should('have.length', 2) + cy.getByTestID('dropdown--item Read/Write Token').click() + cy.getByTestID('overlay--container').should('be.visible') + + //check cancel + cy.getByTestID('button--cancel').click() + cy.getByTestID('overlay--container').should('not.be.visible') + cy.getByTestID('table-row').should('have.length', 4) + + // open overlay - again + cy.getByTestID('dropdown-button--gen-token').click() + cy.getByTestIDSubStr('dropdown--item').should('have.length', 2) + cy.getByTestID('dropdown--item Read/Write Token').click() + cy.getByTestID('overlay--container').should('be.visible') + + //Create a token //todo filters in this or seperate test + cy.getByTestID('input-field--descr').type('Jeton 01') + cy.getByTestID('builder-card--body') + .eq(0) + .within(() => { + cy.getByTitle('Click to filter by Sicilsky Bull').click() + cy.getByTitle('Click to filter by A la Carta').click() + }) + cy.getByTestID('builder-card--body') + .eq(1) + .within(() => { + cy.getByTitle('Click to filter by Sicilsky Bull').click() + }) + + cy.getByTestID('button--save').click() + cy.getByTestID('overlay--container').should('not.be.visible') + + //Verify token + cy.getByTestID('table-row') + .should('have.length', 5) + .contains('Jeton 01') + .should('be.visible') + cy.getByTestID('table-row') + .contains('Jeton 01') + .click() + cy.getByTestID('overlay--container').should('be.visible') + cy.getByTestID('overlay--header').should('contain', 'Jeton 01') + cy.getByTestID('permissions-section').should('have.length', 2) + + cy.getByTestID('permissions-section') + .contains('buckets-Sicilsky Bull') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('buckets-Sicilsky Bull') + .parent() + .parent() + .within(() => { + cy.getByTestID('permissions--item').should('have.length', 2) + cy.getByTestID('permissions--item') + .contains('write') + .should('be.visible') + cy.getByTestID('permissions--item') + .contains('read') + .should('be.visible') + }) + + cy.getByTestID('permissions-section') + .contains('buckets-A la Carta') + .parent() + .parent() + .within(() => { + cy.getByTestID('permissions--item').should('have.length', 1) + cy.getByTestID('permissions--item') + .contains('write') + .should('not.be.visible') + cy.getByTestID('permissions--item') + .contains('read') + .should('be.visible') + }) + }) + + it('can view a token', () => { + cy.getByTestID('table-row') + .contains('token test \u0950') + .click() + + //title match + cy.getByTestID('overlay--container').should('be.visible') + cy.getByTestID('overlay--header').should('contain', 'token test \u0950') + + //summary match + cy.getByTestID('permissions-section').should('have.length', 4) + cy.getByTestID('permissions-section') + .contains('views') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('documents') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('dashboards') + .should('be.visible') + cy.getByTestID('permissions-section') + .contains('buckets') + .should('be.visible') + + //copy to clipboard + notification + cy.getByTestID('button-copy').click() + cy.getByTestID('notification-success').should($msg => { + expect($msg).to.contain('has been copied to clipboard') + }) + //todo check system clipboard + + //close button + cy.getByTestID('overlay--header').within(() => { + cy.get('button').click() + }) + cy.getByTestID('overlay--container').should('not.be.visible') + }) +}) diff --git a/ui/cypress/index.d.ts b/ui/cypress/index.d.ts index 67d978ead4..cff5c624be 100644 --- a/ui/cypress/index.d.ts +++ b/ui/cypress/index.d.ts @@ -17,8 +17,10 @@ import { createScraper, fluxEqual, createTelegraf, + createToken, createDashboardTemplate, writeData, + getByTestIDSubStr, } from './support/commands' declare global { @@ -36,12 +38,14 @@ declare global { getByTestID: typeof getByTestID getByInputName: typeof getByInputName getByTitle: typeof getByTitle + getByTestIDSubStr: typeof getByTestIDSubStr createAndAddLabel: typeof createAndAddLabel createLabel: typeof createLabel createBucket: typeof createBucket createScraper: typeof createScraper fluxEqual: typeof fluxEqual createTelegraf: typeof createTelegraf + createToken: typeof createToken writeData: typeof writeData } } diff --git a/ui/cypress/support/commands.ts b/ui/cypress/support/commands.ts index 80c9aa0fe5..89b0f93fa6 100644 --- a/ui/cypress/support/commands.ts +++ b/ui/cypress/support/commands.ts @@ -237,6 +237,27 @@ export const createTelegraf = ( }) } +/* +[{action: 'write', resource: {type: 'views'}}, + {action: 'write', resource: {type: 'documents'}}, + {action: 'write', resource: {type: 'dashboards'}}, + {action: 'write', resource: {type: 'buckets'}}]} + */ + +export const createToken = ( + orgId: string, + description: string, + status: string, + permissions: object[] +): Cypress.Chainable => { + return cy.request('POST', 'api/v2/authorizations', { + orgID: orgId, + description: description, + status: status, + permissions: permissions, + }) +} + // TODO: have to go through setup because we cannot create a user w/ a password via the user API export const setupUser = (): Cypress.Chainable => { return cy.fixture('user').then(({username, password, org, bucket}) => { @@ -274,12 +295,16 @@ export const getByTestID = (dataTest: string): Cypress.Chainable => { return cy.get(`[data-testid="${dataTest}"]`) } +export const getByTestIDSubStr = (dataTest: string): Cypress.Chainable => { + return cy.get(`[data-testid*="${dataTest}"]`) +} + export const getByInputName = (name: string): Cypress.Chainable => { return cy.get(`input[name=${name}]`) } export const getByTitle = (name: string): Cypress.Chainable => { - return cy.get(`[title=${name}]`) + return cy.get(`[title="${name}"]`) } // custom assertions @@ -306,6 +331,7 @@ Cypress.Commands.add('fluxEqual', fluxEqual) Cypress.Commands.add('getByTestID', getByTestID) Cypress.Commands.add('getByInputName', getByInputName) Cypress.Commands.add('getByTitle', getByTitle) +Cypress.Commands.add('getByTestIDSubStr', getByTestIDSubStr) // auth flow Cypress.Commands.add('signin', signin) @@ -335,6 +361,9 @@ Cypress.Commands.add('flush', flush) // tasks Cypress.Commands.add('createTask', createTask) +//Tokems +Cypress.Commands.add('createToken', createToken) + // variables Cypress.Commands.add('createVariable', createVariable) diff --git a/ui/src/authorizations/components/BucketsTokenOverlay.tsx b/ui/src/authorizations/components/BucketsTokenOverlay.tsx index 9d1851b7bc..9b232f4976 100644 --- a/ui/src/authorizations/components/BucketsTokenOverlay.tsx +++ b/ui/src/authorizations/components/BucketsTokenOverlay.tsx @@ -96,6 +96,7 @@ class BucketsTokenOverlay extends PureComponent { placeholder="Describe this new token" value={description} onChange={this.handleInputChange} + testID="input-field--descr" /> @@ -143,6 +144,7 @@ class BucketsTokenOverlay extends PureComponent { text="Cancel" icon={IconFont.Remove} onClick={this.handleDismiss} + testID="button--cancel" />