Skip to content

Commit c568e9c

Browse files
authored
Easy cookie (#72)
1 parent 7acd544 commit c568e9c

File tree

8 files changed

+114
-5
lines changed

8 files changed

+114
-5
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Light my Request
22

33
[![Greenkeeper badge](https://badges.greenkeeper.io/fastify/light-my-request.svg)](https://greenkeeper.io/)
4-
5-
[![Build Status](https://travis-ci.org/fastify/light-my-request.svg?branch=master)](https://travis-ci.org/fastify/light-my-request) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
4+
[![Build Status](https://travis-ci.org/fastify/light-my-request.svg?branch=master)](https://travis-ci.org/fastify/light-my-request)
5+
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
66

77
Injects a fake HTTP request/response into a node HTTP server for simulating server logic, writing tests, or debugging.
88
Does not use a socket connection so can be run against an inactive server (server not in listen mode).
@@ -146,6 +146,7 @@ Injects a fake request into an HTTP server.
146146
- `authority` - a string specifying the HTTP HOST header value to be used if no header is provided, and the `url`
147147
does not include an authority component. Defaults to `'localhost'`.
148148
- `headers` - an optional object containing request headers.
149+
- `cookies` - an optional object containing key-value pairs that will be encoded and added to `cookie` header. If the header is already set, the data will be appended.
149150
- `remoteAddress` - an optional string specifying the client remote address. Defaults to `'127.0.0.1'`.
150151
- `payload` - an optional request payload. Can be a string, Buffer, Stream or object. If the payload is string, Buffer or Stream is used as is as the request payload. Oherwise it is serialized with `JSON.stringify` forcing the request to have the `Content-type` equal to `application/json`
151152
- `query` - an optional object containing query parameters.
@@ -173,6 +174,7 @@ Injects a fake request into an HTTP server.
173174
- `rawPayload` - the raw payload as a Buffer.
174175
- `trailers` - an object containing the response trailers.
175176
- `json` - a function that parses the `application/json` response payload and returns an object. Throws if the content type does not contain `application/json`.
177+
- `cookies` - a getter that parses the `set-cookie` response header and returns an array with all the cookies and their metadata.
176178

177179
Note: You can also pass a string in place of the `options` object as a shorthand for `{url: string, method: 'GET'}`.
178180

@@ -184,7 +186,7 @@ Checks if given object `obj` is a *light-my-request* `Request` object.
184186

185187
There are following methods you can used as chaining:
186188
- `delete`, `get`, `head`, `options`, `patch`, `post`, `put`, `trace`. They will set the HTTP request method and also the request URL.
187-
- `body`, `headers`, `payload`, `query`. They can be used to set the request options object.
189+
- `body`, `headers`, `payload`, `query`, `cookies`. They can be used to set the request options object.
188190

189191
And finally you need to call `end`. It has the signature `function (callback)`.
190192
If you invoke `end` without a callback function, the method will return a promise, thus you can:

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ declare namespace LightMyRequest {
7979
payload: string
8080
body: string
8181
json: () => object
82+
cookies: Array<object>
8283
}
8384

8485
interface Chain {
@@ -94,6 +95,7 @@ declare namespace LightMyRequest {
9495
headers: (headers: http.IncomingHttpHeaders | http.OutgoingHttpHeaders) => Chain
9596
payload: (payload: InjectPayload) => Chain
9697
query: (query: object) => Chain
98+
cookies: (query: object) => Chain
9799
end: (callback?: CallbackFunc) => Chain | Promise<Response>
98100
}
99101
}

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ const schema = {
3131
properties: {
3232
url: urlSchema,
3333
path: urlSchema,
34+
cookies: {
35+
type: 'object',
36+
additionalProperties: true
37+
},
3438
headers: {
3539
type: 'object',
3640
additionalProperties: true
@@ -143,6 +147,7 @@ httpMethods.forEach(method => {
143147

144148
const chainMethods = [
145149
'body',
150+
'cookies',
146151
'headers',
147152
'payload',
148153
'query'

lib/request.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
const { Readable } = require('readable-stream')
66
const util = require('util')
7+
const cookie = require('cookie')
78

89
const parseURL = require('./parseURL')
910

@@ -27,6 +28,7 @@ function hostHeaderFromURL (parsedURL) {
2728
* @param {(Object|String)} options.url || options.path
2829
* @param {String} [options.method='GET']
2930
* @param {String} [options.remoteAddress]
31+
* @param {Object} [options.cookies]
3032
* @param {Object} [options.headers]
3133
* @param {Object} [options.query]
3234
* @param {any} [options.payload]
@@ -50,6 +52,15 @@ function Request (options) {
5052
this.headers['user-agent'] = this.headers['user-agent'] || 'lightMyRequest'
5153
this.headers.host = this.headers.host || options.authority || hostHeaderFromURL(parsedURL)
5254

55+
if (options.cookies) {
56+
const { cookies } = options
57+
const cookieValues = Object.keys(cookies).map(key => cookie.serialize(key, cookies[key]))
58+
if (this.headers.cookie) {
59+
cookieValues.unshift(this.headers.cookie)
60+
}
61+
this.headers.cookie = cookieValues.join('; ')
62+
}
63+
5364
this.connection = {
5465
remoteAddress: options.remoteAddress || '127.0.0.1'
5566
}

lib/response.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const http = require('http')
44
const { Writable } = require('readable-stream')
55
const util = require('util')
66

7+
const setCookie = require('set-cookie-parser')
8+
79
function Response (req, onEnd, reject) {
810
http.ServerResponse.call(this, {
911
method: req.method,
@@ -107,7 +109,10 @@ function generatePayload (response) {
107109
headers: response._lightMyRequest.headers,
108110
statusCode: response.statusCode,
109111
statusMessage: response.statusMessage,
110-
trailers: {}
112+
trailers: {},
113+
get cookies () {
114+
return setCookie.parse(this)
115+
}
111116
}
112117

113118
// Prepare payload and trailers

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"main": "index.js",
66
"dependencies": {
77
"ajv": "^6.10.2",
8-
"readable-stream": "^3.4.0"
8+
"cookie": "^0.4.0",
9+
"readable-stream": "^3.4.0",
10+
"set-cookie-parser": "^2.4.1"
911
},
1012
"types": "index.d.ts",
1113
"devDependencies": {

test/index.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ inject(dispatch, { method: 'get', url: '/' }, (err, res) => {
1919
expectType<Response>(res)
2020
console.log(res.payload)
2121
expectType<Function>(res.json)
22+
console.log(res.cookies)
2223
})
2324

2425
inject(dispatch)

test/test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,24 @@ test('chainable api: body method should work correctly', (t) => {
10461046
})
10471047
})
10481048

1049+
test('chainable api: cookie', (t) => {
1050+
t.plan(2)
1051+
1052+
function dispatch (req, res) {
1053+
res.writeHead(200, { 'Content-Type': 'text/plain' })
1054+
res.end(req.headers.cookie)
1055+
}
1056+
1057+
inject(dispatch)
1058+
.get('http://example.com:8080/hello')
1059+
.body('test')
1060+
.cookies({ hello: 'world', fastify: 'rulez' })
1061+
.end((err, res) => {
1062+
t.error(err)
1063+
t.equal(res.body, 'hello=world; fastify=rulez')
1064+
})
1065+
})
1066+
10491067
test('chainable api: body method should throw if already invoked', (t) => {
10501068
t.plan(1)
10511069

@@ -1349,3 +1367,66 @@ function readStream (stream, callback) {
13491367
return callback(Buffer.concat(chunks))
13501368
})
13511369
}
1370+
1371+
test('send cookie', (t) => {
1372+
t.plan(3)
1373+
const dispatch = function (req, res) {
1374+
res.writeHead(200, { 'Content-Type': 'text/plain' })
1375+
res.end(req.headers.host + '|' + req.headers.cookie)
1376+
}
1377+
1378+
inject(dispatch, { url: 'http://example.com:8080/hello', cookies: { foo: 'bar', grass: 'àìùòlé' } }, (err, res) => {
1379+
t.error(err)
1380+
t.equal(res.payload, 'example.com:8080|foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9')
1381+
t.equal(res.rawPayload.toString(), 'example.com:8080|foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9')
1382+
})
1383+
})
1384+
1385+
test('send cookie with header already set', (t) => {
1386+
t.plan(3)
1387+
const dispatch = function (req, res) {
1388+
res.writeHead(200, { 'Content-Type': 'text/plain' })
1389+
res.end(req.headers.host + '|' + req.headers.cookie)
1390+
}
1391+
1392+
inject(dispatch, {
1393+
url: 'http://example.com:8080/hello',
1394+
headers: { cookie: 'custom=one' },
1395+
cookies: { foo: 'bar', grass: 'àìùòlé' }
1396+
}, (err, res) => {
1397+
t.error(err)
1398+
t.equal(res.payload, 'example.com:8080|custom=one; foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9')
1399+
t.equal(res.rawPayload.toString(), 'example.com:8080|custom=one; foo=bar; grass=%C3%A0%C3%AC%C3%B9%C3%B2l%C3%A9')
1400+
})
1401+
})
1402+
1403+
test('read cookie', (t) => {
1404+
t.plan(3)
1405+
const dispatch = function (req, res) {
1406+
res.setHeader('Set-Cookie', [
1407+
'type=ninja',
1408+
'dev=me; Expires=Fri, 17 Jan 2020 20:26:08 -0000; Max-Age=1234; Domain=.home.com; Path=/wow; Secure; HttpOnly; SameSite=Strict'
1409+
])
1410+
res.writeHead(200, { 'Content-Type': 'text/plain' })
1411+
res.end(req.headers.host + '|' + req.headers.cookie)
1412+
}
1413+
1414+
inject(dispatch, { url: 'http://example.com:8080/hello', cookies: { foo: 'bar' } }, (err, res) => {
1415+
t.error(err)
1416+
t.equal(res.payload, 'example.com:8080|foo=bar')
1417+
t.deepEqual(res.cookies, [
1418+
{ name: 'type', value: 'ninja' },
1419+
{
1420+
name: 'dev',
1421+
value: 'me',
1422+
expires: new Date('Fri, 17 Jan 2020 20:26:08 -0000'),
1423+
maxAge: 1234,
1424+
domain: '.home.com',
1425+
path: '/wow',
1426+
secure: true,
1427+
httpOnly: true,
1428+
sameSite: 'Strict'
1429+
}
1430+
])
1431+
})
1432+
})

0 commit comments

Comments
 (0)