From 607915b4040fe15440e9327693d55610aa27ff4d Mon Sep 17 00:00:00 2001 From: Hinell Date: Fri, 15 Feb 2019 20:11:30 +0300 Subject: [PATCH] Add: `fs` option & tests. Closes #160 Release 0.17.0 * Add: new option to specify which file system to use to serve files by default. * Add: exported erros for basic file system interface check * Add: basic tests via global injection --- .travis.yml | 4 ++-- HISTORY.md | 7 ++++++ README.md | 20 +++++++++++++++- index.js | 25 ++++++++++++++++---- package.json | 64 +++++++++++++++++++++++++++------------------------- test.js | 15 ++++++++++++ test/send.js | 31 +++++++++++++++++++++++++ 7 files changed, 127 insertions(+), 39 deletions(-) create mode 100644 test.js diff --git a/.travis.yml b/.travis.yml index f23b83d..554d815 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ node_js: - "3.3" - "4.9" - "5.12" - - "6.15" + - "6.14" - "7.10" - - "8.13" + - "8.12" - "9.11" - "10.12" sudo: false diff --git a/HISTORY.md b/HISTORY.md index 375d85d..e68ef78 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,13 @@ unreleased ========== +0.17.0 / 2018-02-07 +========== + * Add `fs` option + * test: + - Add test.js for programmatic testing and debugging + - Add `test:d` script command + * deps: depd@~2.0.0 - Replace internal `eval` usage with `Function` constructor - Use instance methods on `process` to check for listeners diff --git a/README.md b/README.md index 179e8c3..c1acb6e 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,16 @@ [![Windows Build][appveyor-image]][appveyor-url] [![Test Coverage][coveralls-image]][coveralls-url] +> _This is a fork of an original [send](https://github.com/pillarjs/send) module. See changes made by this fork in [history file](./HISTORY.MD#0170--2018-02-07)._ + Send is a library for streaming files from the file system as a http response supporting partial responses (Ranges), conditional-GET negotiation (If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since), high test coverage, and granular events which may be leveraged to take appropriate actions in your application or framework. -Looking to serve up entire folders mapped to URLs? Try [serve-static](https://www.npmjs.org/package/serve-static). + +Looking to serve up entire folders mapped to URLs? Try [serve-static (forked version)](/serve-static). ## Installation @@ -119,6 +122,15 @@ Serve files relative to `path`. Byte offset at which the stream starts, defaults to 0. The start is inclusive, meaning `start: 2` will include the 3rd byte in the stream. +##### fs + +**Default**: [require('fs')](https://nodejs.org/api/fs.html) + +File system to serve files by default. +```js + send(req, path, { fs: mockedFileSystem }) +``` + #### Events The `SendStream` is an event emitter and will emit the following events: @@ -171,6 +183,12 @@ $ DEBUG=send node app $ npm install $ npm test ``` +### Debugging tests +In order to debug testing you can run programmatic mocha instance by command: +``` +$ npm test:d +``` +> _[package.json.scripts.test:d](./package.json)_ ## Examples diff --git a/index.js b/index.js index 0b7a39d..2928a1a 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,6 @@ var encodeUrl = require('encodeurl') var escapeHtml = require('escape-html') var etag = require('etag') var fresh = require('fresh') -var fs = require('fs') var mime = require('mime') var ms = require('ms') var onFinished = require('on-finished') @@ -84,6 +83,10 @@ function send (req, path, options) { return new SendStream(req, path, options) } +var errors = {} +errors.INVALID_OPTION_FS_STAT = new TypeError('Invalid option: Incompatible file system interface: fs.stat() method is expected') +errors.INVALID_OPTION_FS_CRTSTRM = new TypeError('Invalid option: Incompatible file system interface: fs.createReadStream() method is expected') +send.errors = errors /** * Initialize a `SendStream` with the given `path`. * @@ -102,6 +105,18 @@ function SendStream (req, path, options) { this.path = path this.req = req + // Dynamically import `fs` if not it is not provided by config + this.fs = opts.fs || require('fs') + + // Checking if provided "fs" is compatible + if (typeof this.fs.stat !== 'function') { + throw errors.INVALID_OPTION_FS_STAT + } + + if (typeof this.fs.createReadStream !== 'function') { + throw errors.INVALID_OPTION_FS_CRTSTRM + } + this._acceptRanges = opts.acceptRanges !== undefined ? Boolean(opts.acceptRanges) : true @@ -718,7 +733,7 @@ SendStream.prototype.sendFile = function sendFile (path) { var self = this debug('stat "%s"', path) - fs.stat(path, function onstat (err, stat) { + this.fs.stat(path, function onstat (err, stat) { if (err && err.code === 'ENOENT' && !extname(path) && path[path.length - 1] !== sep) { // not found, check extensions return next(err) @@ -739,7 +754,7 @@ SendStream.prototype.sendFile = function sendFile (path) { var p = path + '.' + self._extensions[i++] debug('stat "%s"', p) - fs.stat(p, function (err, stat) { + self.fs.stat(p, function (err, stat) { if (err) return next(err) if (stat.isDirectory()) return next() self.emit('file', p, stat) @@ -767,7 +782,7 @@ SendStream.prototype.sendIndex = function sendIndex (path) { var p = join(path, self._index[i]) debug('stat "%s"', p) - fs.stat(p, function (err, stat) { + self.fs.stat(p, function (err, stat) { if (err) return next(err) if (stat.isDirectory()) return next() self.emit('file', p, stat) @@ -793,7 +808,7 @@ SendStream.prototype.stream = function stream (path, options) { var res = this.res // pipe - var stream = fs.createReadStream(path, options) + var stream = this.fs.createReadStream(path, options) this.emit('stream', stream) stream.pipe(res) diff --git a/package.json b/package.json index 7dc1e1c..a410393 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,48 @@ { - "name": "send", - "description": "Better streaming static file server with Range and conditional-GET support", - "version": "0.16.2", - "author": "TJ Holowaychuk ", + "name" : "send", + "description" : "Better streaming static file server with Range and conditional-GET support", + "version" : "0.17.0", + "author" : "TJ Holowaychuk ", "contributors": [ "Douglas Christopher Wilson ", "James Wyatt Cready ", - "Jesús Leganés Combarro " + "Jesús Leganés Combarro ", + "Davrononv Alexander Alisherovich " ], - "license": "MIT", + "license" : "MIT", "repository": "pillarjs/send", - "keywords": [ + "keywords" : [ "static", "file", "server" ], "dependencies": { - "debug": "2.6.9", - "depd": "~2.0.0", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.1", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "debug" : "2.6.9", + "depd" : "~2.0.0", + "destroy" : "~1.0.4", + "encodeurl" : "~1.0.2", + "escape-html" : "~1.0.3", + "etag" : "~1.8.1", + "fresh" : "0.5.2", + "http-errors" : "~1.7.1", + "mime" : "1.6.0", + "ms" : "2.1.1", + "on-finished" : "~2.3.0", "range-parser": "~1.2.0", - "statuses": "~1.5.0" + "statuses" : "~1.5.0" }, "devDependencies": { - "after": "0.8.2", - "eslint": "5.8.0", + "after" : "0.8.2", + "eslint" : "5.8.0", "eslint-config-standard": "12.0.0", - "eslint-plugin-import": "2.14.0", - "eslint-plugin-markdown": "1.0.0-rc.1", - "eslint-plugin-node": "7.0.1", - "eslint-plugin-promise": "4.0.1", + "eslint-plugin-import" : "2.14.0", + "eslint-plugin-markdown": "1.0.0-beta.6", + "eslint-plugin-node" : "7.0.1", + "eslint-plugin-promise" : "4.0.1", "eslint-plugin-standard": "4.0.0", - "istanbul": "0.4.5", - "mocha": "5.2.0", - "supertest": "3.3.0" + "istanbul" : "0.4.5", + "mocha" : "5.2.0", + "supertest" : "3.3.0" }, "files": [ "HISTORY.md", @@ -53,9 +54,10 @@ "node": ">= 0.8.0" }, "scripts": { - "lint": "eslint --plugin markdown --ext js,md .", - "test": "mocha --check-leaks --reporter spec --bail", - "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --check-leaks --reporter spec", + "lint" : "eslint --plugin markdown --ext js,md .", + "test" : "mocha --check-leaks --reporter spec --bail", + "test:d" : "node --inspect-brk test.js", + "test-ci" : "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --check-leaks --reporter spec", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --check-leaks --reporter dot" } } diff --git a/test.js b/test.js new file mode 100644 index 0000000..96c60a4 --- /dev/null +++ b/test.js @@ -0,0 +1,15 @@ +var Mocha = require('mocha') +var path = require('path') + +// Instantiate a Mocha instance. +var mocha = new Mocha() + +var testDir = '/test/' +mocha.addFile( + path.join(__dirname, testDir, 'send.js') +) +// Run the tests. +mocha.run(function (failures) { + // exit with non-zero status if there were failures + process.exitCode = failures ? 1 : 0 +}) diff --git a/test/send.js b/test/send.js index 7586fb4..6e9a7db 100644 --- a/test/send.js +++ b/test/send.js @@ -1418,6 +1418,37 @@ describe('send(file, options)', function () { }) }) }) + + describe('fs', function () { + var FS_STAT = send.errors.INVALID_OPTION_FS_STAT + var CRTSTRM = send.errors.INVALID_OPTION_FS_CRTSTRM + it('must provide fs.stat()', function (done) { + try { + send(void 0, '', { fs: { createReadStream: function () {} } }) + } catch (err) { + if (err === FS_STAT) { + done() + } else { + done(new Error('Must throw INVALID_OPTION_FS_STAT')) + } + return + } + done() + }) + it('must implement fs.createReadStream()', function (done) { + try { + send(void 0, '', { fs: { stat: function () {} } }) + } catch (err) { + if (err === CRTSTRM) { + done() + } else { + done(new Error('Must throw INVALID_OPTION_FS_CRTSTRM')) + } + return + } + done() + }) + }) }) describe('send.mime', function () {