UNPKG

mochapi

Version:
269 lines (268 loc) 10.8 kB
// Copyright 2015 The Home Depot // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var http = require("http"); var fs = require("fs"); var URL = require("url"); var queryString = require("query-string"); var formidable = require("formidable"); var pjson = require("pjson"); var jsonplus_1 = require("jsonplus"); // Colors updates the String object require("colors"); var Mapi = (function () { function Mapi(args) { // Get args var dbFile = args[0], port = args[1] ? Number(args[1]) : 9000, hostname = args[2] || "localhost"; if (dbFile) { this.map = jsonplus_1.parse(this.readFile(args[0])); } else { this.usage("Please provide a DB"); return this.exit(1); } console.log("%s %s", "Mock server started".green, ("http://" + hostname + ":" + port + "/_mapi/").magenta.underline); this.createServer(port, hostname); } /** * Exits application with given status */ Mapi.prototype.exit = function (status) { return process.exit(status); }; /** * Creates a server */ Mapi.prototype.createServer = function (port, hostname) { http.createServer(this.server.bind(this)).listen(port, hostname); }; /** * Prints the usage information */ Mapi.prototype.usage = function (errorMessage) { if (errorMessage === void 0) { errorMessage = ""; } console.log(errorMessage.red); console.log("Usage:".green.underline); console.log(" mapi db.json".yellow, " # Just point a file as database".grey); console.log(" mapi db.json 8080".yellow, " # You can set a port as well".grey); console.log(" mapi db.json 8080 127.0.0.1".yellow, " # You can set a hostname as well".grey); console.log("Version: %s".green, pjson.version); console.log("More details on %s".green, pjson.homepage); }; /** * Reads the given file and returns it as a string */ Mapi.prototype.readFile = function (fileName) { var file; try { file = fs.readFileSync(fileName, { encoding: "utf-8" }); } catch (e) { this.usage("Could not read " + fileName); return this.exit(1); } return file; }; /** * Send a response to the request with given content and status code */ Mapi.prototype.sendResponse = function (ServerResponse, content, status) { if (status === void 0) { status = 200; } ServerResponse.writeHead(status, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }); ServerResponse.end(content); return ServerResponse; }; Mapi.prototype.serveStatic = function (ServerResponse, filename, mimeType) { var stats, fileStream; try { stats = fs.lstatSync(filename); // throws if path doesn't exist } catch (e) { ServerResponse.writeHead(404, { "Content-Type": "text/plain" }); ServerResponse.write("404 Not Found\n"); ServerResponse.end(); return ServerResponse; } if (stats.isFile()) { // path exists, is a file ServerResponse.writeHead(200, { "Content-Type": mimeType }); fileStream = fs.createReadStream(filename); fileStream.pipe(ServerResponse); } return ServerResponse; }; /** * Writes an entry to server logs */ Mapi.prototype.log = function (status, url, message) { if (message === void 0) { message = ""; } console.log("- %s %s, %s", // Pick color for the status code ("[ " + status + " ]")[status === 200 ? "green" : "red"], url.yellow, message.grey); return message; }; Mapi.prototype.searchMapRegExp = function (url) { var _this = this; // Determines whether url should be treated as a regular expression var rgxToken = ":", // Get all the keys to look for wildcards // TODO: Find all these keys during initialization and cache the results urls = Object.keys(this.map), entry; // Going through all endpoints, create a regexp from wildcards and // try to match them to URL provided. urls.forEach(function (endpoint) { // Detect regex if (endpoint.indexOf(":") === 0) { // Remove the rgxToken from the current endpoint var expression = endpoint.slice(1, endpoint.length).trim(); // Create a REGEXP from the current endpoint var rgx = new RegExp(expression, "gim"); if (rgx.test(url.noTrailing) || rgx.test(url.trailing)) { entry = _this.map[endpoint]; } } else if (endpoint.indexOf("*") !== -1) { // First sanitize all possible REGEXP signs var sanitized = endpoint.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); // Then find sanitized * and replace it with URL ready wildcard var rgx = new RegExp(sanitized.replace(/\\\*/g, "([^\\/]*?)"), "gim"); // Try url with regexp, make sure to test // with trailing slash as well. if (rgx.test(url.noTrailing) || rgx.test(url.trailing)) { entry = _this.map[endpoint]; } } }); return entry; }; /** * Searches request URL in the endpoint map with given method. Returns information found. */ Mapi.prototype.searchMap = function (url, method) { if (method === void 0) { method = "GET"; } var entry, result = {}; entry = this.map[url.noTrailing] || this.map[url.trailing] || this.searchMapRegExp(url); // If url is not in the map if (entry === undefined) { result.notFound = true; } else { result = { url: url.original }; // Make sure data exists if (entry[method]) { result.fixture = entry[method].response; result.status = entry[method].status || 200; } else if (entry.ALL) { result.fixture = entry.ALL.response; result.status = entry.ALL.status || 200; } else { result.notFound = true; } } // Return the found response return result; }; /** * Normalizes the url but creating trailing and non trailing slash * versions of it. it also contains the original url */ Mapi.prototype.normalizeUrl = function (url) { var cleaned, result = { original: url }, parsed; cleaned = url.replace(/\/+/g, "/").replace("/mapi", "/api"); parsed = URL.parse(cleaned); if (/\/$/.test(parsed.pathname)) { result.trailing = URL.format(parsed); parsed.pathname = parsed.pathname.replace(/\/$/, ""); result.noTrailing = URL.format(parsed); } else { result.noTrailing = URL.format(parsed); parsed.pathname = parsed.pathname + "/"; result.trailing = URL.format(parsed); } return result; }; /** * Takes a normalized url and appends a given query string */ Mapi.prototype.appendQueryString = function (url, query) { url.original += "\\?" + query; url.noTrailing += "?" + query; url.trailing += "?" + query; return url; }; Mapi.prototype.getEndpoint = function (ServerRequest, callback) { var reqUrl = this.normalizeUrl(ServerRequest.url); if (ServerRequest.method === "POST") { var post = new formidable.IncomingForm(); post.parse(ServerRequest, function (err, params, files) { var query = queryString.stringify(params); if (query.length > 0) { //Assumes the POST url is a regex, so we escape the ? reqUrl = this.appendQueryString(reqUrl, query); } callback(this.searchMap(reqUrl, ServerRequest.method)); }.bind(this)); } else { callback(this.searchMap(reqUrl, ServerRequest.method)); } }; /** * Handles the requests and sends response back accordingly. */ Mapi.prototype.server = function (ServerRequest, ServerResponse) { var response, status, logMessage, endpoint, reqUrl = this.normalizeUrl(ServerRequest.url); this.getEndpoint(ServerRequest, function (endpoint) { if (endpoint.notFound !== true) { // if url found in the endpoint map, display the // fixture data with status code response = JSON.stringify(endpoint.fixture); status = endpoint.status; logMessage = endpoint.url; } else if (reqUrl.noTrailing === "/favicon.ico") { // Serve this file statically return this.serveStatic(ServerResponse, "src/images/favicon.ico", "image/x-icon"); } else if (reqUrl.noTrailing === "/_mapi") { // for this url, display all mocked API Endpoints response = JSON.stringify(Object.keys(this.map)); status = 200; logMessage = "show all urls"; } else { if (this.map.default404) { response = this.map.default404.ALL.response; status = this.map.default404.ALL.status; logMessage = "default 404"; } else { // If URL was not found display 404 message response = "{ \"error\": \"Could not find " + reqUrl.original + "\" }"; status = 404; logMessage = "url not mapped"; } } this.log(status, reqUrl.original, logMessage); this.sendResponse(ServerResponse, response, status); }.bind(this)); }; return Mapi; })(); exports.Mapi = Mapi;