diff --git a/assets/js/application.js b/assets/js/application.js index 6619680..a4506c2 100644 --- a/assets/js/application.js +++ b/assets/js/application.js @@ -8,6 +8,7 @@ function showDiv(divId, element, val) { document.addEventListener('DOMContentLoaded', function (event) { const select = document.getElementById('access-token-validity') + if (!select) return select.onchange = () => showDiv('custom-access-token-validity-element', select, 'custom') showDiv('custom-access-token-validity-element', select, 'custom') }) diff --git a/assets/js/initMOJFilterPage.js b/assets/js/initMOJFilterPage.js index 0a5b310..9a14625 100644 --- a/assets/js/initMOJFilterPage.js +++ b/assets/js/initMOJFilterPage.js @@ -5,7 +5,10 @@ new MOJFrontend.FilterToggleButton({ container: $('.moj-action-bar__filter'), showText: 'Show filter', hideText: 'Hide filter', - classes: 'govuk-button--secondary', + classes: 'govuk-button--secondary toggle-filter-button', + attributes: { + 'data-qa': 'toggle-filter-button', + }, }, closeButton: { container: $('.moj-filter__header-action'), diff --git a/integration_tests/e2e/add-base-client.cy.ts b/integration_tests/e2e/add-base-client.cy.ts new file mode 100644 index 0000000..a14e3e1 --- /dev/null +++ b/integration_tests/e2e/add-base-client.cy.ts @@ -0,0 +1,124 @@ +import Page from '../pages/page' +import AddBaseClientGrantPage from '../pages/addBaseClientGrant' +import AddBaseClientDetailsPage from '../pages/addBaseClientDetails' +import ViewBaseClientListPage from '../pages/viewBaseClientList' + +const visitAddBaseClientPage = (): AddBaseClientGrantPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/new' }) + return Page.verifyOnPage(AddBaseClientGrantPage) +} + +const visitAddWithClientCredentialsPage = (): AddBaseClientDetailsPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/new?grant=client-credentials' }) + return Page.verifyOnPage(AddBaseClientDetailsPage) +} + +context('Add client page', () => { + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubManageUser') + cy.task('stubListBaseClients') + }) + + context('Add base client choose grant screen', () => { + let addBaseClientGrantPage: AddBaseClientGrantPage + + beforeEach(() => { + addBaseClientGrantPage = visitAddBaseClientPage() + }) + + it('User can see select a grant type radio buttons', () => { + addBaseClientGrantPage.grantTypeRadioGroup().should('be.visible') + }) + + it('Client credentials is selected by default', () => { + addBaseClientGrantPage.clientCredentialsRadio().should('have.attr', 'checked') + }) + + it('Authorization code is disabled (for now)', () => { + addBaseClientGrantPage.authorizationCodeRadio().should('have.attr', 'disabled') + }) + + it('User clicks cancel to return to home screen', () => { + addBaseClientGrantPage.cancelLink().click() + Page.verifyOnPage(ViewBaseClientListPage) + }) + + it('User clicks continue to got to add base client details screen', () => { + addBaseClientGrantPage.continueButton().click() + Page.verifyOnPage(AddBaseClientDetailsPage) + }) + }) + + context('Add base client enter details screen - client credentials', () => { + let addBaseClientDetailsPage: AddBaseClientDetailsPage + + beforeEach(() => { + addBaseClientDetailsPage = visitAddWithClientCredentialsPage() + }) + + it('User can see base-client form inputs', () => { + addBaseClientDetailsPage.baseClientIdInput().should('be.visible') + addBaseClientDetailsPage.baseClientServiceRadioButton().should('exist') + addBaseClientDetailsPage.baseClientPersonalRadioButton().should('exist') + addBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().should('be.visible') + addBaseClientDetailsPage.baseClientApprovedScopesInput().should('be.visible') + }) + + it('User can see audit trail form inputs', () => { + addBaseClientDetailsPage.auditTrailDetailsInput().should('be.visible') + }) + + it('User can see grant details form inputs', () => { + addBaseClientDetailsPage.grantTypeInput().should('be.visible') + addBaseClientDetailsPage.grantAuthoritiesInput().should('be.visible') + addBaseClientDetailsPage.grantDatabaseUsernameInput().should('be.visible') + }) + + it('User can see config form inputs', () => { + addBaseClientDetailsPage.configDoesExpireCheckbox().should('exist') + addBaseClientDetailsPage.configAllowedIpsInput().should('be.visible') + }) + + context('Access token validity dropdown', () => { + it('Custom input is initially hidden', () => { + addBaseClientDetailsPage.baseClientAccessTokenValidityInput().should('not.be.visible') + }) + + it('Custom input is shown when custom option is selected', () => { + addBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().select('Custom') + addBaseClientDetailsPage.baseClientAccessTokenValidityInput().should('be.visible') + }) + }) + + context('Allow client to expire ', () => { + it('Does expire checkbox is unchecked by default', () => { + addBaseClientDetailsPage.configDoesExpireCheckbox().should('not.be.checked') + }) + + it('Expiry days input is shown if checkbox is selected', () => { + addBaseClientDetailsPage.configDoesExpireCheckbox().click() + addBaseClientDetailsPage.configExpiryDaysInput().should('be.visible') + }) + }) + + it('User clicks cancel to return to home screen', () => { + addBaseClientDetailsPage.cancelLink().click() + Page.verifyOnPage(ViewBaseClientListPage) + }) + + it('User clicks continue to post new client details screen', () => { + // enter a base client id + addBaseClientDetailsPage.baseClientIdInput().type('new-client-id') + + // set up to check the POST request + cy.intercept('POST', '/base-clients/new', req => { + const { body } = req + expect(body).to.contain('baseClientId=new-client-id') + }) + + addBaseClientDetailsPage.saveButton().click() + }) + }) +}) diff --git a/integration_tests/e2e/edit-base-client-deployment.cy.ts b/integration_tests/e2e/edit-base-client-deployment.cy.ts new file mode 100644 index 0000000..7c6e5d2 --- /dev/null +++ b/integration_tests/e2e/edit-base-client-deployment.cy.ts @@ -0,0 +1,58 @@ +import Page from '../pages/page' +import ViewBaseClientPage from '../pages/viewBaseClient' +import EditBaseClientDeploymentDetailsPage from '../pages/editBaseClientDeploymentDetails' + +const visitEditBaseClientDeploymentDetailsPage = (): EditBaseClientDeploymentDetailsPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1/deployment' }) + return Page.verifyOnPage(EditBaseClientDeploymentDetailsPage) +} + +context('Edit base client deployment details page', () => { + let editBaseClientDeploymentDetailsPage: EditBaseClientDeploymentDetailsPage + + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubManageUser') + cy.task('stubListBaseClients') + cy.task('stubGetBaseClient') + cy.task('stubGetListClientInstancesList') + editBaseClientDeploymentDetailsPage = visitEditBaseClientDeploymentDetailsPage() + }) + + it('User can see contact details form inputs', () => { + editBaseClientDeploymentDetailsPage.baseClientSummaryList().should('be.visible') + editBaseClientDeploymentDetailsPage.deploymentTeamInput().should('be.visible') + editBaseClientDeploymentDetailsPage.deploymentTeamContactInput().should('be.visible') + editBaseClientDeploymentDetailsPage.deploymentTeamSlackInput().should('be.visible') + }) + + it('User can see platform details form inputs', () => { + editBaseClientDeploymentDetailsPage.platformHostingRadios().should('be.visible') + editBaseClientDeploymentDetailsPage.platformNamespaceInput().should('be.visible') + editBaseClientDeploymentDetailsPage.platformDeploymentInput().should('be.visible') + editBaseClientDeploymentDetailsPage.platformSecretNameInput().should('be.visible') + editBaseClientDeploymentDetailsPage.platformSecretKeyInput().should('be.visible') + editBaseClientDeploymentDetailsPage.platformClientIdInput().should('be.visible') + editBaseClientDeploymentDetailsPage.platformDeploymentInfoInput().should('be.visible') + }) + + it('User clicks cancel to return to base-client screen', () => { + editBaseClientDeploymentDetailsPage.cancelLink().click() + Page.verifyOnPage(ViewBaseClientPage) + }) + + it('User clicks continue to post new client deployment details', () => { + // enter new audit details + editBaseClientDeploymentDetailsPage.deploymentTeamInput().clear() + editBaseClientDeploymentDetailsPage.deploymentTeamInput().type('HAHA') + + // set up to check the POST request + cy.intercept('POST', '/base-clients/base_client_id_1/deployment', req => { + const { body } = req + expect(body).to.contain('team=HAHA') + }) + + editBaseClientDeploymentDetailsPage.saveButton().click() + }) +}) diff --git a/integration_tests/e2e/edit-base-client-details.cy.ts b/integration_tests/e2e/edit-base-client-details.cy.ts new file mode 100644 index 0000000..6d4dc13 --- /dev/null +++ b/integration_tests/e2e/edit-base-client-details.cy.ts @@ -0,0 +1,86 @@ +import Page from '../pages/page' +import EditBaseClientDetailsPage from '../pages/editBaseClientDetails' +import ViewBaseClientPage from '../pages/viewBaseClient' + +const visitEditBaseClientDetailsPage = (): EditBaseClientDetailsPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1/edit' }) + return Page.verifyOnPage(EditBaseClientDetailsPage) +} + +context('Edit base client details page', () => { + let editBaseClientDetailsPage: EditBaseClientDetailsPage + + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubManageUser') + cy.task('stubListBaseClients') + cy.task('stubGetBaseClient') + cy.task('stubGetListClientInstancesList') + editBaseClientDetailsPage = visitEditBaseClientDetailsPage() + }) + + it('User can see base-client form inputs', () => { + editBaseClientDetailsPage.baseClientIdInput().should('exist') + editBaseClientDetailsPage.baseClientServiceRadioButton().should('exist') + editBaseClientDetailsPage.baseClientPersonalRadioButton().should('exist') + editBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().should('be.visible') + editBaseClientDetailsPage.baseClientApprovedScopesInput().should('be.visible') + }) + + it('User can see audit trail form inputs', () => { + editBaseClientDetailsPage.auditTrailDetailsInput().should('be.visible') + }) + + it('User can see grant details form inputs', () => { + editBaseClientDetailsPage.grantTypeInput().should('be.visible') + editBaseClientDetailsPage.grantAuthoritiesInput().should('be.visible') + editBaseClientDetailsPage.grantDatabaseUsernameInput().should('be.visible') + }) + + it('User can see config form inputs', () => { + editBaseClientDetailsPage.configDoesExpireCheckbox().should('exist') + editBaseClientDetailsPage.configAllowedIpsInput().should('be.visible') + }) + + context('Access token validity dropdown', () => { + it('Custom input is initially hidden', () => { + editBaseClientDetailsPage.baseClientAccessTokenValidityInput().should('not.be.visible') + }) + + it('Custom input is shown when custom option is selected', () => { + editBaseClientDetailsPage.baseClientAccessTokenValidityDropdown().select('Custom') + editBaseClientDetailsPage.baseClientAccessTokenValidityInput().should('be.visible') + }) + }) + + context('Allow client to expire ', () => { + it('Does expire checkbox is unchecked by default', () => { + editBaseClientDetailsPage.configDoesExpireCheckbox().should('not.be.checked') + }) + + it('Expiry days input is shown if checkbox is selected', () => { + editBaseClientDetailsPage.configDoesExpireCheckbox().click() + editBaseClientDetailsPage.configExpiryDaysInput().should('be.visible') + }) + }) + + it('User clicks cancel to return to base-client screen', () => { + editBaseClientDetailsPage.cancelLink().click() + Page.verifyOnPage(ViewBaseClientPage) + }) + + it('User clicks continue to post new client details screen', () => { + // enter new audit details + editBaseClientDetailsPage.auditTrailDetailsInput().clear() + editBaseClientDetailsPage.auditTrailDetailsInput().type('updated') + + // set up to check the POST request + cy.intercept('POST', '/base-clients/base_client_id_1/edit', req => { + const { body } = req + expect(body).to.contain('audit=updated') + }) + + editBaseClientDetailsPage.saveButton().click() + }) +}) diff --git a/integration_tests/e2e/edit-client-instances.cy.ts b/integration_tests/e2e/edit-client-instances.cy.ts new file mode 100644 index 0000000..1a86f02 --- /dev/null +++ b/integration_tests/e2e/edit-client-instances.cy.ts @@ -0,0 +1,108 @@ +import Page from '../pages/page' +import ViewBaseClientPage from '../pages/viewBaseClient' +import ViewClientSecretsPage from '../pages/viewClientSecrets' +import ConfirmDeleteClientPage from '../pages/confirmDeleteClient' + +const visitBaseClientPage = (): ViewBaseClientPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1' }) + return Page.verifyOnPage(ViewBaseClientPage) +} + +const visitConfirmDeleteClientPage = (): ConfirmDeleteClientPage => { + cy.signIn({ + failOnStatusCode: true, + redirectPath: '/base-clients/base_client_id_1/clients/base_client_id_1_01/delete', + }) + return Page.verifyOnPage(ConfirmDeleteClientPage) +} + +context('Base client page - client instances', () => { + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubGetBaseClient') + cy.task('stubManageUser') + cy.task('stubGetListClientInstancesList') + }) + + context('Add client instances', () => { + let baseClientsPage: ViewBaseClientPage + + beforeEach(() => { + cy.task('stubAddClientInstance') + baseClientsPage = visitBaseClientPage() + }) + + it('User can click Add on base-client page to create new client instance', () => { + baseClientsPage.addClientInstanceButton().click() + Page.verifyOnPage(ViewClientSecretsPage) + }) + + context('Client secrets page', () => { + let clientSecretsPage: ViewClientSecretsPage + + beforeEach(() => { + baseClientsPage.addClientInstanceButton().click() + clientSecretsPage = Page.verifyOnPage(ViewClientSecretsPage) + }) + + it('User can see client secrets', () => { + clientSecretsPage.secretsTable().should('be.visible') + }) + + it('User can click continue to be taken back to base client page', () => { + clientSecretsPage.continueButton().click() + Page.verifyOnPage(ViewBaseClientPage) + }) + }) + }) + + context('Delete client instances', () => { + let confirmDeleteClientPage: ConfirmDeleteClientPage + + beforeEach(() => { + cy.task('stubDeleteClientInstance') + confirmDeleteClientPage = visitConfirmDeleteClientPage() + }) + + it('User can see warning message', () => { + confirmDeleteClientPage.warningMessage().should('be.visible') + }) + + it('Should not have error message by default', () => { + confirmDeleteClientPage.errorMessage().should('not.exist') + }) + + it('User can fill out confirm then click Delete on base-client page to delete client instance', () => { + confirmDeleteClientPage.confirmInput().clear() + confirmDeleteClientPage.confirmInput().type('base_client_id_1_01') + + confirmDeleteClientPage.deleteButton().click() + Page.verifyOnPage(ViewBaseClientPage) + }) + + it('User does not fill out Confirmation input clicking delete returns to page with error', () => { + confirmDeleteClientPage.confirmInput().clear() + + confirmDeleteClientPage.deleteButton().click() + confirmDeleteClientPage = Page.verifyOnPage(ConfirmDeleteClientPage) + + confirmDeleteClientPage.errorMessage().should('be.visible') + }) + + it('User fills out incorrect Confirmation input clicking delete returns to page with error', () => { + confirmDeleteClientPage.confirmInput().clear() + confirmDeleteClientPage.confirmInput().type('incorrect_client_id') + + confirmDeleteClientPage.deleteButton().click() + confirmDeleteClientPage = Page.verifyOnPage(ConfirmDeleteClientPage) + + confirmDeleteClientPage.errorMessage().should('be.visible') + }) + + it('User can click cancel to return to base client screen', () => { + confirmDeleteClientPage.cancelButton().click() + Page.verifyOnPage(ViewBaseClientPage) + }) + }) +}) diff --git a/integration_tests/e2e/view-base-client-list.cy.ts b/integration_tests/e2e/view-base-client-list.cy.ts new file mode 100644 index 0000000..12eae2f --- /dev/null +++ b/integration_tests/e2e/view-base-client-list.cy.ts @@ -0,0 +1,88 @@ +import Page from '../pages/page' +import ViewBaseClientListPage from '../pages/viewBaseClientList' +import ViewBaseClientPage from '../pages/viewBaseClient' +import NewBaseClientGrantPage from '../pages/newBaseClientGrant' + +const visitBaseClientListPage = (): ViewBaseClientListPage => { + cy.signIn() + cy.visit('/') + return Page.verifyOnPage(ViewBaseClientListPage) +} + +context('Homepage - list base-clients', () => { + let listBaseClientsPage: ViewBaseClientListPage + + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubListBaseClients') + cy.task('stubGetBaseClient') + cy.task('stubManageUser') + cy.task('stubGetListClientInstancesList') + + listBaseClientsPage = visitBaseClientListPage() + }) + + it('User can see base-client list', () => { + listBaseClientsPage.baseClientList().should('have.length', 3) + }) + + it('User can click through to base-client', () => { + listBaseClientsPage.baseClientList().first().children('a').click() + Page.verifyOnPage(ViewBaseClientPage) + }) + + it('User can see Add New button', () => { + listBaseClientsPage.addNewBaseClient().should('exist') + }) + + it('User can click through to new base-client page', () => { + listBaseClientsPage.addNewBaseClient().click() + Page.verifyOnPage(NewBaseClientGrantPage) + }) + + it('Filter page is hidden', () => { + listBaseClientsPage.filterPanel().should('not.be.visible') + }) + + it('Toggle filter button has text Show filter', () => { + listBaseClientsPage.toggleFilterButton().should('have.text', 'Show filter') + }) + + it('On click shows filter panel and updates text to Hide filter', () => { + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.filterPanel().should('be.visible') + listBaseClientsPage.toggleFilterButton().should('have.text', 'Hide filter') + }) + + it('On second click hides filter panel and reverts text to Show filter', () => { + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.filterPanel().should('not.be.visible') + listBaseClientsPage.toggleFilterButton().should('have.text', 'Show filter') + }) + + it('On second click hides filter panel and reverts text to Show filter', () => { + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.filterPanel().should('not.be.visible') + listBaseClientsPage.toggleFilterButton().should('have.text', 'Show filter') + }) + + it('On click Apply filter hides the filter', () => { + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.applyFilterButton().click() + + listBaseClientsPage.filterPanel().should('not.be.visible') + }) + + it('On click Apply with filter content limits rows in table', () => { + listBaseClientsPage.toggleFilterButton().click() + listBaseClientsPage.roleFilterInputBox().type('ROLE_TWO') + + listBaseClientsPage.applyFilterButton().click() + + listBaseClientsPage.filterPanel().should('not.be.visible') + listBaseClientsPage.baseClientList().should('have.length', 2) + }) +}) diff --git a/integration_tests/e2e/view-base-client.cy.ts b/integration_tests/e2e/view-base-client.cy.ts new file mode 100644 index 0000000..3930459 --- /dev/null +++ b/integration_tests/e2e/view-base-client.cy.ts @@ -0,0 +1,80 @@ +import Page from '../pages/page' +import ViewBaseClientPage from '../pages/viewBaseClient' +import ViewClientSecretsPage from '../pages/viewClientSecrets' +import ConfirmDeleteClientPage from '../pages/confirmDeleteClient' +import EditBaseClientDetailsPage from '../pages/editBaseClientDetails' +import EditBaseClientDeploymentDetailsPage from '../pages/editBaseClientDeploymentDetails' + +const visitBaseClientPage = (): ViewBaseClientPage => { + cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1' }) + return Page.verifyOnPage(ViewBaseClientPage) +} + +context('Base client page', () => { + let baseClientsPage: ViewBaseClientPage + + beforeEach(() => { + cy.task('reset') + cy.task('stubSignIn') + cy.task('stubGetBaseClient') + cy.task('stubManageUser') + cy.task('stubGetListClientInstancesList') + cy.task('stubAddClientInstance') + + baseClientsPage = visitBaseClientPage() + }) + + context('Client instances', () => { + it('User can see client instance list', () => { + baseClientsPage.clientInstanceRows().should('have.length', 3) + }) + + it('User can click add to create new client instance', () => { + baseClientsPage.addClientInstanceButton().click() + Page.verifyOnPage(ViewClientSecretsPage) + }) + + it('User can click delete to be taken to delete confirmation page', () => { + baseClientsPage.clientInstanceDeleteButtons().first().click() + Page.verifyOnPage(ConfirmDeleteClientPage) + }) + }) + + context('Base client details', () => { + it('User can see base client details table', () => { + baseClientsPage.baseClientDetailsTable().should('be.visible') + }) + + it('User can see audit trail table', () => { + baseClientsPage.baseClientAuditTable().should('be.visible') + }) + + it('User can see client credentials table', () => { + baseClientsPage.baseClientClientCredentialsTable().should('be.visible') + }) + + it('User can see config table', () => { + baseClientsPage.baseClientConfigTable().should('be.visible') + }) + + it('User can click Change client details to be taken to edit page', () => { + baseClientsPage.changeClientDetailsLink().click() + Page.verifyOnPage(EditBaseClientDetailsPage) + }) + }) + + context('Deployment details', () => { + it('User can see deployment contacts table', () => { + baseClientsPage.baseClientDeploymentContactTable().should('be.visible') + }) + + it('User can see deployment platform table', () => { + baseClientsPage.baseClientDeploymentPlatformTable().should('be.visible') + }) + + it('User can click Change client details to be taken to edit deployment page', () => { + baseClientsPage.changeDeploymentDetailsLink().click() + Page.verifyOnPage(EditBaseClientDeploymentDetailsPage) + }) + }) +}) diff --git a/integration_tests/index.d.ts b/integration_tests/index.d.ts index ce64a17..4bb604a 100644 --- a/integration_tests/index.d.ts +++ b/integration_tests/index.d.ts @@ -4,6 +4,6 @@ declare namespace Cypress { * Custom command to signIn. Set failOnStatusCode to false if you expect and non 200 return code * @example cy.signIn({ failOnStatusCode: boolean }) */ - signIn(options?: { failOnStatusCode: boolean }): Chainable + signIn(options?: { failOnStatusCode: boolean; redirectPath?: string }): Chainable } } diff --git a/integration_tests/mockApis/baseClientsApi.ts b/integration_tests/mockApis/baseClientsApi.ts index e76dbd1..1f1bf9a 100644 --- a/integration_tests/mockApis/baseClientsApi.ts +++ b/integration_tests/mockApis/baseClientsApi.ts @@ -2,6 +2,8 @@ import { stubFor } from './wiremock' import { listBaseClientsResponseMock, getBaseClientResponseMock, + getListClientInstancesResponseMock, + getSecretsResponseMock, } from '../../server/data/localMockData/baseClientsResponseMock' export default { @@ -25,7 +27,7 @@ export default { return stubFor({ request: { method: 'GET', - urlPattern: `/baseClientsApi/base-clients/baseClientId`, + urlPattern: `/baseClientsApi/base-clients/base_client_id_1`, }, response: { status: 200, @@ -36,4 +38,67 @@ export default { }, }) }, + + stubGetListClientInstancesList: () => { + return stubFor({ + request: { + method: 'GET', + urlPattern: `/baseClientsApi/base-clients/base_client_id_1/clients`, + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + jsonBody: getListClientInstancesResponseMock, + }, + }) + }, + + stubGetClientDeploymentDetails: () => { + return stubFor({ + request: { + method: 'GET', + urlPattern: `/baseClientsApi/base-clients/base_client_id_1/deployment`, + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + jsonBody: getListClientInstancesResponseMock, + }, + }) + }, + + stubAddClientInstance: () => { + return stubFor({ + request: { + method: 'POST', + urlPattern: `/baseClientsApi/base-clients/base_client_id_1/clients`, + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + jsonBody: getSecretsResponseMock, + }, + }) + }, + + stubDeleteClientInstance: () => { + return stubFor({ + request: { + method: 'DELETE', + urlPattern: `/baseClientsApi/base-clients/base_client_id_1/clients/base_client_id_1_01`, + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + }, + }) + }, } diff --git a/integration_tests/mockApis/manageUsersApi.ts b/integration_tests/mockApis/manageUsersApi.ts index f715d28..8ebefcc 100644 --- a/integration_tests/mockApis/manageUsersApi.ts +++ b/integration_tests/mockApis/manageUsersApi.ts @@ -33,7 +33,7 @@ const stubUserRoles = () => headers: { 'Content-Type': 'application/json;charset=UTF-8', }, - jsonBody: [{ roleCode: 'SOME_USER_ROLE' }], + jsonBody: [{ roleCode: 'ROLE_CLIENT_CREDENTIALS' }], }, }) diff --git a/integration_tests/pages/addBaseClientDetails.ts b/integration_tests/pages/addBaseClientDetails.ts new file mode 100644 index 0000000..59bc3e2 --- /dev/null +++ b/integration_tests/pages/addBaseClientDetails.ts @@ -0,0 +1,46 @@ +import Page, { PageElement } from './page' + +export default class AddBaseClientDetailsPage extends Page { + constructor() { + super('Add new client') + } + + baseClientIdInput = (): PageElement => cy.get('[data-qa="base-client-id-input"]') + + baseClientServiceRadioButton = (): PageElement => cy.get('[data-qa="base-client-service-radio"]') + + baseClientPersonalRadioButton = (): PageElement => cy.get('[data-qa="base-client-personal-radio"]') + + baseClientTypeRadios = (): PageElement => cy.get('[data-qa="base-client-type-radios"]') + + baseClientAccessTokenValidityDropdown = (): PageElement => + cy.get('[data-qa="base-client-access-token-validity-dropdown"]') + + baseClientAccessTokenValidityInput = (): PageElement => cy.get('[data-qa="base-client-access-token-validity-input"]') + + baseClientApprovedScopesInput = (): PageElement => cy.get('[data-qa="base-client-approved-scopes-input"]') + + auditTrailDetailsInput = (): PageElement => cy.get('[data-qa="audit-trail-details-input"]') + + grantTypeInput = (): PageElement => cy.get('[data-qa="grant-type-input"]') + + grantAuthoritiesInput = (): PageElement => cy.get('[data-qa="grant-authorities-input"]') + + grantDatabaseUsernameInput = (): PageElement => cy.get('[data-qa="grant-database-username-input"]') + + grantRedirectUrisInput = (): PageElement => cy.get('[data-qa="grant-redirect-uris-input"]') + + grantJwtFieldsInput = (): PageElement => cy.get('[data-qa="grant-jwt-fields-input"]') + + grantAzureAdLoginFlowCheckboxes = (): PageElement => cy.get('[data-qa="grant-azure-ad-login-flow-checkboxes"]') + + configDoesExpireCheckbox = (): PageElement => cy.get('[data-qa="config-does-expire-checkbox"]') + + configExpiryDaysInput = (): PageElement => cy.get('[data-qa="config-expiry-days-input"]') + + configAllowedIpsInput = (): PageElement => cy.get('[data-qa="config-allowed-ips-input"]') + + saveButton = (): PageElement => cy.get('[data-qa="save-button"]') + + cancelLink = (): PageElement => cy.get('[data-qa="cancel-link"]') +} diff --git a/integration_tests/pages/addBaseClientGrant.ts b/integration_tests/pages/addBaseClientGrant.ts new file mode 100644 index 0000000..5d91d28 --- /dev/null +++ b/integration_tests/pages/addBaseClientGrant.ts @@ -0,0 +1,17 @@ +import Page from './page' + +export default class AddBaseClientGrantPage extends Page { + constructor() { + super('Select a grant type') + } + + grantTypeRadioGroup = () => cy.get('[data-qa="grant-type-radio-group"]') + + clientCredentialsRadio = () => cy.get('[data-qa="client-credentials-radio"]') + + authorizationCodeRadio = () => cy.get('[data-qa="authorization-code-radio"]') + + continueButton = () => cy.get('[data-qa="continue-button"]') + + cancelLink = () => cy.get('[data-qa="cancel-link"]') +} diff --git a/integration_tests/pages/confirmDeleteClient.ts b/integration_tests/pages/confirmDeleteClient.ts new file mode 100644 index 0000000..559b17e --- /dev/null +++ b/integration_tests/pages/confirmDeleteClient.ts @@ -0,0 +1,17 @@ +import Page, { PageElement } from './page' + +export default class ConfirmDeleteClientPage extends Page { + constructor() { + super('Delete client?') + } + + warningMessage = (): PageElement => cy.get('[data-qa="delete-warning"]') + + confirmInput = (): PageElement => cy.get('[data-qa="confirm-input"]') + + errorMessage = (): PageElement => cy.get('[data-qa="error-message"]') + + deleteButton = (): PageElement => cy.get('[data-qa="delete-button"]') + + cancelButton = (): PageElement => cy.get('[data-qa="cancel-button"]') +} diff --git a/integration_tests/pages/editBaseClientDeploymentDetails.ts b/integration_tests/pages/editBaseClientDeploymentDetails.ts new file mode 100644 index 0000000..a35a42b --- /dev/null +++ b/integration_tests/pages/editBaseClientDeploymentDetails.ts @@ -0,0 +1,33 @@ +import Page, { PageElement } from './page' + +export default class EditBaseClientDeploymentDetailsPage extends Page { + constructor() { + super('Edit deployment details') + } + + baseClientSummaryList = (): PageElement => cy.get('[data-qa="base-client-summary-list"]') + + deploymentTeamInput = (): PageElement => cy.get('[data-qa="deployment-team-input"]') + + deploymentTeamContactInput = (): PageElement => cy.get('[data-qa="deployment-team-contact-input"]') + + deploymentTeamSlackInput = (): PageElement => cy.get('[data-qa="deployment-team-slack-input"]') + + platformHostingRadios = (): PageElement => cy.get('[data-qa="platform-hosting-radios"]') + + platformNamespaceInput = (): PageElement => cy.get('[data-qa="platform-namespace-input"]') + + platformDeploymentInput = (): PageElement => cy.get('[data-qa="platform-deployment-input"]') + + platformSecretNameInput = (): PageElement => cy.get('[data-qa="platform-secret-name-input"]') + + platformSecretKeyInput = (): PageElement => cy.get('[data-qa="platform-secret-key-input"]') + + platformClientIdInput = (): PageElement => cy.get('[data-qa="platform-client-id-input"]') + + platformDeploymentInfoInput = (): PageElement => cy.get('[data-qa="platform-deployment-info-input"]') + + saveButton = (): PageElement => cy.get('[data-qa="save-button"]') + + cancelLink = (): PageElement => cy.get('[data-qa="cancel-link"]') +} diff --git a/integration_tests/pages/editBaseClientDetails.ts b/integration_tests/pages/editBaseClientDetails.ts new file mode 100644 index 0000000..1a60c99 --- /dev/null +++ b/integration_tests/pages/editBaseClientDetails.ts @@ -0,0 +1,46 @@ +import Page, { PageElement } from './page' + +export default class EditBaseClientDetailsPage extends Page { + constructor() { + super('Edit base client details') + } + + baseClientIdInput = (): PageElement => cy.get('[data-qa="base-client-id-input"]') + + baseClientServiceRadioButton = (): PageElement => cy.get('[data-qa="base-client-service-radio"]') + + baseClientPersonalRadioButton = (): PageElement => cy.get('[data-qa="base-client-personal-radio"]') + + baseClientTypeRadios = (): PageElement => cy.get('[data-qa="base-client-type-radios"]') + + baseClientAccessTokenValidityDropdown = (): PageElement => + cy.get('[data-qa="base-client-access-token-validity-dropdown"]') + + baseClientAccessTokenValidityInput = (): PageElement => cy.get('[data-qa="base-client-access-token-validity-input"]') + + baseClientApprovedScopesInput = (): PageElement => cy.get('[data-qa="base-client-approved-scopes-input"]') + + auditTrailDetailsInput = (): PageElement => cy.get('[data-qa="audit-trail-details-input"]') + + grantTypeInput = (): PageElement => cy.get('[data-qa="grant-type-input"]') + + grantAuthoritiesInput = (): PageElement => cy.get('[data-qa="grant-authorities-input"]') + + grantDatabaseUsernameInput = (): PageElement => cy.get('[data-qa="grant-database-username-input"]') + + grantRedirectUrisInput = (): PageElement => cy.get('[data-qa="grant-redirect-uris-input"]') + + grantJwtFieldsInput = (): PageElement => cy.get('[data-qa="grant-jwt-fields-input"]') + + grantAzureAdLoginFlowCheckboxes = (): PageElement => cy.get('[data-qa="grant-azure-ad-login-flow-checkboxes"]') + + configDoesExpireCheckbox = (): PageElement => cy.get('[data-qa="config-does-expire-checkbox"]') + + configExpiryDaysInput = (): PageElement => cy.get('[data-qa="config-expiry-days-input"]') + + configAllowedIpsInput = (): PageElement => cy.get('[data-qa="config-allowed-ips-input"]') + + saveButton = (): PageElement => cy.get('[data-qa="save-button"]') + + cancelLink = (): PageElement => cy.get('[data-qa="cancel-link"]') +} diff --git a/integration_tests/pages/newBaseClientGrant.ts b/integration_tests/pages/newBaseClientGrant.ts new file mode 100644 index 0000000..30cf6ef --- /dev/null +++ b/integration_tests/pages/newBaseClientGrant.ts @@ -0,0 +1,7 @@ +import Page from './page' + +export default class NewBaseClientGrantPage extends Page { + constructor() { + super('Select a grant type') + } +} diff --git a/integration_tests/pages/viewBaseClient.ts b/integration_tests/pages/viewBaseClient.ts new file mode 100644 index 0000000..52f3cdd --- /dev/null +++ b/integration_tests/pages/viewBaseClient.ts @@ -0,0 +1,33 @@ +import Page from './page' + +export default class ViewBaseClientPage extends Page { + constructor() { + super('Client:') + } + + clientInstanceList = () => cy.get('[data-qa="client-instance-list"]') + + clientInstanceRows = () => this.clientInstanceList().find('tr') + + clientInstanceDeleteButtons = () => this.clientInstanceList().find('[data-qa="delete-client-instance-link"]') + + addClientInstanceButton = () => cy.get('[data-qa="add-new-client-button"]') + + baseClientDetailsTable = () => cy.get('[data-qa="base-client-details-table"]') + + baseClientAuditTable = () => cy.get('[data-qa="base-client-audit-table"]') + + baseClientClientCredentialsTable = () => cy.get('[data-qa="base-client-client-credentials-table"]') + + baseClientAuthorizationCodeTable = () => cy.get('[data-qa="base-client-authorization-code-table"]') + + baseClientConfigTable = () => cy.get('[data-qa="base-client-config-table"]') + + baseClientDeploymentContactTable = () => cy.get('[data-qa="base-client-deployment-contact-table"]') + + baseClientDeploymentPlatformTable = () => cy.get('[data-qa="base-client-deployment-platform-table"]') + + changeClientDetailsLink = () => cy.get('[data-qa="change-client-details-link"]') + + changeDeploymentDetailsLink = () => cy.get('[data-qa="change-deployment-details-link"]') +} diff --git a/integration_tests/pages/viewBaseClientList.ts b/integration_tests/pages/viewBaseClientList.ts new file mode 100644 index 0000000..e260725 --- /dev/null +++ b/integration_tests/pages/viewBaseClientList.ts @@ -0,0 +1,19 @@ +import Page, { PageElement } from './page' + +export default class ViewBaseClientListPage extends Page { + constructor() { + super('OAuth Client details') + } + + public baseClientList = (): PageElement => cy.get('[data-qa=baseClientList]') + + public addNewBaseClient = (): PageElement => cy.get('[data-qa=addNewBaseClientButton]') + + public toggleFilterButton = (): PageElement => cy.get('.toggle-filter-button') + + public applyFilterButton = (): PageElement => cy.get('.govuk-button').contains('Apply filters') + + public filterPanel = (): PageElement => cy.get('.moj-filter') + + public roleFilterInputBox = (): PageElement => cy.get('[data-qa=roleFilterInputBox]') +} diff --git a/integration_tests/pages/viewClientSecrets.ts b/integration_tests/pages/viewClientSecrets.ts new file mode 100644 index 0000000..dbc40c8 --- /dev/null +++ b/integration_tests/pages/viewClientSecrets.ts @@ -0,0 +1,11 @@ +import Page, { PageElement } from './page' + +export default class ViewClientSecretsPage extends Page { + constructor() { + super('Client has been added') + } + + secretsTable = (): PageElement => cy.get('[data-qa="secrets-table"]') + + continueButton = (): PageElement => cy.get('[data-qa="continue-button"]') +} diff --git a/integration_tests/support/commands.ts b/integration_tests/support/commands.ts index e8d0a00..27393fa 100644 --- a/integration_tests/support/commands.ts +++ b/integration_tests/support/commands.ts @@ -1,4 +1,5 @@ -Cypress.Commands.add('signIn', (options = { failOnStatusCode: true }) => { - cy.request('/') - return cy.task('getSignInUrl').then((url: string) => cy.visit(url, options)) +Cypress.Commands.add('signIn', (options = { failOnStatusCode: true, redirectPath: '/' }) => { + const { failOnStatusCode, redirectPath } = options + cy.request(redirectPath) + return cy.task('getSignInUrl').then((url: string) => cy.visit(url, { failOnStatusCode })) }) diff --git a/server/data/localMockData/baseClientsResponseMock.ts b/server/data/localMockData/baseClientsResponseMock.ts index 0d9623c..37958b3 100644 --- a/server/data/localMockData/baseClientsResponseMock.ts +++ b/server/data/localMockData/baseClientsResponseMock.ts @@ -1,4 +1,9 @@ -import { GetBaseClientResponse, ListBaseClientsResponse } from '../../interfaces/baseClientApi/baseClientResponse' +import { + ClientSecretsResponse, + GetBaseClientResponse, + ListBaseClientsResponse, + ListClientInstancesResponse, +} from '../../interfaces/baseClientApi/baseClientResponse' export const listBaseClientsResponseMock: ListBaseClientsResponse = { clients: [ @@ -7,6 +12,7 @@ export const listBaseClientsResponseMock: ListBaseClientsResponse = { clientType: 'SERVICE', teamName: null, grantType: 'client_credentials', + roles: 'ROLE_ONE, ROLE_TWO', count: 1, }, { @@ -21,13 +27,14 @@ export const listBaseClientsResponseMock: ListBaseClientsResponse = { clientType: 'SERVICE', teamName: 'Team 2', grantType: 'client_credentials', + roles: 'ROLE_TWO, ROLE_THREE', count: 1, }, ], } export const getBaseClientResponseMock: GetBaseClientResponse = { - clientId: 'baseClientId1', + clientId: 'base_client_id_1', scopes: ['read', 'write'], authorities: ['ROLE_CLIENT_CREDENTIALS'], ips: [], @@ -48,3 +55,24 @@ export const getBaseClientResponseMock: GetBaseClientResponse = { deploymentInfo: 'deployment deployment info', }, } + +export const getListClientInstancesResponseMock: ListClientInstancesResponse = { + clients: [ + { + clientId: 'base_client_id_1_01', + created: '2020-01-01T00:00:00.000', + }, + { + clientId: 'base_client_id_1_02', + created: '2020-01-01T00:00:00.000', + }, + ], + grantType: 'client_credentials', +} + +export const getSecretsResponseMock: ClientSecretsResponse = { + clientId: 'base_client_id_1_03', + clientSecret: 'aaa', + base64ClientId: 'bbb', + base64ClientSecret: 'ccc', +} diff --git a/server/views/pages/base-client.njk b/server/views/pages/base-client.njk index e368421..b98901b 100644 --- a/server/views/pages/base-client.njk +++ b/server/views/pages/base-client.njk @@ -46,14 +46,20 @@ text: "" } ], - rows: presenter.clientsTable + rows: presenter.clientsTable, + attributes: { + "data-qa": "client-instance-list" + } }) }}
{{ govukButton({ - text: "Add new client" + text: "Add new client", + attributes: { + "data-qa": "add-new-client-button" + } }) }}
@@ -63,7 +69,7 @@

Base client details

@@ -97,7 +103,10 @@ html: toLinesHtml(baseClient.scopes) } ] - ] + ], + attributes: { + "data-qa": "base-client-details-table" + } }) }} {{ govukTable({ @@ -114,7 +123,10 @@ },{ text: baseClient.audit }] - ] + ], + attributes: { + "data-qa": "base-client-audit-table" + } }) }} @@ -148,7 +160,10 @@ text: baseClient.clientCredentials.databaseUserName } ] - ] + ], + attributes: { + "data-qa": "base-client-client-credentials-table" + } }) }} {% endif %} @@ -187,7 +202,10 @@ text: presenter.skipToAzureField } ] - ] + ], + attributes: { + "data-qa": "base-client-authorization-code-table" + } }) }} {% endif %} @@ -214,7 +232,10 @@ html: toLinesHtml(baseClient.config.allowedIPs) } ] - ] + ], + attributes: { + "data-qa": "base-client-config-table" + } }) }} @@ -275,7 +296,10 @@ text: presenter.serviceEnabledLabel } ] - ] + ], + attributes: { + "data-qa": "base-client-service-table" + } }) }} {% endif %} @@ -284,7 +308,7 @@

Deployment details

@@ -319,7 +343,10 @@ text: baseClient.deployment.teamSlack } ] - ] + ], + attributes: { + "data-qa": "base-client-deployment-contact-table" + } }) }} {{ govukTable({ @@ -380,8 +407,10 @@ text: baseClient.deployment.deploymentInfo } ] - - ] + ], + attributes: { + "data-qa": "base-client-deployment-platform-table" + } }) }} diff --git a/server/views/pages/base-clients.njk b/server/views/pages/base-clients.njk index 3c0d4c6..822bfb8 100644 --- a/server/views/pages/base-clients.njk +++ b/server/views/pages/base-clients.njk @@ -33,6 +33,9 @@ text: 'Role', classes: 'govuk-label--m' }, + attributes: { + 'data-qa': 'roleFilterInputBox' + }, value: presenter.filter.roleSearch }) }} @@ -127,7 +130,10 @@ items: [{ text: 'Add new client', href:"/base-clients/new", - classes: 'govuk-button--primary' + classes: 'govuk-button--primary', + attributes: { + 'data-qa': 'addNewBaseClientButton' + } }] }) }} diff --git a/server/views/pages/delete-client-instance.njk b/server/views/pages/delete-client-instance.njk index 47518d0..571c812 100644 --- a/server/views/pages/delete-client-instance.njk +++ b/server/views/pages/delete-client-instance.njk @@ -25,17 +25,24 @@ {% if isLastClient %} {{ govukWarningText({ text: "Deleting this client will also delete base-client '" + baseClient.baseClientId + "'.", - iconFallbackText: "Warning" + iconFallbackText: "Warning", + attributes: { + "data-qa": "delete-warning" + } }) }} {% endif %} {{ govukWarningText({ text: "Deleted clients cannot be restored!", - iconFallbackText: "Warning" + iconFallbackText: "Warning", + attributes: { + "data-qa": "delete-warning" + } }) }}
+ {% if error %} {{ govukInput({ label: { text: "Confirmation" @@ -44,21 +51,49 @@ html: "Type \"" + clientId + "\" to confirm" }, errorMessage: { - text: error + text: error, + attributes: { + "data-qa": "error-message" + } }, classes: "govuk-!-width-two-thirds", id: "confirm", - name: "confirm" + name: "confirm", + attributes: { + "data-qa": "confirm-input" + } }) }} + {% else %} + {{ govukInput({ + label: { + text: "Confirmation" + }, + hint: { + html: "Type \"" + clientId + "\" to confirm" + }, + classes: "govuk-!-width-two-thirds", + id: "confirm", + name: "confirm", + attributes: { + "data-qa": "confirm-input" + } + }) }} + {% endif %} {{ govukButton({ text: "Delete client instance", type: "submit", - classes: "govuk-button--warning" + classes: "govuk-button--warning", + attributes: { + "data-qa": "delete-button" + } }) }} {{ govukButton({ text: "Cancel", - href: "/base-clients/" + baseClient.baseClientId + href: "/base-clients/" + baseClient.baseClientId, + attributes: { + "data-qa": "cancel-button" + } }) }}
diff --git a/server/views/pages/edit-base-client-deployment.njk b/server/views/pages/edit-base-client-deployment.njk index 06fb71f..64c412d 100644 --- a/server/views/pages/edit-base-client-deployment.njk +++ b/server/views/pages/edit-base-client-deployment.njk @@ -44,7 +44,10 @@ text: baseClient.baseClientId } } - ] + ], + attributes: { + "data-qa": "base-client-summary-list" + } }) }} @@ -59,7 +62,10 @@ }, id: "team", name: "team", - value: baseClient.deployment.team + value: baseClient.deployment.team, + attributes: { + "data-qa": "deployment-team-input" + } }) }} {{ govukInput({ @@ -68,7 +74,10 @@ }, id: "team-contact", name: "teamContact", - value: baseClient.deployment.teamContact + value: baseClient.deployment.teamContact, + attributes: { + "data-qa": "deployment-team-contact-input" + } }) }} {{ govukInput({ @@ -77,7 +86,10 @@ }, id: "team-slack", name: "teamSlack", - value: baseClient.deployment.teamSlack + value: baseClient.deployment.teamSlack, + attributes: { + "data-qa": "deployment-team-slack-input" + } }) }} @@ -104,7 +116,10 @@ text: "Other" } ], - value: baseClient.deployment.hosting + value: baseClient.deployment.hosting, + attributes: { + "data-qa": "platform-hosting-radios" + } }) }}
@@ -115,7 +130,10 @@ }, id: "namespace", name: "namespace", - value: baseClient.deployment.namespace + value: baseClient.deployment.namespace, + attributes: { + "data-qa": "platform-namespace-input" + } }) }} {{ govukInput({ @@ -124,7 +142,10 @@ }, id: "deployment", name: "deployment", - value: baseClient.deployment.deployment + value: baseClient.deployment.deployment, + attributes: { + "data-qa": "platform-deployment-input" + } }) }} {{ govukInput({ @@ -133,7 +154,10 @@ }, id: "secret-name", name: "secretName", - value: baseClient.deployment.secretName + value: baseClient.deployment.secretName, + attributes: { + "data-qa": "platform-secret-name-input" + } }) }} {{ govukInput({ @@ -142,7 +166,10 @@ }, id: "client-id-key", name: "clientIdKey", - value: baseClient.deployment.clientIdKey + value: baseClient.deployment.clientIdKey, + attributes: { + "data-qa": "platform-client-id-input" + } }) }} {{ govukInput({ @@ -151,7 +178,10 @@ }, id: "secret-key", name: "secretKey", - value: baseClient.deployment.secretKey + value: baseClient.deployment.secretKey, + attributes: { + "data-qa": "platform-secret-key-input" + } }) }}
@@ -164,16 +194,22 @@ text: "Deployment info", isPageHeading: false }, - value: baseClient.deployment.deploymentInfo + value: baseClient.deployment.deploymentInfo, + attributes: { + "data-qa": "platform-deployment-info-input" + } }) }}
{{ govukButton({ text: "Save", type: "submit", - preventDoubleClick: true + preventDoubleClick: true, + attributes: { + "data-qa": "save-button" + } }) }} - Cancel + Cancel
diff --git a/server/views/pages/edit-base-client-details.njk b/server/views/pages/edit-base-client-details.njk index acec658..abd0bde 100644 --- a/server/views/pages/edit-base-client-details.njk +++ b/server/views/pages/edit-base-client-details.njk @@ -44,7 +44,10 @@ classes: "govuk-!-width-two-thirds", id: "base-client-id", value: baseClient.baseClientId, - disabled: true + disabled: true, + attributes: { + "data-qa": "base-client-id-input" + } }) }} {{ govukRadios({ @@ -59,14 +62,23 @@ items: [ { value: "SERVICE", - text: "Service" + text: "Service", + attributes: { + "data-qa": "base-client-service-radio" + } }, { value: "PERSONAL", - text: "Personal" + text: "Personal", + attributes: { + "data-qa": "base-client-personal-radio" + } } ], - value: baseClient.clientType + value: baseClient.clientType, + attributes: { + "data-qa": "base-client-type-radios" + } }) }} {{ govukSelect({ @@ -98,7 +110,10 @@ text: "Custom" } ], - value: presenter.accessTokenValidityDropdown + value: presenter.accessTokenValidityDropdown, + attributes: { + "data-qa": "base-client-access-token-validity-dropdown" + } }) }}
@@ -109,7 +124,10 @@ classes: "govuk-!-width-one-half", id: "custom-access-token-validity", name: "customAccessTokenValidity", - value: presenter.accessTokenValidityText + value: presenter.accessTokenValidityText, + attributes: { + "data-qa": "base-client-access-token-validity-input" + } }) }}
@@ -123,7 +141,10 @@ hint: { text: "read, write ..." }, - value: toLines(baseClient.scopes) + value: toLines(baseClient.scopes), + attributes: { + "data-qa": "base-client-approved-scopes-input" + } }) }}

Audit trail

@@ -137,7 +158,10 @@ hint: { text: "jira tickets, slack messages ..." }, - value: baseClient.audit + value: baseClient.audit, + attributes: { + "data-qa": "audit-trail-details-input" + } }) }}

Grant details

@@ -151,7 +175,10 @@ id: "grant-type", name: "grantType", value: "Client credentials", - disabled: true + disabled: true, + attributes: { + "data-qa": "grant-type-input" + } }) }} {{ govukTextarea({ @@ -161,7 +188,10 @@ text: "Authorities", isPageHeading: false }, - value: toLines(baseClient.clientCredentials.authorities) + value: toLines(baseClient.clientCredentials.authorities), + attributes: { + "data-qa": "grant-authorities-input" + } }) }} {{ govukInput({ @@ -171,7 +201,10 @@ classes: "govuk-!-width-two-thirds", id: "database-username", name: "databaseUsername", - value: baseClient.clientCredentials.databaseUsername + value: baseClient.clientCredentials.databaseUsername, + attributes: { + "data-qa": "grant-database-username-input" + } }) }} {% endif %} @@ -184,7 +217,10 @@ id: "grant-type", name: "grantType", value: "Authorization code", - disabled: true + disabled: true, + attributes: { + "data-qa": "grant-type-input" + } }) }} {{ govukTextarea({ @@ -194,7 +230,10 @@ text: "Registered redirect URIs", isPageHeading: false }, - value: toLines(baseClient.authorizationCode.redirectUris) + value: toLines(baseClient.authorizationCode.redirectUris), + attributes: { + "data-qa": "grant-redirect-uris-input" + } }) }} {{ govukInput({ label: { @@ -206,7 +245,10 @@ classes: "govuk-!-width-one-third", id: "jwt-fields", name: "jwtFields", - value: baseClient.authorizationCode.jwtFields + value: baseClient.authorizationCode.jwtFields, + attributes: { + "data-qa": "grant-jwt-fields-input" + } }) }} @@ -227,7 +269,10 @@ text: "Auto redirect", checked: baseClient.authorisationCode.azureAdLoginFlow } - ] + ], + attributes: { + "data-qa": "grant-azure-ad-login-flow-checkboxes" + } }) }} {% endif %} @@ -240,10 +285,13 @@ text: "allow client to expire", checked: presenter.expiry, conditional: { - html: "" + html: "" } } - ] + ], + attributes: { + "data-qa": "config-does-expire-checkbox" + } }) }} {{ govukTextarea({ @@ -256,16 +304,22 @@ hint: { html: "One IP address/CIDR notation per line
81.134.202.29/32 - mojvpn
35.176.93.186/32 - global-protect
35.178.209.113/32 - cloudplatform-1
3.8.51.207/32 - cloudplatform-2
35.177.252.54/32 - cloudplatform-3" }, - value: toLines(baseClient.config.allowedIPs) + value: toLines(baseClient.config.allowedIPs), + attributes: { + "data-qa": "config-allowed-ips-input" + } }) }}
{{ govukButton({ text: "Save", type: "submit", - preventDoubleClick: true + preventDoubleClick: true, + attributes: { + "data-qa": "save-button" + } }) }} - Cancel + Cancel
diff --git a/server/views/pages/new-base-client-details.njk b/server/views/pages/new-base-client-details.njk index 3b25733..99f9966 100644 --- a/server/views/pages/new-base-client-details.njk +++ b/server/views/pages/new-base-client-details.njk @@ -41,7 +41,10 @@ id: "base-client-id", name: "baseClientId", value: baseClient.baseClientId, - errorMessage: errorMessage + errorMessage: errorMessage, + attributes: { + "data-qa": "base-client-id-input" + } }) }} {{ govukRadios({ @@ -56,14 +59,23 @@ items: [ { value: "SERVICE", - text: "Service" + text: "Service", + attributes: { + "data-qa": "base-client-service-radio" + } }, { value: "PERSONAL", - text: "Personal" + text: "Personal", + attributes: { + "data-qa": "base-client-personal-radio" + } } ], - value: baseClient.clientType + value: baseClient.clientType, + attributes: { + "data-qa": "base-client-type-radios" + } }) }} {{ govukSelect({ @@ -95,6 +107,9 @@ text: "Custom" } ], + attributes: { + "data-qa": "base-client-access-token-validity-dropdown" + }, value: presenter.accessTokenValidityDropdown }) }} @@ -106,7 +121,10 @@ classes: "govuk-!-width-one-half", id: "custom-access-token-validity", name: "customAccessTokenValidity", - value: presenter.accessTokenValidityText + value: presenter.accessTokenValidityText, + attributes: { + "data-qa": "base-client-access-token-validity-input" + } }) }} @@ -120,7 +138,10 @@ classes: "govuk-!-width-one-third", id: "approved-scopes", name: "approvedScopes", - value: toLines(baseClient.scopes) + value: toLines(baseClient.scopes), + attributes: { + "data-qa": "base-client-approved-scopes-input" + } }) }}

Audit trail

@@ -134,7 +155,10 @@ hint: { text: "jira tickets, slack messages ..." }, - value: baseClient.audit + value: baseClient.audit, + attributes: { + "data-qa": "audit-trail-details-input" + } }) }}

Grant details

@@ -148,7 +172,10 @@ id: "grant-type", name: "grantType", value: "Client credentials", - disabled: true + disabled: true, + attributes: { + "data-qa": "grant-type-input" + } }) }} {{ govukTextarea({ @@ -158,7 +185,10 @@ text: "Authorities", isPageHeading: false }, - value: toLines(baseClient.clientCredentials.authorities) + value: toLines(baseClient.clientCredentials.authorities), + attributes: { + "data-qa": "grant-authorities-input" + } }) }} {{ govukInput({ @@ -168,7 +198,10 @@ classes: "govuk-!-width-two-thirds", id: "database-username", name: "databaseUsername", - value: baseClient.clientCredentials.databaseUsername + value: baseClient.clientCredentials.databaseUsername, + attributes: { + "data-qa": "grant-database-username-input" + } }) }} {% endif %} @@ -181,7 +214,10 @@ id: "grant-type", name: "grantType", value: "Authorization code", - disabled: true + disabled: true, + attributes: { + "data-qa": "grant-type-input" + } }) }} {{ govukTextarea({ @@ -191,7 +227,10 @@ text: "Registered redirect URIs", isPageHeading: false }, - value: toLines(baseClient.authorizationCode.redirectUris) + value: toLines(baseClient.authorizationCode.redirectUris), + attributes: { + "data-qa": "grant-redirect-uris-input" + } }) }} {{ govukInput({ label: { @@ -203,7 +242,10 @@ classes: "govuk-!-width-one-third", id: "jwt-fields", name: "jwtFields", - value: baseClient.authorizationCode.jwtFields + value: baseClient.authorizationCode.jwtFields, + attributes: { + "data-qa": "grant-jwt-fields-input" + } }) }} @@ -224,7 +266,10 @@ text: "Auto redirect", checked: baseClient.authorisationCode.azureAdLoginFlow } - ] + ], + attributes: { + "data-qa": "grant-azure-ad-login-flow-checkboxes" + } }) }} {% endif %} @@ -237,8 +282,11 @@ text: "allow client to expire", checked: presenter.expiry, conditional: { - html: "" - } + html: "" + }, + attributes: { + "data-qa": "config-does-expire-checkbox" + } } ] }) }} @@ -253,16 +301,22 @@ hint: { html: "One IP address/CIDR notation per line
81.134.202.29/32 - mojvpn
35.176.93.186/32 - global-protect
35.178.209.113/32 - cloudplatform-1
3.8.51.207/32 - cloudplatform-2
35.177.252.54/32 - cloudplatform-3" }, - value: toLines(baseClient.config.allowedIPs) + value: toLines(baseClient.config.allowedIPs), + attributes: { + "data-qa": "config-allowed-ips-input" + } }) }}
{{ govukButton({ text: "Save", type: "submit", - preventDoubleClick: true + preventDoubleClick: true, + attributes: { + "data-qa": "save-button" + } }) }} - Cancel + Cancel
diff --git a/server/views/pages/new-base-client-grant.njk b/server/views/pages/new-base-client-grant.njk index 5f116b9..b8c5bb5 100644 --- a/server/views/pages/new-base-client-grant.njk +++ b/server/views/pages/new-base-client-grant.njk @@ -30,14 +30,23 @@ items: [ { value: "client-credentials", - text: "Client credentials" + text: "Client credentials", + attributes: { + "data-qa": "client-credentials-radio" + } }, { value: "authorization-code", text: "Authorization code", - disabled: false + disabled: true, + attributes: { + "data-qa": "authorization-code-radio" + } } - ] + ], + attributes: { + "data-qa": "grant-type-radio-group" + } }) }} @@ -45,9 +54,12 @@ {{ govukButton({ text: "Continue", type: "submit", - preventDoubleClick: true + preventDoubleClick: true, + attributes: { + "data-qa": "continue-button" + } }) }} - Cancel + Cancel diff --git a/server/views/pages/new-base-client-success.njk b/server/views/pages/new-base-client-success.njk index af79057..9b76aac 100644 --- a/server/views/pages/new-base-client-success.njk +++ b/server/views/pages/new-base-client-success.njk @@ -29,13 +29,19 @@ [{ text: "new clientSecret" }, { text: secrets.clientSecret}], [{ text: "base64 clientId" }, { text: secrets.base64ClientId}], [{ text: "base64 clientSecret"}, { text: secrets.base64ClientSecret}] - ] + ], + attributes: { + "data-qa": "secrets-table" + } }) }}
{{ govukButton({ text: "Continue", - href: "/base-clients/" + baseClientId + href: "/base-clients/" + baseClientId, + attributes: { + "data-qa": "continue-button" + } }) }}
diff --git a/server/views/presenters/listBaseClientsPresenter.ts b/server/views/presenters/listBaseClientsPresenter.ts index 9fc0822..642526f 100644 --- a/server/views/presenters/listBaseClientsPresenter.ts +++ b/server/views/presenters/listBaseClientsPresenter.ts @@ -75,6 +75,9 @@ const indexTableRows = (data: BaseClient[], filter?: BaseClientListFilter) => { return dataItems.map(item => [ { html: `${item.baseClientId}`, + attributes: { + 'data-qa': 'baseClientList', + }, }, { html: item.count > 1 ? `${item.count}` : '', @@ -116,21 +119,29 @@ export const filterBaseClient = (baseClient: BaseClient, filter: BaseClientListF } } - if (baseClient.grantType === 'client_credentials' && !filter.clientCredentials) { + const grantType = baseClient.grantType ? baseClient.grantType.trim().toLowerCase() : '' + const clientType = baseClient.clientType ? baseClient.clientType.trim().toLowerCase() : '' + + if (grantType === 'client_credentials' && !filter.clientCredentials) { return false } - if (baseClient.grantType === 'authorisation_code' && !filter.authorisationCode) { + if (grantType === 'authorisation_code' && !filter.authorisationCode) { return false } - if (baseClient.clientType === 'PERSONAL' && !filter.personalClientType) { + if (clientType === 'personal' && !filter.personalClientType) { return false } - if (baseClient.clientType === 'SERVICE' && !filter.serviceClientType) { + if (clientType === 'service' && !filter.serviceClientType) { return false } - return filter.blankClientType + + if (clientType === '' && !filter.blankClientType) { + return false + } + + return true } export default (data: BaseClient[], filter?: BaseClientListFilter) => { diff --git a/server/views/presenters/viewBaseClientPresenter.test.ts b/server/views/presenters/viewBaseClientPresenter.test.ts index b3d680e..76878f1 100644 --- a/server/views/presenters/viewBaseClientPresenter.test.ts +++ b/server/views/presenters/viewBaseClientPresenter.test.ts @@ -43,8 +43,8 @@ describe('viewBaseClientPresenter', () => { // Then the dates are formatted as DD/MM/YYYY const expected = [ - 'delete', - 'delete', + 'delete', + 'delete', ] const actual = presenter.clientsTable.map(row => row[3].html) expect(expected).toEqual(actual) diff --git a/server/views/presenters/viewBaseClientPresenter.ts b/server/views/presenters/viewBaseClientPresenter.ts index 90839f8..e0443f7 100644 --- a/server/views/presenters/viewBaseClientPresenter.ts +++ b/server/views/presenters/viewBaseClientPresenter.ts @@ -12,10 +12,10 @@ export default (baseClient: BaseClient, clients: Client[]) => { html: item.created.toLocaleDateString('en-GB'), }, { - html: item.accessed.toLocaleDateString('en-GB'), + html: item.accessed ? item.accessed.toLocaleDateString('en-GB') : '', }, { - html: `delete`, + html: `delete`, }, ]), expiry: baseClient.config.expiryDate ? `Yes - days remaining ${daysRemaining(baseClient.config.expiryDate)}` : 'No',