530 lines
14 KiB
JavaScript
530 lines
14 KiB
JavaScript
/* eslint no-unused-expressions: 0 */
|
|
const {expect} = require('chai');
|
|
const sinon = require('sinon');
|
|
const bunyan = require('bunyan');
|
|
const Promise = require('bluebird');
|
|
const _ = require('lodash');
|
|
const fs = require('fs');
|
|
const nodePath = require('path');
|
|
|
|
const FtpServer = require('../src');
|
|
const FtpClient = require('@icetee/ftp');
|
|
|
|
describe('Integration', function () {
|
|
this.timeout(4000);
|
|
|
|
let client;
|
|
let sandbox;
|
|
let log = bunyan.createLogger({name: 'test-runner'});
|
|
let server;
|
|
|
|
let connection;
|
|
const clientDirectory = `${process.cwd()}/test_tmp`;
|
|
|
|
before(() => {
|
|
return startServer({url: 'ftp://127.0.0.1:8880'});
|
|
});
|
|
|
|
beforeEach(() => {
|
|
sandbox = sinon.sandbox.create().usingPromise(Promise);
|
|
});
|
|
|
|
afterEach(() => sandbox.restore());
|
|
|
|
after(() => server.close());
|
|
|
|
before(() => {
|
|
directoryPurge(clientDirectory);
|
|
fs.mkdirSync(clientDirectory);
|
|
});
|
|
after(() => directoryPurge(clientDirectory));
|
|
|
|
function readFile(path) {
|
|
return new Promise((resolve, reject) => {
|
|
fs.readFile(path, 'utf8', (err, data) => {
|
|
if (err) reject(err);
|
|
else resolve(data);
|
|
});
|
|
});
|
|
}
|
|
|
|
function startServer(options = {}) {
|
|
server = new FtpServer(_.assign({
|
|
log,
|
|
pasv_url: '127.0.0.1',
|
|
pasv_min: 8881,
|
|
greeting: ['hello', 'world'],
|
|
anonymous: true
|
|
}, options));
|
|
server.on('login', (data, resolve) => {
|
|
connection = data.connection;
|
|
resolve({root: clientDirectory});
|
|
});
|
|
|
|
return server.listen();
|
|
}
|
|
|
|
function connectClient(options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
client = new FtpClient();
|
|
client.once('ready', () => resolve(client));
|
|
client.once('error', (err) => reject(err));
|
|
client.connect(_.assign({
|
|
host: server.url.hostname,
|
|
port: server.url.port,
|
|
user: 'test',
|
|
password: 'test'
|
|
}, options));
|
|
})
|
|
.then((instance) => {
|
|
client = instance;
|
|
});
|
|
}
|
|
|
|
function closeClient() {
|
|
return new Promise((resolve, reject) => {
|
|
client.once('close', () => resolve());
|
|
client.once('error', (err) => reject(err));
|
|
client.logout((err) => {
|
|
expect(err).to.be.undefined;
|
|
});
|
|
});
|
|
}
|
|
|
|
function directoryPurge(dir) {
|
|
const dirExists = fs.existsSync(dir);
|
|
if (!dirExists) return;
|
|
|
|
const list = fs.readdirSync(dir);
|
|
list.map((item) => nodePath.resolve(dir, item)).forEach((item) => {
|
|
const itemExists = fs.existsSync(dir);
|
|
if (!itemExists) return;
|
|
|
|
const stat = fs.statSync(item);
|
|
if (stat.isDirectory()) directoryPurge(item);
|
|
else fs.unlinkSync(item);
|
|
});
|
|
|
|
fs.rmdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
function runFileSystemTests(name) {
|
|
|
|
before(() => {
|
|
directoryPurge(`${clientDirectory}/${name}/`);
|
|
fs.mkdirSync(`${clientDirectory}/${name}/`);
|
|
fs.writeFileSync(`${clientDirectory}/${name}/fake.txt`, 'Fake file');
|
|
});
|
|
|
|
after(() => directoryPurge(`${clientDirectory}/${name}/`));
|
|
|
|
it('STAT', (done) => {
|
|
client.status((err, status) => {
|
|
expect(err).to.not.exist;
|
|
expect(status).to.equal('Status OK');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('SYST', (done) => {
|
|
client.system((err, os) => {
|
|
expect(err).to.not.exist;
|
|
expect(os).to.equal('UNIX');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('CWD ..', (done) => {
|
|
client.cwd('..', (err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.equal('/');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it(`CWD ${name}`, (done) => {
|
|
client.cwd(`${name}`, (err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.equal(`/${name}`);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('PWD', (done) => {
|
|
client.pwd((err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.equal(`/${name}`);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('LIST .', (done) => {
|
|
client.list('.', (err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.be.an('array');
|
|
expect(data.length).to.equal(1);
|
|
expect(data[0].name).to.equal('fake.txt');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('LIST fake.txt', (done) => {
|
|
client.list('fake.txt', (err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.be.an('array');
|
|
expect(data.length).to.equal(1);
|
|
expect(data[0].name).to.equal('fake.txt');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('STOR fail.txt', (done) => {
|
|
const buffer = Buffer.from('test text file');
|
|
const fsPath = `${clientDirectory}/${name}/fail.txt`;
|
|
|
|
sandbox.stub(connection.fs, 'write').callsFake(function () {
|
|
const stream = fs.createWriteStream(fsPath, {flags: 'w+', autoClose: false});
|
|
stream.on('error', () => fs.existsSync(fsPath) && fs.unlinkSync(fsPath));
|
|
stream.on('close', () => stream.end());
|
|
setImmediate(() => stream.emit('error', new Error('STOR fail test')));
|
|
return stream;
|
|
});
|
|
|
|
client.put(buffer, 'fail.txt', (err) => {
|
|
expect(err).to.exist;
|
|
setImmediate(() => {
|
|
const fileExists = fs.existsSync(fsPath);
|
|
expect(fileExists).to.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('STOR tést.txt', (done) => {
|
|
const buffer = Buffer.from('test text file');
|
|
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
|
|
|
connection.once('STOR', (err) => {
|
|
expect(err).to.not.exist;
|
|
});
|
|
|
|
client.put(buffer, 'tést.txt', (err) => {
|
|
expect(err).to.not.exist;
|
|
setImmediate(() => {
|
|
expect(fs.existsSync(fsPath)).to.equal(true);
|
|
fs.readFile(fsPath, (fserr, data) => {
|
|
expect(fserr).to.not.exist;
|
|
expect(data.toString()).to.equal('test text file');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('STOR logo.png', (done) => {
|
|
const logo = `${__dirname}/../logo.png`;
|
|
const fsPath = `${clientDirectory}/${name}/logo.png`;
|
|
|
|
client.put(logo, 'logo.png', (err) => {
|
|
expect(err).to.not.exist;
|
|
setImmediate(() => {
|
|
expect(fs.existsSync(fsPath)).to.equal(true);
|
|
|
|
const logoContents = fs.readFileSync(logo);
|
|
const transferedContects = fs.readFileSync(fsPath);
|
|
|
|
expect(logoContents.equals(transferedContects));
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('APPE tést.txt', (done) => {
|
|
const buffer = Buffer.from(', awesome!');
|
|
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
|
client.append(buffer, 'tést.txt', (err) => {
|
|
expect(err).to.not.exist;
|
|
setImmediate(() => {
|
|
expect(fs.existsSync(fsPath)).to.equal(true);
|
|
fs.readFile(fsPath, (fserr, data) => {
|
|
expect(fserr).to.not.exist;
|
|
expect(data.toString()).to.equal('test text file, awesome!');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('RETR tést.txt', (done) => {
|
|
connection.once('RETR', (err) => {
|
|
expect(err).to.not.exist;
|
|
});
|
|
|
|
client.get('tést.txt', (err, stream) => {
|
|
expect(err).to.not.exist;
|
|
let text = '';
|
|
|
|
stream.on('data', (data) => {
|
|
text += data.toString();
|
|
});
|
|
|
|
stream.on('finish', () => {
|
|
expect(text).to.equal('test text file, awesome!');
|
|
done();
|
|
});
|
|
|
|
stream.resume();
|
|
});
|
|
});
|
|
|
|
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(`${clientDirectory}/${name}/tést.txt`)).to.equal(false);
|
|
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(true);
|
|
fs.readFile(`${clientDirectory}/${name}/awesome.txt`, (fserr, data) => {
|
|
expect(fserr).to.not.exist;
|
|
expect(data.toString()).to.equal('test text file, awesome!');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('SIZE awesome.txt', (done) => {
|
|
client.size('awesome.txt', (err, size) => {
|
|
expect(err).to.not.exist;
|
|
expect(size).to.be.a('number');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('MDTM awesome.txt', (done) => {
|
|
client.lastMod('awesome.txt', (err, modTime) => {
|
|
expect(err).to.not.exist;
|
|
expect(modTime).to.be.instanceOf(Date);
|
|
expect(modTime.toISOString()).to.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it.skip('MLSD .', (done) => {
|
|
client.mlsd('.', () => {
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('SITE CHMOD 700 awesome.txt', (done) => {
|
|
client.site('CHMOD 600 awesome.txt', (err) => {
|
|
expect(err).to.not.exist;
|
|
fs.stat(`${clientDirectory}/${name}/awesome.txt`, (fserr, stats) => {
|
|
expect(fserr).to.not.exist;
|
|
const mode = stats.mode.toString(8);
|
|
expect(/600$/.test(mode)).to.equal(true);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('DELE awesome.txt', (done) => {
|
|
client.delete('awesome.txt', (err) => {
|
|
expect(err).to.not.exist;
|
|
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('MKD témp', (done) => {
|
|
const path = `${clientDirectory}/${name}/témp`;
|
|
if (fs.existsSync(path)) {
|
|
fs.rmdirSync(path);
|
|
}
|
|
client.mkdir('témp', (err) => {
|
|
expect(err).to.not.exist;
|
|
expect(fs.existsSync(path)).to.equal(true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('MKD témp multiple levels deep', (done) => {
|
|
const path = `${clientDirectory}/${name}/témp`;
|
|
if (fs.existsSync(path)) {
|
|
fs.rmdirSync(path, {recursive: true});
|
|
}
|
|
|
|
client.mkdir('témp/first/second', (err) => {
|
|
expect(err).to.not.exist;
|
|
expect(fs.existsSync(path)).to.equal(true);
|
|
|
|
fs.rmdirSync(path, {recursive: true});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('CWD témp', (done) => {
|
|
const path = `${clientDirectory}/${name}/témp`;
|
|
fs.mkdirSync(path)
|
|
|
|
client.cwd('témp', (err, data) => {
|
|
expect(err).to.not.exist;
|
|
expect(data).to.to.be.a('string');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('CDUP', (done) => {
|
|
client.cdup((err) => {
|
|
expect(err).to.not.exist;
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('RMD témp', (done) => {
|
|
client.rmdir('témp', (err) => {
|
|
expect(err).to.not.exist;
|
|
expect(fs.existsSync(`${clientDirectory}/${name}/témp`)).to.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('CDUP', (done) => {
|
|
client.cdup((err) => {
|
|
expect(err).to.not.exist;
|
|
done();
|
|
});
|
|
});
|
|
}
|
|
|
|
describe('Server events', function () {
|
|
const disconnect = sinon.spy();
|
|
const login = sinon.spy();
|
|
|
|
before(() => {
|
|
server.on('login', login);
|
|
server.on('disconnect', disconnect);
|
|
return connectClient({
|
|
host: server.url.hostname,
|
|
port: server.url.port,
|
|
user: 'test',
|
|
password: 'test'
|
|
});
|
|
});
|
|
|
|
after(() => {
|
|
server.off('login', login);
|
|
server.off('disconnect', disconnect);
|
|
})
|
|
|
|
it('should fire a login event on connect', () => {
|
|
expect(login.calledOnce).to.be.true;
|
|
});
|
|
|
|
it('should fire a close event on disconnect', (done) => {
|
|
client.end();
|
|
setTimeout(() => {
|
|
expect(disconnect.calledOnce).to.be.true;
|
|
done();
|
|
}, 100)
|
|
});
|
|
});
|
|
|
|
describe('#ASCII', function () {
|
|
before(() => {
|
|
return connectClient({
|
|
host: server.url.hostname,
|
|
port: server.url.port,
|
|
user: 'test',
|
|
password: 'test'
|
|
});
|
|
});
|
|
|
|
after(() => closeClient(client));
|
|
|
|
it('TYPE A', (done) => {
|
|
client.ascii((err) => {
|
|
expect(err).to.not.exist;
|
|
done();
|
|
});
|
|
});
|
|
|
|
runFileSystemTests('ascii');
|
|
});
|
|
|
|
describe('#BINARY', function () {
|
|
before(() => {
|
|
return connectClient({
|
|
host: server.url.hostname,
|
|
port: server.url.port,
|
|
user: 'test',
|
|
password: 'test'
|
|
});
|
|
});
|
|
|
|
after(() => closeClient(client));
|
|
|
|
it('TYPE I', (done) => {
|
|
client.binary((err) => {
|
|
expect(err).to.not.exist;
|
|
done();
|
|
});
|
|
});
|
|
|
|
runFileSystemTests('binary');
|
|
});
|
|
|
|
describe('#EXPLICIT', function () {
|
|
before(() => {
|
|
return server.close()
|
|
.then(() => Promise.all([
|
|
readFile(`${process.cwd()}/test/cert/server.key`),
|
|
readFile(`${process.cwd()}/test/cert/server.crt`),
|
|
readFile(`${process.cwd()}/test/cert/server.csr`)
|
|
]))
|
|
.then(([key, cert, ca]) => startServer({
|
|
url: 'ftp://127.0.0.1:8881',
|
|
tls: {key, cert, ca}
|
|
}))
|
|
.then(() => {
|
|
return connectClient({
|
|
secure: true,
|
|
secureOptions: {
|
|
rejectUnauthorized: false,
|
|
checkServerIdentity: () => undefined
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
after(() => closeClient());
|
|
|
|
runFileSystemTests('explicit');
|
|
});
|
|
|
|
describe.skip('#IMPLICIT', function () {
|
|
before(() => {
|
|
return server.close()
|
|
.then(() => Promise.all([
|
|
readFile(`${process.cwd()}/test/cert/server.key`),
|
|
readFile(`${process.cwd()}/test/cert/server.crt`),
|
|
readFile(`${process.cwd()}/test/cert/server.csr`)
|
|
]))
|
|
.then(([key, cert, ca]) => startServer({
|
|
url: 'ftps://127.0.0.1:8882',
|
|
tls: {key, cert, ca}
|
|
}))
|
|
.then(() => {
|
|
return connectClient({
|
|
secure: 'implicit',
|
|
secureOptions: {
|
|
rejectUnauthorized: false,
|
|
checkServerIdentity: () => undefined
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
after(() => closeClient());
|
|
|
|
runFileSystemTests('implicit');
|
|
});
|
|
});
|