From 18ff55b7dd7fbe1206b68909cbb624a53835abb8 Mon Sep 17 00:00:00 2001 From: Alan Plum Date: Tue, 23 Feb 2016 15:17:59 +0100 Subject: [PATCH] Add SyntheticRequest tests --- js/server/bootstrap/modules/internal.js | 6 +- .../modules/@arangodb/foxx/router/request.js | 73 ++- .../modules/@arangodb/foxx/router/tree.js | 3 +- .../modules/@arangodb/foxx/test-utils.js | 52 ++ .../tests/shell/shell-foxx-request-spec.js | 474 +++++++++++++++++- 5 files changed, 559 insertions(+), 49 deletions(-) create mode 100644 js/server/modules/@arangodb/foxx/test-utils.js diff --git a/js/server/bootstrap/modules/internal.js b/js/server/bootstrap/modules/internal.js index a60b49996c..4de673ae8f 100644 --- a/js/server/bootstrap/modules/internal.js +++ b/js/server/bootstrap/modules/internal.js @@ -328,7 +328,11 @@ exports.loadStartup = function (path) { //////////////////////////////////////////////////////////////////////////////// if (global.SYS_RAW_REQUEST_BODY) { - exports.rawRequestBody = global.SYS_RAW_REQUEST_BODY; + const $_RAW_BODY_BUFFER = Symbol.for('@arangodb/request.rawBodyBuffer'); + const getRawBodyBuffer = global.SYS_RAW_REQUEST_BODY; + exports.rawRequestBody = function (req) { + return req[$_RAW_BODY_BUFFER] || getRawBodyBuffer(req); + }; delete global.SYS_RAW_REQUEST_BODY; } diff --git a/js/server/modules/@arangodb/foxx/router/request.js b/js/server/modules/@arangodb/foxx/router/request.js index 906ef173db..04d20583f2 100644 --- a/js/server/modules/@arangodb/foxx/router/request.js +++ b/js/server/modules/@arangodb/foxx/router/request.js @@ -22,6 +22,8 @@ //////////////////////////////////////////////////////////////////////////////// const parseUrl = require('url').parse; +const formatUrl = require('url').format; +const joinPath = require('path').posix.join; const typeIs = require('type-is').is; const accepts = require('accepts'); const parseRange = require('range-parser'); @@ -36,7 +38,7 @@ module.exports = class SyntheticRequest { this._raw = req; this.context = context; this.suffix = req.suffix.join('/'); - this.baseUrl = `/_db/${encodeURIComponent(this._raw.database)}`; + this.baseUrl = joinPath('/_db', encodeURIComponent(this._raw.database)); this.path = this._url.pathname; this.pathParams = {}; this.queryParams = querystring.decode(this._url.query); @@ -66,7 +68,11 @@ module.exports = class SyntheticRequest { // Express compat get originalUrl() { - return this.baseUrl + this._url.pathname + (this._url.search || ''); + return joinPath( + '/_db', + encodeURIComponent(this._raw.database), + this._url.pathname + ) + (this._url.search || ''); } get secure() { @@ -82,39 +88,54 @@ module.exports = class SyntheticRequest { return Boolean(header && header.toLowerCase() === 'xmlhttprequest'); } - accepts(types) { + accepts() { const accept = accepts(this); - return accept.type(types); + return accept.types.apply(accept, arguments); } acceptsCharsets() { const accept = accepts(this); - return accept.charset.apply(accept, arguments); + return accept.charsets.apply(accept, arguments); } acceptsEncodings() { const accept = accepts(this); - return accept.encoding.apply(accept, arguments); + return accept.encodings.apply(accept, arguments); } acceptsLanguages() { const accept = accepts(this); - return accept.language.apply(accept, arguments); + return accept.languages.apply(accept, arguments); + } + + range(size) { + const range = this.headers.range; + if (!range) { + return undefined; + } + return parseRange(size, range); } get(name) { - name = name.toLowerCase(); - if (name === 'referer' || name === 'referrer') { + const lc = name.toLowerCase(); + if (lc === 'referer' || lc === 'referrer') { return this.headers.referer || this.headers.referrer; } - return this.headers[name]; + return this.headers[lc]; + } + + header(name) { + return this.get(name); } is(mediaType) { if (!this.headers['content-type']) { return false; } - return Boolean(typeIs(this.headers['content-type'], mediaType)); + if (!Array.isArray(mediaType)) { + mediaType = Array.prototype.slice.call(arguments); + } + return typeIs(this, mediaType); } // idiosyncratic @@ -139,18 +160,26 @@ module.exports = class SyntheticRequest { return value; } - makeAbsolute(path) { - return this.protocol + '://' + this.hostname + ( - (this.secure ? this.port !== 443 : this.port !== 80) - ? ':' + this.port : '' - ) + this.baseUrl + path; - } - - ranges(size) { - if (!this.headers.range) { - return []; + makeAbsolute(path, query) { + const opts = { + protocol: this.protocol, + hostname: this.hostname, + port: (this.secure ? this.port !== 443 : this.port !== 80) && this.port, + pathname: joinPath( + '/_db', + encodeURIComponent(this._raw.database), + this.context.mount, + path + ) + }; + if (query) { + if (typeof query === 'string') { + opts.search = query; + } else { + opts.query = query; + } } - return parseRange(size, this.headers.range); + return formatUrl(opts); } }; diff --git a/js/server/modules/@arangodb/foxx/router/tree.js b/js/server/modules/@arangodb/foxx/router/tree.js index 921cdd5e45..173f7744a7 100644 --- a/js/server/modules/@arangodb/foxx/router/tree.js +++ b/js/server/modules/@arangodb/foxx/router/tree.js @@ -22,6 +22,7 @@ //////////////////////////////////////////////////////////////////////////////// const _ = require('lodash'); +const joinPath = require('path').posix.join; const querystring = require('querystring'); const httpError = require('http-errors'); const union = require('@arangodb/util').union; @@ -269,7 +270,7 @@ function dispatch(route, req, res) { req.path = '/' + item.path.join('/'); req.suffix = item.suffix.join('/'); if (req.suffix) { - req.path += '/' + req.suffix.join('/'); + req.path = joinPath(req.path, req.suffix.join('/')); } res._responses = item._responses; req.reverse = function (routeName, params, suffix) { diff --git a/js/server/modules/@arangodb/foxx/test-utils.js b/js/server/modules/@arangodb/foxx/test-utils.js new file mode 100644 index 0000000000..c7c2ea80c2 --- /dev/null +++ b/js/server/modules/@arangodb/foxx/test-utils.js @@ -0,0 +1,52 @@ +'use strict'; +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2016 ArangoDB GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Alan Plum +//////////////////////////////////////////////////////////////////////////////// + +exports.createNativeRequest = function (opts) { + const req = { + requestType: opts.method || 'get', + protocol: opts.protocol || 'http', + database: opts.db || '_system', + url: `${opts.mount || ''}${opts.path || ''}${opts.search || ''}`, + suffix: opts.path ? opts.path.split('/').filter(Boolean) : [], + headers: opts.headers || {}, + cookies: opts.cookies || {}, + server: opts.server || { + address: '127.0.0.1', + port: opts.port || 8529 + }, + client: opts.client || { + address: '127.0.0.1', + port: 33333 + } + }; + if (opts.body) { + req[Symbol.for('@arangodb/request.rawBodyBuffer')] = ( + opts.body instanceof Buffer ? opts.body : new Buffer( + typeof opts.body !== 'string' + ? JSON.stringify(opts.body, null, 2) + : opts.body + ) + ); + } + return req; +}; diff --git a/js/server/tests/shell/shell-foxx-request-spec.js b/js/server/tests/shell/shell-foxx-request-spec.js index 5afb4bd754..b5d631a51c 100644 --- a/js/server/tests/shell/shell-foxx-request-spec.js +++ b/js/server/tests/shell/shell-foxx-request-spec.js @@ -1,26 +1,9 @@ /*global describe, it */ 'use strict'; const expect = require('chai').expect; +const crypto = require('@arangodb/crypto'); const SyntheticRequest = require('@arangodb/foxx/router/request'); - -function createNativeRequest(opts) { - return { - requestType: opts.method || 'get', - protocol: opts.protocol || 'http', - database: opts.db || '_system', - url: `${opts.mount || ''}${opts.path || ''}${opts.query || ''}`, - suffix: opts.path ? opts.path.split('/').filter(Boolean) : [], - headers: opts.headers || {}, - server: opts.server || { - address: '127.0.0.1', - port: opts.port || 8529 - }, - client: opts.client || { - address: '127.0.0.1', - port: 33333 - } - }; -} +const createNativeRequest = require('@arangodb/foxx/test-utils').createNativeRequest; describe('SyntheticRequest', function () { describe('protocol/secure', function () { @@ -73,6 +56,7 @@ describe('SyntheticRequest', function () { expect(req.secure).to.equal(false); }); }); + describe('hostname/port', function () { it('defaults to the host header of the native request', function () { const rawReq = createNativeRequest({ @@ -143,6 +127,7 @@ describe('SyntheticRequest', function () { expect(req.port).to.equal(5678); }); }); + describe('remoteAddress(es)', function () { it('defaults to the client info of the native request', function () { const rawReq = createNativeRequest({ @@ -206,6 +191,7 @@ describe('SyntheticRequest', function () { expect(req.remoteAddresses).to.eql(ips); }); }); + describe('remotePort', function () { it('defaults to the client info of the native request', function () { const rawReq = createNativeRequest({ @@ -251,6 +237,7 @@ describe('SyntheticRequest', function () { expect(req.remotePort).to.equal(9999); }); }); + describe('queryParams', function () { it('is correctly derived from the native request when empty', function () { const rawReq = createNativeRequest({}); @@ -259,7 +246,7 @@ describe('SyntheticRequest', function () { }); it('is correctly derived from the native request', function () { const rawReq = createNativeRequest({ - query: '?a=1&b=2&c=3' + search: '?a=1&b=2&c=3' }); const req = new SyntheticRequest(rawReq, {}); expect(req.queryParams).to.eql({ @@ -269,6 +256,7 @@ describe('SyntheticRequest', function () { }); }); }); + describe('baseUrl', function () { it('is correctly derived from the native request', function () { const rawReq = createNativeRequest({ @@ -280,6 +268,7 @@ describe('SyntheticRequest', function () { expect(req.baseUrl).to.equal('/_db/bananas'); }); }); + describe('headers', function () { it('exposes the headers of the native request', function () { const headers = {}; @@ -290,6 +279,7 @@ describe('SyntheticRequest', function () { expect(req.headers).to.equal(headers); }); }); + describe('method', function () { it('exposes the method of the native request', function () { const rawReq = createNativeRequest({ @@ -299,54 +289,59 @@ describe('SyntheticRequest', function () { expect(req.method).to.equal('potato'); }); }); + describe('originalUrl', function () { it('exposes the root relative URL of the native request', function () { const rawReq = createNativeRequest({ db: 'bananas', mount: '/hi', path: '/friend/of/mine', - query: '?hello=yes&goodbye=indeed' + search: '?hello=yes&goodbye=indeed' }); const req = new SyntheticRequest(rawReq, {}); expect(req.originalUrl).to.equal('/_db/bananas/hi/friend/of/mine?hello=yes&goodbye=indeed'); }); }); + describe('url', function () { it('exposes the database relative URL of the native request', function () { const rawReq = createNativeRequest({ db: 'bananas', mount: '/hi', path: '/friend/of/mine', - query: '?hello=yes&goodbye=indeed' + search: '?hello=yes&goodbye=indeed' }); const req = new SyntheticRequest(rawReq, {}); expect(req.url).to.equal('/hi/friend/of/mine?hello=yes&goodbye=indeed'); }); }); + describe('path', function () { it('exposes the database relative pathname of the native request', function () { const rawReq = createNativeRequest({ db: 'bananas', mount: '/hi', path: '/friend/of/mine', - query: '?hello=yes&goodbye=indeed' + search: '?hello=yes&goodbye=indeed' }); const req = new SyntheticRequest(rawReq, {}); expect(req.path).to.equal('/hi/friend/of/mine'); }); }); + describe('suffix', function () { it('exposes the linearized suffix of the native request', function () { const rawReq = createNativeRequest({ db: 'bananas', mount: '/hi', path: '/friend/of/mine', - query: '?hello=yes&goodbye=indeed' + search: '?hello=yes&goodbye=indeed' }); const req = new SyntheticRequest(rawReq, {}); expect(req.suffix).to.equal('friend/of/mine'); }); }); + describe('xhr', function () { it('is false if the XHR header was not set', function () { const rawReq = createNativeRequest({}); @@ -368,5 +363,434 @@ describe('SyntheticRequest', function () { expect(req.xhr).to.equal(false); }); }); - it('TODO'); + + describe('cookie', function () { + it('returns nothing if the cookie is not set', function () { + const rawReq = createNativeRequest({}); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana')).to.equal(undefined); + }); + it('returns the value of the cookie if it is set', function () { + const rawReq = createNativeRequest({ + cookies: {banana: 'hello'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana')).to.equal('hello'); + }); + it('returns nothing if a secret is provided but no signature exists', function () { + const rawReq = createNativeRequest({ + cookies: {banana: 'hello'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', {secret: 'potato'})).to.equal(undefined); + }); + it('returns nothing if a secret is provided but the signature is invalid', function () { + const rawReq = createNativeRequest({ + cookies: { + banana: 'hello', + 'banana.sig': 'invalid' + } + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', {secret: 'potato'})).to.equal(undefined); + }); + it('returns the value if a secret is provided and the signature matches', function () { + const rawReq = createNativeRequest({ + cookies: { + banana: 'hello', + 'banana.sig': crypto.hmac('potato', 'hello') + } + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', {secret: 'potato'})).to.equal('hello'); + }); + it('accepts a secret instead of an options object', function () { + const rawReq = createNativeRequest({ + cookies: { + banana: 'hello', + 'banana.sig': crypto.hmac('potato', 'hello') + } + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', 'potato')).to.equal('hello'); + }); + it('correctly handles other algorithms', function () { + const rawReq = createNativeRequest({ + cookies: { + banana: 'hello', + 'banana.sig': crypto.hmac('potato', 'hello', 'sha512') + } + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', { + secret: 'potato', + algorithm: 'sha512' + })).to.equal('hello'); + }); + it('correctly handles algorithm mismatches', function () { + const rawReq = createNativeRequest({ + cookies: { + banana: 'hello', + 'banana.sig': crypto.hmac('potato', 'hello', 'sha256') + } + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.cookie('banana', { + secret: 'potato', + algorithm: 'sha512' + })).to.equal(undefined); + }); + }); + + describe('makeAbsolute', function () { + it('correctly generates absolute URLs', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:9999'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('/another/place', 'x=y&z=w')).to.equal( + 'https://www.example.com:9999/_db/bananas/foxx/another/place?x=y&z=w' + ); + }); + it('also accepts a query params object', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:9999'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('/another/place', {x: 'y', z: 'w'})).to.equal( + 'https://www.example.com:9999/_db/bananas/foxx/another/place?x=y&z=w' + ); + }); + it('omits the port if port is 80 and protocol is http', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:80'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'http' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('/another/place', 'x=y&z=w')).to.equal( + 'http://www.example.com/_db/bananas/foxx/another/place?x=y&z=w' + ); + }); + it('omits the port if port is 443 and protocol is https', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:443'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('/another/place', 'x=y&z=w')).to.equal( + 'https://www.example.com/_db/bananas/foxx/another/place?x=y&z=w' + ); + }); + it('works without query params', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:9999'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('/another/place')).to.equal( + 'https://www.example.com:9999/_db/bananas/foxx/another/place' + ); + }); + it('correctly handles non-root paths', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:9999'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('another/place')).to.equal( + 'https://www.example.com:9999/_db/bananas/foxx/another/place' + ); + }); + it('correctly handles relative paths', function () { + const rawReq = createNativeRequest({ + db: 'bananas', + mount: '/foxx', + headers: {host: 'www.example.com:9999'}, + path: '/some/place/special', + search: '?a=1&b=2', + protocol: 'https' + }); + const req = new SyntheticRequest(rawReq, {mount: '/foxx'}); + expect(req.makeAbsolute('../another/place')).to.equal( + 'https://www.example.com:9999/_db/bananas/another/place' + ); + }); + }); + + describe('accepts', function () { + it('returns the acceptable type', function () { + const rawReq = createNativeRequest({ + headers: {accept: 'application/json, text/javascript; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.accepts(['text/javascript'])).to.equal('text/javascript'); + }); + it('returns the preferred acceptable type', function () { + const rawReq = createNativeRequest({ + headers: {accept: 'application/json, text/javascript; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.accepts(['text/javascript', 'json'])).to.equal('json'); + }); + it('returns false if no type is acceptable', function () { + const rawReq = createNativeRequest({ + headers: {accept: 'application/json, text/javascript; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.accepts(['kitty/soft', 'kitty/warm'])).to.equal(false); + }); + it('accepts multiple arguments instead of an array', function () { + const rawReq = createNativeRequest({ + headers: {accept: 'application/json, text/javascript; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.accepts('potato', 'json')).to.equal('json'); + }); + }); + + describe('acceptsCharsets', function () { + it('returns the acceptable charset', function () { + const rawReq = createNativeRequest({ + headers: {'accept-charset': 'utf-8, iso-8859-1; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsCharsets(['iso-8859-1'])).to.equal('iso-8859-1'); + }); + it('returns the preferred acceptable charset', function () { + const rawReq = createNativeRequest({ + headers: {'accept-charset': 'utf-8, iso-8859-1; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsCharsets(['iso-8859-1', 'utf-8'])).to.equal('utf-8'); + }); + it('returns false if no charset is acceptable', function () { + const rawReq = createNativeRequest({ + headers: {'accept-charset': 'utf-8, iso-8859-1; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsCharsets(['kitty-soft', 'kitty-warm'])).to.equal(false); + }); + it('accepts multiple arguments instead of an array', function () { + const rawReq = createNativeRequest({ + headers: {'accept-charset': 'utf-8, iso-8859-1; q=0.01'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsCharsets('potato', 'utf-8')).to.equal('utf-8'); + }); + }); + + describe('acceptsEncodings', function () { + it('returns the acceptable encoding', function () { + const rawReq = createNativeRequest({ + headers: {'accept-encoding': 'gzip, deflate, sdch'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsEncodings(['deflate'])).to.equal('deflate'); + }); + it('returns the preferred acceptable encoding', function () { + const rawReq = createNativeRequest({ + headers: {'accept-encoding': 'gzip, deflate, sdch'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsEncodings(['deflate', 'gzip'])).to.equal('gzip'); + }); + it('returns false if no encoding is acceptable', function () { + const rawReq = createNativeRequest({ + headers: {'accept-encoding': 'gzip, deflate, sdch'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsEncodings(['softkitty', 'warmkitty'])).to.equal(false); + }); + it('accepts multiple arguments instead of an array', function () { + const rawReq = createNativeRequest({ + headers: {'accept-encoding': 'gzip, deflate, sdch'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsEncodings('potato', 'gzip')).to.equal('gzip'); + }); + }); + + describe('acceptsLanguages', function () { + it('returns the acceptable language', function () { + const rawReq = createNativeRequest({ + headers: {'accept-language': 'en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,de-DE;q=0.2'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsLanguages(['de-DE'])).to.equal('de-DE'); + }); + it('returns the preferred acceptable language', function () { + const rawReq = createNativeRequest({ + headers: {'accept-language': 'en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,de-DE;q=0.2'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsLanguages(['de-DE', 'en-GB'])).to.equal('en-GB'); + }); + it('returns false if no language is acceptable', function () { + const rawReq = createNativeRequest({ + headers: {'accept-language': 'en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,de-DE;q=0.2'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsLanguages(['kitty-SOFT', 'kitty-WARM'])).to.equal(false); + }); + it('accepts multiple arguments instead of an array', function () { + const rawReq = createNativeRequest({ + headers: {'accept-language': 'en-GB,en;q=0.8,en-US;q=0.6,de;q=0.4,de-DE;q=0.2'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.acceptsLanguages('potato', 'en-GB')).to.equal('en-GB'); + }); + }); + + describe('range', function () { + it('returns -2 for malformed header', function () { + const rawReq = createNativeRequest({ + headers: {range: 'banana'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.range(Infinity)).to.equal(-2); + }); + it('returns -1 for invalid header range', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=500-20'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.range(Infinity)).to.equal(-1); + }); + it('parses single ranges', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=0-499'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(Infinity); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 0, end: 499}); + }); + it('parses multiple ranges', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=0-499,500-749'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(Infinity); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 2); + expect(range[0]).to.eql({start: 0, end: 499}); + expect(range[1]).to.eql({start: 500, end: 749}); + }); + it('caps end at size', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=0-999'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(100); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 0, end: 99}); + }); + it('parses "-X"', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=-400'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(1000); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 600, end: 999}); + }); + it('parses "X-"', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=400-'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(1000); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 400, end: 999}); + }); + it('parses "0-"', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=0-'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(1000); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 0, end: 999}); + }); + it('parses "-1"', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=-1'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(1000); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 999, end: 999}); + }); + it('parses "0-0"', function () { + const rawReq = createNativeRequest({ + headers: {range: 'bananas=0-0'} + }); + const req = new SyntheticRequest(rawReq, {}); + const range = req.range(Infinity); + expect(range).to.have.a.property('type', 'bananas'); + expect(range).to.have.a.property('length', 1); + expect(range[0]).to.eql({start: 0, end: 0}); + }); + }); + + describe('is', function () { + it('returns false if no content-type is set', function () { + const rawReq = createNativeRequest({}); + const req = new SyntheticRequest(rawReq, {}); + expect(req.is(['application/xhtml+xml', 'text/html'])).to.equal(false); + }); + it('returns the first matching mime type', function () { + const rawReq = createNativeRequest({ + headers: {'content-type': 'text/html; charset=utf-8'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.is(['application/xhtml+xml', 'text/html'])).to.equal('text/html'); + }); + it('returns false if no mime type matches', function () { + const rawReq = createNativeRequest({ + headers: {'content-type': 'text/html; charset=utf-8'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.is(['application/xhtml+xml'])).to.equal(false); + }); + it('accepts multiple arguments instead of an array', function () { + const rawReq = createNativeRequest({ + headers: {'content-type': 'text/html; charset=utf-8'} + }); + const req = new SyntheticRequest(rawReq, {}); + expect(req.is('application/xhtml+xml', 'text/html')).to.equal('text/html'); + }); + }); });