Compare commits

...

17 Commits

Author SHA1 Message Date
Tyler Stewart
5394908a6b Merge pull request #29 from trs/fix-tls-check
Fix encoding/transfer type
2017-06-20 17:20:23 -06:00
Tyler Stewart
3e7bd5bcf9 chore: update licence format
Still MIT, just updating to ensure GitHub recognizes it
2017-06-20 17:09:49 -06:00
Tyler Stewart
175b422c5f chore: update dependencies 2017-06-20 17:02:05 -06:00
Tyler Stewart
b2a9851204 fix: ensure utf8 support; allows accent characters 2017-06-20 16:56:26 -06:00
Tyler Stewart
977dd1579a fix(TYPE): correctly set types, only use for data connections 2017-06-20 16:55:37 -06:00
Tyler Stewart
176b2b7ca8 fix: actually check _tls 2017-06-20 14:47:44 -06:00
Tyler Stewart
63777c0d74 Merge pull request #26 from trs/test-updates
Test updates
2017-06-16 15:17:31 -06:00
Tyler Stewart
9be8ffa60d test: add and update tests 2017-06-09 15:00:01 -06:00
Tyler Stewart
b8cd6022e1 fix: assign tls options to empty object 2017-06-09 14:40:19 -06:00
Tyler Stewart
0618a3c675 fix(passive): throw error on invalid port range 2017-06-09 14:39:21 -06:00
Tyler Stewart
3c533a5fbc Merge pull request #25 from trs/export-fs
Export fs
2017-06-09 10:31:42 -06:00
Tyler Stewart
3d0a58ca15 docs(readme): update file system override details
master

export-fs

export-fs
2017-06-09 10:28:03 -06:00
Tyler Stewart
4b4c809af8 feat: export FileSystem and FtpSrv
Non breaking, as it still exports FtpSrv by default
2017-06-09 10:28:03 -06:00
Tyler Stewart
a234534de0 Merge pull request #23 from trs/write-stream-close
feat(fs): close stream before ending
2017-06-08 17:35:58 -06:00
Tyler Stewart
635fb35341 fix(stor): end stream if no close listener present 2017-06-08 17:31:02 -06:00
Tyler Stewart
51a6448ac2 fix(stor): ensure stream is destroyed once used up 2017-06-08 17:23:14 -06:00
Tyler Stewart
4d8a69615c feat(fs): close stream before ending
This allows processing of the received data before a response is sent to the client.

write-stream-close
2017-06-08 17:17:09 -06:00
51 changed files with 696 additions and 634 deletions

22
LICENSE
View File

@@ -1,9 +1,21 @@
ftp-srv Copyright (c) 2017 Tyler Stewart
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Copyright (c) 2017 Tyler Stewart
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -150,11 +150,26 @@ Occurs when an error arises in the client connection.
See the [command registry](src/commands/registration) for a list of all implemented FTP commands.
## File System
The default file system can be overwritten to use your own implementation.
The default [file system](src/fs.js) can be overwritten to use your own implementation.
This can allow for virtual file systems, and more.
Each connection can set it's own file system based on the user.
Custom file systems can implement the following variables depending on the developers needs.
The default file system is exported and can be extended as needed:
```js
const {FtpSrv, FileSystem} = require('ftp-srv');
class MyFileSystem extends FileSystem {
constructor() {
super(...arguments);
}
get(fileName) {
...
}
}
```
Custom file systems can implement the following variables depending on the developers needs:
### Methods
#### [`currentDirectory()`](src/fs.js#L29)

6
ftp-srv.js Normal file
View File

@@ -0,0 +1,6 @@
const FtpSrv = require('./src');
const FileSystem = require('./src/fs');
module.exports = FtpSrv;
module.exports.FtpSrv = FtpSrv;
module.exports.FileSystem = FileSystem;

12
package-lock.json generated
View File

@@ -3722,9 +3722,9 @@
"dev": true
},
"sinon": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.2.tgz",
"integrity": "sha1-xDqcVw8yuqwRWVBc/u0ZEIhV34k=",
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.5.tgz",
"integrity": "sha1-mi/A/41SbacW8wlTqixl1RiRf2w=",
"dev": true
},
"slash": {
@@ -4210,9 +4210,9 @@
"dev": true
},
"uuid": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
"integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
},
"validate-npm-package-license": {
"version": "3.0.1",

View File

@@ -8,10 +8,11 @@
"ftp-srv",
"ftp-svr",
"ftpd",
"server"
"server",
"ftpserver"
],
"license": "MIT",
"main": "src/index.js",
"main": "ftp-srv.js",
"files": [
"src"
],
@@ -52,7 +53,7 @@
"bunyan": "^1.8.10",
"lodash": "^4.17.4",
"moment": "^2.18.1",
"uuid": "^3.0.1",
"uuid": "^3.1.0",
"when": "^3.7.8"
},
"devDependencies": {
@@ -75,7 +76,7 @@
"npm-run-all": "4.0.2",
"rimraf": "2.6.1",
"semantic-release": "^6.3.6",
"sinon": "^2.3.2"
"sinon": "^2.3.5"
},
"engines": {
"node": ">=6.x",

View File

@@ -9,7 +9,7 @@ module.exports = {
const feat = _.get(registry[cmd], 'flags.feat', null);
if (feat) return _.concat(feats, feat);
return feats;
}, [])
}, ['UTF8'])
.map(feat => ` ${feat}`);
return features.length
? this.reply(211, 'Extensions supported', ...features, 'End')

View File

@@ -17,7 +17,7 @@ module.exports = {
return when.promise((resolve, reject) => {
dataSocket.on('error', err => stream.emit('error', err));
stream.on('data', data => dataSocket.write(data, this.encoding));
stream.on('data', data => dataSocket.write(data, this.transferType));
stream.on('end', () => resolve(this.reply(226)));
stream.on('error', err => reject(err));
this.reply(150).then(() => dataSocket.resume());

View File

@@ -18,13 +18,18 @@ module.exports = {
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append}))
.then(stream => {
return when.promise((resolve, reject) => {
stream.on('error', err => dataSocket.emit('error', err));
stream.once('error', err => dataSocket.emit('error', err));
stream.once('finish', () => resolve(this.reply(226, fileName)));
// Emit `close` if stream has a close listener, otherwise emit `finish` with the end() method
// It is assumed that the `close` handler will call the end() method
dataSocket.once('end', () => stream.listenerCount('close') ? stream.emit('close') : stream.end());
dataSocket.once('error', err => reject(err));
dataSocket.on('data', data => stream.write(data, this.transferType));
dataSocket.on('end', () => stream.end(() => resolve(this.reply(226, fileName))));
dataSocket.on('error', err => reject(err));
dataSocket.on('data', data => stream.write(data, this.encoding));
this.reply(150).then(() => dataSocket.resume());
});
})
.finally(() => when.try(stream.destroy.bind(stream)));
})
.catch(when.TimeoutError, err => {
log.error(err);

View File

@@ -1,20 +1,20 @@
const _ = require('lodash');
const ENCODING_TYPES = {
A: 'utf8',
I: 'binary',
L: 'binary'
};
module.exports = {
directive: 'TYPE',
handler: function ({command} = {}) {
const encoding = _.upperCase(command.arg);
if (!ENCODING_TYPES.hasOwnProperty(encoding)) return this.reply(501);
this.encoding = ENCODING_TYPES[encoding];
return this.reply(200);
if (/^A[0-9]?$/i.test(command.arg)) {
this.transferType = 'ascii';
} else if (/^L[0-9]?$/i.test(command.arg) || /^I$/i.test(command.arg)) {
this.transferType = 'binary';
} else {
return this.reply(501);
}
return this.reply(200, `Switch to "${this.transferType}" transfer mode.`);
},
syntax: '{{cmd}} <mode>',
description: 'Set the transfer mode, binary (I) or utf8 (A)'
description: 'Set the transfer mode, binary (I) or ascii (A)',
flags: {
feat: 'TYPE A,I,L'
}
};

View File

@@ -15,7 +15,7 @@ class FtpConnection {
this.id = uuid.v4();
this.log = options.log.child({id: this.id, ip: this.ip});
this.commands = new Commands(this);
this.encoding = 'utf8';
this.transferType = 'binary';
this.bufferSize = false;
this.connector = new BaseConnector(this);
@@ -84,7 +84,7 @@ class FtpConnection {
if (!letter.socket) letter.socket = options.socket ? options.socket : this.commandSocket;
if (!letter.message) letter.message = DEFAULT_MESSAGE[options.code] || 'No information';
if (!letter.encoding) letter.encoding = this.encoding;
if (!letter.encoding) letter.encoding = 'utf8';
return when(letter.message) // allow passing in a promise as a message
.then(message => {
letter.message = message;
@@ -102,7 +102,7 @@ class FtpConnection {
const packet = !letter.raw ? _.compact([letter.code || options.code, letter.message]).join(seperator) : letter.message;
if (letter.socket && letter.socket.writable) {
this.log.trace({port: letter.socket.address().port, packet}, 'Reply');
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, packet}, 'Reply');
letter.socket.write(packet + '\r\n', letter.encoding, err => {
if (err) {
this.log.error(err);

View File

@@ -26,7 +26,7 @@ class Active extends Connector {
return closeExistingServer()
.then(() => {
this.dataSocket = new Socket();
this.dataSocket.setEncoding(this.encoding);
this.dataSocket.setEncoding(this.connection.transferType);
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
this.dataSocket.connect({ host, port, family }, () => {
this.dataSocket.pause();

View File

@@ -54,7 +54,7 @@ class Passive extends Connector {
this.dataSocket = socket;
}
this.dataSocket.connected = true;
this.dataSocket.setEncoding(this.connection.encoding);
this.dataSocket.setEncoding(this.connection.transferType);
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
this.dataSocket.on('close', () => {
this.log.debug('Passive connection closed');
@@ -89,7 +89,8 @@ class Passive extends Connector {
this.server.options.pasv_range.split('-').map(v => v ? parseInt(v) : v) :
[this.server.options.pasv_range];
return findPort(min, max);
} else return undefined;
}
throw new errors.ConnectorError('Invalid pasv_range');
}
}

View File

@@ -68,7 +68,8 @@ class FileSystem {
write(fileName, {append = false} = {}) {
const {fsPath} = this._resolvePath(fileName);
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+'});
stream.on('error', () => fs.unlink(fsPath));
stream.once('error', () => fs.unlink(fsPath));
stream.once('close', () => stream.end());
return stream;
}

View File

@@ -94,8 +94,8 @@ class FtpServer {
}
setupTLS(_tls) {
if (!tls) return false;
return _.assign(_tls, {
if (!_tls) return false;
return _.assign({}, _tls, {
cert: _tls.cert ? fs.readFileSync(_tls.cert) : undefined,
key: _tls.key ? fs.readFileSync(_tls.key) : undefined,
ca: _tls.ca ? Array.isArray(_tls.ca) ? _tls.ca.map(_ca => fs.readFileSync(_ca)) : [fs.readFileSync(_tls.ca)] : undefined

View File

@@ -25,29 +25,25 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful | no active connection', done => {
it('// successful | no active connection', () => {
mockClient.connector.waitForConnection.restore();
sandbox.stub(mockClient.connector, 'waitForConnection').rejects();
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
expect(mockClient.connector.end.callCount).to.equal(0);
expect(mockClient.reply.args[0][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('// successful | active connection', done => {
cmdFn()
it('// successful | active connection', () => {
return cmdFn()
.then(() => {
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
expect(mockClient.connector.end.callCount).to.equal(1);
expect(mockClient.reply.args[0][0]).to.equal(426);
expect(mockClient.reply.args[1][0]).to.equal(226);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
});

View File

@@ -22,31 +22,25 @@ describe(CMD, function () {
sandbox.restore();
});
it('TLS // supported', done => {
cmdFn({command: { arg: 'TLS', directive: CMD}})
it('TLS // supported', () => {
return cmdFn({command: { arg: 'TLS', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(234);
expect(mockClient.secure).to.equal(true);
done();
})
.catch(done);
});
});
it('SSL // not supported', done => {
cmdFn({command: { arg: 'SSL', directive: CMD}})
it('SSL // not supported', () => {
return cmdFn({command: { arg: 'SSL', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
it('bad // bad', done => {
cmdFn({command: { arg: 'bad', directive: CMD}})
it('bad // bad', () => {
return cmdFn({command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -25,13 +25,11 @@ describe(CMD, function () {
sandbox.restore();
});
it('.. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('.. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.chdir.args[0][0]).to.equal('..');
done();
})
.catch(done);
});
});
});

View File

@@ -23,63 +23,56 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs chdir command', done => {
it('fails on no fs chdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.chdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('test // successful', done => {
it('test // successful', () => {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').resolves('/test');
cmdFn({log, command: { arg: 'test', directive: CMD}})
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.chdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').rejects(new Error('Bad'));
cmdFn({log, command: { arg: 'bad', directive: CMD}})
return cmdFn({log, command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
expect(mockClient.fs.chdir.args[0][0]).to.equal('bad');
done();
})
.catch(done);
});
});
});

View File

@@ -23,51 +23,45 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs delete command', done => {
it('fails on no fs delete command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.delete.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.delete.restore();
sandbox.stub(mockClient.fs, 'delete').rejects(new Error('Bad'));
cmdFn({log, command: { arg: 'bad', directive: CMD}})
return cmdFn({log, command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
expect(mockClient.fs.delete.args[0][0]).to.equal('bad');
done();
})
.catch(done);
});
});
});

View File

@@ -19,39 +19,31 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn({command: { directive: CMD }})
it('// successful', () => {
return cmdFn({command: { directive: CMD }})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(211);
done();
})
.catch(done);
});
});
it('help // successful', done => {
cmdFn({command: { arg: 'help', directive: CMD}})
it('help // successful', () => {
return cmdFn({command: { arg: 'help', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
done();
})
.catch(done);
});
});
it('help // successful', done => {
cmdFn({command: { arg: 'allo', directive: CMD}})
it('allo // successful', () => {
return cmdFn({command: { arg: 'allo', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
cmdFn({command: { arg: 'bad', directive: CMD}})
it('bad // unsuccessful', () => {
return cmdFn({command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(502);
done();
})
.catch(done);
});
});
});

View File

@@ -87,33 +87,31 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs list command', done => {
it('fails on no fs list command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(3);
@@ -121,12 +119,10 @@ describe(CMD, function () {
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('testfile.txt // successful', done => {
it('testfile.txt // successful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testfile.txt',
@@ -147,7 +143,7 @@ describe(CMD, function () {
isDirectory: () => false
});
cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(2);
@@ -155,31 +151,25 @@ describe(CMD, function () {
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('. // unsuccessful', done => {
it('. // unsuccessful', () => {
mockClient.fs.list.restore();
sandbox.stub(mockClient.fs, 'list').rejects(new Error());
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(451);
done();
})
.catch(done);
});
});
it('. // unsuccessful (timeout)', done => {
it('. // unsuccessful (timeout)', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').returns(when.reject(new when.TimeoutError()));
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
});

View File

@@ -23,50 +23,44 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs get command', done => {
it('fails on no fs get command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
//expect(mockClient.reply.args[0][1]).to.equal('20111010172411.000');
done();
})
.catch(done);
});
});
it('. // unsuccessful', done => {
it('. // unsuccessful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects(new Error());
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
});

View File

@@ -23,63 +23,56 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs mkdir command', done => {
it('fails on no fs mkdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: {arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('test // successful', done => {
it('test // successful', () => {
mockClient.fs.mkdir.restore();
sandbox.stub(mockClient.fs, 'mkdir').resolves('test');
cmdFn({log, command: {arg: 'test', directive: CMD}})
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.mkdir.restore();
sandbox.stub(mockClient.fs, 'mkdir').rejects(new Error('Bad'));
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
expect(mockClient.fs.mkdir.args[0][0]).to.equal('bad');
done();
})
.catch(done);
});
});
});

View File

@@ -19,21 +19,17 @@ describe(CMD, function () {
sandbox.restore();
});
it('S // successful', done => {
cmdFn({command: {arg: 'S'}})
it('S // successful', () => {
return cmdFn({command: {arg: 'S'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('Q // unsuccessful', done => {
cmdFn({command: {arg: 'Q'}})
it('Q // unsuccessful', () => {
return cmdFn({command: {arg: 'Q'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -86,8 +86,8 @@ describe(CMD, function () {
sandbox.restore();
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(3);
@@ -95,12 +95,10 @@ describe(CMD, function () {
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('testfile.txt // successful', done => {
it('testfile.txt // successful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testfile.txt',
@@ -121,7 +119,7 @@ describe(CMD, function () {
isDirectory: () => false
});
cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(2);
@@ -129,8 +127,6 @@ describe(CMD, function () {
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
done();
})
.catch(done);
});
});
});

View File

@@ -24,61 +24,51 @@ describe(CMD, function () {
sandbox.restore();
});
it('pass // successful', done => {
cmdFn({log, command: {arg: 'pass', directive: CMD}})
it('pass // successful', () => {
return cmdFn({log, command: {arg: 'pass', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.args[0]).to.eql(['anonymous', 'pass']);
done();
})
.catch(done);
});
});
it('// successful (already authenticated)', done => {
it('// successful (already authenticated)', () => {
mockClient.server.options.anonymous = true;
mockClient.authenticated = true;
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
expect(mockClient.login.callCount).to.equal(0);
mockClient.server.options.anonymous = false;
mockClient.authenticated = false;
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects('bad');
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects({});
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
delete mockClient.username;
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
});

View File

@@ -20,38 +20,32 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful', done => {
cmdFn()
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.secure = true;
mockClient.server._tls = {};
cmdFn({command: {arg: '0'}})
return cmdFn({command: {arg: '0'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.bufferSize).to.equal(0);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.secure = true;
mockClient.server._tls = {};
cmdFn({command: {arg: '10'}})
return cmdFn({command: {arg: '10'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.bufferSize).to.equal(10);
done();
})
.catch(done);
});
});
});

View File

@@ -22,33 +22,27 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no argument', done => {
cmdFn()
it('// unsuccessful | no argument', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
it('// unsuccessful | invalid argument', done => {
cmdFn({ command: { arg: '1,2,3,4,5' } })
it('// unsuccessful | invalid argument', () => {
return cmdFn({ command: { arg: '1,2,3,4,5' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
it('// successful', done => {
cmdFn({ command: { arg: '192,168,0,100,137,214' } })
it('// successful', () => {
return cmdFn({ command: { arg: '192,168,0,100,137,214' } })
.then(() => {
const [ip, port] = ActiveConnector.prototype.setupConnection.args[0];
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(ip).to.equal('192.168.0.100');
expect(port).to.equal(35286);
done();
})
.catch(done);
});
});
});

View File

@@ -20,56 +20,46 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful', done => {
cmdFn()
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
it('// unsuccessful - no bufferSize', done => {
it('// unsuccessful - no bufferSize', () => {
mockClient.server._tls = {};
mockClient.secure = true;
cmdFn({command: {arg: 'P'}})
return cmdFn({command: {arg: 'P'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.bufferSize = 0;
mockClient.secure = true;
cmdFn({command: {arg: 'p'}})
return cmdFn({command: {arg: 'p'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('// unsuccessful - unsupported', done => {
it('// unsuccessful - unsupported', () => {
mockClient.secure = true;
cmdFn({command: {arg: 'C'}})
return cmdFn({command: {arg: 'C'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(536);
done();
})
.catch(done);
});
});
it('// unsuccessful - unknown', done => {
it('// unsuccessful - unknown', () => {
mockClient.secure = true;
cmdFn({command: {arg: 'QQ'}})
return cmdFn({command: {arg: 'QQ'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -23,61 +23,53 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs currentDirectory command', done => {
it('fails on no fs currentDirectory command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('// successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('// successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.fs.currentDirectory.restore();
sandbox.stub(mockClient.fs, 'currentDirectory').resolves('/test');
cmdFn({log, command: {arg: 'test', directive: CMD}})
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
done();
})
.catch(done);
});
});
it('// unsuccessful', done => {
it('// unsuccessful', () => {
mockClient.fs.currentDirectory.restore();
sandbox.stub(mockClient.fs, 'currentDirectory').rejects(new Error('Bad'));
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
});

View File

@@ -18,12 +18,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.close.callCount).to.equal(1);
done();
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,75 @@
const when = require('when');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'RETR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
connector: {
waitForConnection: () => when.resolve({
resume: () => {}
}),
end: () => {}
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
read: () => {}
};
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
});
});
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
});
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(551);
});
});
});

View File

@@ -24,47 +24,39 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('test // unsuccessful | file get fails', done => {
it('test // unsuccessful | file get fails', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('test // successful', done => {
cmdFn({ log: mockLog, command: { arg: 'test' } })
it('test // successful', () => {
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.fs.get.args[0][0]).to.equal('test');
expect(mockClient.reply.args[0][0]).to.equal(350);
done();
})
.catch(done);
});
});
});

View File

@@ -25,58 +25,48 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no renameFrom set', done => {
it('// unsuccessful | no renameFrom set', () => {
delete mockClient.renameFrom;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('new // unsuccessful | rename fails', done => {
it('new // unsuccessful | rename fails', () => {
mockClient.fs.rename.restore();
sandbox.stub(mockClient.fs, 'rename').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'new' } })
return cmdFn({ log: mockLog, command: { arg: 'new' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('new // successful', done => {
cmdFn({ command: { arg: 'new' } })
it('new // successful', () => {
return cmdFn({ command: { arg: 'new' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.rename.args[0]).to.eql(['test', 'new']);
done();
})
.catch(done);
});
});
});

View File

@@ -22,45 +22,37 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// unsuccessful | file get fails', done => {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// successful', done => {
cmdFn({ command: { arg: 'test' } })
it('// successful', () => {
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
done();
})
.catch(done);
});
});
});

View File

@@ -23,49 +23,41 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(211);
done();
})
.catch(done);
});
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// unsuccessful | file get fails', done => {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(450);
done();
})
.catch(done);
});
});
it('// successful | file', done => {
it('// successful | file', () => {
sandbox.stub(mockClient.fs, 'get').returns({
name: 'test_file',
dev: 2114,
@@ -85,15 +77,13 @@ describe(CMD, function () {
isDirectory: () => false
});
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(212);
done();
})
.catch(done);
});
});
it('// successful | directory', done => {
it('// successful | directory', () => {
sandbox.stub(mockClient.fs, 'list').returns([{
name: 'test_file',
dev: 2114,
@@ -132,11 +122,9 @@ describe(CMD, function () {
isDirectory: () => true
});
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
done();
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,75 @@
const when = require('when');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'STOR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
connector: {
waitForConnection: () => when.resolve({
resume: () => {}
}),
end: () => {}
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
write: () => {}
};
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
});
});
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
});
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
});

View File

@@ -30,54 +30,46 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// successful | given name is unique', done => {
it('// successful | given name is unique', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects({});
cmdFn({ command: { arg: 'good' } })
return cmdFn({ command: { arg: 'good' } })
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');
expect(call.command).to.have.property('arg');
expect(call.command.arg).to.eql('good');
expect(mockClient.fs.getUniqueName.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('// successful | generates unique name', done => {
cmdFn({ command: { arg: 'bad' } })
it('// successful | generates unique name', () => {
return cmdFn({ command: { arg: 'bad' } })
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');
expect(call.command).to.have.property('arg');
expect(call.command.arg).to.eql('4');
expect(mockClient.fs.getUniqueName.callCount).to.equal(1);
done();
})
.catch(done);
});
});
});

View File

@@ -19,21 +19,17 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn({command: { arg: 'F' } })
it('// successful', () => {
return cmdFn({command: { arg: 'F' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('// unsuccessful', done => {
cmdFn({command: { arg: 'X' } })
it('// unsuccessful', () => {
return cmdFn({command: { arg: 'X' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(215);
done();
})
.catch(done);
});
});
});

View File

@@ -13,50 +13,42 @@ describe(CMD, function () {
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.encoding = null;
mockClient.transferType = null;
sandbox.spy(mockClient, 'reply');
});
afterEach(() => {
sandbox.restore();
});
it('A // successful', done => {
cmdFn({ command: { arg: 'A' } })
it('A // successful', () => {
return cmdFn({ command: { arg: 'A' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('utf8');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('ascii');
});
});
it('I // successful', done => {
cmdFn({ command: { arg: 'I' } })
it('I // successful', () => {
return cmdFn({ command: { arg: 'I' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('binary');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('binary');
});
});
it('L // successful', done => {
cmdFn({ command: { arg: 'L' } })
it('L // successful', () => {
return cmdFn({ command: { arg: 'L' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('binary');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('binary');
});
});
it('X // successful', done => {
cmdFn({ command: { arg: 'X' } })
it('X // successful', () => {
return cmdFn({ command: { arg: 'X' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.encoding).to.equal(null);
done();
})
.catch(done);
expect(mockClient.transferType).to.equal(null);
});
});
});

View File

@@ -28,70 +28,80 @@ describe(CMD, function () {
sandbox.restore();
});
it('test // successful | prompt for password', done => {
cmdFn({ command: { arg: 'test' } })
it('test // successful | prompt for password', () => {
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(331);
done();
})
.catch(done);
});
});
it('test // successful | anonymous login', done => {
it('test // successful | anonymous login', () => {
mockClient.server.options = {anonymous: true};
cmdFn({ command: { arg: 'anonymous' } })
return cmdFn({ command: { arg: 'anonymous' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(1);
done();
})
.catch(done);
});
});
it('test // unsuccessful | no username provided', done => {
cmdFn({ command: { } })
it('test // unsuccessful | no username provided', () => {
return cmdFn({ command: { } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.login.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('test // unsuccessful | already set username', done => {
it('test // unsuccessful | already set username', () => {
mockClient.username = 'test';
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
expect(mockClient.login.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('test // successful | regular login if anonymous is true', done => {
it('test // successful | regular login if anonymous is true', () => {
mockClient.server.options = {anonymous: true};
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(331);
expect(mockClient.login.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('test // successful | anonymous login with set username', done => {
it('test // successful | anonymous login with set username', () => {
mockClient.server.options = {anonymous: 'sillyrabbit'};
cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(1);
done();
})
.catch(done);
});
});
it('test // unsuccessful | anonymous login fails', () => {
mockClient.server.options = {anonymous: true};
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'anonymous' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
expect(mockClient.login.callCount).to.equal(1);
});
});
it('test // successful | does not login if already authenticated', () => {
mockClient.authenticated = true;
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(0);
});
});
});

View File

@@ -3,6 +3,7 @@ const {expect} = require('chai');
const sinon = require('sinon');
const net = require('net');
const tls = require('tls');
const ActiveConnector = require('../../src/connector/active');
const findPort = require('../../src/helpers/find-port');
@@ -33,34 +34,49 @@ describe('Connector - Active //', function () {
server.close(done);
});
it('sets up a connection', function (done) {
active.setupConnection('127.0.0.1', PORT)
it('sets up a connection', function () {
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
done();
})
.catch(done);
});
});
it('destroys existing connection, then sets up a connection', function (done) {
it('destroys existing connection, then sets up a connection', function () {
const destroyFnSpy = sandbox.spy(active.dataSocket, 'destroy');
active.setupConnection('127.0.0.1', PORT)
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(destroyFnSpy.callCount).to.equal(1);
expect(active.dataSocket).to.exist;
done();
})
.catch(done);
});
});
it('waits for connection', function (done) {
active.setupConnection('127.0.0.1', PORT)
it('waits for connection', function () {
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
return active.waitForConnection();
})
.then(() => done())
.catch(done);
.then(dataSocket => {
expect(dataSocket.connected).to.equal(true);
expect(dataSocket instanceof net.Socket).to.equal(true);
expect(dataSocket instanceof tls.TLSSocket).to.equal(false);
});
});
it('upgrades to a secure connection', function () {
mockConnection.secure = true;
mockConnection.server = { _tls: {} };
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
return active.waitForConnection();
})
.then(dataSocket => {
expect(dataSocket.connected).to.equal(true);
expect(dataSocket instanceof net.Socket).to.equal(true);
expect(dataSocket instanceof tls.TLSSocket).to.equal(true);
});
});
});

View File

@@ -20,6 +20,10 @@ describe('Connector - Passive //', function () {
};
let sandbox;
function shouldNotResolve() {
throw new Error('Should not resolve');
}
before(() => {
passive = new PassiveConnector(mockConnection);
});
@@ -36,45 +40,49 @@ describe('Connector - Passive //', function () {
sandbox.restore();
});
it('cannot wait for connection with no server', function (done) {
passive.waitForConnection()
.then(() => done('should not happen'))
it('cannot wait for connection with no server', function () {
return passive.waitForConnection()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('ConnectorError');
done();
});
});
it('has invalid pasv range', function (done) {
it('no pasv range provided', function () {
delete mockConnection.server.options.pasv_range;
return passive.setupServer()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('ConnectorError');
});
});
it('has invalid pasv range', function () {
mockConnection.server.options.pasv_range = -1;
passive.setupServer()
.then(() => done('should not happen'))
return passive.setupServer()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('RangeError');
done();
});
});
it('sets up a server', function (done) {
passive.setupServer()
it('sets up a server', function () {
return passive.setupServer()
.then(() => {
expect(passive.dataServer).to.exist;
done();
})
.catch(done);
});
});
it('destroys existing server, then sets up a server', function (done) {
it('destroys existing server, then sets up a server', function () {
const closeFnSpy = sandbox.spy(passive.dataServer, 'close');
passive.setupServer()
return passive.setupServer()
.then(() => {
expect(closeFnSpy.callCount).to.equal(1);
expect(passive.dataServer).to.exist;
done();
})
.catch(done);
});
});
it('refuses connection with different remote address', function (done) {
@@ -97,8 +105,8 @@ describe('Connector - Passive //', function () {
.catch(done);
});
it('accepts connection', function (done) {
passive.setupServer()
it('accepts connection', function () {
return passive.setupServer()
.then(() => {
expect(passive.dataServer).to.exist;
@@ -109,8 +117,6 @@ describe('Connector - Passive //', function () {
.then(() => {
expect(passive.dataSocket).to.exist;
passive.end();
done();
})
.catch(done);
});
});
});

View File

@@ -2,10 +2,9 @@ const {expect} = require('chai');
const escapePath = require('../../src/helpers/escape-path');
describe('helpers // escape-path', function () {
it('escapes quotes', done => {
it('escapes quotes', () => {
const string = '"test"';
const escapedString = escapePath(string);
expect(escapedString).to.equal('""test""');
done();
});
});

View File

@@ -17,22 +17,19 @@ describe('helpers // find-port', function () {
sandbox.restore();
});
it('finds a port', done => {
findPort(1)
it('finds a port', () => {
return findPort(1)
.then(port => {
expect(Server.prototype.listen.callCount).to.be.above(1);
expect(port).to.be.above(1);
done();
})
.catch(done);
});
});
it('does not find a port', done => {
findPort(1, 2)
.then(() => done('no'))
it('does not find a port', () => {
return findPort(1, 2)
.then(() => expect(1).to.equal(2)) // should not happen
.catch(err => {
expect(err).to.exist;
done();
});
});
});

View File

@@ -1,36 +1,51 @@
const {expect} = require('chai');
const sinon = require('sinon');
const resolveHost = require('../../src/helpers/resolve-host');
describe('helpers //resolve-host', function () {
this.timeout(4000);
it('fetches ip address', done => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => sandbox.restore());
it('fetches ip address', () => {
const hostname = '0.0.0.0';
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
done();
})
.catch(done);
});
});
it('fetches ip address', done => {
it('fetches ip address', () => {
const hostname = null;
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
done();
})
.catch(done);
});
});
it('does nothing', done => {
it('does nothing', () => {
const hostname = '127.0.0.1';
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.equal(hostname);
done();
})
.catch(done);
});
});
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

@@ -14,7 +14,7 @@ describe('FtpServer', function () {
let server;
let client;
before(done => {
before(() => {
server = new FtpServer(process.env.FTP_URL, {
log,
pasv_range: process.env.PASV_RANGE,
@@ -22,14 +22,14 @@ describe('FtpServer', function () {
key: `${process.cwd()}/test/cert/server.key`,
cert: `${process.cwd()}/test/cert/server.crt`,
ca: `${process.cwd()}/test/cert/server.csr`
}
},
greeting: ['hello', 'world']
});
server.on('login', (data, resolve) => {
resolve({root: process.cwd()});
});
server.listen()
.then(() => done());
return server.listen();
});
after(() => {
server.close();
@@ -109,12 +109,12 @@ describe('FtpServer', function () {
});
});
it('STOR test.txt', done => {
it('STOR tést.txt', done => {
const buffer = Buffer.from('test text file');
client.put(buffer, 'test.txt', err => {
client.put(buffer, 'tést.txt', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/test.txt')).to.equal(true);
fs.readFile('./test/test.txt', (fserr, data) => {
expect(fs.existsSync('./test/tést.txt')).to.equal(true);
fs.readFile('./test/tést.txt', (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file');
done();
@@ -122,11 +122,11 @@ describe('FtpServer', function () {
});
});
it('APPE test.txt', done => {
it('APPE tést.txt', done => {
const buffer = Buffer.from(', awesome!');
client.append(buffer, 'test.txt', err => {
client.append(buffer, 'tést.txt', err => {
expect(err).to.not.exist;
fs.readFile('./test/test.txt', (fserr, data) => {
fs.readFile('./test/tést.txt', (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file, awesome!');
done();
@@ -134,8 +134,8 @@ describe('FtpServer', function () {
});
});
it('RETR test.txt', done => {
client.get('test.txt', (err, stream) => {
it('RETR tést.txt', done => {
client.get('tést.txt', (err, stream) => {
expect(err).to.not.exist;
let text = '';
stream.on('data', data => {
@@ -148,10 +148,10 @@ describe('FtpServer', function () {
});
});
it('RNFR test.txt, RNTO awesome.txt', done => {
client.rename('test.txt', 'awesome.txt', err => {
it('RNFR tést.txt, RNTO awesome.txt', done => {
client.rename('tést.txt', 'awesome.txt', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/test.txt')).to.equal(false);
expect(fs.existsSync('./test/tést.txt')).to.equal(false);
expect(fs.existsSync('./test/awesome.txt')).to.equal(true);
fs.readFile('./test/awesome.txt', (fserr, data) => {
expect(fserr).to.not.exist;
@@ -196,19 +196,19 @@ describe('FtpServer', function () {
});
});
it('MKD tmp', done => {
if (fs.existsSync('./test/tmp')) {
fs.rmdirSync('./test/tmp');
it('MKD témp', done => {
if (fs.existsSync('./test/témp')) {
fs.rmdirSync('./test/témp');
}
client.mkdir('tmp', err => {
client.mkdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tmp')).to.equal(true);
expect(fs.existsSync('./test/témp')).to.equal(true);
done();
});
});
it('CWD tmp', done => {
client.cwd('tmp', (err, data) => {
it('CWD témp', done => {
client.cwd('témp', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
done();
@@ -222,10 +222,10 @@ describe('FtpServer', function () {
});
});
it('RMD tmp', done => {
client.rmdir('tmp', err => {
it('RMD témp', done => {
client.rmdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tmp')).to.equal(false);
expect(fs.existsSync('./test/témp')).to.equal(false);
done();
});
});