Files
notifications-admin/tests/javascripts/registerSecurityKey.test.js
Leo Hemsted 2c55f4d0ce hard-code html error message for errorBanner
turns out that we're only using errorBanner with a static message, and
it's also full of rich html content. This means that it's probably
better to put it in the html templates with other content, rather than
hidden away in js files if we can help it.

Since there are two places, had to dupe the error message but i think
that's fine as i don't anticipate this error message being used in
significantly more places.

making it a string is a bit gross and means we don't get nice syntax
highlighting on it, but as it needs to be passed in to a jinja macro
that's the way it has to go unfortunately.
2021-09-14 18:43:27 +01:00

171 lines
5.2 KiB
JavaScript

beforeAll(() => {
window.CBOR = require('../../node_modules/cbor-js/cbor.js')
require('../../app/assets/javascripts/registerSecurityKey.js')
// populate missing values to allow consistent jest.spyOn()
window.fetch = () => {}
window.navigator.credentials = { create: () => { } }
window.GOVUK.ErrorBanner = {
showBanner: () => { },
hideBanner: () => { }
};
})
afterAll(() => {
require('./support/teardown.js')
// restore window attributes to their original undefined state
delete window.fetch
delete window.navigator.credentials
})
describe('Register security key', () => {
let button
beforeEach(() => {
// disable console.error() so we don't see it in test output
// you might need to comment this out to debug some failures
jest.spyOn(console, 'error').mockImplementation(() => {})
document.body.innerHTML = `
<a href="#" role="button" draggable="false" class="govuk-button govuk-button--secondary" data-module="register-security-key">
Register a key
</a>`
button = document.querySelector('[data-module="register-security-key"]')
window.GOVUK.modules.start()
})
afterEach(() => {
jest.restoreAllMocks()
})
test('creates a new credential and reloads', (done) => {
jest.spyOn(window, 'fetch').mockImplementationOnce((_url) => {
// initial fetch of options from the server
// options from the server are CBOR-encoded
const webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions
})
})
jest.spyOn(window.navigator.credentials, 'create').mockImplementation((options) => {
expect(options).toEqual('options')
// fake PublicKeyCredential response from WebAuthn API
// both of the nested properties are Array(Buffer) objects
return Promise.resolve({
response: {
attestationObject: [1, 2, 3],
clientDataJSON: [4, 5, 6],
}
})
})
jest.spyOn(window, 'fetch').mockImplementationOnce((_url, options) => {
// subsequent POST of credential data to server
const decodedData = window.CBOR.decode(options.body)
expect(decodedData.clientDataJSON).toEqual(new Uint8Array([4,5,6]))
expect(decodedData.attestationObject).toEqual(new Uint8Array([1,2,3]))
expect(options.headers['X-CSRFToken']).toBe()
return Promise.resolve({ ok: true })
})
jest.spyOn(window.location, 'reload').mockImplementation(() => {
// signal that the async promise chain was called
done()
})
// this will make the test fail if the error banner is displayed
jest.spyOn(window.GOVUK.ErrorBanner, 'showBanner').mockImplementation(() => {
done('didnt expect the banner to be shown')
})
button.click()
})
test.each([
['network'],
['server'],
])('errors if fetching WebAuthn options fails (%s error)', (errorType, done) => {
jest.spyOn(window, 'fetch').mockImplementation((_url) => {
if (errorType == 'network') {
return Promise.reject('error')
} else {
return Promise.resolve({ ok: false, statusText: 'error' })
}
})
jest.spyOn(window.GOVUK.ErrorBanner, 'showBanner').mockImplementation(() => {
done()
})
button.click()
})
test.each([
['network error'],
['internal server error'],
['bad request'],
])('errors if sending WebAuthn credentials fails (%s)', (errorType, done) => {
jest.spyOn(window, 'fetch').mockImplementationOnce((_url) => {
// initial fetch of options from the server
const webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions
})
})
jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
// fake PublicKeyCredential response from WebAuthn API
return Promise.resolve({ response: {} })
})
jest.spyOn(window, 'fetch').mockImplementationOnce((_url) => {
// subsequent POST of credential data to server
switch (errorType) {
case 'network error':
return Promise.reject('error')
case 'bad request':
message = Promise.resolve(window.CBOR.encode('error'))
return Promise.resolve({ ok: false, arrayBuffer: () => message })
case 'internal server error':
message = Promise.reject('encoding error')
return Promise.resolve({ ok: false, arrayBuffer: () => message, statusText: 'error' })
}
})
jest.spyOn(window.GOVUK.ErrorBanner, 'showBanner').mockImplementation(() => {
done()
})
button.click()
})
test('errors if comms with the authenticator fails', (done) => {
jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
return Promise.reject(new DOMException('error'))
})
jest.spyOn(window, 'fetch').mockImplementation((_url) => {
// initial fetch of options from the server
const webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions
})
})
jest.spyOn(window.GOVUK.ErrorBanner, 'showBanner').mockImplementation(() => {
done()
})
button.click()
})
})