feat: remove host resolution (#143)

* fix: correctly try additional ports on EADDRINUSE

* feat: remove passive url host resoltion

See: https://github.com/trs/ftp-srv/issues/139

* feat: require pasv url to be specified

* fix(pasv): check for set pasv url

* chore: update scripts

* fix(cli): remove undefined values

* chore: add cli to eslint verify

* chore: run eslint

* chore: update scripts

* feat: add maximum number of retries to port selection

* chore: simplify eslint config

* chore: simplify dev dependencies

* chore: generate contributors

* chore: update readme contributors
This commit is contained in:
Tyler Stewart
2019-02-21 18:39:07 +00:00
committed by GitHub
parent 03ff982959
commit 36f331d15d
26 changed files with 2003 additions and 1497 deletions

View File

@@ -23,10 +23,10 @@ base-build: &base-build
- node_modules - node_modules
- run: - run:
name: Lint name: Lint
command: npm run verify:js command: npm run verify
- run: - run:
name: Test name: Test
command: npm run test:once command: npm run test
jobs: jobs:
test_node_10: test_node_10:

View File

@@ -1,9 +1,2 @@
# START_CONFIT_GENERATED_CONTENT
# Common folders to ignore
node_modules/* node_modules/*
bower_components/* bower_components/*
# Config folder (optional - you might want to lint this...)
config/*
# END_CONFIT_GENERATED_CONTENT

5
.gitignore vendored
View File

@@ -1,7 +1,6 @@
test_tmp/
node_modules/ node_modules/
dist/ dist/
reports/
npm-debug.log npm-debug.log
.nyc_output/
test_tmp/

View File

@@ -16,5 +16,10 @@
- Any new fixes are features should include new or updated [tests](/test). - Any new fixes are features should include new or updated [tests](/test).
- Commits follow the [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit), please review and commit accordingly - Commits follow the [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit), please review and commit accordingly
- Submit your pull requests to the `master` branch, these will normally be merged into a seperate branch for any finally changes before being merged into `master`. - Submit your pull requests to the `master` branch, these will normally be merged into a separate branch for any finally changes before being merged into `master`.
- Submit any bugs or requests to the issues page in Github. - Submit any bugs or requests to the issues page in Github.
## Setup
- Clone the repository `git clone`
- Install dependencies `npm install`

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018 Tyler Stewart Copyright (c) 2019 Tyler Stewart
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -242,7 +242,7 @@ connection.on('RNTO', (error, fileName) => { ... });
Occurs when a file is renamed. Occurs when a file is renamed.
`error` if successful, will be `null` `error` if successful, will be `null`
`fileName` name of the file that was renamed `fileName` name of the file that was renamed
## Supported Commands ## Supported Commands
@@ -321,32 +321,34 @@ __Used in:__ `SITE CHMOD`
Returns a unique file name to write to Returns a unique file name to write to
__Used in:__ `STOU` __Used in:__ `STOU`
<!--[RM_CONTRIBUTING]-->
## Contributing ## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). See [CONTRIBUTING.md](CONTRIBUTING.md).
<!--[]-->
## Contributors ## Contributors
- [OzairP](https://github.com/OzairP) - [OzairP](https://github.com/OzairP)
- [qchar](https://github.com/qchar)
- [jorinvo](https://github.com/jorinvo)
- [voxsoftware](https://github.com/voxsoftware)
- [pkeuter](https://github.com/pkeuter)
- [TimLuq](https://github.com/TimLuq) - [TimLuq](https://github.com/TimLuq)
- [edin-mg](https://github.com/edin-m) - [crabl](https://github.com/crabl)
- [hirviid](https://github.com/hirviid)
- [DiegoRBaquero](https://github.com/DiegoRBaquero) - [DiegoRBaquero](https://github.com/DiegoRBaquero)
- [edin-m](https://github.com/edin-m)
- [voxsoftware](https://github.com/voxsoftware)
- [jorinvo](https://github.com/jorinvo)
- [Johnnyrook777](https://github.com/Johnnyrook777) - [Johnnyrook777](https://github.com/Johnnyrook777)
- [qchar](https://github.com/qchar)
- [mikejestes](https://github.com/mikejestes)
- [pkeuter](https://github.com/pkeuter)
- [qiansc](https://github.com/qiansc)
- [broofa](https://github.com/broofa)
- [lafin](https://github.com/lafin)
- [alancnet](https://github.com/alancnet)
- [zgwit](https://github.com/zgwit)
<!--[RM_LICENSE]-->
## License ## License
This software is licensed under the MIT Licence. See [LICENSE](LICENSE). This software is licensed under the MIT Licence. See [LICENSE](LICENSE).
<!--[]-->
## References ## References
- [https://cr.yp.to/ftp.html](https://cr.yp.to/ftp.html) - [https://cr.yp.to/ftp.html](https://cr.yp.to/ftp.html)

View File

@@ -113,11 +113,15 @@ function setupState(_args) {
} }
function startFtpServer(_state) { function startFtpServer(_state) {
// Remove null/undefined options so they get set to defaults, below
for (const key in _state) {
if (_state[key] === undefined) delete _state[key];
}
function checkLogin(data, resolve, reject) { function checkLogin(data, resolve, reject) {
const user = _state.credentials[data.username] const user = _state.credentials[data.username];
if (_state.anonymous || (user && user.password === data.password)) { if (_state.anonymous || user && user.password === data.password) {
return resolve({root: (user && user.root) || _state.root}); return resolve({root: user && user.root || _state.root});
} }
return reject(new errors.GeneralError('Invalid username or password', 401)); return reject(new errors.GeneralError('Invalid username or password', 401));

View File

@@ -1,38 +0,0 @@
'use strict';
module.exports = {
types: [
{value: 'feat', name: 'feat: A new feature'},
{value: 'fix', name: 'fix: A bug fix'},
{value: 'docs', name: 'docs: Documentation only changes'},
{value: 'style', name: 'style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)'},
{value: 'refactor', name: 'refactor: A code change that neither fixes a bug nor adds a feature'},
{value: 'perf', name: 'perf: A code change that improves performance'},
{value: 'test', name: 'test: Adding missing tests'},
{value: 'chore', name: 'chore: Changes to the build process or auxiliary tools\n and libraries such as documentation generation'},
{value: 'revert', name: 'revert: Revert to a commit'},
{value: 'WIP', name: 'WIP: Work in progress'}
],
scopes: [],
// it needs to match the value for field type. Eg.: 'fix'
/*
scopeOverrides: {
fix: [
{name: 'merge'},
{name: 'style'},
{name: 'e2eTest'},
{name: 'unitTest'}
]
},
*/
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
// Appends the branch name to the footer of the commit. Useful for tracking commits after branches have been merged
appendBranchNameToCommitMessage: false
};

View File

@@ -1,5 +0,0 @@
test/**/*.spec.js
--reporter mocha-multi-reporters
--reporter-options configFile=config/testUnit/reporters.json
--ui bdd
--bail

View File

@@ -1,6 +0,0 @@
{
"reporterEnabled": "spec",
"mochaJunitReporterReporterOptions": {
"mochaFile": "reports/junit.xml"
}
}

View File

@@ -1,162 +0,0 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"mocha": true,
"es6": true
},
"plugins": [
"mocha",
"node"
],
"rules": {
"mocha/no-exclusive-tests": 2,
"no-warning-comments": [
1,
{
"terms": ["todo", "fixme", "xxx"],
"location": "start"
},
],
"object-curly-spacing": [
2,
"never"
],
"array-bracket-spacing": [
2,
"never"
],
"brace-style": [
2,
"1tbs"
],
"consistent-return": 0,
"indent": [
"error",
2,
{
"SwitchCase": 1,
"MemberExpression": "off"
}
],
"no-multiple-empty-lines": [
2,
{
"max": 2
}
],
"no-use-before-define": [
2,
"nofunc"
],
"one-var": [
2,
"never"
],
"quote-props": [
2,
"as-needed"
],
"quotes": [
2,
"single"
],
"keyword-spacing": 2,
"space-before-function-paren": [
2,
{
"anonymous": "always",
"named": "never"
}
],
"space-in-parens": [
2,
"never"
],
"strict": [
2,
"global"
],
"curly": [
2,
"multi-line"
],
"eol-last": 2,
"key-spacing": [
2,
{
"beforeColon": false,
"afterColon": true
}
],
"no-eval": 2,
"no-with": 2,
"space-infix-ops": 2,
"dot-notation": [
2,
{
"allowKeywords": true
}
],
"eqeqeq": 2,
"no-alert": 2,
"no-caller": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-implied-eval": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-unused-expressions": 2,
"yoda": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-undef-init": 2,
"no-console": 1,
"camelcase": [
0,
{
"properties": "never"
}
],
"comma-spacing": 2,
"comma-dangle": 1,
"new-cap": 2,
"new-parens": 2,
"arrow-parens": [2, "always"],
"no-array-constructor": 2,
"array-callback-return": 1,
"no-extra-parens": 2,
"no-new-object": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-underscore-dangle": 0,
"no-fallthrough": 0,
"semi": 2,
"semi-spacing": [
2,
{
"before": false,
"after": true
}
]
},
"parserOptions": {
"emcaVersion": 6,
"sourceType": "module",
"impliedStrict": true
}
}

View File

@@ -0,0 +1,27 @@
const {get} = require('https');
get('https://api.github.com/repos/trs/ftp-srv/contributors', {
headers: {
'User-Agent': 'Chrome'
}
}, (res) => {
let response = '';
res.on('data', (data) => {
response += data;
});
res.on('end', () => {
const contributors = JSON.parse(response)
.filter((contributor) => contributor.type === 'User');
for (const contributor of contributors) {
const url = contributor.html_url;
const username = contributor.login;
const markdown = `- [${username}](${url})\n`;
process.stdout.write(markdown);
}
});
}).on('error', (err) => {
process.stderr.write(err);
});

2896
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "ftp-srv", "name": "ftp-srv",
"version": "0.0.0-development", "version": "0.0.0",
"description": "Modern, extensible FTP Server", "description": "Modern, extensible FTP Server",
"keywords": [ "keywords": [
"ftp", "ftp",
@@ -26,26 +26,40 @@
}, },
"scripts": { "scripts": {
"pre-release": "npm run verify", "pre-release": "npm run verify",
"commitmsg": "cz-customizable-ghooks",
"dev": "cross-env NODE_ENV=development npm run verify:watch",
"prepush": "npm run verify && npm run test:once --silent",
"semantic-release": "semantic-release", "semantic-release": "semantic-release",
"start": "npm run dev", "test": "mocha test/**/*.spec.js --ui bdd --bail",
"test": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts -w", "verify": "eslint src/**/*.js test/**/*.js bin/**/*.js"
"test:once": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts",
"verify": "npm run verify:js --silent",
"verify:js": "eslint -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js success",
"verify:js:fix": "eslint --fix -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js:fix success"
}, },
"release": { "release": {
"verifyConditions": "condition-circle" "verifyConditions": "condition-circle"
}, },
"config": { "husky": {
"commitizen": { "hooks": {
"path": "node_modules/cz-customizable" "pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"node": true,
"mocha": true,
"es6": true
}, },
"cz-customizable": { "parserOptions": {
"config": "config/release/commitMessageConfig.js" "ecmaVersion": 6,
"sourceType": "module"
} }
}, },
"dependencies": { "dependencies": {
@@ -58,29 +72,20 @@
"yargs": "^11.0.0" "yargs": "^11.0.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^7.5.2",
"@commitlint/config-conventional": "^7.5.0",
"@icetee/ftp": "^1.0.2", "@icetee/ftp": "^1.0.2",
"chai": "^4.0.2", "chai": "^4.2.0",
"condition-circle": "^1.6.0", "condition-circle": "^2.0.2",
"cross-env": "3.1.4", "eslint": "^5.14.1",
"cz-customizable": "5.2.0", "husky": "^1.3.1",
"cz-customizable-ghooks": "1.5.0", "lint-staged": "^8.1.4",
"dotenv": "^4.0.0",
"eslint": "4.5.0",
"eslint-config-google": "0.8.0",
"eslint-friendly-formatter": "3.0.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-node": "5.1.1",
"husky": "0.13.3",
"istanbul": "0.4.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mocha-junit-reporter": "1.13.0", "rimraf": "^2.6.1",
"mocha-multi-reporters": "1.1.5",
"rimraf": "2.6.1",
"semantic-release": "^15.10.6", "semantic-release": "^15.10.6",
"sinon": "^2.3.5" "sinon": "^2.3.5"
}, },
"engines": { "engines": {
"node": ">=6.x", "node": ">=6.x"
"npm": ">=5.x"
} }
} }

View File

@@ -3,6 +3,10 @@ const PassiveConnector = require('../../connector/passive');
module.exports = { module.exports = {
directive: 'PASV', directive: 'PASV',
handler: function () { handler: function () {
if (!this.server.options.pasv_url) {
return this.reply(502);
}
this.connector = new PassiveConnector(this); this.connector = new PassiveConnector(this);
return this.connector.setupServer() return this.connector.setupServer()
.then((server) => { .then((server) => {

View File

@@ -1,33 +1,56 @@
const net = require('net'); const net = require('net');
const Promise = require('bluebird');
const errors = require('../errors'); const errors = require('../errors');
function* portNumberGenerator(min, max) { const MAX_PORT = 65535;
const MAX_PORT_CHECK_ATTEMPT = 5;
function* portNumberGenerator(min, max = MAX_PORT) {
let current = min; let current = min;
while (true) { while (true) {
if (current > 65535 || current > max) { if (current > MAX_PORT || current > max) {
current = min; current = min;
} }
yield current++; yield current++;
} }
} }
function getNextPortFactory(min, max = Infinity) { function getNextPortFactory(host, portMin, portMax, maxAttempts = MAX_PORT_CHECK_ATTEMPT) {
const nextPortNumber = portNumberGenerator(min, max); const nextPortNumber = portNumberGenerator(portMin, portMax);
const portCheckServer = net.createServer(); const portCheckServer = net.createServer();
portCheckServer.maxConnections = 0; portCheckServer.maxConnections = 0;
portCheckServer.on('error', () => {
portCheckServer.listen(nextPortNumber.next().value);
});
return () => new Promise((resolve) => { return () => new Promise((resolve, reject) => {
portCheckServer.once('listening', () => { let attemptCount = 0;
const {port} = portCheckServer.address(); const tryGetPort = () => {
portCheckServer.close(() => resolve(port)); attemptCount++;
}); if (attemptCount > maxAttempts) {
portCheckServer.listen(nextPortNumber.next().value); reject(new errors.ConnectorError('Unable to find valid port'));
}) return;
.catch(RangeError, (err) => Promise.reject(new errors.ConnectorError(err.message))); }
const {value: port} = nextPortNumber.next();
portCheckServer.removeAllListeners();
portCheckServer.once('error', (err) => {
if (['EADDRINUSE'].includes(err.code)) {
tryGetPort();
} else {
reject(err);
}
});
portCheckServer.once('listening', () => {
portCheckServer.close(() => resolve(port));
});
try {
portCheckServer.listen(port, host);
} catch (err) {
reject(err);
}
};
tryGetPort();
});
} }
module.exports = { module.exports = {

View File

@@ -1,25 +0,0 @@
const http = require('http');
const Promise = require('bluebird');
const errors = require('../errors');
const IP_WEBSITE = 'http://api.ipify.org/';
module.exports = function (hostname) {
return new Promise((resolve, reject) => {
if (!hostname || hostname === '0.0.0.0') {
let ip = '';
http.get(IP_WEBSITE, (response) => {
if (response.statusCode !== 200) {
return reject(new errors.GeneralError('Unable to resolve hostname', response.statusCode));
}
response.setEncoding('utf8');
response.on('data', (chunk) => {
ip += chunk;
});
response.on('end', () => {
resolve(ip);
});
});
} else resolve(hostname);
});
};

View File

@@ -7,7 +7,6 @@ const tls = require('tls');
const EventEmitter = require('events'); const EventEmitter = require('events');
const Connection = require('./connection'); const Connection = require('./connection');
const resolveHost = require('./helpers/resolve-host');
const {getNextPortFactory} = require('./helpers/find-port'); const {getNextPortFactory} = require('./helpers/find-port');
class FtpServer extends EventEmitter { class FtpServer extends EventEmitter {
@@ -36,6 +35,7 @@ class FtpServer extends EventEmitter {
this.log = this.options.log; this.log = this.options.log;
this.url = nodeUrl.parse(this.options.url); this.url = nodeUrl.parse(this.options.url);
this.getNextPasvPort = getNextPortFactory( this.getNextPasvPort = getNextPortFactory(
_.get(this, 'options.pasv_url'),
_.get(this, 'options.pasv_min'), _.get(this, 'options.pasv_min'),
_.get(this, 'options.pasv_max')); _.get(this, 'options.pasv_max'));
@@ -67,22 +67,21 @@ class FtpServer extends EventEmitter {
} }
listen() { listen() {
return resolveHost(this.options.pasv_url || this.url.hostname) if (!this.options.pasv_url) {
.then((pasvUrl) => { this.log.warn('Passive URL not set. Passive connections not available.');
this.options.pasv_url = pasvUrl; }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.server.once('error', reject); this.server.once('error', reject);
this.server.listen(this.url.port, this.url.hostname, (err) => { this.server.listen(this.url.port, this.url.hostname, (err) => {
this.server.removeListener('error', reject); this.server.removeListener('error', reject);
if (err) return reject(err); if (err) return reject(err);
this.log.info({ this.log.info({
protocol: this.url.protocol.replace(/\W/g, ''), protocol: this.url.protocol.replace(/\W/g, ''),
ip: this.url.hostname, ip: this.url.hostname,
port: this.url.port port: this.url.port
}, 'Listening'); }, 'Listening');
resolve('Listening'); resolve('Listening');
});
}); });
}); });
} }

View File

@@ -9,7 +9,7 @@ const ActiveConnector = require('../../src/connector/active');
const {getNextPortFactory} = require('../../src/helpers/find-port'); const {getNextPortFactory} = require('../../src/helpers/find-port');
describe('Connector - Active //', function () { describe('Connector - Active //', function () {
let getNextPort = getNextPortFactory(1024); let getNextPort = getNextPortFactory('::', 1024);
let PORT; let PORT;
let active; let active;
let mockConnection = {}; let mockConnection = {};

View File

@@ -20,7 +20,7 @@ describe('Connector - Passive //', function () {
}, },
server: { server: {
url: '', url: '',
getNextPasvPort: getNextPortFactory(1024) getNextPasvPort: getNextPortFactory('::', 1024)
} }
}; };
let sandbox; let sandbox;
@@ -48,7 +48,7 @@ describe('Connector - Passive //', function () {
describe('setup', function () { describe('setup', function () {
before(function () { before(function () {
sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory()); sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory('::'));
}); });
it('no pasv range provided', function (done) { it('no pasv range provided', function (done) {
@@ -56,7 +56,7 @@ describe('Connector - Passive //', function () {
passive.setupServer() passive.setupServer()
.catch((err) => { .catch((err) => {
try { try {
expect(err.name).to.equal('ConnectorError'); expect(err.name).to.contain('RangeError');
done(); done();
} catch (ex) { } catch (ex) {
done(ex); done(ex);
@@ -68,7 +68,7 @@ describe('Connector - Passive //', function () {
describe('setup', function () { describe('setup', function () {
let connection; let connection;
before(function () { before(function () {
sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory(-1, -1)); sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory('::', -1, -1));
connection = new PassiveConnector(mockConnection); connection = new PassiveConnector(mockConnection);
}); });
@@ -76,7 +76,7 @@ describe('Connector - Passive //', function () {
it('has invalid pasv range', function (done) { it('has invalid pasv range', function (done) {
connection.setupServer() connection.setupServer()
.catch((err) => { .catch((err) => {
expect(err.name).to.equal('ConnectorError'); expect(err.name).to.contain('RangeError');
done(); done();
}); });
}); });

View File

@@ -1,52 +1,30 @@
/* eslint no-unused-expressions: 0 */ /* eslint no-unused-expressions: 0 */
const {expect} = require('chai'); const {expect} = require('chai');
const net = require('net'); const net = require('net');
const sinon = require('sinon');
const {getNextPortFactory} = require('../../src/helpers/find-port'); const {getNextPortFactory} = require('../../src/helpers/find-port');
describe('helpers // find-port', function () { describe('helpers // find-port', function () {
let sandbox; describe('keeps trying new ports', () => {
let getNextPort; let getNextPort;
let serverAlreadyRunning;
beforeEach((done) => {
const host = '0.0.0.0';
getNextPort = getNextPortFactory(host, 8821);
beforeEach(() => { serverAlreadyRunning = net.createServer();
sandbox = sinon.sandbox.create().usingPromise(Promise); serverAlreadyRunning.listen(8821, host, () => done());
getNextPort = getNextPortFactory(1, 2);
});
afterEach(() => {
sandbox.restore();
});
it('finds a port', () => {
sandbox.stub(net.Server.prototype, 'listen').callsFake(function (port) {
this.address = () => ({port});
setImmediate(() => this.emit('listening'));
}); });
return getNextPort() afterEach((done) => {
.then((port) => { serverAlreadyRunning.close(() => done());
expect(port).to.equal(1);
});
});
it('restarts count', () => {
sandbox.stub(net.Server.prototype, 'listen').callsFake(function (port) {
this.address = () => ({port});
setImmediate(() => this.emit('listening'));
}); });
return getNextPort() it('test', () => {
.then((port) => { return getNextPort()
expect(port).to.equal(1); .then((port) => {
}) expect(port).to.equal(8822);
.then(() => getNextPort()) });
.then((port) => {
expect(port).to.equal(2);
})
.then(() => getNextPort())
.then((port) => {
expect(port).to.equal(1);
}); });
}); });
}); });

View File

@@ -1,51 +0,0 @@
const {expect} = require('chai');
const sinon = require('sinon');
const resolveHost = require('../../src/helpers/resolve-host');
describe('helpers //resolve-host', function () {
this.timeout(4000);
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create().usingPromise(Promise);
});
afterEach(() => sandbox.restore());
it('fetches ip address', () => {
const hostname = '0.0.0.0';
return resolveHost(hostname)
.then((resolvedHostname) => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
});
});
it('fetches ip address', () => {
const hostname = null;
return resolveHost(hostname)
.then((resolvedHostname) => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
});
});
it('does nothing', () => {
const hostname = '127.0.0.1';
return resolveHost(hostname)
.then((resolvedHostname) => {
expect(resolvedHostname).to.equal(hostname);
});
});
it('fails on getting hostname', () => {
sandbox.stub(require('http'), 'get').callsFake(function (url, cb) {
cb({
statusCode: 420
});
});
return resolveHost(null)
.then(() => expect(1).to.equal(2))
.catch((err) => {
expect(err.code).to.equal(420);
});
});
});

View File

@@ -48,6 +48,7 @@ describe('Integration', function () {
function startServer(options = {}) { function startServer(options = {}) {
server = new FtpServer(_.assign({ server = new FtpServer(_.assign({
log, log,
pasv_url: '127.0.0.1',
pasv_min: 8881, pasv_min: 8881,
greeting: ['hello', 'world'], greeting: ['hello', 'world'],
anonymous: true anonymous: true

View File

@@ -1,4 +1,3 @@
require('dotenv').load();
const bunyan = require('bunyan'); const bunyan = require('bunyan');
const fs = require('fs'); const fs = require('fs');
const FtpServer = require('../src'); const FtpServer = require('../src');