Files
ftp-server/test/index.spec.js
2025-04-17 12:33:43 +02:00

550 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')
})
})