Files
notifications-admin/tests/javascripts/registerSecurityKey.test.js
Leo Hemsted e765f98a02 use mockImplementationOnce to define separate return vals for fetch
rather than having a gross if/else, we can define separately. This means
we can separate the asserts and test setups for the first fetch (get)
and the second fetch (post), which means we can arrange all the mocks in
the order they're called in the function, significantly enhancing
legibility of the tests
2021-06-02 11:54:18 +01:00

169 lines
5.2 KiB
JavaScript

beforeAll(() => {
window.CBOR = require('../../node_modules/cbor-js/cbor.js')
require('../../app/assets/javascripts/registerSecurityKey.js')
// 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(() => {})
// ensure window.alert() is implemented to simplify errors
jest.spyOn(window, 'alert').mockImplementation(() => {})
// populate missing values to allow consistent jest.spyOn()
window.fetch = () => {}
window.navigator.credentials = { create: () => {} }
})
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(() => {
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()
})
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 alert is called
jest.spyOn(window, 'alert').mockImplementation((msg) => {
done(msg)
})
button.click()
})
test.each([
['network'],
['server'],
])('alerts 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, 'alert').mockImplementation((msg) => {
expect(msg).toEqual('Error during registration.\n\nerror')
done()
})
button.click()
})
test.each([
['network error'],
['internal server error'],
['bad request'],
])('alerts 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, 'alert').mockImplementation((msg) => {
expect(msg).toEqual('Error during registration.\n\nerror')
done()
})
button.click()
})
test('alerts 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, 'alert').mockImplementation((msg) => {
expect(msg).toEqual('Error during registration.\n\nerror')
done()
})
button.click()
})
})