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
This commit is contained in:
Leo Hemsted
2021-05-25 18:43:01 +01:00
parent d05f127e41
commit e765f98a02

View File

@@ -36,6 +36,17 @@ describe('Register security key', () => {
}) })
test('creates a new credential and reloads', (done) => { 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) => { jest.spyOn(window.navigator.credentials, 'create').mockImplementation((options) => {
expect(options).toEqual('options') expect(options).toEqual('options')
@@ -49,29 +60,23 @@ describe('Register security key', () => {
}) })
}) })
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(() => { jest.spyOn(window.location, 'reload').mockImplementation(() => {
// signal that the async promise chain was called // signal that the async promise chain was called
done() done()
}) })
jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => { // this will make the test fail if the alert is called
// initial fetch of options from the server jest.spyOn(window, 'alert').mockImplementation((msg) => {
if (!options.method) { done(msg)
// options from the server are CBOR-encoded
webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions
})
// subsequent POST of credential data to server
} else {
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 })
}
}) })
button.click() button.click()
@@ -81,7 +86,7 @@ describe('Register security key', () => {
['network'], ['network'],
['server'], ['server'],
])('alerts if fetching WebAuthn options fails (%s error)', (errorType, done) => { ])('alerts if fetching WebAuthn options fails (%s error)', (errorType, done) => {
jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => { jest.spyOn(window, 'fetch').mockImplementation((_url) => {
if (errorType == 'network') { if (errorType == 'network') {
return Promise.reject('error') return Promise.reject('error')
} else { } else {
@@ -102,32 +107,32 @@ describe('Register security key', () => {
['internal server error'], ['internal server error'],
['bad request'], ['bad request'],
])('alerts if sending WebAuthn credentials fails (%s)', (errorType, done) => { ])('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(() => { jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
// fake PublicKeyCredential response from WebAuthn API // fake PublicKeyCredential response from WebAuthn API
return Promise.resolve({ response: {} }) return Promise.resolve({ response: {} })
}) })
jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => { jest.spyOn(window, 'fetch').mockImplementationOnce((_url) => {
// initial fetch of options from the server
if (!options.method) {
webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions
})
// subsequent POST of credential data to server // subsequent POST of credential data to server
} else { switch (errorType) {
switch (errorType) { case 'network error':
case 'network error': return Promise.reject('error')
return Promise.reject('error') case 'bad request':
case 'bad request': message = Promise.resolve(window.CBOR.encode('error'))
message = Promise.resolve(window.CBOR.encode('error')) return Promise.resolve({ ok: false, arrayBuffer: () => message })
return Promise.resolve({ ok: false, arrayBuffer: () => message }) case 'internal server error':
case 'internal server error': message = Promise.reject('encoding error')
message = Promise.reject('encoding error') return Promise.resolve({ ok: false, arrayBuffer: () => message, statusText: 'error' })
return Promise.resolve({ ok: false, arrayBuffer: () => message, statusText: 'error' })
}
} }
}) })
@@ -144,9 +149,9 @@ describe('Register security key', () => {
return Promise.reject(new DOMException('error')) return Promise.reject(new DOMException('error'))
}) })
jest.spyOn(window, 'fetch').mockImplementation((_url, options) => { jest.spyOn(window, 'fetch').mockImplementation((_url) => {
// initial fetch of options from the server // initial fetch of options from the server
webauthnOptions = window.CBOR.encode('options') const webauthnOptions = window.CBOR.encode('options')
return Promise.resolve({ return Promise.resolve({
ok: true, arrayBuffer: () => webauthnOptions ok: true, arrayBuffer: () => webauthnOptions