pretty much fully finished version 1

This commit is contained in:
Jonatan Nilsson 2021-10-09 00:12:56 +00:00
parent 26c9b4a27e
commit c8290126fa
13 changed files with 2045 additions and 212 deletions

13
LICENSE Normal file
View file

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View file

@ -1,7 +1,7 @@
import assert from 'assert'
import Benchmark from 'benchmarkjs-pretty'
import { koaRouter1, koaRouter2 } from './router_koa.js'
import { flaskaRouter1, flaskaRouter2, flaskaClassRouter1, flaskaClassRouter2 } from './router_flaska.js'
import { flaskaRouter1, flaskaRouter2 } from './router_flaska.js'
import { expressRouter1, expressRouter2 } from './router_express.js'
import * as consts from './const.js'
@ -10,6 +10,216 @@ let testData = null
consts.overrideDummy(function() { testData = assertOk })
function TestPromiseCreators() {
let resolveTrue = Promise.resolve(true)
let asyncFunc = async function() {
let check = await resolveTrue
if (check !== true) {
throw new Error('false')
}
}
return new Benchmark.default('test different promise creation methods')
.add('new Promise()', function() {
return new Promise((res, rej) => {
try {
resolveTrue.then(function(check) {
res(check)
})
} catch (err) {
rej(err)
}
})
})
.add('new Promise() static', function() {
return new Promise((res, rej) => {
res(true)
})
})
.add('new Promise() static with try catch', function() {
return new Promise((res, rej) => {
try {
res(true)
} catch (err) {
rej(err)
}
})
})
.add('new Promise() static with one then()', function() {
return new Promise((res, rej) => {
res(true)
}).then(function(check) {
return check
})
})
.add('Promise.resolve()', function() {
return Promise.resolve().then(function() {
return resolveTrue
}).then(function(check) {
return check
})
})
.add('resolved promise one then()', function() {
return resolveTrue.then(function(check) {
return check
})
})
.add('resolved promise but four then()', function() {
return resolveTrue.then(function(check) {
return check
})
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
})
.add('resolved promise but twenty then()', function() {
return resolveTrue.then(function(check) {
if (check !== true) {
throw new Error('false')
}
return true
})
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
.then(function(item) { return item })
})
.add('async function()', async function() {
let check = await resolveTrue
if (check !== true) {
throw new Error('false')
}
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
process.exit(1)
})
}
function TestObjectAssign() {
const base = {
state: {},
log: {
info: function() { },
warn: function() { },
error: function() { },
},
router1: flaskaRouter1,
router2: flaskaRouter2,
}
const req = {
test: 1,
test2: 2,
koa1: koaRouter1,
koa2: koaRouter2,
test: function() {},
}
const res = {
express1: expressRouter1,
express2: expressRouter2,
test: function() {},
}
let propMaker = {}
let propMakerAlt = {}
Object.keys(base).forEach(function(key) {
propMaker[key] = {
enumerable: true,
value: base[key],
writable: true,
configurable: true,
}
propMakerAlt[key] = {
enumerable: true,
value: base[key],
writable: true,
configurable: true,
}
})
propMakerAlt.req = {
enumerable: true,
get: function() {
return req
},
configurable: true,
}
propMakerAlt.res = {
enumerable: true,
get: function() {
return res
},
configurable: true,
}
function register1(ctx) {
ctx.log = base.log
}
function register2(ctx) {
ctx.router1 = flaskaRouter1
}
function register3(ctx) {
ctx.router2 = flaskaRouter2
}
return new Benchmark.default('test different method to initialize objects)')
.add('Object.assign()', function() {
let ctx = {
req: req,
res: res,
}
Object.assign(ctx, base)
ctx.log.info()
})
.add('register functions with direct assignment', function() {
let ctx = {
state: {},
req: req,
res: res,
}
register1(ctx)
register2(ctx)
register3(ctx)
ctx.log.info()
})
/*.add('Object.create() all props', function() {
let ctx = Object.create({}, propMakerAlt)
ctx.log.info()
})
.add('Object.defineProperties()', function() {
let ctx = {
req: req,
res: res,
}
Object.defineProperties(ctx, propMaker)
ctx.log.info()
})
.add('Object.defineProperties() all props', function() {
let ctx = { }
Object.defineProperties(ctx, propMakerAlt)
ctx.log.info()
})*/
.run()
.then(function() {}, function(e) {
console.error('error:', e)
process.exit(1)
})
}
function TestSmallStaticRoute() {
return new Benchmark.default('Small router static route benchmark: /api/staff (16 routes registered)')
.add('expressjs', function() {
@ -28,10 +238,6 @@ function TestSmallStaticRoute() {
testData = flaskaRouter1.match('/api/staff')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter1.match('/api/staff')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
@ -57,10 +263,6 @@ function TestSmallParamRoute() {
testData = flaskaRouter1.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter1.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
@ -86,10 +288,6 @@ function TestLargeStaticRoute() {
testData = flaskaRouter2.match('/api/staff')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/staff')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
@ -115,10 +313,6 @@ function TestLargeParamRoute() {
testData = flaskaRouter2.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/staff/justatest')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
@ -144,10 +338,6 @@ function TestLargeParamLargeUrlRoute() {
testData = flaskaRouter2.match('/api/products/justatest/sub_products/foobar')
assert.ok(testData.handler)
})
.add('bottle-router-alt', function() {
testData = flaskaClassRouter2.match('/api/products/justatest/sub_products/foobar')
assert.ok(testData.handler)
})
.run()
.then(function() {}, function(e) {
console.error('error:', e)
@ -156,6 +346,8 @@ function TestLargeParamLargeUrlRoute() {
}
TestSmallStaticRoute()
// TestObjectAssign()
// TestPromiseCreators()
.then(function() {
return TestSmallParamRoute()
})

View file

@ -1,25 +1,18 @@
import { FlaskaRouter, FlaskaRouterClass } from '../flaska.mjs'
import { FlaskaRouter } from '../flaska.mjs'
import * as consts from './const.js'
const router1 = new FlaskaRouter()
const router2 = new FlaskaRouter()
const classRouter1 = new FlaskaRouterClass()
const classRouter2 = new FlaskaRouterClass()
for (let key in consts.allRoutes) {
router1.addRoute(consts.allRoutes[key], consts.dummy)
classRouter1.addRoute(consts.allRoutes[key], consts.dummy)
}
for (let key in consts.allManyRoutes) {
router2.addRoute(consts.allManyRoutes[key], consts.dummy)
classRouter2.addRoute(consts.allManyRoutes[key], consts.dummy)
}
export {
router1 as flaskaRouter1,
router2 as flaskaRouter2,
classRouter1 as flaskaClassRouter1,
classRouter2 as flaskaClassRouter2,
}

View file

@ -1,3 +1,6 @@
import http from 'http'
import stream from 'stream'
/**
* Router
*/
@ -12,9 +15,41 @@ class Branch {
}
}
export const ErrorCodes = {
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
}
// Taken from https://github.com/nfp-projects/koa-lite/blob/master/lib/statuses.js
const statuses = {
100: 'Continue', 101: 'Switching Protocols', 102: 'Processing', 103: 'Early Hints',
200: 'OK', 201: 'Created', 202: 'Accepted', 203: 'Non-Authoritative Information', 204: 'No Content', 205: 'Reset Content', 206: 'Partial Content', 207: 'Multi-Status', 208: 'Already Reported', 226: 'IM Used',
300: 'Multiple Choices', 301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 305: 'Use Proxy', 306: '(Unused)', 307: 'Temporary Redirect', 308: 'Permanent Redirect',
400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 406: 'Not Acceptable', 407: 'Proxy Authentication Required', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 411: 'Length Required', 412: 'Precondition Failed', 413: 'Payload Too Large', 414: 'URI Too Long', 415: 'Unsupported Media Type', 416: 'Range Not Satisfiable', 417: 'Expectation Failed', 418: 'I\'m a teapot', 421: 'Misdirected Request', 422: 'Unprocessable Entity', 423: 'Locked', 424: 'Failed Dependency', 425: 'Too Early', 426: 'Upgrade Required', 428: 'Precondition Required', 429: 'Too Many Requests', 431: 'Request Header Fields Too Large', 451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', 505: 'HTTP Version Not Supported', 506: 'Variant Also Negotiates', 507: 'Insufficient Storage', 508: 'Loop Detected', 509: 'Bandwidth Limit Exceeded', 510: 'Not Extended', 511: 'Network Authentication Required',
redirect: {
300: true,
301: true,
302: true,
303: true,
305: true,
307: true,
308: true
},
empty: {
204: true,
205: true,
304: true
}
}
const __paramMapName = '__param'
const __fullParamMapName = '__fullparam'
function assertIsHandler(handler, name) {
if (typeof(handler) !== 'function') {
throw new Error(`${name} was called with a handler that was not a function`)
}
}
export class FlaskaRouter {
constructor() {
this.root = new Branch()
@ -33,9 +68,7 @@ export class FlaskaRouter {
if (middlewares && typeof(middlewares) === 'function') {
middlewares = [middlewares]
}
if (typeof(handler) !== 'function') {
throw new Error(`route "${route}" was missing a handler`)
}
assertIsHandler(handler, 'addRoute()')
let start = 1
let end = 1
@ -150,172 +183,34 @@ export class FlaskaRouter {
else if (output = branch.children.get(__paramMapName)) {
branch = output
params[branch.paramName] = name
} else {
return null
}
i++
end = start = i
char = url[i]
}
if (i >= url.length) {
else if (output = branch.children.get(__fullParamMapName)) {
params[output.fullparamName] = url.slice(start)
return {
handler: branch.handler,
middlewares: branch.middlewares,
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
if (char === '/') {
end = start = i + 1
} else {
end++
if (output = this.root.children.get(__fullParamMapName)) {
params = {
[output.fullparamName]: url.slice(1)
}
}
return null
}
}
export function FlaskaRouter() {
this.root = new Branch()
}
FlaskaRouter.prototype.addRoute = function(route, orgMiddlewares, orgHandler) {
if (route[0] !== '/')
throw new Error(`route "${route}" must start with forward slash`)
let middlewares = orgMiddlewares
let handler = orgHandler
if (!orgHandler) {
handler = orgMiddlewares
middlewares = []
}
if (middlewares && typeof(middlewares) === 'function') {
middlewares = [middlewares]
}
if (typeof(handler) !== 'function') {
throw new Error(`route "${route}" was missing a handler`)
}
let start = 1
let end = 1
let name = ''
let param = ''
let isParam = false
let isFullParam = false
let branch = this.root
if (route.indexOf(':') < 0) {
let name = route
if (name.length > 1 && name[name.length - 1] === '/') {
name = name.slice(0, -1)
}
let child = new Branch()
branch.children.set(name, child)
child.handler = handler
child.middlewares = middlewares
}
for (let i = 1; i <= route.length; i++) {
if ((i === route.length || route[i] === '/') && end > start) {
if (branch.fullparamName) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
let child
name = route.substring(start, end)
if (isFullParam) {
param = name
name = __fullParamMapName
} else if (isParam) {
param = name
name = __paramMapName
}
if (branch.children.has(name)) {
child = branch.children.get(name)
}
else if (isParam && !isFullParam && branch.children.has(__fullParamMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
else if (isFullParam && branch.children.has(__paramMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a partial param child`)
}
else {
child = new Branch()
branch.children.set(name, child)
}
branch = child
end = i
start = i
if (isParam) {
if (branch.paramName && branch.paramName !== param) {
throw new Error(`route "${route}" conflicts with pre-existing param name of ${branch.paramName} instead of ${param}`)
}
if (isFullParam) {
branch.fullparamName = param
} else {
branch.paramName = param
}
isParam = false
}
} else if (route[i] === '/' && end === start) {
throw new Error(`route "${route}" has missing path name inbetween slashes`)
}
if (i === route.length) {
branch.handler = handler
branch.middlewares = middlewares
continue
}
if (route[i] === ':') {
if (isParam) {
isFullParam = true
}
isParam = true
end = start = i + 1
}
else if (route[i] === '/') {
end = start = i + 1
}
else {
end++
}
}
}
FlaskaRouter.prototype.match = function(orgUrl) {
let url = orgUrl
if (url.length > 1 && url[url.length - 1] === '/') {
url = url.slice(0, -1)
}
let branch = this.root
let start = 1
let end = 1
let output
let name
let char
let params = {}
if (output = branch.children.get(url)) {
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
for (let i = 1; i <= url.length; i++) {
char = url[i]
if ((i === url.length || char === '/') && end > start) {
name = url.slice(start, end)
if (output = branch.children.get(name)) {
branch = output
}
else if (output = branch.children.get(__paramMapName)) {
branch = output
params[branch.paramName] = name
} else {
return null
}
i++
end = start = i
char = url[i]
}
if (i >= url.length) {
// Check branch.handler. This can happen if route /::path is added
// and request is '/' it will attempt to match root which will fail
if (i >= url.length && branch.handler) {
return {
handler: branch.handler,
middlewares: branch.middlewares,
@ -328,9 +223,313 @@ FlaskaRouter.prototype.match = function(orgUrl) {
end++
}
}
if (output = this.root.children.get(__fullParamMapName)) {
params = {
[output.fullparamName]: url.slice(1)
}
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
return null
}
}
export function Flaska() {
/**
* Flaska
*/
export class Flaska {
constructor(opts, orgHttp = http, orgStream = stream) {
this._before = []
this._beforeCompiled = null
this._beforeAsync = []
this._beforeAsyncCompiled = null
this._after = []
this._afterCompiled = null
this._afterAsync = []
this._afterAsyncCompiled = null
this._on404 = function(ctx) {
ctx.status = 404
ctx.body = {
status: 404,
message: statuses[404],
}
}
this._backuperror = this._onerror = function(err, ctx) {
ctx.log.error(err)
ctx.status = 500
ctx.body = {
status: 500,
message: statuses[500],
}
}
this._onreqerror = function(err, ctx) {
ctx.log.error(err)
ctx.res.statusCode = ctx.statusCode = 400
ctx.res.end()
ctx.finished = true
}
this._onreserror = function(err, ctx) {
ctx.log.error(err)
}
let options = opts || {}
this.log = options.log || console
this.http = orgHttp
this.stream = orgStream
this.server = null
this.routers = {
'GET': new FlaskaRouter(),
'POST': new FlaskaRouter(),
'PUT': new FlaskaRouter(),
'DELETE': new FlaskaRouter(),
'OPTIONS': new FlaskaRouter(),
'PATCH': new FlaskaRouter(),
}
this.get = this.routers.GET.addRoute.bind(this.routers.GET)
this.post = this.routers.POST.addRoute.bind(this.routers.POST)
this.put = this.routers.PUT.addRoute.bind(this.routers.PUT)
this.delete = this.routers.DELETE.addRoute.bind(this.routers.DELETE)
this.options = this.routers.OPTIONS.addRoute.bind(this.routers.OPTIONS)
this.patch = this.routers.PATCH.addRoute.bind(this.routers.PATCH)
}
_assertIsHandler(handler, name) {
if (typeof(handler) !== 'function') {
throw new Error(`${name} was called with a handler that was not a function`)
}
}
devMode() {
this._backuperror = this._onerror = function(err, ctx) {
ctx.log.error(err)
ctx.status = 500
ctx.body = {
status: 500,
message: `${statuses[500]}: ${err.message}`,
stack: err.stack || '',
}
}
}
on404(handler) {
assertIsHandler(handler, 'on404()')
this._on404 = handler
}
onerror(handler) {
assertIsHandler(handler, 'onerror()')
this._onerror = handler
}
onreqerror(handler) {
assertIsHandler(handler, 'onreqerror()')
this._onreqerror = handler
}
onreserror(handler) {
assertIsHandler(handler, 'onreserror()')
this._onreserror = handler
}
onerror(handler) {
assertIsHandler(handler, 'onerror()')
this._onerror = handler
}
before(handler) {
assertIsHandler(handler, 'before()')
this._before.push(handler)
}
beforeAsync(handler) {
assertIsHandler(handler, 'beforeAsync()')
this._beforeAsync.push(handler)
}
after(handler) {
assertIsHandler(handler, 'after()')
this._after.push(handler)
}
afterAsync(handler) {
assertIsHandler(handler, 'afterAsync()')
this._afterAsync.push(handler)
}
requestStart(req, res) {
let url = req.url
let search = ''
let hasSearch = url.indexOf('?')
if (hasSearch > 0) {
search = url.slice(hasSearch)
url = url.slice(0, hasSearch)
}
let ctx = {
req: req,
res: res,
method: req.method,
url: url,
search: search,
state: {},
status: 200,
body: null,
type: null,
length: null,
}
req.on('error', (err) => {
this._onreqerror(err, ctx)
})
res.on('error', (err) => {
this._onreserror(err, ctx)
})
req.on('aborted', function() {
ctx.aborted = true
})
req.on('close', () => {
this.requestEnded()
})
try {
this._beforeCompiled(ctx)
if (this._beforeAsyncCompiled) {
return this._beforeAsyncCompiled(ctx)
.then(() => {
this.requestStartInternal(ctx)
}).catch(err => {
this.requestEnd(err, ctx)
})
}
this.requestStartInternal(ctx)
}
catch (err) {
this.requestEnd(err, ctx)
}
}
requestStartInternal(ctx) {
let route = this.routers[ctx.method].match(ctx.url)
ctx.params = route.params
if (route.middlewares.length) {
let middle = this.handleMiddleware(ctx, route.middlewares, 0)
if (middle && middle.then) {
return middle.then(() => {
return route.handler(ctx)
})
.then(() => {
this.requestEnd(null, ctx)
}, err => {
this.requestEnd(err, ctx)
})
}
}
let handler = route.handler(ctx)
if (handler && handler.then) {
return handler.then(() => {
this.requestEnd(null, ctx)
}, err => {
this.requestEnd(err, ctx)
})
}
this.requestEnd(null, ctx)
}
handleMiddleware(ctx, middles, index) {
for (let i = index; i < middles.length; i++) {
let res = middles[i](ctx)
if (res && res.then) {
return res.then(() => {
return this.handleMiddleware(ctx, middles, i + 1)
})
}
}
}
requestEnd(err, ctx) {
if (err) {
this._onerror(err, ctx)
return ctx.res.end()
}
if (ctx.req.complete) {
return
}
ctx.res.statusCode = ctx.status
if (statuses.empty[ctx.status]) {
return ctx.res.end()
}
let body = ctx.body
let length = 0
if (body && typeof(body.pipe) === 'function') {
ctx.res.setHeader('Content-Type', ctx.type || 'application/octet-stream')
return this.stream.pipeline(body, ctx.res, function() {})
}
if (typeof(body) === 'object') {
body = JSON.stringify(body)
length = Buffer.byteLength(body)
ctx.res.setHeader('Content-Type', 'application/json; charset=utf-8')
} else {
body = body.toString()
length = Buffer.byteLength(body)
ctx.res.setHeader('Content-Type', ctx.type || 'text/plain; charset=utf-8')
}
ctx.res.setHeader('Content-Length', length)
ctx.res.end(body)
}
requestEnded(ctx) {
this._afterCompiled(ctx)
if (this._afterAsyncCompiled) {
return this._afterAsyncCompiled(ctx).then()
}
}
compile() {
let types = ['before', 'after']
for (let i = 0; i < types.length; i++) {
let type = types[i]
let args = ''
let body = ''
for (let i = 0; i < this['_' + type].length; i++) {
args += `a${i}, `
body += `a${i}(ctx);`
}
args += 'ctx'
let func = new Function(args, body)
this[`_${type}Compiled`] = func.bind(this, ...this['_' + type])
if (this[`_${type}Async`].length) {
args = ''
body = 'return Promise.all(['
for (let i = 0; i < this[`_${type}Async`].length; i++) {
args += `a${i}, `
body += `a${i}(ctx),`
}
args += 'ctx'
body += '])'
func = new Function(args, body)
this[`_${type}AsyncCompiled`] = func.bind(this, ...this[`_${type}Async`])
}
}
}
listen(port, cb) {
if (typeof(port) !== 'number') {
throw new Error('Flaska.listen() called with non-number in port')
}
this.compile()
this.server = this.http.createServer(this.requestStart.bind(this))
this.server.listen(port, cb)
}
}

17
test.mjs Normal file
View file

@ -0,0 +1,17 @@
import { Flaska } from './flaska.mjs'
const port = 51024
const flaska = new Flaska({}, )
flaska.get('/', function(ctx) {
return new Promise(function(res, rej) {
ctx.body = { status: true }
setTimeout(res, 1000)
}).then(function() {
process.stdout.write(".")
})
})
flaska.listen(port, function() {
console.log('listening on port', port)
})

View file

@ -1,39 +1,6 @@
import http from 'http'
import { URL } from 'url'
// taken from isobject npm library
function isObject(val) {
return val != null && typeof val === 'object' && Array.isArray(val) === false
}
function defaults(options, def) {
let out = { }
if (options) {
Object.keys(options || {}).forEach(key => {
out[key] = options[key]
if (Array.isArray(out[key])) {
out[key] = out[key].map(item => {
if (isObject(item)) return defaults(item)
return item
})
} else if (out[key] && typeof out[key] === 'object') {
out[key] = defaults(options[key], def && def[key])
}
})
}
if (def) {
Object.keys(def).forEach(function(key) {
if (typeof out[key] === 'undefined') {
out[key] = def[key]
}
})
}
return out
}
import { defaults } from './helper.mjs'
export default function Client(port, opts) {
this.options = defaults(opts, {})
@ -48,6 +15,7 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
return new Promise((resolve, reject) => {
const opts = defaults(defaults(options, {
agent: false,
method: method,
timeout: 500,
protocol: urlObj.protocol,
@ -64,7 +32,10 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
}
req.on('error', reject)
req.on('timeout', function() { reject(new Error(`Request ${method} ${path} timed out`)) })
req.on('timeout', function() {
console.log(req.destroy())
reject(new Error(`Request ${method} ${path} timed out`))
})
req.on('response', res => {
res.setEncoding('utf8')
let output = ''
@ -79,7 +50,7 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
} catch (e) {
return reject(new Error(`${e.message} while decoding: ${output}`))
}
if (output.status) {
if (output.status && typeof(output.status) === 'number') {
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
err.body = output
return reject(err)
@ -88,6 +59,7 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
})
})
req.end()
return req
})
}

487
test/flaska.api.test.mjs Normal file
View file

@ -0,0 +1,487 @@
import { Eltro as t, assert} from 'eltro'
import { Flaska } from '../flaska.mjs'
import { spy, createCtx, fakeHttp } from './helper.mjs'
const faker = fakeHttp()
t.test('should be able to override the http', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(flaska.http, faker)
})
t.test('it should have all the common verbs', function() {
let flaska = new Flaska({}, faker)
assert.ok(flaska.get)
assert.strictEqual(typeof(flaska.get), 'function')
assert.ok(flaska.post)
assert.strictEqual(typeof(flaska.post), 'function')
assert.ok(flaska.put)
assert.strictEqual(typeof(flaska.put), 'function')
assert.ok(flaska.delete)
assert.strictEqual(typeof(flaska.delete), 'function')
assert.ok(flaska.options)
assert.strictEqual(typeof(flaska.options), 'function')
assert.ok(flaska.patch)
assert.strictEqual(typeof(flaska.patch), 'function')
})
t.describe('#log', function() {
t.test('default have a logger valid', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(typeof(flaska.log.error), 'function')
assert.strictEqual(typeof(flaska.log.info), 'function')
assert.strictEqual(typeof(flaska.log.warn), 'function')
})
t.test('allow overwriting in options', function() {
const assertFunction = function() { return 1 }
let flaska = new Flaska({ log: {
error: assertFunction,
info: assertFunction,
warn: assertFunction,
debug: assertFunction,
} }, faker)
assert.strictEqual(flaska.log.error, assertFunction)
assert.strictEqual(flaska.log.info, assertFunction)
assert.strictEqual(flaska.log.warn, assertFunction)
assert.strictEqual(flaska.log.debug, assertFunction)
})
})
let specialHandlers = ['on404', 'onerror', 'onreqerror', 'onreserror']
specialHandlers.forEach(function(type) {
t.describe(`#${type}()`, function() {
t.test('exist', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(typeof(flaska[type]), 'function')
})
t.test('validate handler', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska[type]() }, /[Ff]unction/)
assert.throws(function() { flaska[type]('asdf') }, /[Ff]unction/)
assert.throws(function() { flaska[type]('123') }, /[Ff]unction/)
assert.throws(function() { flaska[type]([]) }, /[Ff]unction/)
assert.throws(function() { flaska[type]({}) }, /[Ff]unction/)
assert.throws(function() { flaska[type](1234) }, /[Ff]unction/)
})
if (type !== 'on404') {
t.test('should call ctx.log.error correctly', function() {
const assertError = new Error('Samuraism')
let flaska = new Flaska({}, faker)
let ctx = createCtx()
flaska['_' + type](assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
})
}
if (type === 'onreqerror') {
t.test('default sends 400 immediately', function() {
let flaska = new Flaska({}, faker)
let ctx = createCtx()
flaska['_' + type](new Error(), ctx)
assert.strictEqual(ctx.res.statusCode, 400)
assert.strictEqual(ctx.finished, true)
assert.strictEqual(ctx.res.end.callCount, 1)
assert.strictEqual(ctx.res.end.firstCall.length, 0)
})
}
t.test('register it into flaska', function() {
const assertFunction = function() { return true }
let flaska = new Flaska({}, faker)
flaska[type](assertFunction)
assert.strictEqual(flaska['_' + type], assertFunction)
})
})
})
t.describe('_on404', function() {
t.test('a valid function', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(typeof(flaska._on404), 'function')
})
t.test('default valid handling of context', function() {
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._on404(ctx)
assert.strictEqual(ctx.status, 404)
assert.deepStrictEqual(ctx.body, {
status: 404,
message: 'Not Found',
})
})
})
t.describe('_onerror', function() {
t.test('a valid function', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(typeof(flaska._onerror), 'function')
})
t.test('default valid handling of context', function() {
const assertError = new Error('should not be seen')
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._onerror(assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
assert.strictEqual(ctx.status, 500)
assert.deepStrictEqual(ctx.body, {
status: 500,
message: 'Internal Server Error',
})
})
})
t.describe('#devMode()', function() {
t.test('turns on debug mode', function() {
const assertErrorMessage = 'Fascination'
const assertError = new Error(assertErrorMessage)
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._onerror(assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
assert.strictEqual(ctx.status, 500)
assert.deepStrictEqual(ctx.body, {
status: 500,
message: 'Internal Server Error',
})
ctx = createCtx()
flaska._backuperror(assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
assert.strictEqual(ctx.status, 500)
assert.deepStrictEqual(ctx.body, {
status: 500,
message: 'Internal Server Error',
})
flaska.devMode()
ctx = createCtx()
flaska._onerror(assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
assert.strictEqual(ctx.status, 500)
assert.strictEqual(ctx.body.status, 500)
assert.match(ctx.body.message, /Internal Server Error/)
assert.match(ctx.body.message, new RegExp(assertErrorMessage))
assert.ok(ctx.body.stack)
ctx = createCtx()
flaska._backuperror(assertError, ctx)
assert.strictEqual(ctx.log.error.callCount, 1)
assert.strictEqual(ctx.log.error.firstCall[0], assertError)
assert.strictEqual(ctx.status, 500)
assert.strictEqual(ctx.body.status, 500)
assert.match(ctx.body.message, /Internal Server Error/)
assert.match(ctx.body.message, new RegExp(assertErrorMessage))
assert.ok(ctx.body.stack)
})
})
t.describe('#before()', function() {
t.test('should throw if not a function', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska.before() }, /[Ff]unction/)
assert.throws(function() { flaska.before('asdf') }, /[Ff]unction/)
assert.throws(function() { flaska.before('123') }, /[Ff]unction/)
assert.throws(function() { flaska.before([]) }, /[Ff]unction/)
assert.throws(function() { flaska.before({}) }, /[Ff]unction/)
assert.throws(function() { flaska.before(1234) }, /[Ff]unction/)
})
t.test('add handler to preflight list', function() {
const assertFunction = function() {}
let flaska = new Flaska({}, faker)
assert.ok(flaska._before)
flaska.before(assertFunction)
assert.strictEqual(flaska._before.length, 1)
assert.strictEqual(flaska._before[0], assertFunction)
})
})
t.describe('#beforeAsync()', function() {
t.test('should throw if not a function', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska.beforeAsync() }, /[Ff]unction/)
assert.throws(function() { flaska.beforeAsync('asdf') }, /[Ff]unction/)
assert.throws(function() { flaska.beforeAsync('123') }, /[Ff]unction/)
assert.throws(function() { flaska.beforeAsync([]) }, /[Ff]unction/)
assert.throws(function() { flaska.beforeAsync({}) }, /[Ff]unction/)
assert.throws(function() { flaska.beforeAsync(1234) }, /[Ff]unction/)
})
t.test('add handler to preflight list', function() {
const assertFunction = function() {}
let flaska = new Flaska({}, faker)
assert.ok(flaska._beforeAsync)
flaska.beforeAsync(assertFunction)
assert.strictEqual(flaska._beforeAsync.length, 1)
assert.strictEqual(flaska._beforeAsync[0], assertFunction)
})
})
t.describe('#after()', function() {
t.test('should throw if not a function', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska.after() }, /[Ff]unction/)
assert.throws(function() { flaska.after('asdf') }, /[Ff]unction/)
assert.throws(function() { flaska.after('123') }, /[Ff]unction/)
assert.throws(function() { flaska.after([]) }, /[Ff]unction/)
assert.throws(function() { flaska.after({}) }, /[Ff]unction/)
assert.throws(function() { flaska.after(1234) }, /[Ff]unction/)
})
t.test('add handler to preflight list', function() {
const assertFunction = function() {}
let flaska = new Flaska({}, faker)
assert.ok(flaska._after)
flaska.after(assertFunction)
assert.strictEqual(flaska._after.length, 1)
assert.strictEqual(flaska._after[0], assertFunction)
})
})
t.describe('#afterAsync()', function() {
t.test('should throw if not a function', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska.afterAsync() }, /[Ff]unction/)
assert.throws(function() { flaska.afterAsync('asdf') }, /[Ff]unction/)
assert.throws(function() { flaska.afterAsync('123') }, /[Ff]unction/)
assert.throws(function() { flaska.afterAsync([]) }, /[Ff]unction/)
assert.throws(function() { flaska.afterAsync({}) }, /[Ff]unction/)
assert.throws(function() { flaska.afterAsync(1234) }, /[Ff]unction/)
})
t.test('add handler to preflight list', function() {
const assertFunction = function() {}
let flaska = new Flaska({}, faker)
assert.ok(flaska._afterAsync)
flaska.afterAsync(assertFunction)
assert.strictEqual(flaska._afterAsync.length, 1)
assert.strictEqual(flaska._afterAsync[0], assertFunction)
})
})
t.describe('#compile()', function() {
t.test('join all before together in one function', function() {
let flaska = new Flaska({}, faker)
flaska.before(function(ctx) { ctx.a = 1 })
flaska.before(function(ctx) { ctx.b = 2 })
flaska.before(function(ctx) { ctx.c = 3 })
flaska.before(function(ctx) { ctx.d = 4 })
assert.notOk(flaska._beforeCompiled)
flaska.compile()
assert.ok(flaska._beforeCompiled)
assert.notOk(flaska._beforeAsyncCompiled)
assert.strictEqual(typeof(flaska._beforeCompiled), 'function')
let ctx = createCtx()
flaska._beforeCompiled(ctx)
assert.strictEqual(ctx.a, 1)
assert.strictEqual(ctx.b, 2)
assert.strictEqual(ctx.c, 3)
assert.strictEqual(ctx.d, 4)
})
t.test('join all beforeAsync together in one function', function() {
let flaska = new Flaska({}, faker)
flaska.beforeAsync(function(ctx) { return Promise.resolve().then(function() { ctx.a = 1 }) })
flaska.beforeAsync(function(ctx) { ctx.b = 2 })
flaska.beforeAsync(function(ctx) { return new Promise(function(res) { ctx.c = 3; res() }) })
flaska.beforeAsync(function(ctx) { ctx.d = 4 })
assert.notOk(flaska._beforeAsyncCompiled)
flaska.compile()
assert.ok(flaska._beforeAsyncCompiled)
assert.strictEqual(typeof(flaska._beforeAsyncCompiled), 'function')
let ctx = createCtx()
return flaska._beforeAsyncCompiled(ctx).then(function() {
assert.strictEqual(ctx.a, 1)
assert.strictEqual(ctx.b, 2)
assert.strictEqual(ctx.c, 3)
assert.strictEqual(ctx.d, 4)
})
})
t.test('join all after together in one function', function() {
let flaska = new Flaska({}, faker)
flaska.after(function(ctx) { ctx.a = 1 })
flaska.after(function(ctx) { ctx.b = 2 })
flaska.after(function(ctx) { ctx.c = 3 })
flaska.after(function(ctx) { ctx.d = 4 })
assert.notOk(flaska._afterCompiled)
flaska.compile()
assert.ok(flaska._afterCompiled)
assert.notOk(flaska._afterAsyncCompiled)
assert.strictEqual(typeof(flaska._afterCompiled), 'function')
let ctx = createCtx()
flaska._afterCompiled(ctx)
assert.strictEqual(ctx.a, 1)
assert.strictEqual(ctx.b, 2)
assert.strictEqual(ctx.c, 3)
assert.strictEqual(ctx.d, 4)
})
t.test('join all afterAsync together in one function', function() {
let flaska = new Flaska({}, faker)
flaska.afterAsync(function(ctx) { return Promise.resolve().then(function() { ctx.a = 1 }) })
flaska.afterAsync(function(ctx) { ctx.b = 2 })
flaska.afterAsync(function(ctx) { return new Promise(function(res) { ctx.c = 3; res() }) })
flaska.afterAsync(function(ctx) { ctx.d = 4 })
assert.notOk(flaska._afterAsyncCompiled)
flaska.compile()
assert.ok(flaska._afterAsyncCompiled)
assert.strictEqual(typeof(flaska._afterAsyncCompiled), 'function')
let ctx = createCtx()
return flaska._afterAsyncCompiled(ctx).then(function() {
assert.strictEqual(ctx.a, 1)
assert.strictEqual(ctx.b, 2)
assert.strictEqual(ctx.c, 3)
assert.strictEqual(ctx.d, 4)
})
})
})
t.describe('#handleMiddleware()', function() {
t.test('should work with empty array', function() {
let flaska = new Flaska({}, faker)
flaska.handleMiddleware({}, [], 0)
})
t.test('should work with correct index', function() {
let checkIsTrue = false
let flaska = new Flaska({}, faker)
flaska.handleMiddleware({}, [
function() { throw new Error('should not be thrown') },
function() { throw new Error('should not be thrown') },
function() { throw new Error('should not be thrown') },
function() { checkIsTrue = true },
], 3)
assert.strictEqual(checkIsTrue, true)
})
t.test('should work with static functions', function() {
const assertCtx = createCtx({ a: 1 })
let checkCounter = 0
let flaska = new Flaska({}, faker)
flaska.handleMiddleware(assertCtx, [
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
], 0)
assert.strictEqual(checkCounter, 5)
})
t.test('should work with random promises inbetween', function() {
const assertCtx = createCtx({ a: 1 })
let checkCounter = 0
let flaska = new Flaska({}, faker)
let result = flaska.handleMiddleware(assertCtx, [
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 1); checkCounter++ },
function(ctx) { return new Promise(function(res) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 2); checkCounter++; res() }) },
function(ctx) { return Promise.resolve().then(function() { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 3); checkCounter++ }) },
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 4); checkCounter++ },
function(ctx) { return Promise.resolve().then(function() { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 5); checkCounter++ }) },
function(ctx) { return Promise.resolve().then(function() { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 6); checkCounter++ }) },
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 7); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 8); checkCounter++ },
], 0)
assert.ok(result)
assert.strictEqual(typeof(result.then), 'function')
return result.then(function() {
assert.strictEqual(checkCounter, 9)
})
})
t.test('should work with rejected promises inbetween', async function() {
const assertError = { a: 1 }
let checkCounter = 0
let flaska = new Flaska({}, faker)
let err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return new Promise(function(res, rej) { rej(assertError) }) },
function() { throw new Error('should not be seen') },
], 0))
assert.strictEqual(err, assertError)
err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return Promise.reject(assertError) },
function() { throw new Error('should not be seen') },
], 0))
assert.strictEqual(err, assertError)
err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return Promise.resolve() },
function() { throw assertError },
], 0))
assert.strictEqual(err, assertError)
})
})
t.describe('#listen()', function() {
t.test('it should throw if missing port', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska.listen() }, /[Pp]ort/)
assert.throws(function() { flaska.listen('asdf') }, /[Pp]ort/)
assert.throws(function() { flaska.listen('123') }, /[Pp]ort/)
assert.throws(function() { flaska.listen([]) }, /[Pp]ort/)
assert.throws(function() { flaska.listen({}) }, /[Pp]ort/)
assert.throws(function() { flaska.listen(function() {}) }, /[Pp]ort/)
})
t.test('it should automatically call compile', function() {
let assertCalled = false
let flaska = new Flaska({}, faker)
flaska.compile = function() { assertCalled = true }
flaska.listen(404)
assert.strictEqual(assertCalled, true)
})
t.test('call http correctly', function() {
const assertPort = 325897235
const assertCb = function() { }
let checkPort = null
let checkListenCb = null
let testFaker = fakeHttp(null, function(port, cb) {
checkPort = port
checkListenCb = cb
})
let flaska = new Flaska({}, testFaker)
assert.ok(flaska.requestStart)
flaska.requestStart = function() {
checkInternalThis = this
checkIsTrue = true
}
flaska.listen(assertPort, assertCb)
assert.strictEqual(checkPort, assertPort)
assert.strictEqual(checkListenCb, assertCb)
})
t.test('register requestStart if no async', function() {
let checkIsTrue = false
let checkInternalThis = null
let checkHandler = null
let testFaker = fakeHttp(function(cb) {
checkHandler = cb
})
let flaska = new Flaska({}, testFaker)
assert.ok(flaska.requestStart)
flaska.requestStart = function() {
checkInternalThis = this
checkIsTrue = true
}
flaska.listen(404)
assert.strictEqual(typeof(checkHandler), 'function')
assert.notStrictEqual(checkHandler, flaska.requestStart)
assert.notStrictEqual(checkIsTrue, true)
assert.notStrictEqual(checkInternalThis, flaska)
checkHandler()
assert.strictEqual(checkIsTrue, true)
assert.strictEqual(checkInternalThis, flaska)
})
})

495
test/flaska.in.test.mjs Normal file
View file

@ -0,0 +1,495 @@
import { Eltro as t, assert} from 'eltro'
import { Flaska } from '../flaska.mjs'
import { fakeHttp, createReq, spy, createRes } from './helper.mjs'
const faker = fakeHttp()
t.describe('#requestStart()', function() {
t.test('calls on correct events on both req and res', function(cb) {
const assertErrorOne = new Error('Erics Birth')
const assertErrorTwo = new Error('Contactic Eric')
const onReqError = spy()
const onResError = spy()
const onEnded = spy()
const assertReq = createReq({ url: '', a: 1 })
const assertRes = createRes({ b: 2 })
let flaska = new Flaska({}, faker)
flaska.onreqerror(onReqError)
flaska.onreserror(onResError)
flaska.requestEnded = onEnded
flaska.requestEnd = function(err, ctx) {
try {
assert.ok(err)
assert.strictEqual(assertReq.on.callCount, 3)
assert.strictEqual(assertRes.on.callCount, 1)
assert.strictEqual(assertRes.on.firstCall[0], 'error')
assert.strictEqual(typeof(assertRes.on.firstCall[1]), 'function')
assertRes.on.firstCall[1](assertErrorTwo, ctx)
assert.strictEqual(onResError.callCount, 1)
assert.strictEqual(onResError.firstCall[0], assertErrorTwo)
assert.strictEqual(onResError.firstCall[1], ctx)
assert.strictEqual(assertReq.on.firstCall[0], 'error')
assert.strictEqual(typeof(assertReq.on.firstCall[1]), 'function')
assertReq.on.firstCall[1](assertErrorOne, ctx)
assert.strictEqual(onReqError.callCount, 1)
assert.strictEqual(onReqError.firstCall[0], assertErrorOne)
assert.strictEqual(onReqError.firstCall[1], ctx)
assert.strictEqual(assertReq.on.secondCall[0], 'aborted')
assert.strictEqual(typeof(assertReq.on.secondCall[1]), 'function')
assert.notStrictEqual(ctx.aborted, false)
assertReq.on.secondCall[1]()
assert.strictEqual(ctx.aborted, true)
assert.strictEqual(assertReq.on.thirdCall[0], 'close')
assert.strictEqual(typeof(assertReq.on.thirdCall[1]), 'function')
assert.strictEqual(onEnded.called, false)
assertReq.on.thirdCall[1]()
assert.strictEqual(onEnded.called, true)
// Test abort and close
cb()
} catch (err) { cb(err) }
}
flaska._beforeCompiled = function(ctx) {
throw new Error()
}
flaska.requestStart(assertReq, assertRes)
})
t.test('calls beforeCompiled correctly', function(cb) {
const assertError = new Error('test')
const assertReq = createReq({ url: '', a: 1 })
const assertRes = createRes({ b: 2 })
let flaska = new Flaska({}, faker)
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.strictEqual(err, assertError)
assert.deepStrictEqual(ctx.state, {})
assert.strictEqual(ctx.req, assertReq)
assert.strictEqual(ctx.res, assertRes)
cb()
} catch (err) { cb(err) }
}
flaska._beforeCompiled = function(ctx) {
assert.strictEqual(ctx.req, assertReq)
assert.strictEqual(ctx.res, assertRes)
throw assertError
}
flaska.requestStart(assertReq, assertRes)
})
t.test('calls beforeAsyncCompiled correctly if defined', function(cb) {
const assertError = new Error('test')
const assertReq = createReq({ url: '', a: 1 })
const assertRes = createRes({ b: 2 })
let flaska = new Flaska({}, faker)
flaska.compile()
flaska._beforeAsyncCompiled = function(ctx) {
assert.strictEqual(ctx.req, assertReq)
assert.strictEqual(ctx.res, assertRes)
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.strictEqual(err, assertError)
assert.deepStrictEqual(ctx.state, {})
assert.strictEqual(ctx.req, assertReq)
assert.strictEqual(ctx.res, assertRes)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(assertReq, assertRes)
})
t.test('calls correct router with correct url and context', function(cb) {
const assertError = new Error('test')
const assertMethod = 'test'
const assertPath = '/test/me'
const assertSearch = '?asdf=test'
let flaska = new Flaska({}, faker)
flaska.compile()
flaska.routers.test = {
match: function(path) {
assert.strictEqual(path, assertPath)
throw assertError
}
}
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx.url, assertPath)
assert.strictEqual(ctx.search, assertSearch)
assert.strictEqual(ctx.method, assertMethod)
assert.strictEqual(ctx.status, 200)
assert.strictEqual(ctx.body, null)
assert.strictEqual(ctx.type, null)
assert.strictEqual(ctx.length, null)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: assertPath + assertSearch,
method: assertMethod,
}), createRes())
})
t.test('calls correct router with correct url and context if beforeAsync', function(cb) {
const assertError = new Error('test')
const assertMethod = 'test'
const assertPath = '/test/me'
const assertSearch = '?asdf=test'
let flaska = new Flaska({}, faker)
flaska.compile()
flaska._beforeAsyncCompiled = function() { return Promise.resolve() }
flaska.routers.test = {
match: function(path) {
assert.strictEqual(path, assertPath)
throw assertError
}
}
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx.url, assertPath)
assert.strictEqual(ctx.search, assertSearch)
assert.strictEqual(ctx.method, assertMethod)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: assertPath + assertSearch,
method: assertMethod,
}), createRes())
})
t.test('calls handleMiddleware correctly', function(cb) {
const assertError = new Error('test')
const assertMiddles = [1, 2]
const assertParams = { a: 1, b: 2 }
let checkMiddleCtx = null
let flaska = new Flaska({}, faker)
flaska.compile()
flaska.routers.GET.match = function() {
return {
handler: function() {},
middlewares: assertMiddles,
params: assertParams,
}
}
flaska.handleMiddleware = function(ctx, middles, index) {
assert.strictEqual(index, 0)
assert.strictEqual(middles, assertMiddles)
checkMiddleCtx = ctx
throw assertError
}
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx, checkMiddleCtx)
assert.strictEqual(ctx.params, assertParams)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '',
method: 'GET',
}), createRes({}))
})
t.test('calls route handler correctly', function(cb) {
const assertError = new Error('test')
let checkHandlerCtx = null
let handler = function(ctx) {
assert.strictEqual(ctx.params.id, 'test')
checkHandlerCtx = ctx
throw assertError
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', handler)
flaska.compile()
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx, checkHandlerCtx)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test',
method: 'GET',
}), createRes())
})
t.test('should work with synchronous handler', function(cb) {
const assertBody = { a: 1 }
let handler = function(ctx) {
assert.strictEqual(ctx.params.path, 'test/something/here')
ctx.body = assertBody
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', handler)
flaska.compile()
flaska.handleMiddleware = function() {
throw new Error('should not be called')
}
flaska.requestEnd = function(err, ctx) {
try {
assert.notOk(err)
assert.ok(ctx)
assert.strictEqual(ctx.body, assertBody)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test/something/here',
method: 'GET',
}), createRes())
})
t.test('calls handleMiddleware correctly if is promise', function(cb) {
const assertError = new Error('test')
const assertMiddles = [1]
let flaska = new Flaska({}, faker)
flaska.compile()
flaska.routers.GET.match = function() {
return {
handler: function() {},
middlewares: assertMiddles,
}
}
flaska.handleMiddleware = function() {
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '',
method: 'GET',
}), createRes())
})
t.test('calls route handler correctly with promise middle', function(cb) {
const assertError = new Error('test')
let checkHandlerCtx = null
let handler = function(ctx) {
assert.strictEqual(ctx.params.id, 'test')
checkHandlerCtx = ctx
throw assertError
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
flaska.compile()
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx, checkHandlerCtx)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test',
method: 'GET',
}), createRes())
})
t.test('calls promise route handler correctly with promise middle', function(cb) {
const assertError = new Error('test')
let checkHandlerCtx = null
let handler = function(ctx) {
assert.strictEqual(ctx.params.id, 'test')
checkHandlerCtx = ctx
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
flaska.compile()
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx, checkHandlerCtx)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test',
method: 'GET',
}), createRes())
})
t.test('should work with promise middle and promise handler correctly', function(cb) {
const assertBody = { a: 1 }
const assertState = { b: 2 }
let middle = function(ctx) {
return Promise.resolve().then(function() {
ctx.state = assertState
})
}
let handler = function(ctx) {
assert.strictEqual(ctx.params.path, 'test/something/here')
assert.strictEqual(ctx.state, assertState)
return Promise.resolve().then(function() {
ctx.body = assertBody
})
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', [middle], handler)
flaska.compile()
flaska.requestEnd = function(err, ctx) {
try {
assert.notOk(err)
assert.ok(ctx)
assert.strictEqual(ctx.body, assertBody)
assert.strictEqual(ctx.state, assertState)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test/something/here',
method: 'GET',
}), createRes())
})
t.test('calls route handler correctly if promise', function(cb) {
const assertError = new Error('test')
let checkHandlerCtx = null
let handler = function(ctx) {
assert.strictEqual(ctx.params.id, 'test')
checkHandlerCtx = ctx
return Promise.resolve().then(function() {
return Promise.reject(assertError)
})
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', handler)
flaska.compile()
flaska.requestEnd = function(err, ctx) {
if (err && err !== assertError) return cb(err)
try {
assert.ok(err)
assert.ok(ctx)
assert.strictEqual(err, assertError)
assert.strictEqual(ctx, checkHandlerCtx)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test',
method: 'GET',
}), createRes())
})
t.test('should work with promise handler', function(cb) {
const assertBody = { a: 1 }
let handler = function(ctx) {
return Promise.resolve().then(function() {
assert.strictEqual(ctx.params.path, 'test/something/here')
ctx.body = assertBody
})
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', [], handler)
flaska.compile()
flaska.handleMiddleware = function() {
throw new Error('should not be called')
}
flaska.requestEnd = function(err, ctx) {
try {
assert.notOk(err)
assert.ok(ctx)
assert.strictEqual(ctx.body, assertBody)
cb()
} catch (err) { cb(err) }
}
flaska.requestStart(createReq({
url: '/test/something/here',
method: 'GET',
}), createRes())
})
})

249
test/flaska.out.test.mjs Normal file
View file

@ -0,0 +1,249 @@
import { Eltro as t, assert} from 'eltro'
import { Flaska, FlaskaRouter } from '../flaska.mjs'
import { fakeHttp, createCtx, spy } from './helper.mjs'
const fakerHttp = fakeHttp()
const fakeStream = { pipeline: spy() }
t.describe('#requestEnd()', function() {
t.test('calls onerror correctly on error', function(cb) {
const assertError = new Error('test')
const assertBody = { a: 1 }
let onFinish = function() {
try {
assert.strictEqual(ctx.status, 501)
assert.strictEqual(ctx.body, assertBody)
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({}, onFinish)
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.onerror(function(err, inctx) {
assert.strictEqual(err, assertError)
assert.strictEqual(inctx, ctx)
inctx.status = 501
inctx.body = assertBody
})
flaska.requestEnd(assertError, ctx)
})
t.test('call res and end correctly when dealing with objects', function(cb) {
const assertStatus = 202
// Calculated manually just in case
const assertBodyLength = 7
const assertBody = { a: 1 }
let onFinish = function(body) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.body, assertBody)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.callCount, 2)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], 'application/json; charset=utf-8')
assert.strictEqual(ctx.res.setHeader.secondCall[0], 'Content-Length')
assert.strictEqual(ctx.res.setHeader.secondCall[1], assertBodyLength)
assert.ok(body)
assert.strictEqual(body, '{"a":1}')
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
}, onFinish)
ctx.body = assertBody
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
t.test('call res and end correctly when dealing with strings', function(cb) {
const assertStatus = 206
// Calculated manually just in case
const assertBodyLength = 4
const assertBody = 'eða'
let onFinish = function(body) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.body, assertBody)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.callCount, 2)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], 'text/plain; charset=utf-8')
assert.strictEqual(ctx.res.setHeader.secondCall[0], 'Content-Length')
assert.strictEqual(ctx.res.setHeader.secondCall[1], assertBodyLength)
assert.ok(body)
assert.strictEqual(body, assertBody)
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
}, onFinish)
ctx.body = assertBody
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
t.test('call res and end correctly when dealing with numbers', function(cb) {
const assertStatus = 211
// Calculated manually just in case
const assertBodyLength = 7
const assertBody = 4214124
let onFinish = function(body) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.body, assertBody)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.callCount, 2)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], 'text/plain; charset=utf-8')
assert.strictEqual(ctx.res.setHeader.secondCall[0], 'Content-Length')
assert.strictEqual(ctx.res.setHeader.secondCall[1], assertBodyLength)
assert.ok(body)
assert.strictEqual(body, assertBody.toString())
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
}, onFinish)
ctx.body = assertBody
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
t.test('call res and end correctly when dealing with custom type', function(cb) {
const assertStatus = 209
const assertBody = 'test'
const assertType = 'something/else'
let onFinish = function(body) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], assertType)
assert.strictEqual(body, assertBody)
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
body: assertBody,
}, onFinish)
ctx.type = assertType
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
t.test('call pipeline correctly when dealing with pipe', function(cb) {
const assertStatus = 211
const assertType = 'herp/derp'
const assertBody = { pipe: function() {} }
let onFinish = function(source, target, callback) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], assertType)
assert.strictEqual(source, assertBody)
assert.strictEqual(target, ctx.res)
assert.strictEqual(typeof(callback), 'function')
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
})
fakeStream.pipeline = onFinish
ctx.body = assertBody
ctx.type = assertType
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
t.test('call pipe should have default type', function(cb) {
let onFinish = function(source, target) {
try {
assert.strictEqual(ctx.res.statusCode, 200)
assert.strictEqual(ctx.res.setHeader.firstCall[0], 'Content-Type')
assert.strictEqual(ctx.res.setHeader.firstCall[1], 'application/octet-stream')
assert.strictEqual(source, ctx.body)
assert.strictEqual(target, ctx.res)
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({})
ctx.body = { pipe: function() {} }
fakeStream.pipeline = onFinish
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
const emptyStatuses = [204, 205, 304]
emptyStatuses.forEach(function(status) {
t.test('call res and end correctly when dealing with status ' + status, function(cb) {
const assertStatus = status
const assertNotBody = 'test'
const assertNotType = 'something/else'
let onFinish = function(body) {
try {
assert.strictEqual(ctx.status, assertStatus)
assert.strictEqual(ctx.res.statusCode, assertStatus)
assert.strictEqual(ctx.res.setHeader.callCount, 0)
assert.notOk(body)
cb()
} catch (err) { cb(err) }
}
const ctx = createCtx({
status: assertStatus,
body: assertNotBody,
}, onFinish)
ctx.type = assertNotType
let flaska = new Flaska({}, fakerHttp, fakeStream)
flaska.requestEnd(null, ctx)
})
})
})
t.describe('#requestEnded()', function() {
t.test('calls afterCompiled correctly', function() {
const assertError = new Error('test')
const assertCtx = createCtx()
let flaska = new Flaska({}, fakerHttp)
flaska._afterCompiled = function(ctx) {
assert.strictEqual(ctx, assertCtx)
throw assertError
}
assert.throws(function() {
flaska.requestEnded(assertCtx)
}, /test/)
})
t.test('calls afterAsyncCompiled correctly if defined', async function() {
const assertError = new Error('test')
const assertCtx = createCtx()
let flaska = new Flaska({}, fakerHttp)
flaska.compile()
flaska._afterAsyncCompiled = function(ctx) {
assert.strictEqual(ctx, assertCtx)
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
let err = await assert.isRejected(flaska.requestEnded(assertCtx))
assert.strictEqual(err, assertError)
})
})

View file

@ -1,5 +0,0 @@
import { Eltro as t, assert} from 'eltro'
import { Flaska } from '../flaska.mjs'
t.describe('Flaska', function() {
})

118
test/helper.mjs Normal file
View file

@ -0,0 +1,118 @@
const indexMap = [
'firstCall',
'secondCall',
'thirdCall',
]
export function spy() {
let calls = []
let called = 0
let func = function(...args) {
func.called = true
calls.push(args)
if (called < indexMap.length) {
func[indexMap[called]] = args
}
called++
func.callCount = called
}
func.called = false
func.callCount = called
func.onCall = function(i) {
return calls[i]
}
for (let i = 0; i < indexMap.length; i++) {
func[indexMap] = null
}
return func
}
export function fakeHttp(inj1, inj2) {
let intermediate = {
createServer: function(cb) {
if (inj1) inj1(cb)
intermediate.fakeRequest = cb
return {
listen: function(port, cb) {
if (inj2) inj2(port, cb)
else if (cb) cb()
}
}
}
}
return intermediate
}
export function createReq(def) {
return defaults(def, {
on: spy(),
})
}
export function createRes(def) {
return defaults(def, {
statusCode: 0,
end: spy(),
setHeader: spy(),
write: spy(),
on: spy(),
writeHead: spy(),
pipe: spy(),
})
}
export function createCtx(def, endHandler) {
return defaults(def, {
req: createReq(),
res: createRes({ end: endHandler || spy() }),
finished: false,
method: 'GET',
url: '/test',
search: '',
state: {},
status: 200,
body: null,
type: null,
length: null,
log: {
error: spy(),
info: spy(),
warn: spy(),
debug: spy(),
}
})
}
// taken from isobject npm library
function isObject(val) {
return val != null && typeof val === 'object' && Array.isArray(val) === false
}
export function defaults(options, def) {
let out = { }
if (options) {
Object.keys(options || {}).forEach(key => {
out[key] = options[key]
if (Array.isArray(out[key])) {
out[key] = out[key].map(item => {
if (isObject(item)) return defaults(item)
return item
})
} else if (out[key] && typeof out[key] === 'object') {
out[key] = defaults(options[key], def && def[key])
}
})
}
if (def) {
Object.keys(def).forEach(function(key) {
if (typeof out[key] === 'undefined') {
out[key] = def[key]
}
})
}
return out
}

29
test/http.test.mjs Normal file
View file

@ -0,0 +1,29 @@
import { Eltro as t, assert} from 'eltro'
import { Flaska } from '../flaska.mjs'
import Client from './client.mjs'
const port = 51024
const flaska = new Flaska({})
const client = new Client(port)
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
t.before(function(cb) {
flaska.listen(port, cb)
})
t.describe('', function() {
t.test('/ should return status true', function() {
return client.get().then(function(body) {
assert.deepEqual(body, { status: true })
})
})
})
t.after(function(cb) {
setTimeout(function() {
flaska.server.close(cb)
}, 1000)
})

View file

@ -200,6 +200,50 @@ t.describe('#match()', function() {
assert.strictEqual(result.params.id, assertParameter)
})
t.test('match full path variable paths', function() {
const assertParameter = 'bla/bla/bla'
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test/::id', function() { assertMatched = true })
let result = router.match('/test/' + assertParameter)
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/' + assertParameter + '/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
})
t.test('match full path root path properly', function() {
const assertParamFunc = function() { }
const assertFullFunc = function() { }
let router = new FlaskaRouter()
router.addRoute('/test/:bla', assertParamFunc)
router.addRoute('/::id', assertFullFunc)
let result = router.match('/test/123')
assert.strictEqual(result.handler, assertParamFunc)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
assert.strictEqual(result.params.bla, '123')
result = router.match('/test/123/asdf')
assert.strictEqual(result.handler, assertFullFunc)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
assert.strictEqual(result.params.id, 'test/123/asdf')
assert.notOk(result.params.bla)
})
t.test('match paths properly', function() {
let assertMatched = true
let router = new FlaskaRouter()
@ -223,6 +267,36 @@ t.describe('#match()', function() {
assert.strictEqual(result.params.id, 'asdf')
})
t.test('more comprehensive testing', function() {
const assertFunction = function() { }
const assertRootFunction = function() { }
const assertFailFunction = function() { }
let router = new FlaskaRouter()
router.addRoute('/test/:id', assertFunction)
router.addRoute('/test/test/::path', assertFunction)
router.addRoute('/foo/::path', assertFunction)
router.addRoute('/::path', assertFailFunction)
assert.strictEqual(router.match('/test/123').handler, assertFunction)
assert.strictEqual(router.match('/test/asdfasdg').handler, assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda').handler, assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').handler, assertFunction)
assert.strictEqual(router.match('/foo/123').handler, assertFunction)
assert.strictEqual(router.match('/foo/bar/baz/test').handler, assertFunction)
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').handler, assertFailFunction)
assert.ok(router.match('/test/foo/bar'))
assert.strictEqual(router.match('/test/foo/bar').handler, assertFailFunction)
assert.ok(router.match('/'))
assert.strictEqual(router.match('/').handler, assertFailFunction)
assert.ok(router.match('/something/else/goes/here'))
assert.strictEqual(router.match('/something/else/goes/here').handler, assertFailFunction)
router.addRoute('/', assertRootFunction)
assert.ok(router.match('/'))
assert.strictEqual(router.match('/').handler, assertRootFunction)
})
t.test('return null when no match is found', function() {
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { })