/availity-ekko

Mock server simulating Availity API rest services

Primary LanguageJavaScriptMIT LicenseMIT

availity-ekko

Mock server simulating Availity API rest services

License NPM Dependency Status Linux Passing Windows Passing

Table of Contents

Intro

Develop web applications without heavy back-end services by running a simple Express http server which can deliver mock responses.

Responses can be JSON or other formats to simulate REST services. Access-Control HTTP Headers are set by default to allow CORS requests. Mock services are configured in the routes.json file.

This server can return other file types besides XML or JSON (PDFs, images, etc). The appropriate response headers will be automatically set for different file types. For a complete list of file types supported view the mime types here.

Server Configuration

The default server configuration can be found in config.js. Pass a different configuration file to the Ekko server to override the defaults.

var path = require('path');
var Ekko = require('availity-ekko');

var configPath = path.join(__dirname, 'path/to/config.js');
var ekko = new Ekko(configPath);
ekko.start();

Ekko also supports overriding defaults using command line arguments (useful to setup different configurations in WebStorm). The CLI commands are equivalent to the config.js object using dot notation. Using example configuration below, run node index.js --severs.web.port=8888 to override the web server port for development mode.

{
  development: {
    ...
    servers: {
      web: {
        host: "0.0.0.0",
        port: 9999 // --severs.web.port=8888
      }
    }
    ...
  }
}

Route Configuration

The routes.json defines the mock responses for rest services. Below are some sample scenarios that should help you understand the configuration options.

The mock configuration supports deep nested introspection of JSON and multi-part form data when matching routes. See Example 6 below.

Example 1
"v1/route1": {
  "file": "example1.json" // match for GET|PUT|POST|DELETE
}
Example 2
"v1/route2": {
  "latency": 250, // latency in (ms)
  "file": "example2.json", // match for all GET|PUT|POST|DELETE requests
  "status": 201 // return status code 201
}
Example 3
"v1/route3": {
  "file": "example3.json", // match for GET|PUT|DELETE requests
  "post": "example1.json" // match for POST requests
}
Example 4
"v1/route4": {
  "get": "example1.json", // match for all GET requests
  "put": "example2.json", // match for all PUT requests
  "post": "example3.json", // match for all POST requests
  "delete": "example4.json" // match for all DELETE requests
}
Example 5 Query Params
"v1/route5": {
  "file": "example1.json", // match for all POST|PUT|DELETE requests
  "get": [
    {
      "file": "example2.json",
      "status": 200, // default status code is 200
      "params": { // match for GET /v1/router?a=1&b=2&c=3
        "a": "1",
        "b": "2",
        "c": "3"
      }
    },
    {
      "file": "example3.json",
      "params": { // match for GET /v1/router?a=1&a=2&a=3&a=4
        "a": [1, 2, 3, 4]
      }
    },
    {
      "file": "example4.json",
      "params": { // Regular expression configruation for matching params
        "a": { // match for GET /v1/router?a=1 OR /v1/router?a=2 OR /v1/router?a=3
            pattern: "1|2|3",
            flags: "i" // Javascript regex flags to ignore case
        }
      }
    },
  ]
}
Example 6 POST Params
"v1/route6": {
  "file": "example1.json", // match for all GET|PUT|DELETE requests
  "post": [
    {
      "file": "example2.json",
      "params": { // match for POST with JSON payload {"a": 1}
        "a": 1
      }
    },
    {
      "file": "example3.json",
      "params": { // match for POST with JSON payload {a: {b: {c: "1"} } }
        "a.b.c": 1 // config allows for nested attributes
      }
    },
    {
      "file": "example4.json",
      "params": { // match for POST with JSON payload {a : {b: [0,1,2] } }
        "a.b[2]": 2 // config allows for nested array attributes
      }
    }
  ]
},
Example 7 Multipart
<form action="/api/v1/users" method="post" enctype="multipart/form-data">
  <p><input type="text" name="a" value="example">
  <p><input type="file" name="b"> <!--the name of the file is used below to match and score the proper response -->
  <p><button type="submit">Submit</button>
</for
"v1/route7": {
  "file": "example1.json", // match for all GET|PUT|DELETE requests
  "post": [
    {
      "file": "example2.json" // default response if none match below
    },
    {
      "file": "example3.json",
      "params": { // match for form submit where form fields a=1 and b="sample.pdf"
        "a": 1,
        "b": "sample.pdf"
      }
    },
    {
      "file": "example4.json",
      "params": { // match for form submit where form fields a=2 and b="another.name.jpg"
        "a": 2,
        "b": "another.name.jpg"
      }
    }
  ]
}
Example 8 Async Responses
"v1/route8": {
  "file": "example1.json",
  "get": [
    {
      "file": "example1.json",
      "response": [
        {
          // match for first GET request to /v1/route8
          "status": 202,
          "file": "example1.json"
        },
        {
          // match for second GET request to /v1/route8
          "status": 201,
          "file": "example2.json"
        }
      ]
    }
  ]
}
Example 9 Async with repeat option
"v1/route10": {
    "get": [
      {
        "file": "example1.json",
        "response": [
          {
            "status": 202,
            "file": "example1.json",
            "repeat": 3
          },
          {
            "status": 202,
            "file": "example2.json"
          },
          {
            "status": 202,
            "file": "example3.json",
            "repeat": 4
          },
          {
            "status": 201,
            "file": "example4.json"
          }
        ]
      }
    ]
  }
Example 10 Header Matching
"v1/route11": {
  "file": "example1.json",
  "get": [
    {
      "file": "example2.json",
      "headers": { // match for GET with header pair b:2
        "b": "2"
      }
    },
    {
      "file": "example3.json",
      "headers": { // match for GET with header pair b:3
        "c": "3"
      }
    }
  ]
}
Example 11 Url Redirect
"v1/route9": {
  "url": "http://www.google.com"
}

Proxy Configuration

You define Ekko server configurations in config.json. Each configuration requires a host. Other configuration options are outlined below. You must have a configuration called web that is used to serve static files and the proxy server. An example configuration looks like this:

Example 1
{
    user: 'johndoe', // global set `RemoteUser` header across all proxy requests
    servers: {
        web: { // (required) server used for static resources
            host: "0.0.0.0",
            port: 9999
        }
    }
}

If you omit the port, or set it to 0, Ekko will let the OS assign a random open port. This allows you to run multiple servers without keeping track of all ports being used. (see Example 2)

Example 2 Dynamic Port (Ekko only)
servers: {
    web: {
        host: "0.0.0.0",
        port: 0 // dynamic port
    }
}
Example 3 Proxy
servers: {
    web: {
        host: "127.0.0.1",
        port: 9999
    },
    api: {
        host: "127.0.0.1",
        port: 7777, // port number to proxied server
        proxy: true, // defaults to false.  when true the proxy is enabled
        headers: {
            "userid": "johndoe" // set custom header for proxy requests to this server
        },
        proxies:
        [
            {
                context: "/api", // if url context matches the proxy is triggered for all routes
                rewrite: { // (optional) allows url to be rewritten before forwarding request to a proxied server
                    from: "^/api", // convert /api/v1/ping
                    to: "" // to /v1/ping
                }
            }
        ]
    }
}
Example 4 Multiple contexts
servers: {
    web: {
        host: "127.0.0.1",
        port: 9999
    },
    api: {
        host: "127.0.0.1",
        port: 7777,
        proxy: true,
        proxies:
        [
            {
                context: "/api",
                rewrite: {
                    from: "^/api",
                    to: ""
                },
                headers: {
                    "userid": "johndoe" // set custom header for proxy requests this context for this server
                }
            },
            {
                context: "/api2", // you can define multiple context's for a proxied server
                rewrite: {
                    from: "^/api2",
                    to: "/v1"
                }
            }
        ]
    }
}
Example 5 Multiple Proxied Servers
servers: {
    web: {
        host: "127.0.0.1",
        port: 9999
    },
    api: {
        host: "127.0.0.1",
        port: 7777,
        proxy: true,
        proxies:
        [
            {
                context: "/api",
                rewrite: {
                    from: "^/api",
                    to: ""
                }
            }
        ]
    },
    other: { // define more servers to proxy
        host: "127.0.0.1",
        port: 8888,
        proxy: true,
        proxies:
        [
            {
                context: "/test",
                rewrite: {
                    from: "^/test",
                    to: ""
                }
            }
        ]
    }
}

Events

Ekko emits events to allow implementations to handle when specific events occur. Descriptions of the events are listed below.

  • av:start - Triggered when the Ekko server has been started.
  • av:stop - Triggered when the Ekko server has been stopped.
  • av:request - Triggered when a request has been received.
  • av:response - Triggered when a response file has been found for the requested route.
  • av:fileNotFound - Triggered when a response file could not be found -- either as a of an undefined route or the route's response file could not be found.
  • av:redirect - Triggered when a route specifies to redirect instead of responding with the contents of a file.

To add event handlers, register the events before starting the Ekko server.

var ekko = new Ekko(configPath);

ekko.on('av:request', function(req) {
    /* your logic here */
});

ekko.start();

Contributing

  1. git clone https://github.com/Availity/availity-ekko
  2. git checkout develop
  3. git pull upstream develop
  4. git checkout -b feature/branch-name
  5. Create some awesome code or fabulous bug fixes
  6. Open a pull request against the develop branch
  7. Wait for a commiter to merge and release

Authors

Robert McGuinness

Kasey Powers

Disclaimer

Open source software components distributed or made available in the Availity Materials are licensed to Company under the terms of the applicable open source license agreements, which may be found in text files included in the Availity Materials.

License

Copyright (c) Availity, LLC