Skip to content
This repository has been archived by the owner on Feb 27, 2022. It is now read-only.

Commit

Permalink
feat(response): add base response class
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Aug 29, 2017
1 parent 1e5edd1 commit 805be5a
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 0 deletions.
170 changes: 170 additions & 0 deletions src/Response/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'use strict'

/*
* adonis-vow
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

const Macroable = require('macroable')
const debug = require('debug')('adonis:vow')
const nodeCookie = require('node-cookie')
const cookieParser = require('../../lib/cookieParser')

module.exports = function (Config) {
debug('creating isolated response for the suite')

/**
* This is base response class to be used
* by other response clients. For example:
* Api client and browser client.
*
* @class BaseResponse
* @constructor
*/
class Response extends Macroable {
constructor (headers) {
super()
this._headers = headers || {}
this._cookieString = this._parseCookies()
this._cookies = null
this._plainCookies = null
}

/**
* Add a new hook before request starts
*
* @method before
* @static
*
* @param {Function} fn
*
* @chainable
*/
static before (fn) {
this._hooks.before.push(fn)
return this
}

/**
* Add a new hook for after request completes
*
* @method after
* @static
*
* @param {Function} fn
*
* @chainable
*/
static after (fn) {
this._hooks.after.push(fn)
return this
}

/**
* Hydrate request constructor properties, macros
* and getters
*
* @method hydrate
* @static
*
* @return {void}
*/
static hydrate () {
super.hydrate()
this._hooks = {
before: [],
after: []
}
}

/**
* Parses the headers cookie into a string. This
* method will remove expired cookies
*
* @method _parseCookies
*
* @return {String}
*
* @private
*/
_parseCookies () {
const setCookieHeader = this._headers['set-cookie'] || []
const cookies = cookieParser.filterExpired(setCookieHeader.map((cookie) => cookieParser.parse(cookie)))
return cookies.map((cookie) => `${cookie.key}=${cookie.value}`).join(';')
}

/**
* A object of response cookies
*
* @attribute cookies
*
* @return {Object}
*/
get cookies () {
const appKey = this.constructor.Config.get('app.appKey')
if (!this._cookies) {
this._cookies = nodeCookie.parse({ headers: { cookie: this._cookieString } }, appKey, !!appKey)
}
return this._cookies
}

/**
* A object of response plain cookies
*
* @attribute cookies
*
* @return {Object}
*/
get plainCookies () {
if (!this._plainCookies) {
this._plainCookies = nodeCookie.parse({ headers: { cookie: this._cookieString } })
}
return this._plainCookies
}

/**
* Execute request hooks in sequence
*
* @method exec
*
* @param {String} event - Must be `before` or `after`
*
* @return {void}
*/
async exec (event) {
if (event !== 'before' && event !== 'after') {
throw new Error(`${event} is not a valid hook event for vow response`)
}

const hooks = this.constructor._hooks[event]
for (const hook of hooks) {
await hook(this)
}
}
}

/**
* Properties for macroable
*/
Response._macros = {}
Response._getters = {}

/**
* For hooks
*/
Response._hooks = {
before: [],
after: []
}

/**
* Reference to the Config provider
*/
Response.Config = Config

return Response
}
1 change: 1 addition & 0 deletions test/unit/request.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const test = require('japa')
const { Config } = require('@adonisjs/sink')
const RequestManager = require('../../src/Request')
const nodeCookie = require('node-cookie')

const sleep = function (time) {
return new Promise((resolve) => {
setTimeout(resolve, time)
Expand Down
199 changes: 199 additions & 0 deletions test/unit/response.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
'use strict'

/*
* adonis-vow
*
* (c) Harminder Virk <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

const http = require('http')
const test = require('japa')
const superagent = require('superagent')
const { Config } = require('@adonisjs/sink')
const ResponseManager = require('../../src/Response')
const nodeCookie = require('node-cookie')
const PORT = '3333'
const BASE_URL = `http://localhost:${PORT}`

const sleep = function (time) {
return new Promise((resolve) => {
setTimeout(resolve, time)
})
}

test.group('Response', () => {
test('instantiate response', (assert) => {
const Response = ResponseManager(new Config())
const response = new Response()
assert.instanceOf(response, Response)
})

test('define response getter', (assert) => {
const Response = ResponseManager(new Config())
Response.getter('foo', () => 'bar')
const response = new Response()
assert.equal(response.foo, 'bar')
})

test('define response macro', (assert) => {
const Response = ResponseManager(new Config())
Response.macro('foo', () => 'bar')
const response = new Response()
assert.equal(response.foo(), 'bar')
})

test('add before response hook', (assert) => {
const Response = ResponseManager(new Config())
const fn = function () {}
Response.before(fn)
assert.deepEqual(Response._hooks.before, [fn])
})

test('add after response hook', (assert) => {
const Response = ResponseManager(new Config())
const fn = function () {}
Response.after(fn)
assert.deepEqual(Response._hooks.after, [fn])
})

test('execute hooks in sequence', async (assert) => {
const Response = ResponseManager(new Config())

const stack = []
Response.before(() => {
stack.push('1')
})

Response.before(async () => {
await sleep(100)
stack.push('2')
})

Response.before(() => {
stack.push('3')
})

const response = new Response()
await response.exec('before')
assert.deepEqual(stack, ['1', '2', '3'])
})

test('throw exception when invalid hook type is passed', async (assert) => {
assert.plan(1)

const Response = ResponseManager(new Config())
const response = new Response()

try {
await response.exec('foo')
} catch ({ message }) {
assert.equal(message, 'foo is not a valid hook event for vow response')
}
})

test('should have access to response instance inside hook', async (assert) => {
assert.plan(1)
const Response = ResponseManager(new Config())

Response.before((req) => {
assert.deepEqual(req, response)
})

const response = new Response()
await response.exec('before')
})

test('read response cookies', async (assert) => {
const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk')
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(new Config())
const response = new Response(res.headers)
assert.deepEqual(response.cookies, { user: 'virk' })
server.close()
})

test('read multiple cookies', async (assert) => {
const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk')
nodeCookie.create(res, 'age', 22)
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(new Config())
const response = new Response(res.headers)
assert.deepEqual(response.cookies, { user: 'virk', age: '22' })
server.close()
})

test('read encrypted cookies', async (assert) => {
const config = new Config()
config.set('app.appKey', 'alongrandomstring')

const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk', {}, config.get('app.appKey'), true)
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(config)
const response = new Response(res.headers)
assert.deepEqual(response.cookies, { user: 'virk' })
server.close()
})

test('return cookies with options', async (assert) => {
const config = new Config()
config.set('app.appKey', 'alongrandomstring')

const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk', { path: '/' })
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(config)
const response = new Response(res.headers)
assert.deepEqual(response.plainCookies, { user: 'virk' })
server.close()
})

test('return null when unable to unparse cookies', async (assert) => {
const config = new Config()
config.set('app.appKey', 'alongrandomstring')

const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk', {})
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(config)
const response = new Response(res.headers)
assert.deepEqual(response.cookies, { user: null })
server.close()
})

test('read plain cookies', async (assert) => {
const config = new Config()
config.set('app.appKey', 'alongrandomstring')

const server = http.createServer((req, res) => {
nodeCookie.create(res, 'user', 'virk', {})
res.end()
}).listen(PORT)

const res = await superagent.get(BASE_URL)
const Response = ResponseManager(config)
const response = new Response(res.headers)
assert.deepEqual(response.plainCookies, { user: 'virk' })
server.close()
})
})

0 comments on commit 805be5a

Please sign in to comment.