test(e2e): Bonitoo cypress tokens (#13895)

* added writeData command

* better junit reporting

* adding package-lock.json as per circle-ci hint

* delinting

* removed chainable .only.

* removed commented line

* starting onboarding test

* onboarding assert

* complete onboarding to quick start

* additional onboarding tests

* delinting

* adding field requirements test

* fixing merge conflicts

* WIP - updating to new org ctx - stablizing

* WIP - linting - stablizing

* fixing merge conflicts

* refactor onboarding test to leverage data-testid

* fixing jstests with new data-testid attribs

* resynch promql.go

* refactor add last data-testid replacements onb test

* replacing lost asserts

* improving assert

* passing in additional data-testid values

* removing bin_gen.go from branch

* starting tokens test

* troubleshoot tokens test

* WIP tokens test - clean up after troubleshoot

* WIP - tokens tests

* synching tokens test with clockface changes

* updating testID in tokens test

* re-enable all current tokens tests

* linted tokens.test.ts

* stablize beforeEach in tokens.test
pull/13614/head
karel-rehor 2019-05-16 19:48:20 +02:00 committed by Andrew Watkins
parent 7b2d76c77b
commit e9ccde1d78
10 changed files with 400 additions and 7 deletions

View File

@ -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<Organization>('@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')
})
})

View File

@ -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
}
}

View File

@ -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<Cypress.Response> => {
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<Cypress.Response> => {
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)

View File

@ -96,6 +96,7 @@ class BucketsTokenOverlay extends PureComponent<Props, State> {
placeholder="Describe this new token"
value={description}
onChange={this.handleInputChange}
testID="input-field--descr"
/>
</Form.Element>
<Form.Element label="">
@ -143,6 +144,7 @@ class BucketsTokenOverlay extends PureComponent<Props, State> {
text="Cancel"
icon={IconFont.Remove}
onClick={this.handleDismiss}
testID="button--cancel"
/>
<Button
@ -150,6 +152,7 @@ class BucketsTokenOverlay extends PureComponent<Props, State> {
icon={IconFont.Checkmark}
color={ComponentColor.Success}
type={ButtonType.Submit}
testID="button--save"
/>
</ComponentSpacer>
</ComponentSpacer>

View File

@ -26,6 +26,8 @@ export default class GenerateTokenDropdown extends PureComponent<Props> {
buttonSize={ComponentSize.Small}
widthPixels={160}
onChange={this.handleSelect}
testID="dropdown--gen-token"
buttonTestID="dropdown-button--gen-token"
>
{this.optionItems}
</Dropdown>

View File

@ -60,6 +60,7 @@ class TokensTab extends PureComponent<Props, State> {
placeholder="Filter Tokens..."
onChange={this.handleChangeSearchTerm}
widthPixels={256}
testID="input-field--filter"
/>
<GenerateTokenDropdown
onSelectAllAccess={this.handleGenerateAllAccess}

View File

@ -45,6 +45,7 @@ class CopyButton extends PureComponent<Props> {
titleText="Copy to Clipboard"
text="Copy to Clipboard"
onClick={this.handleClickCopy}
testID="button-copy"
/>
</CopyToClipboard>
)

View File

@ -51,7 +51,7 @@ class EditableName extends Component<Props, State> {
const {name, onEditName, hrefValue, noNameString, testID} = this.props
return (
<div className={this.className}>
<div className={this.className} data-testid={testID}>
<SpinnerContainer
loading={this.state.loading}
spinnerComponent={<TechnoSpinner diameterPixels={20} />}
@ -63,7 +63,7 @@ class EditableName extends Component<Props, State> {
<div
className="editable-name--toggle"
onClick={this.handleStartEditing}
data-testid={testID}
data-testid={testID + '--toggle'}
>
<span className="icon pencil" />
</div>

View File

@ -12,6 +12,7 @@ import {IconFont} from 'src/clockface'
interface Props {
mode?: PermissionsWidgetMode
id: string
testID?: string
label: string
selected: PermissionsWidgetSelection
onToggle?: (
@ -22,10 +23,14 @@ interface Props {
class PermissionsWidgetItem extends Component<Props> {
public render() {
const {label} = this.props
const {label, testID} = this.props
return (
<li className={this.className} onClick={this.handleClick}>
<li
className={this.className}
onClick={this.handleClick}
data-testid={testID || 'permissions--item'}
>
{this.checkbox}
<label className="permissions-widget--item-label">{label}</label>
</li>

View File

@ -21,14 +21,18 @@ interface Props {
title: string
onSelectAll?: (sectionID: string) => void
onDeselectAll?: (sectionID: string) => void
testID?: string
}
class PermissionsWidgetSection extends Component<Props> {
public render() {
const {title} = this.props
const {title, testID} = this.props
return (
<section className="permissions-widget--section">
<section
className="permissions-widget--section"
data-testid={testID || 'permissions-section'}
>
<header className="permissions-widget--section-heading">
<h3 className="permissions-widget--section-title">{title}</h3>
{this.selectionButtons}