Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nodecaf

Package Overview
Dependencies
Maintainers
1
Versions
80
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nodecaf - npm Package Compare versions

Comparing version 0.6.0 to 0.7.0

lib/cli/help.js

5

bin/nodecaf.js

@@ -5,2 +5,7 @@ #!node

if(process.argv[2] == '-h')
process.argv[2] = 'help';
else if(process.argv[2] == '-v')
process.argv[2] = 'version';
let cf = path.resolve(__dirname, '../lib/cli', process.argv[2] + '.js');

@@ -7,0 +12,0 @@

@@ -8,2 +8,21 @@ # Nodecaf Changelog

## [v0.7.0] - 2019-07-07
### Added
- support for YAML config files
- app method to filter requests by body content-type app-wide
- top-level function to define per-route content-type filtering rules
- default generic request body description to open api doc operations
- accepted mime-types to operation request body api doc
- global CLI help command to list available commands and usage
- CLI command to output version of the globally installed Nodecaf
- `--no-optional` flag to install command output by `nodecaf init`
### Fixed
- CLI error: unknown type "as-is" on cli options
- CLI init error when lib or bin directory already exists
### Changed
- error messages to not be JSON by default
## [v0.6.0] - 2019-06-24

@@ -124,1 +143,2 @@

[v0.6.0]: https://gitlab.com/GCSBOSS/nodecaf/-/tags/v0.6.0
[v0.7.0]: https://gitlab.com/GCSBOSS/nodecaf/-/tags/v0.7.0

34

lib/app-server.js

@@ -1,2 +0,1 @@

const os = require('os');
const fs = require('fs');

@@ -7,21 +6,13 @@ const http = require('http');

const compression = require('compression');
const fileUpload = require('express-fileupload');
const { defaultErrorHandler, addRoute } = require('./route-adapter');
const { parseTypes } = require('./parse-types');
const setupLogger = require('./logger');
const errors = require('./errors');
const HTTP_VERBS = ['get', 'post', 'patch', 'put', 'head'];
const noop = Function.prototype;
function parsePlainTextBody(req, res, next){
if(Object.keys(req.body).length > 0)
return next();
req.setEncoding('utf8');
req.body = '';
req.on('data', chunk => req.body += chunk);
req.on('end', next);
}
function routeNotFoundHandler(req, res, next){
next(errors.NotFound('NotFound'));
next(errors.NotFound());
}

@@ -44,2 +35,3 @@

this.server = null;
this.accepts = false;

@@ -51,9 +43,2 @@ // Setup logger.

this.express.use(compression());
this.express.use(express.json({ strict: false }));
this.express.use(express.urlencoded({ extended: true }));
this.express.use(fileUpload({
useTempFiles: true,
tempFileDir: os.tmpdir()
}));
this.express.use(parsePlainTextBody);

@@ -70,2 +55,11 @@ // Create adapted versions of all Express routing methods.

/* o\
Define a whitelist of accepted request body mime-types for all routes
in the app. Effectively blocks all requests whose mime-type is not one
of @types. May be overriden by route specific accepts.
\o */
accept(types){
this.accepts = parseTypes(types);
}
/* o\
Execute the @callback to define user routes, exposing all REST methods

@@ -76,3 +70,3 @@ as arguments. Meant to shorten the way you define routes and plug in the

api(callback){
callback(this.routerFuncs);
callback.bind(this)(this.routerFuncs);
this.express.use(routeNotFoundHandler);

@@ -79,0 +73,0 @@ this.express.use(defaultErrorHandler.bind(this));

@@ -69,4 +69,4 @@ const path = require('path');

confPath: [ 'c', 'Conf file path', 'file', undefined ],
confType: [ false, 'Conf file extension', 'as-is', 'toml' ],
name: [ 'n', 'A name/title for the app', 'as-is', undefined ]
confType: [ false, 'Conf file extension', 'string', 'toml' ],
name: [ 'n', 'A name/title for the app', 'string', undefined ]
});

@@ -84,4 +84,8 @@

console.log('Generating basic file structure...');
if(fs.existsSync(projDir + '/lib'))
throw new Error('The \'lib\' directory already exists');
if(fs.existsSync(projDir + '/bin'))
throw new Error('The \'bin\' directory already exists');
input.confType = generateConfFile(input);

@@ -97,4 +101,7 @@ generateRunFile(input, projDir, projName);

if(!('nodecaf' in (pkgInfo.dependencies || [])))
console.log('Install nodecaf localy with:\n npm i nodecaf');
console.log('Install nodecaf localy with:\n npm i --no-optional nodecaf');
console.log('Install your app run binary with:\n npm link');
};
module.exports.description = 'Generates a skelleton Nodecaf project file ' +
'structure in the current directory';

@@ -19,5 +19,5 @@ const path = require('path');

apiPath: [ false, 'The path to your API file (defaults to ./lib/api.js)', 'file', './lib/api.js' ],
type: [ 't', 'A type of output file [yaml || json] (defaults to json)', 'as-is', 'yaml' ],
type: [ 't', 'A type of output file [yaml || json] (defaults to json)', 'string', 'yaml' ],
confPath: [ 'c', 'Conf file path', 'file', undefined ],
confType: [ false, 'Conf file extension', 'as-is', 'toml' ],
confType: [ false, 'Conf file extension', 'string', 'toml' ],
outFile: [ 'o', 'Output file (required)', 'file', undefined ]

@@ -49,1 +49,4 @@ });

};
module.exports.description = 'Generates an Open API compliant document of a ' +
'given Nodecaf API';
const fs = require('fs');
const toml = require('toml');
const TOML = require('toml');
const YAML = require('yaml');
const path = require('path');
const loaders = {
toml: conf => toml.parse(fs.readFileSync(conf))
toml: conf => TOML.parse(fs.readFileSync(conf)),
yaml: conf => YAML.parse(fs.readFileSync(conf, 'utf8'))
}

@@ -9,0 +11,0 @@

@@ -0,9 +1,7 @@

const createError = require('http-errors');
function composeError(type, status, msg){
let e = new Error(msg);
e.type = type;
e.status = status;
let data = { message: msg };
e.body = JSON.stringify(data);
return e;
msg = msg || '';
msg = typeof msg == 'object' ? JSON.stringify(msg) : String(msg);
return createError(status, msg, { type: type });
}

@@ -18,2 +16,3 @@

InvalidContent: msg => composeError('InvalidContent', 400, msg),
BadRequest: msg => composeError('BadRequest', 400, msg),

@@ -20,0 +19,0 @@ parse(err, msg){

@@ -1,49 +0,7 @@

const assert = require('assert');
const AppServer = require('./app-server');
const loadConf = require('./conf-loader');
/* istanbul ignore next */
function term(app, debug){
app.stop();
if(debug)
setTimeout(() => process.exit(0), 1000);
else
console.log('%s is shutting down', app.name);
}
/* istanbul ignore next */
function die(app, debug, err, origin){
if(app && app.log)
app.log.fatal({ err: err }, 'FATAL ERROR');
if(debug)
console.log('Unhandled Exception', err, origin);
else
console.log('Unhandled Exception', err.message, origin);
process.exit(1);
}
module.exports = {
async run({ init, confType, confPath }){
assert.equal(typeof init, 'function');
// Load conf and inputs it on the app class.
let settings = loadConf(confType || 'toml', confPath || false);
let app = init(settings);
assert(app instanceof AppServer);
// Handle signals.
let debug = settings.debug || false;
process.on('SIGINT', term.bind(null, app, debug));
process.on('SIGTERM', term.bind(null, app, debug));
process.on('uncaughtException', die.bind(null, app, debug));
process.on('unhandledRejection', die.bind(null, app, debug));
// Starts the app.
await app.start();
console.log('%s listening at %s', app.name, app.server.address().port);
},
AppServer: AppServer,
assertions: require('./assertions')
run: require('./run'),
AppServer: require('./app-server'),
assertions: require('./assertions'),
accept: require('./parse-types').accept
}
const express = require('express');
const { parseTypes } = require('./parse-types');
const HTTP_VERBS = ['get', 'post', 'patch', 'put', 'head'];

@@ -11,2 +13,5 @@

description: 'The server has faced an error state caused by unknown reasons.'
},
Success: {
description: 'The request has been processed without any issues'
}

@@ -16,2 +21,68 @@ }

function buildDefaultSchemas(){
return {
MissingType: { type: 'string', description: 'Missing \'Content-Type\' header' },
BadType: { type: 'string', description: 'Unsupported content type' }
}
}
function buildResponses(opts){
let r = {
500: { $ref: '#/components/responses/ServerFault' },
200: { $ref: '#/components/responses/Success' }
};
if(opts.accept)
r[400] = {
description: 'Bad Request',
content: {
'text/plain': {
schema: {
oneOf: [
{ '$ref': '#/components/schemas/MissingType' },
{ '$ref': '#/components/schemas/BadType' }
]
}
}
}
};
return r;
}
function buildDefaultRequestBody(){
return {
description: 'Any request body type/format.',
content: { '*/*': {} }
}
}
function buildCustomRequestBodies(accepts){
return {
description: 'Accepts the following types: ' + accepts.join(', '),
content: accepts.reduce((a, c) => ({ ...a, [c]: {} }), {})
}
}
function parseRouteHandlers(handlers){
let opts = {};
for(let h of handlers)
if(typeof h == 'object')
opts = { ...opts, ...h };
return opts;
}
function parsePathParams(params){
return params.map(k => ({
name: k.name,
in: 'path',
description: k.description || '',
required: true,
deprecated: k.deprecated || false,
schema: { type: 'string' }
}));
}
/* o\

@@ -31,14 +102,22 @@ Add summary and description to a route.

\o */
function addOp(method, path){
function addOp(method, path, ...handlers){
// this => app
let paths = this.paths;
paths[path] = paths[path] || {};
let p = this.paths[path] || {};
this.paths[path] = p;
// Reference the global responses used.
paths[path][method] = {
responses: {
500: { $ref: '#/components/responses/ServerFault' }
}
// Asseble reqests and responses data.
let opts = parseRouteHandlers(handlers);
let responses = buildResponses(opts);
let accs = opts.accept || this.accepts;
let reqBody = !accs ? buildDefaultRequestBody() : buildCustomRequestBodies(accs);
// Asseble basic operation object.
p[method] = {
responses: responses,
requestBody: reqBody
};
if(method in { get: true, head: true, delete: true })
delete p[method].requestBody;
// Add express route.

@@ -49,13 +128,5 @@ this.router[method](path, Function.prototype);

this.router.stack.forEach(l => {
if(l.route.path !== path || paths[path].parameters)
if(l.route.path !== path || p.parameters)
return;
paths[path].parameters = l.keys.map(k => ({
name: k.name,
in: 'path',
description: k.description || '',
required: true,
deprecated: k.deprecated || false,
schema: { type: 'string' }
}));
p.parameters = parsePathParams(l.keys);
});

@@ -100,2 +171,10 @@

/* o\
Define allowed mime-types for request accross the entire app. Can be
overriden by route specific settings.
\o */
accept(types){
this.accepts = parseTypes(types);
}
/* o\

@@ -106,3 +185,3 @@ Execute the @callback to define user routes, exposing all REST methods

api(callback){
callback(this.routerFuncs);
callback.bind(this)(this.routerFuncs);
}

@@ -119,3 +198,4 @@

components: {
responses: buildDefaultRESTResponses()
responses: buildDefaultRESTResponses(),
schemas: buildDefaultSchemas()
}

@@ -122,0 +202,0 @@ };

@@ -0,4 +1,51 @@

const os = require('os');
const express = require('express');
const getRawBody = require('raw-body');
const contentType = require('content-type');
const fileUpload = require('express-fileupload');
const adaptErrors = require('./a-sync-error-adapter');
const errors = require('./errors');
const parsers = {
'application/json': express.json({ strict: false }),
'application/x-www-form-urlencoded': express.urlencoded({ extended: true }),
'multipart/form-data': fileUpload({ useTempFiles: true, tempFileDir: os.tmpdir() })
};
function filter(custom, req, res, next){
// this => app
if(!custom && !this.accepts)
return next();
let ct = req.headers['content-type'];
if(!ct)
return next(errors.BadRequest('Missing \'Content-Type\' header'));
let arr = custom || this.accepts;
if(!arr.includes(ct))
return next(errors.BadRequest('Unsupported content type \'' + ct + '\''));
next();
}
async function parse(req, res, next){
try{
var ct = contentType.parse(req);
}
catch(e){
ct = { type: 'plain/text', parameters: { charset: 'utf8' } };
}
if(ct.type in parsers)
return parsers[ct.type](req, res, next);
req.body = await getRawBody(req, {
length: req.headers['content-length'],
encoding: ct.parameters.charset
});
next();
}
function triggerError(input, err, msg){

@@ -22,3 +69,3 @@ // this => app

query: req.query, params: req.params, body: req.body,
flash: res.locals, conf: app.settings, log: app.log || null
flash: res.locals, conf: app.settings, log: app.log
};

@@ -43,14 +90,25 @@

addRoute(method, path, ...route){
// this => app
let customAccept = false;
let aRoutes = [];
// Loop through th route handlers adapting them.
route = route.map(handler => {
for(let handler of route){
if(handler.accept){
customAccept = handler.accept;
continue;
}
if(typeof handler !== 'function')
throw Error('Trying to add non-function route handler');
return adaptHandler(this, handler);
});
aRoutes.push(adaptHandler(this, handler));
}
let accept = filter.bind(this, customAccept);
// Physically add the adapted route to Express.
this.express[method](path, ...route);
this.express[method](path, accept, parse, ...aRoutes);
return { desc: Function.prototype };

@@ -80,3 +138,3 @@ },

// Handle known REST errors.
res.status(err.status).end(err.body);
res.status(err.status).end(err.message);

@@ -83,0 +141,0 @@ // Log unexpected errors sent to the user.

{
"name": "nodecaf",
"version": "0.6.0",
"version": "0.7.0",
"description": "Nodecaf is an Express framework for developing REST APIs in a quick and convenient manner.",

@@ -43,3 +43,5 @@ "main": "lib/main.js",

"express": "^4.17.1",
"express-fileupload": "^1.1.4",
"express-fileupload": "^1.1.5",
"http-errors": "^1.7.3",
"mime": "^2.4.4",
"toml": "^3.0.0",

@@ -49,7 +51,7 @@ "yaml": "^1.6.0"

"devDependencies": {
"form-data": "^2.3.3",
"muhb": "0.0.2",
"swagger-parser": "^7.0.0",
"form-data": "^2.4.0",
"muhb": "^0.1.1",
"swagger-parser": "^7.0.1",
"wtfnode": "^0.8.0"
}
}
# [Nodecaf](https://gitlab.com/GCSBOSS/nodecaf)
> Docs for version v0.6.x.
> Docs for version v0.7.x.

@@ -9,3 +9,3 @@ Nodecaf is an Express framework for developing REST APIs in a quick and

- Useful [handler arguments](#handlers-args).
- Built-in TOML [settings file support](#settings-file).
- Built-in [settings file support](#settings-file).
- [Out-of-the-box logging](#logging) through Bunyan.

@@ -21,2 +21,3 @@ - Seamless support for [async functions as route handlers](#async-handlers).

source of truth.
- Functions to [filter request bodies](#filter-requests-by-mime-type) by mime-type.
- CLI command to [generate a basic Nodecaf project structure](#init-project).

@@ -48,3 +49,3 @@ - CLI command to [generate an OpenAPI document](#open-api-support) or your APIs.

// Expose things to all routes putting them on the 'shared' object.
// Expose things to all routes putting them in the 'shared' object.
let shared = {};

@@ -110,4 +111,4 @@ app.expose(shared);

## Manual
Beyond all the cool features of Express has to offer, check out how to use all
the awesome goodies Nodecaf can give you.
On top of all the cool features Express offers, check out how to use all
the awesome goodies Nodecaf introduces.

@@ -128,3 +129,3 @@ ### Handler Args

- `req`, `res`, `next`: Basically the good old parameters used regularly in Express.
- `req`, `res`, `next`: The good old parameters used regularly in Express.
- `query`, `parameters`, `body`: Shortcuts to the homonymous properties of `req`.

@@ -134,5 +135,5 @@ They contain respectively the query string, the URL parameters, and the request

- `flash`: Is a shortcut to Express `req.locals`. Keys inserted in this a object
are preserved for the lifetime of a request and can be accessed on all handlers
are preserved for the lifetime of a request and can be accessed in all handlers
of a route chain.
- `conf`: This object contain the entire
- `conf`: This object contains the entire
[application configuration data](#settings-file).

@@ -148,4 +149,4 @@ - `log`: A bunyan logger instance. Use it to [log custom events](#logging) of

Nodecaf allow you to read a configuration file in the TOML format (we plan to
add more in the future) and use it's data on all routes and server configuration.
Nodecaf allow you to read a configuration file and use it's data in all routes
and server configuration.

@@ -157,9 +158,11 @@ Use this feature to manage:

> [generate a project with configuration file already plugged in](#init-project)
Suported config formats: **TOML**, **YAML**
> Check out how to [generate a project with configuration file already plugged in](#init-project)
To setup a config file for an existing project, open the binary for your server
on `bin/proj-name.js`. Then add a `confPath` key to the run parameter object
in `bin/proj-name.js`. Then add a `confPath` key to the run parameter object
whose value must be a string path pointing to your conf file.
The data in the config file can be accessed on `lib/main.js` through the first
The data in the config file can be accessed in `lib/main.js` through the first
parameter of the exported `init` function:

@@ -173,3 +176,3 @@

You can also use the config data through [it's handler arg](#handler-args) on
You can also use the config data through [it's handler arg](#handler-args) in
all route handlers as follows:

@@ -194,3 +197,3 @@

You can also setup a file log on your settings file to be automatically
You can also setup a log file in your settings file to be automatically
transferred to the `conf` argument of `init`.

@@ -203,3 +206,3 @@

On your route handlers, use the `log` handler arg as a
In your route handlers, use the `log` handler arg as a
[bunyan](https://github.com/trentm/node-bunyan) instance:

@@ -226,7 +229,6 @@

Nodecaf brings a useful feature of accepting async functions as route handlers
with zero configuration. The real deal is that all rejections/error within your
async handler will be gracefully handled by the same routine the deals with
regular functions. Allowing you to avoid callback hell without creating bogus
adapters for your promises.
Nodecaf brings the useful feature of accepting async functions as route handlers
with zero configuration. All rejections/error within your async handler will be
gracefully handled by the same routine the deals with regular functions. You will
be able to avoid callback hell without creating bogus adapters for your promises.

@@ -248,4 +250,4 @@ ```js

In Nodecaf, all uncaught synchronous errors happening inside route handler code
is automatically converted into a harmless RESTful 500.
In Nodecaf, any uncaught synchronous error happening inside route handler will be
automatically converted into a harmless RESTful 500.

@@ -275,8 +277,10 @@ ```js

- `NotFound`: 404
- `Unauthorized`: 401
- `ServerFault`: 500
- `InvalidActionForState`: 405
- `InvalidCredentials`: 400
- `InvalidContent`: 400
| Error name | Status Code |
|------------|-------------|
| `NotFound` | **404** |
| `Unauthorized` | **401** |
| `ServerFault` | **500** |
| `InvalidActionForState` | **405** |
| `InvalidCredentials` | **400** |
| `InvalidContent` | **400** |

@@ -294,4 +298,4 @@ ```js

You can always deal with uncaught exceptions on all routes through a default
global error handler. In your `lib/main.js` add a `onRuoteError` function
You can always deal with uncaught exceptions in all routes through a default
global error handler. In your `lib/main.js` add an `onRuoteError` function
property to the `app`.

@@ -317,3 +321,3 @@

Nodecaf provides you with an assertion module containing functions to generate
the most common REST outputs based on some condition. Checn an example to
the most common REST outputs based on some condition. Check an example to
trigger a 404 in case a database record doesn't exist.

@@ -337,6 +341,8 @@

- `valid`: `InvalidContent`
- `authorized`: `Unauthorized`
- `authn`: `InvalidCredentials`
- `able`: `InvalidActionForState`
| Method | Error to be output |
|--------|--------------------|
| `valid` | `InvalidContent` |
| `authorized` | `Unauthorized` |
| `authn` | `InvalidCredentials` |
| `able` | `InvalidActionForState` |

@@ -392,2 +398,38 @@ To use it with callback style functions, pass the `error` handler arg as the

### Filter Requests by Mime-type
Nodecaf allow you to reject request bodies whose mime-type is not in a defined
white-list. Denied requests will receive a 400 response with the apporpriate
message.
Define a filter for the entire app on your `api.js`:
```js
module.exports = function({ }){
this.accept(['json', 'text/html']);
}
```
Override the global accept per route on your `api.js`:
```js
const { accept } = require('nodecaf');
module.exports = function({ post, put }){
// Define global accept rules
this.accept(['json', 'text/html']);
// Obtain accepts settings
let json = accept('json');
let img = accept([ 'png', 'jpg', 'svg', 'image/*' ]);
// Prepend accept definition in each route chain
post('/my/json/thing', json, myJSONHandler);
post('/my/img/thing', img, myImageHandler);
}
```
### API Description

@@ -446,6 +488,6 @@

`nodecaf init` Generates a skelleton Nodecaf project file structure on the current
`nodecaf init` Generates a skelleton Nodecaf project file structure in the current
directory.
> You must already have a well-formed package.json on the target directory.
> You must already have a well-formed package.json in the target directory.

@@ -457,6 +499,7 @@ **Options**

- `-n --name [string]`: A name/title for the generated app structure
- `--confType (yaml | toml)`: The type for the generated config file
#### Open API Support
`nodecaf openapi` Generates a [Open API](https://www.openapis.org/) compliant
`nodecaf openapi` Generates an [Open API](https://www.openapis.org/) compliant
document of a given Nodecaf API.

@@ -471,1 +514,2 @@

- `-c --confPath [file]`: The config file to be considered
- `--confType (yaml | toml)`: The type of the given config file

@@ -43,2 +43,11 @@ //const wtf = require('wtfnode');

it('Should fail when \'lib\' or \'bin\' directories already exist', () => {
fs.copyFileSync(resDir + 'test-package.json', './package.json');
fs.mkdirSync('./bin');
assert.throws( () => init({}), /already exists/g);
fs.rmdirSync('./bin');
fs.mkdirSync('./lib');
assert.throws( () => init({}), /already exists/g);
});
it('Should generate basic structure files', () => {

@@ -128,2 +137,18 @@ fs.copyFileSync(resDir + 'test-package.json', './package.json');

describe('nodecaf -h', () => {
const help = require('../lib/cli/help');
it('Should output the top-level CLI help', () => {
let text = help();
assert(/Commands\:/.test(text));
assert(text.length > 100);
});
});
describe('nodecaf -v', () => {
const version = require('../lib/cli/version');
it('Should output a proper version number', () => {
assert(/^v\d+\.\d+\.\d+$/.test(version()));
});
});
});

@@ -130,0 +155,0 @@

//const wtf = require('wtfnode');
const assert = require('assert');
// Address for the tests' local servers to listen.
const LOCAL_HOST = 'http://localhost:80/'
describe('Conf Loader', () => {

@@ -21,2 +24,7 @@ const loadConf = require('../lib/conf-loader');

});
it('Should properly load an YAML file and generate an object', () => {
let obj = loadConf('yaml', './test/res/conf.yaml');
assert.strictEqual(obj.key, 'value');
});
});

@@ -47,33 +55,2 @@

describe('Route Adapter', () => {
const { EventEmitter } = require('events');
const { addRoute } = require('../lib/route-adapter');
it('Should fail when anything other than a function is passed', () => {
let ee = new EventEmitter();
assert.throws( () => addRoute.bind(ee)('get', '/foo', null) );
});
it('Should add adapted handler to chosen route', () => {
let ee = new EventEmitter();
ee.express = {
foo(path){ assert.strictEqual(path, 'foo') }
};
addRoute.bind(ee)('foo', 'foo', function bar(){ });
});
it('Should pass all the required args to adapted function', () => {
let ee = new EventEmitter();
let fn;
ee.express = {
foo(path, handler){ fn = handler }
};
addRoute.bind(ee)('foo', 'foo', function bar(args){
assert.strictEqual(typeof args, 'object');
});
fn('foo', 'bar', 'foobar');
});
});
describe('AppServer', () => {

@@ -102,3 +79,3 @@ const AppServer = require('../lib/app-server');

await app.start();
let { status } = await get('http://127.0.0.1:80/');
let { status } = await get(LOCAL_HOST);
assert.strictEqual(status, 404);

@@ -151,3 +128,3 @@ await app.stop();

await app.start();
let { status } = await post('http://127.0.0.1:80/foo');
let { status } = await post(LOCAL_HOST + 'foo');
assert.strictEqual(status, 500);

@@ -166,3 +143,3 @@ await app.stop();

await app.start();
let { body } = await get('http://127.0.0.1:80/bar');
let { body } = await get(LOCAL_HOST + 'bar');
assert.strictEqual(body, 'bar');

@@ -183,3 +160,3 @@ await app.stop();

await app.start();
let { body } = await post('http://127.0.0.1:80/bar');
let { body } = await post(LOCAL_HOST + 'bar');
assert.strictEqual(body, 'foobar');

@@ -198,3 +175,3 @@ await app.stop();

try{
await get('http://127.0.0.1:80/');
await get(LOCAL_HOST);
}

@@ -230,6 +207,6 @@ catch(e){

await app.start();
let { status } = await get('http://127.0.0.1:80/');
let { status } = await get(LOCAL_HOST);
assert.strictEqual(status, 404);
await app.restart();
let { status: s } = await get('http://127.0.0.1:80/');
let { status: s } = await get(LOCAL_HOST);
assert.strictEqual(s, 404);

@@ -241,2 +218,58 @@ await app.stop();

describe('#accept', () => {
it('Should reject unwanted content-types API-wide', async () => {
let app = new AppServer();
app.api(function({ post }){
this.accept([ 'urlencoded', 'text/html' ]);
assert(this.accepts.includes('application/x-www-form-urlencoded'));
assert.strictEqual(this.accepts.length, 2);
post('/foo', ({ res }) => res.end());
});
await app.start();
let { body, status } = await post(
LOCAL_HOST + 'foo',
{ 'Content-Type': 'application/json' },
'{"foo":"bar"}'
);
assert.strictEqual(status, 400);
assert(/Unsupported/.test(body));
await app.stop();
});
it('Should reject requests without content-type', async () => {
let app = new AppServer();
app.api(function({ post }){
this.accept('text/html');
post('/foo', ({ res }) => res.end());
});
await app.start();
let { body, status } = await post(
LOCAL_HOST + 'foo',
{ '--no-auto': true },
'{"foo":"bar"}'
);
assert.strictEqual(status, 400);
assert(/Missing/.test(body));
await app.stop();
});
it('Should accept wanted content-types API-wide', async () => {
let app = new AppServer();
app.api(function({ post }){
this.accept([ 'urlencoded', 'text/html' ]);
post('/foo', ({ res }) => res.end());
});
await app.start();
let { status } = await post(
LOCAL_HOST + 'foo',
{ 'Content-Type': 'text/html' },
'{"foo":"bar"}'
);
assert.strictEqual(status, 200);
await app.stop();
});
});
});

@@ -247,4 +280,37 @@

const AppServer = require('../lib/app-server');
const { post, get } = require('muhb');
it('Should expose file content sent as multipart-form', async () => {
const { EventEmitter } = require('events');
const { addRoute } = require('../lib/route-adapter');
it('Should fail when anything other than a function is passed', () => {
let ee = new EventEmitter();
assert.throws( () => addRoute.bind(ee)('get', '/foo', 4) );
});
it('Should add adapted handler to chosen route', () => {
let ee = new EventEmitter();
ee.express = {
foo(path){ assert.strictEqual(path, 'foo') }
};
addRoute.bind(ee)('foo', 'foo', function bar(){ });
});
it('Should pass all the required args to adapted function', async () => {
let app = new AppServer();
app.api(function({ get }){
get('/foo', (obj) => {
assert(obj.res && obj.req && obj.next && obj.body === ''
&& obj.params && obj.query && obj.flash && obj.error
&& obj.conf && obj.log);
obj.res.end();
});
});
await app.start();
let { status } = await get(LOCAL_HOST + 'foo');
assert.strictEqual(status, 200);
await app.stop();
});
it('Should expose file content sent as multipart/form-data', async () => {
const FormData = require('form-data');

@@ -265,3 +331,3 @@ let app = new AppServer();

await new Promise(resolve =>
form.submit('http://localhost/bar/', (err, res) => {
form.submit(LOCAL_HOST + 'bar/', (err, res) => {
assert(res.headers['x-test'] == 'file.txt');

@@ -275,4 +341,2 @@ resolve();

const { post } = require('muhb');
it('Should parse JSON request body payloads', async () => {

@@ -288,3 +352,3 @@ let app = new AppServer();

let { status } = await post(
'http://localhost:80/foobar',
LOCAL_HOST + 'foobar',
{ 'Content-Type': 'application/json' },

@@ -301,3 +365,3 @@ JSON.stringify({foo: 'bar'})

post('/foobar', ({ body, res }) => {
assert.strictEqual(typeof body, 'string');
assert.strictEqual(body, '{"foo":"bar"}');
res.end();

@@ -308,3 +372,4 @@ });

let { status } = await post(
'http://localhost:80/foobar',
LOCAL_HOST + 'foobar',
{ '--no-auto': true },
JSON.stringify({foo: 'bar'})

@@ -326,3 +391,3 @@ );

let { status } = await post(
'http://localhost:80/foobar',
LOCAL_HOST + 'foobar',
{ 'Content-Type': 'application/x-www-form-urlencoded' },

@@ -344,3 +409,3 @@ 'foo=bar'

await app.start();
let { status } = await post('http://localhost:80/foobar?foo=bar');
let { status } = await post(LOCAL_HOST + 'foobar?foo=bar');
assert.strictEqual(status, 200);

@@ -350,8 +415,21 @@ await app.stop();

it('Should output a JSON 404 when no route is found for a given path', async () => {
it('Should output a 404 when no route is found for a given path', async () => {
let app = new AppServer();
app.api(function(){ });
await app.start();
let { status, body } = await post('http://localhost/foobar');
let { status, body } = await post(LOCAL_HOST + 'foobar');
assert.strictEqual(status, 404);
assert.strictEqual(body, '');
await app.stop();
});
it('Should output a JSON when the error message is an object', async () => {
let app = new AppServer();
app.api(function({ post }){
post('/foobar', ({ error }) => {
error('NotFound', { foo: 'bar' });
});
});
await app.start();
let { body } = await post(LOCAL_HOST + 'foobar');
assert.doesNotThrow( () => JSON.parse(body) );

@@ -361,2 +439,42 @@ await app.stop();

describe('Accept setter', () => {
const { accept } = require('../lib/parse-types');
it('Should reject unwanted content-types for the given route', async () => {
let app = new AppServer();
app.api(function({ post }){
let acc = accept([ 'urlencoded', 'text/html' ]);
assert(acc.accept.includes('application/x-www-form-urlencoded'));
post('/foo', acc, ({ res }) => res.end());
});
await app.start();
let { body, status } = await post(
LOCAL_HOST + 'foo',
{ 'Content-Type': 'application/json' },
'{"foo":"bar"}'
);
assert.strictEqual(status, 400);
assert(/Unsupported/.test(body));
await app.stop();
});
it('Should accept wanted content-types for the given route', async () => {
let app = new AppServer();
app.api(function({ post }){
let acc = accept('text/html');
assert(acc.accept.includes('text/html'));
post('/foo', acc, ({ res }) => res.end());
});
await app.start();
let { status } = await post(
LOCAL_HOST + 'foo',
{ 'Content-Type': 'text/html' },
'{"foo":"bar"}'
);
assert.strictEqual(status, 200);
await app.stop();
});
});
});

@@ -388,3 +506,3 @@

} });
let { body } = await get('http://127.0.0.1:80/bar');
let { body } = await get(LOCAL_HOST + 'bar');
assert.strictEqual(body, 'foo');

@@ -442,3 +560,3 @@ await app.stop();

await app.start();
let { status: status } = await post('http://localhost:80/unknown');
let { status: status } = await post(LOCAL_HOST + 'unknown');
assert.strictEqual(status, 500);

@@ -459,5 +577,5 @@ await app.stop();

await app.start();
let { status } = await post('http://localhost:80/known');
let { status } = await post(LOCAL_HOST + 'known');
assert.strictEqual(status, 404);
let { status: s2 } = await post('http://localhost:80/unknown');
let { status: s2 } = await post(LOCAL_HOST + 'unknown');
assert.strictEqual(s2, 500);

@@ -486,7 +604,7 @@ await app.stop();

await app.start();
let { status } = await post('http://localhost:80/known');
let { status } = await post(LOCAL_HOST + 'known');
assert.strictEqual(status, 404);
let { status: s2 } = await post('http://localhost:80/unknown');
let { status: s2 } = await post(LOCAL_HOST + 'unknown');
assert.strictEqual(s2, 500);
let { status: s3 } = await post('http://localhost:80/unknown/object');
let { status: s3 } = await post(LOCAL_HOST + 'unknown/object');
assert.strictEqual(s3, 500);

@@ -512,5 +630,5 @@ await app.stop();

await app.start();
let { status } = await post('http://localhost:80/known');
let { status } = await post(LOCAL_HOST + 'known');
assert.strictEqual(status, 500);
let { status: s2 } = await post('http://localhost:80/unknown');
let { status: s2 } = await post(LOCAL_HOST + 'unknown');
assert.strictEqual(s2, 404);

@@ -532,3 +650,3 @@ assert.strictEqual(count, 2);

await app.start();
let { status } = await post('http://localhost/unknown');
let { status } = await post(LOCAL_HOST + 'unknown');
assert.strictEqual(status, 401);

@@ -563,3 +681,3 @@ await app.stop();

await app.start();
await post('http://localhost:80/foo');
await post(LOCAL_HOST + 'foo');
let data = await fs.promises.readFile(file, 'utf-8');

@@ -581,3 +699,3 @@ assert(data.indexOf('logfile') > 0);

await app.start();
await post('http://localhost:80/foo');
await post(LOCAL_HOST + 'foo');
let data = await fs.promises.readFile(file, 'utf-8');

@@ -596,3 +714,3 @@ assert(data.indexOf('logstream') > 0);

await app.start();
await post('http://localhost:80/foo');
await post(LOCAL_HOST + 'foo');
let data = await fs.promises.readFile(file, 'utf-8');

@@ -611,3 +729,3 @@ assert(data.indexOf('POST') > 0);

await app.start();
await post('http://localhost/foo');
await post(LOCAL_HOST + 'foo');
let data = await fs.promises.readFile(file, 'utf-8');

@@ -618,8 +736,7 @@ assert(data.indexOf('Oh yeah') > 0);

it.skip('Should log errors that crach the server process', async () => {
it.skip('Should log errors that crash the server process', async () => {
let file = path.resolve(dir, 'logstream.txt');
let stream = fs.createWriteStream(file);
let app
await run({ init(){
app = new AppServer({ log: { stream: stream, level: 'fatal' } });
new AppServer({ log: { stream: stream, level: 'fatal' } });
throw new Error('fatality');

@@ -636,2 +753,3 @@ } });

const AppServer = require('../lib/app-server');
const { accept } = require('../lib/parse-types');
const { post } = require('muhb');

@@ -651,3 +769,3 @@

await app.start();
let { body } = await post('http://localhost:80/foo/baz');
let { body } = await post(LOCAL_HOST + 'foo/baz');
assert.strictEqual(body, 'OK');

@@ -657,3 +775,3 @@ await app.stop();

it('Should have app name and verison by default', function(){
it('Should have app name and version by default', function(){
let doc = new APIDoc();

@@ -693,2 +811,37 @@ let spec = doc.spec();

});
it('Should auto-populate operation with permissive requests body', function(){
let doc = new APIDoc();
doc.api( ({ post }) => {
post('/foo', function(){});
post('/baz', function(){});
});
let spec = doc.spec();
assert.strictEqual(typeof spec.paths['/foo'].post.requestBody, 'object');
assert('*/*' in spec.paths['/foo'].post.requestBody.content);
});
it('Should add request body types based on app accepts', function(){
let doc = new APIDoc();
doc.api( function({ post }){
this.accept(['json', 'text/html']);
post('/foo', function(){});
});
let spec = doc.spec();
assert(/following types/.test(spec.paths['/foo'].post.requestBody.description));
assert('application/json' in spec.paths['/foo'].post.requestBody.content);
assert('text/html' in spec.paths['/foo'].post.requestBody.content);
});
it('Should add request body types based on route accepts', function(){
let doc = new APIDoc();
doc.api( function({ post }){
let acc = accept('json');
post('/foo', acc, function(){});
});
let spec = doc.spec();
assert(/following types/.test(spec.paths['/foo'].post.requestBody.description));
assert('application/json' in spec.paths['/foo'].post.requestBody.content);
});
});

@@ -739,3 +892,3 @@

await app.start();
let { status } = await post('http://localhost:80/bar');
let { status } = await post(LOCAL_HOST + 'bar');
assert.strictEqual(status, 500);

@@ -755,5 +908,5 @@ await app.stop();

let r1 = JSON.parse((await post('http://localhost:80/bar')).body).message;
let r2 = JSON.parse((await post('http://localhost:80/bar')).body).message;
let r3 = JSON.parse((await post('http://localhost:80/bar')).body).message;
let r1 = (await post(LOCAL_HOST + 'bar')).body;
let r2 = (await post(LOCAL_HOST + 'bar')).body;
let r3 = (await post(LOCAL_HOST + 'bar')).body;
assert(r1 == r2 && r2 == r3 && r3 == 'errfoobar');

@@ -774,3 +927,3 @@

let m = JSON.parse((await post('http://localhost:80/bar')).body).message;
let m = (await post(LOCAL_HOST + 'bar')).body;
assert.strictEqual(m, 'NotFound');

@@ -794,3 +947,3 @@

await app.start();
await post('http://localhost:80/bar');
await post(LOCAL_HOST + 'bar');
await app.stop();

@@ -797,0 +950,0 @@ assert(gotHere);

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc