app/frontend/javascript/test_support/mock_api.js
/*
Mock the devour api to allow us to test the UI without making real requests to
the server. This is done by adding a mock middleware to the devour api. The
mock middleware intercepts requests and returns a mocked response. The mocked
response is defined by calling mockGet() or mockFail() on the mock middleware.
*/
import devourApi from '@/javascript/shared/devourApi.js'
import sequencescapeResources from '@/javascript/shared/resources.js'
// Provides object equality comparisons. eg.
// isEqual({a:'1'},{a:'1'}) > true
import isEqual from 'fast-deep-equal'
const dummyApiUrl = 'http://www.example.com'
const dummyApiKey = 'mock_api_key'
// Nice user readable summary of the request object
const requestFormatter = function (request) {
// order keys alphabetically
const sorted_keys = Object.keys(request.params).sort()
const parameters = sorted_keys.map((key) => `"${key}": ${JSON.stringify(request.params[key])}`).join(', ')
return `
--------------------------------------------------------------------------------
Method: ${request.method}
Url: ${request.url}
Params: {${parameters}}
--------------------------------------------------------------------------------
`.trim()
}
// Fail the test if we receive an unexpected request and provide information
// to assist with debugging
const raiseUnexpectedRequest = (request, expectedRequests) => {
const formattedExpectedRequests = expectedRequests.map((req) => requestFormatter(req.req)).join('\n ')
const formattedRequest = requestFormatter(request)
throw new Error(`
Unexpected request:
${formattedRequest}
Expected one of:
${formattedExpectedRequests}`)
}
const mockApi = function (resources = sequencescapeResources) {
const devour = devourApi({ apiUrl: dummyApiUrl }, resources, dummyApiKey)
const mockedRequests = []
// Find a request in the mockedRequests array that matches the request
// object. If no match is found, return undefined.
const findRequest = (request) => {
return mockedRequests.find((requestResponse) => {
// devour is a little inconsistent in when it records a data payload
// findAll() for instant leaves data undefined, whereas some of the url
// generation routes (such as grabbing relationships) send an empty object
const { method, url, params, data = {} } = request
return isEqual(requestResponse.req, { method, url, params, data })
})
}
const mockResponseMiddleware = {
name: 'mock-request-response',
mockedRequests: [],
req: (payload) => {
const incomingRequest = payload.req
const mockedRequest = findRequest(incomingRequest)
if (mockedRequest) {
incomingRequest.adapter = function () {
return mockedRequest.res
}
} else {
// Stop things going further, otherwise we risk generating real traffic
incomingRequest.adapter = function () {
return Promise.reject({ message: 'unregistered request' })
}
raiseUnexpectedRequest(incomingRequest, mockedRequests)
}
return payload
},
error: function (payload) {
return Promise.reject(payload)
},
mockGet: (url, params, response) => {
mockedRequests.unshift({
req: { method: 'GET', url: `${dummyApiUrl}/${url}`, data: {}, params }, // Request
res: Promise.resolve({ data: response }), // Response
})
},
mockFail: (url, params, response) => {
// for each error in response.errors, create an object with the indices as keys and the error items as values
const errors = response.errors.reduce((accumulator, error, index) => {
accumulator[index] = error
return accumulator
}, {})
mockedRequests.unshift({
req: { method: 'GET', url: `${dummyApiUrl}/${url}`, data: {}, params }, // Request
res: Promise.reject(errors), // Response
})
},
devour,
}
// Ensure that a 'mock-request-response' middleware is always present in
// the devour.middleware array, either by adding a new one or replacing an existing one.
let mockMiddlewareIndex = devour.middleware.findIndex((mw) => {
mw.name === 'mock-request-response'
})
if (mockMiddlewareIndex === -1) {
devour.middleware.unshift(mockResponseMiddleware) // add mock middleware as the first middleware
} else {
devourApi.replaceMiddleware('mock-request-response', mockResponseMiddleware)
}
return mockResponseMiddleware
}
export default mockApi