Created
April 26, 2019 14:45
-
-
Save mdciotti/3780358ddbef792f9c09d04d238c08e0 to your computer and use it in GitHub Desktop.
Mock API for testing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Simulates asynchronous data loading by wrapping setTimeout in a Promise. | |
* @param {Number} ms the delay in milliseconds to wait before resolving | |
* @param {any} data the data to resolve with | |
* @returns {Promise} a promise that resolves with `data` in `ms` milliseconds | |
*/ | |
async function asyncData(ms, data) { | |
await new Promise(r => setTimeout(r, ms)); | |
return data; | |
} | |
function has(obj, prop) { | |
return Object.prototype.hasOwnProperty.call(obj, prop); | |
} | |
export default class FakeAPI { | |
constructor(delay = 1000) { | |
this.delay = delay; | |
this.routes = { | |
GET: {}, | |
POST: {}, | |
PUT: {}, | |
DELETE: {}, | |
PATCH: {}, | |
}; | |
this.errors = { | |
network: false, | |
server: false, | |
}; | |
} | |
routeExists(route, method = 'GET') { | |
const m = method.toUpperCase(); | |
return has(this.routes, m) && has(this.routes[m], route); | |
} | |
routeImplemented(route, method = 'GET') { | |
const m = method.toUpperCase(); | |
return this.routeExists(route, m) && this.routes[m][route]; | |
} | |
/** | |
* Creates an error Response of the specified type. | |
* @param {Number} status the HTTP status code | |
* @param {String} statusText the HTTP status text | |
* @param {*} data the body JSON that explains the error | |
*/ | |
async errorResponse(status, statusText, data) { | |
const body = await asyncData(this.delay, JSON.stringify(data)); | |
const headers = { 'Content-Length': body.length }; | |
return new Response(body, { status, statusText, headers }); | |
} | |
/** | |
* Defines a route that responds to requests. | |
* @param {String} method the HTTP method to use | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
addRoute(method, route, data) { | |
if (data === null) { | |
this.routes[method][route] = null; | |
return; | |
} | |
let getter; | |
switch (typeof data) { | |
case 'function': getter = data; break; | |
case 'string': getter = () => data; break; | |
case 'undefined': getter = () => null; break; | |
default: getter = () => JSON.stringify(data); | |
} | |
this.routes[method][route] = getter; | |
} | |
/** | |
* Defines a route that responds to GET requests. | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
get(route, data) { | |
this.addRoute('GET', route, data); | |
} | |
/** | |
* Defines a route that responds to POST requests. | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
post(route, data) { | |
this.addRoute('POST', route, data); | |
} | |
/** | |
* Defines a route that responds to DELETE requests. | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
delete(route, data) { | |
this.addRoute('DELETE', route, data); | |
} | |
/** | |
* Defines a route that responds to PUT requests. | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
put(route, data) { | |
this.addRoute('PUT', route, data); | |
} | |
/** | |
* Defines a route that responds to PATCH requests. | |
* @param {String} route the URL path for this resource | |
* @param {*} data the data to be returned by this route | |
*/ | |
patch(route, data) { | |
this.addRoute('PATCH', route, data); | |
} | |
async fetch(route, init) { | |
// return fetch(route, init); | |
const opts = Object.assign({ | |
method: 'GET', | |
headers: { | |
Accept: 'text/plain;charset=UTF-8', | |
'Content-Type': 'text/plain;charset=UTF-8', | |
}, | |
}, init); | |
const m = opts.method.toUpperCase(); | |
if (this.errors.network) { | |
throw new Error('Network error (no connection).'); | |
} | |
if (!this.routeExists(route, m)) { | |
return this.errorResponse(404, 'Not found', { | |
error: { code: 'routenotfound', message: 'The request URL did not match any known routes.' }, | |
}); | |
} | |
if (this.errors.server) { | |
return this.errorResponse(500, 'Internal Server Error', { | |
error: { code: 'unknown', message: 'An unknown error occured.' }, | |
}); | |
} | |
if (!this.routeImplemented(route, m)) { | |
return this.errorResponse(501, 'Not Implemented', { | |
error: { code: 'routenotimplemented', message: 'This route has not yet been defined.' }, | |
}); | |
} | |
const origin = window.location.origin; | |
const req = new Request(`${origin}${route}`, opts); | |
const body = await asyncData(this.delay, this.routes[m][route](req)); | |
return new Response(body, { | |
status: 200, | |
statusText: 'OK', | |
headers: { 'Content-Length': body.length }, | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment