Skip to content

Commit

Permalink
Haar 1906+1907 update flow (#32)
Browse files Browse the repository at this point in the history
* add tests for list base clients page

* add view base client page

* Fix errors in template

* basic view for create new base client screen

* post new base client with error loop

* added controller tests

* add tests for presenter

* add test for expiry today

* correct test comments and refactor time functions

* display edit base clients details page

* Add post update functionality

* update test comments

* update comments

* display edit base clients deployment details page

* add update deployment flow
  • Loading branch information
thomasridd committed Nov 2, 2023
1 parent 4965898 commit fb650aa
Show file tree
Hide file tree
Showing 18 changed files with 923 additions and 38 deletions.
84 changes: 83 additions & 1 deletion server/controllers/baseClientController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import listBaseClientsPresenter from '../views/presenters/listBaseClientsPresent
import createUserToken from '../testutils/createUserToken'
import viewBaseClientPresenter from '../views/presenters/viewBaseClientPresenter'
import nunjucksUtils from '../views/helpers/nunjucksUtils'
import editBaseClientPresenter from '../views/presenters/editBaseClientPresenter'

describe('BaseClientController', () => {
const token = createUserToken(['ADMIN'])
Expand Down Expand Up @@ -109,7 +110,7 @@ describe('BaseClientController', () => {
})

it('if grant is specified with authorization-code renders the details screen', async () => {
// GIVEN a request with grant="client-credentials" parameter
// GIVEN a request with grant="authorization-code" parameter
request = createMock<Request>({ query: { grant: 'authorization-code' } })

// WHEN the create base client page is requested
Expand Down Expand Up @@ -184,4 +185,85 @@ describe('BaseClientController', () => {
})
})
})

describe('update base client details', () => {
it('displays update base client details screen', async () => {
// GIVEN a request to edit a base client
const baseClient = baseClientFactory.build()
baseClientService.getBaseClient.mockResolvedValue(baseClient)
request = createMock<Request>({ params: { baseClientId: baseClient.baseClientId } })

// WHEN the edit base client details page is requested
await baseClientController.displayEditBaseClient()(request, response, next)

// THEN the base client is retrieved from the base client service
expect(baseClientService.getBaseClient).toHaveBeenCalledWith(token, baseClient.baseClientId)

// AND the page is rendered
const presenter = editBaseClientPresenter(baseClient)
expect(response.render).toHaveBeenCalledWith('pages/edit-base-client-details.njk', {
baseClient,
presenter,
...nunjucksUtils,
})
})

it('updates and redirects to view base client screen', async () => {
// GIVEN the service will return without an error
const baseClient = baseClientFactory.build()
request = createMock<Request>({
params: { baseClientId: baseClient.baseClientId },
body: { baseClientId: baseClient.baseClientId },
})
baseClientService.updateBaseClient.mockResolvedValue(new Response())

// WHEN it is posted
await baseClientController.updateBaseClientDetails()(request, response, next)

// THEN the base client service is updated
expect(baseClientService.updateBaseClient).toHaveBeenCalled()

// AND the user is redirected to the view base client page
expect(response.redirect).toHaveBeenCalledWith(`/base-clients/${baseClient.baseClientId}`)
})
})

describe('update base client deployment', () => {
it('displays update base client deployment screen', async () => {
// GIVEN a request to edit base client deployment
const baseClient = baseClientFactory.build()
baseClientService.getBaseClient.mockResolvedValue(baseClient)
request = createMock<Request>({ params: { baseClientId: baseClient.baseClientId } })

// WHEN the edit base client details page is requested
await baseClientController.displayEditBaseClientDeployment()(request, response, next)

// THEN the base client is retrieved from the base client service
expect(baseClientService.getBaseClient).toHaveBeenCalledWith(token, baseClient.baseClientId)

// AND the page is rendered
expect(response.render).toHaveBeenCalledWith('pages/edit-base-client-deployment.njk', {
baseClient,
})
})

it('updates and redirects to view base client screen', async () => {
// GIVEN the service will return without an error
const baseClient = baseClientFactory.build()
request = createMock<Request>({
params: { baseClientId: baseClient.baseClientId },
body: { baseClientId: baseClient.baseClientId },
})
baseClientService.updateBaseClientDeployment.mockResolvedValue(new Response())

// WHEN it is posted
await baseClientController.updateBaseClientDeployment()(request, response, next)

// THEN the base client service is updated
expect(baseClientService.updateBaseClientDeployment).toHaveBeenCalled()

// AND the user is redirected to the view base client page
expect(response.redirect).toHaveBeenCalledWith(`/base-clients/${baseClient.baseClientId}`)
})
})
})
67 changes: 66 additions & 1 deletion server/controllers/baseClientController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BaseClientService } from '../services'
import listBaseClientsPresenter from '../views/presenters/listBaseClientsPresenter'
import viewBaseClientPresenter from '../views/presenters/viewBaseClientPresenter'
import nunjucksUtils from '../views/helpers/nunjucksUtils'
import { mapCreateBaseClientForm } from '../mappers'
import { mapCreateBaseClientForm, mapEditBaseClientDeploymentForm, mapEditBaseClientDetailsForm } from '../mappers'
import { BaseClient } from '../interfaces/baseClientApi/baseClient'
import editBaseClientPresenter from '../views/presenters/editBaseClientPresenter'

Expand Down Expand Up @@ -94,4 +94,69 @@ export default class BaseClientController {
return ''
}
}

public displayEditBaseClient(): RequestHandler {
return async (req, res) => {
const userToken = res.locals.user.token
const { baseClientId } = req.params
const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId)

const presenter = editBaseClientPresenter(baseClient)
res.render('pages/edit-base-client-details.njk', {
baseClient,
presenter,
...nunjucksUtils,
})
}
}

public updateBaseClientDetails(): RequestHandler {
return async (req, res, next) => {
const userToken = res.locals.user.token
const { baseClientId } = req.params

// get current values
const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId)

// map form values to updated base client
const updatedClient = mapEditBaseClientDetailsForm(baseClient, req)

// update base client
await this.baseClientService.updateBaseClient(userToken, updatedClient)

// return to view base client page
res.redirect(`/base-clients/${baseClientId}`)
}
}

public displayEditBaseClientDeployment(): RequestHandler {
return async (req, res) => {
const userToken = res.locals.user.token
const { baseClientId } = req.params
const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId)

res.render('pages/edit-base-client-deployment.njk', {
baseClient,
})
}
}

public updateBaseClientDeployment(): RequestHandler {
return async (req, res, next) => {
const userToken = res.locals.user.token
const { baseClientId } = req.params

// get current values
const baseClient = await this.baseClientService.getBaseClient(userToken, baseClientId)

// map form values to updated base client
const updatedClient = mapEditBaseClientDeploymentForm(baseClient, req)

// update base client
await this.baseClientService.updateBaseClientDeployment(userToken, updatedClient)

// return to view base client page
res.redirect(`/base-clients/${baseClientId}`)
}
}
}
12 changes: 12 additions & 0 deletions server/data/localMockData/baseClientsResponseMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,16 @@ export const getBaseClientResponseMock: GetBaseClientResponse = {
databaseUserName: 'databaseUserName',
validDays: 1,
accessTokenValidityMinutes: 60,
deployment: {
team: 'deployment team',
teamContact: 'deployment team contact',
teamSlack: 'deployment team slack',
hosting: 'deployment hosting',
namespace: 'deployment namespace',
deployment: 'deployment deployment',
secretName: 'deployment secret name',
clientIdKey: 'deployment client id key',
secretKey: 'deployment secret key',
deploymentInfo: 'deployment deployment info',
},
}
12 changes: 12 additions & 0 deletions server/interfaces/baseClientApi/baseClientResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ export interface GetBaseClientResponse {
databaseUserName?: string
validDays?: number
accessTokenValidityMinutes?: number
deployment: {
team: string
teamContact: string
teamSlack: string
hosting: string
namespace: string
deployment: string
secretName: string
clientIdKey: string
secretKey: string
deploymentInfo: string
}
}

export interface ClientSecretsResponse {
Expand Down
11 changes: 1 addition & 10 deletions server/mappers/baseClientApi/getBaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,7 @@ export default (response: GetBaseClientResponse): BaseClient => {
contact: '',
status: '',
},
deployment: {
team: '',
teamContact: '',
teamSlack: '',
hosting: '',
namespace: '',
deployment: '',
secretName: '',
clientIdKey: '',
},
deployment: response.deployment,
config: {
allowedIPs: response.ips ? response.ips : [],
expiryDate: response.validDays
Expand Down
6 changes: 3 additions & 3 deletions server/mappers/baseClientApi/updateBaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { daysRemaining } from '../../utils/utils'

export default (baseClient: BaseClient): UpdateBaseClientRequest => {
return {
scopes: ['read', 'write'],
authorities: ['ROLE_CLIENT_CREDENTIALS'],
ips: [],
scopes: baseClient.scopes,
authorities: baseClient.clientCredentials.authorities,
ips: baseClient.config.allowedIPs,
jiraNumber: baseClient.audit,
databaseUserName: baseClient.clientCredentials.databaseUserName,
validDays: baseClient.config.expiryDate ? daysRemaining(baseClient.config.expiryDate) : null,
Expand Down
20 changes: 1 addition & 19 deletions server/mappers/forms/mapCreateBaseClientForm.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
import type { Request } from 'express'
import { BaseClient } from '../../interfaces/baseClientApi/baseClient'
import { multiSeparatorSplit } from '../../utils/utils'

function getDayOfExpiry(daysRemaining: string) {
const daysRemainingInt = parseIntWithDefault(daysRemaining, 0)
const timeOfExpiry: Date = new Date(Date.now() + daysRemainingInt * 24 * 60 * 60 * 1000)
return timeOfExpiry.toISOString().split('T')[0]
}

function getAccessTokenValiditySeconds(accessTokenValidity: string, customAccessTokenValidity?: string) {
if (accessTokenValidity === 'custom' && customAccessTokenValidity) {
return parseIntWithDefault(customAccessTokenValidity, 0)
}
return parseIntWithDefault(accessTokenValidity, 0)
}

function parseIntWithDefault(value: string, defaultValue: number) {
const parsed = parseInt(value, 10)
return Number.isNaN(parsed) ? defaultValue : parsed
}
import { getAccessTokenValiditySeconds, getDayOfExpiry, multiSeparatorSplit } from '../../utils/utils'

export default (request: Request): BaseClient => {
// valid days is calculated from expiry date
Expand Down
58 changes: 58 additions & 0 deletions server/mappers/forms/mapEditBaseClientDeploymentForm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Request } from 'express'
import { createMock } from '@golevelup/ts-jest'
import { baseClientFactory } from '../../testutils/factories'
import { mapEditBaseClientDeploymentForm } from '../index'

const formRequest = (form: Record<string, unknown>) => {
return createMock<Request>({ body: form })
}

describe('mapEditBaseClientDeploymentForm', () => {
describe('updates deployment details only', () => {
it('updates details only', () => {
// Given a base client with fields populated
const detailedBaseClient = baseClientFactory.build({
accessTokenValidity: 3600,
deployment: {
team: 'deployment team',
},
service: {
serviceName: 'service serviceName',
},
})

// and given an edit deployment request with all fields populated
const request = formRequest({
team: 'team',
teamContact: 'contact',
teamSlack: 'slack',
hosting: 'CLOUDPLATFORM',
namespace: 'b',
deployment: 'c',
secretName: 'd',
clientIdKey: 'e',
secretKey: 'f',
deploymentInfo: 'g',
})

// when the form is mapped
const update = mapEditBaseClientDeploymentForm(detailedBaseClient, request)

// then the deployment details are updated
expect(update.deployment.team).toEqual('team')
expect(update.deployment.teamContact).toEqual('contact')
expect(update.deployment.teamSlack).toEqual('slack')
expect(update.deployment.hosting).toEqual('CLOUDPLATFORM')
expect(update.deployment.namespace).toEqual('b')
expect(update.deployment.deployment).toEqual('c')
expect(update.deployment.secretName).toEqual('d')
expect(update.deployment.clientIdKey).toEqual('e')
expect(update.deployment.secretKey).toEqual('f')
expect(update.deployment.deploymentInfo).toEqual('g')

// but regular client details and service details are not updated
expect(update.accessTokenValidity).toEqual(3600)
expect(update.service.serviceName).toEqual(detailedBaseClient.service.serviceName)
})
})
})
21 changes: 21 additions & 0 deletions server/mappers/forms/mapEditBaseClientDeploymentForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Request } from 'express'
import { BaseClient } from '../../interfaces/baseClientApi/baseClient'

export default (baseClient: BaseClient, request: Request): BaseClient => {
const data = request.body
return {
...baseClient,
deployment: {
team: data.team,
teamContact: data.teamContact,
teamSlack: data.teamSlack,
hosting: data.hosting,
namespace: data.namespace,
deployment: data.deployment,
secretName: data.secretName,
clientIdKey: data.clientIdKey,
secretKey: data.secretKey,
deploymentInfo: data.deploymentInfo,
},
}
}

0 comments on commit fb650aa

Please sign in to comment.