Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
},
"rules": {
"no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
"semi": "off"
"semi": "off",
"space-before-function-paren": "off",
"comma-dangle":"off"
}
}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
node_modules
yarn-error.log
.DS_Store
.vscode
coverage/
mockedFolder
lib/
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

. "$(dirname -- "$0")/_/husky.sh"

pnpm run prettify && pnpm run lint
pnpm run prettier:check && pnpm run lint
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"stylelint.enable": false
}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

# http-server

<!--
https://medium.com/@rehmankhan.exe/form-validation-using-chain-of-responsibility-design-pattern-69c09339461a

https://levelup.gitconnected.com/design-patterns-chain-of-responsibility-pattern-in-javascript-80b3c44d0f4e

https://medium.com/nerd-for-tech/chain-of-responsibility-design-pattern-4efe8de4910d
-->

<!-- DOCS need to be updated -->

This package is meant to be used as a quick way to setup an Express application. The module does not have too much defined. The default functionality is the following.

- `cors` enabled
Expand Down
104 changes: 36 additions & 68 deletions __tests__/app.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,80 +1,48 @@
import request from 'supertest'
import { jest } from '@jest/globals'
import { expressApp } from '../src/app.mjs'
import request from 'supertest';
import { jest } from '@jest/globals';
import { expressApp } from '../src/app.mjs';

const mockedMiddleware = jest.fn
const mockedMiddleware = jest.fn;

describe('App', () => {
it('should be defined', () => {
expect(expressApp).toBeInstanceOf(Function)
})

it('should throw Error if no routes are specified', () => {
expect(() => expressApp({ middleware: [] })).toThrowError('Either routes is not defined or it is not an Array')
})

it('should throw Error if routes is not an Array', () => {
expect(() => expressApp({ routes: 100, middleware: [] })).toThrowError('Either routes is not defined or it is not an Array')
})

it('should throw Error if no middleware are specified', () => {
expect(() => expressApp({ routes: [] })).toThrowError('Either middleware is not defined or it is not an Array')
})

it('should throw Error if middleware is not an Array', () => {
expect(() => expressApp({ routes: [], middleware: 100 })).toThrowError('Either middleware is not defined or it is not an Array')
})

it('should throw Error if middleware is not an array of functions', () => {
expect(() => expressApp({ routes: [], middleware: [100] })).toThrowError('handler must be a function. Actual type is "number"')
})

it('should throw Error if routes do not provide a valid HTTP method', () => {
expect(() => expressApp({ middleware: [mockedMiddleware], routes: [{ method: 'posted' }] })).toThrowError('posted is not a valid HTTP method \n Allowed methods are get - delete - post - put - patch')
})

it('should throw Error if routes do not provide a function for the handler', () => {
expect(() => expressApp({ middleware: [mockedMiddleware], routes: [{ method: 'get', handler: true }] })).toThrowError('handler must be a function. Actual type is "boolean"')
})

it('should throw Error if routes do not provide a string for the path', () => {
expect(() => expressApp({ middleware: [mockedMiddleware], routes: [{ method: 'get', handler: () => {}, path: true }] })).toThrowError('path must be a string. Actual type is "boolean"')
})

it('should throw Error if routes provide an empty string for the path', () => {
expect(() => expressApp({ middleware: [mockedMiddleware], routes: [{ method: 'get', handler: () => {}, path: '' }] })).toThrowError('path can not be an empty string')
})

it('should throw Error if routes provide a string for the path that does not start with "/"', () => {
expect(() => expressApp({ middleware: [mockedMiddleware], routes: [{ method: 'get', handler: () => {}, path: 'somePath' }] })).toThrowError('path has to start with "/"')
})
expect(expressApp).toBeInstanceOf(Function);
});

it('should have a /health route by default', async () => {
const app = expressApp({ middleware: [mockedMiddleware], routes: [] })
const resp = await request(app).get('/health')
const app = expressApp({ middleware: [mockedMiddleware], routes: [] });
const resp = await request(app).get('/health');

expect(resp.ok).toEqual(true)
expect(resp.type).toEqual('text/html')
expect(resp.text).toEqual('ok')
})
expect(resp.ok).toEqual(true);
expect(resp.type).toEqual('text/html');
expect(resp.text).toEqual('ok');
});

it('should handle 404 requests', async () => {
const app = expressApp({ middleware: [mockedMiddleware], routes: [] })
const resp = await request(app).get('/notFound')
const app = expressApp({ middleware: [mockedMiddleware], routes: [] });
const resp = await request(app).get('/notFound');

expect(resp.status).toEqual(404)
expect(resp.type).toEqual('text/html')
expect(resp.text).toEqual('No handler found for /notFound')
})
expect(resp.status).toEqual(404);
expect(resp.type).toEqual('text/html');
expect(resp.text).toEqual('No handler found for /notFound');
});

it('should register all routes passed', async () => {
const routes = [{ method: 'get', handler: (req, res) => { res.json({ value: 100 }) }, path: '/someRoute' }]
const server = expressApp({ routes, middleware: [mockedMiddleware] })

const resp = await request(server).get('/someRoute').send()

expect(resp.ok).toEqual(true)
expect(resp.type).toEqual('application/json')
expect(resp.body).toEqual({ value: 100 })
})
})
const routes = [
{
method: 'get',
handler: (req, res) => {
res.json({ value: 100 });
},
path: '/someRoute',
},
];
const server = expressApp({ routes, middleware: [mockedMiddleware] });

const resp = await request(server).get('/someRoute').send();

expect(resp.ok).toEqual(true);
expect(resp.type).toEqual('application/json');
expect(resp.body).toEqual({ value: 100 });
});
});
114 changes: 96 additions & 18 deletions __tests__/server.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,32 +1,110 @@
import { httpServer } from '../src/server.mjs'
import { jest } from '@jest/globals';
import { httpServer } from '../src/server.mjs';
import {
ValidatorChain,
PortValidator,
OptionsValidator,
RoutesValidator,
MiddlewareValidator,
} from '../src/validator/index.mjs';
import utils from '../src/utils/index.mjs';

let server
let server;

beforeAll(() => {
server = httpServer({ routes: [], middleware: [() => {}] })
})
server = httpServer({ routes: [], middleware: [() => {}] });
});

afterAll(() => {
server.close()
})
afterAll(done => {
server.close(done);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('Server', () => {
it('should be defined', () => {
expect(httpServer).toBeInstanceOf(Function)
})
expect(httpServer).toBeInstanceOf(Function);
});

it('should return an instance of Express Server', () => {
expect(server.constructor.name).toEqual('Server')
})
expect(server.constructor.name).toEqual('Server');
});

it('should default port to 3000 if is not specified', () => {
expect(server.address().port).toEqual(3000)
})
expect(server.address().port).toEqual(3000);
});

it('should set the port number', () => {
const server = httpServer({ port: 6969, routes: [], middleware: [() => {}] })
const server = httpServer({
port: 6969,
routes: [],
middleware: [() => {}],
});

expect(server.address().port).toEqual(6969);
server.close();
});

it('should validate the options by calling all the Validators', () => {
jest.spyOn(ValidatorChain.prototype, 'startValidation');
jest.spyOn(PortValidator.prototype, 'validate');
jest.spyOn(OptionsValidator.prototype, 'validate');
jest.spyOn(RoutesValidator.prototype, 'validate');
jest.spyOn(MiddlewareValidator.prototype, 'validate');

const server = httpServer({
port: 6969,
routes: [],
middleware: [() => {}],
});

expect(ValidatorChain.prototype.startValidation).toBeCalledTimes(1);
expect(PortValidator.prototype.validate).toBeCalledTimes(1);
expect(OptionsValidator.prototype.validate).toBeCalledTimes(1);
expect(RoutesValidator.prototype.validate).toBeCalledTimes(1);
expect(MiddlewareValidator.prototype.validate).toBeCalledTimes(1);

server.close();
});

it('should stop the app if the Validator fails', () => {
// process.exit it's been spied on jest/setupTests.js
jest.spyOn(console, 'error');

const server = httpServer({
port: '6969',
routes: [],
middleware: [() => {}],
});

expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(
'"port" property should be typeof number but string was given'
);
expect(process.exit).toBeCalledTimes(1);
expect(process.exit).toBeCalledWith(1);

server.close();
});

it('should call msgBuilder if the Validator is successful', () => {
jest.spyOn(utils, 'msgBuilder');

const appParams = {
port: 6969,
routes: [],
middleware: [() => {}],
};
const server = httpServer(appParams);

expect(utils.msgBuilder).toBeCalledTimes(1);
expect(utils.msgBuilder).toBeCalledWith({
...appParams,
options: { useCors: true },
});

expect(server.address().port).toEqual(6969)
server.close()
})
})
server.close();
});
});
20 changes: 20 additions & 0 deletions __tests__/utils.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import utils from '../src/utils/index.mjs';

describe('msgBuilder', () => {
it('should be defined', () => {
expect(utils.msgBuilder).toBeInstanceOf(Function);
});

it('should return a msg for the props passed', () => {
const props = {
port: 4000,
routes: [{ handler: () => {}, path: '/some', method: 'get' }],
middleware: [() => {}],
options: { useCors: true },
};

expect(utils.msgBuilder(props)).toEqual(
'Your app is running with the following settings:\nPORT: 4000\nROUTES: [{"path":"/some","method":"get"}]\nMIDDLEWARE: 1 registered\nOPTIONS: {"useCors":true}'
);
});
});
Loading