This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
10 KiB
436 lines
10 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
* @format |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const asyncify = require('async/asyncify'); |
|
const {EventEmitter} = require('events'); |
|
const {dirname} = require.requireActual('path'); |
|
const fs = jest.genMockFromModule('fs'); |
|
const invariant = require('fbjs/lib/invariant'); |
|
const path = require('path'); |
|
const stream = require.requireActual('stream'); |
|
|
|
const noop = () => {}; |
|
|
|
function asyncCallback(cb) { |
|
return function() { |
|
setImmediate(() => cb.apply(this, arguments)); |
|
}; |
|
} |
|
|
|
const mtime = { |
|
getTime: () => Math.ceil(Math.random() * 10000000), |
|
}; |
|
|
|
fs.realpath.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let node; |
|
try { |
|
node = getToNode(filepath); |
|
} catch (e) { |
|
return callback(e); |
|
} |
|
if (node && typeof node === 'object' && node.SYMLINK != null) { |
|
return callback(null, node.SYMLINK); |
|
} |
|
return callback(null, filepath); |
|
}); |
|
|
|
fs.readdirSync.mockImplementation(filepath => Object.keys(getToNode(filepath))); |
|
|
|
fs.readdir.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let node; |
|
try { |
|
node = getToNode(filepath); |
|
if (node && typeof node === 'object' && node.SYMLINK != null) { |
|
node = getToNode(node.SYMLINK); |
|
} |
|
} catch (e) { |
|
return callback(e); |
|
} |
|
|
|
if (!(node && typeof node === 'object' && node.SYMLINK == null)) { |
|
return callback(new Error(filepath + ' is not a directory.')); |
|
} |
|
|
|
return callback(null, Object.keys(node)); |
|
}); |
|
|
|
fs.readFile.mockImplementation(asyncify(fs.readFileSync)); |
|
|
|
fs.readFileSync.mockImplementation(function(filepath, encoding) { |
|
filepath = path.normalize(filepath); |
|
const node = getToNode(filepath); |
|
if (isDirNode(node)) { |
|
throw new Error('Error readFileSync a dir: ' + filepath); |
|
} |
|
if (Buffer.isBuffer(node) && typeof encoding !== 'undefined') { |
|
return node.toString(); |
|
} |
|
return node; |
|
}); |
|
|
|
fs.writeFile.mockImplementation(asyncify(fs.writeFileSync)); |
|
|
|
fs.writeFileSync.mockImplementation((filePath, content, options) => { |
|
filePath = path.normalize(filePath); |
|
if (options == null || typeof options === 'string') { |
|
options = {encoding: options}; |
|
} |
|
invariant( |
|
options.encoding == null || options.encoding === 'utf8', |
|
'`options` argument supports only `null` or `"utf8"`', |
|
); |
|
const dirPath = path.dirname(filePath); |
|
const node = getToNode(dirPath); |
|
if (!isDirNode(node)) { |
|
throw fsError('ENOTDIR', 'not a directory: ' + dirPath); |
|
} |
|
node[path.basename(filePath)] = content; |
|
}); |
|
|
|
const openFds = new Map(); |
|
let nextFd = 3; |
|
|
|
fs.openSync.mockImplementation((filePath, flags) => { |
|
const dirPath = path.dirname(filePath); |
|
const node = getToNode(dirPath); |
|
if (!isDirNode(node)) { |
|
throw fsError('ENOTDIR', 'not a directory: ' + dirPath); |
|
} |
|
node[path.basename(filePath)] = ''; |
|
openFds.set(nextFd, {filePath, flags, node}); |
|
return nextFd++; |
|
}); |
|
|
|
fs.writeSync.mockImplementation((fd, str) => { |
|
invariant(typeof str === 'string', 'only strings supported'); |
|
const data = openFds.get(fd); |
|
if (data == null || data.flags !== 'w') { |
|
throw fsError('EBADF', 'bad file descriptor, write'); |
|
} |
|
data.node[path.basename(data.filePath)] += str; |
|
}); |
|
|
|
fs.closeSync.mockImplementation(fd => { |
|
openFds.delete(fd); |
|
}); |
|
|
|
fs.mkdir.mockImplementation(asyncify(fs.mkdirSync)); |
|
|
|
fs.mkdirSync.mockImplementation((dirPath, mode) => { |
|
const parentPath = path.dirname(dirPath); |
|
const node = getToNode(parentPath); |
|
if (!isDirNode(node)) { |
|
throw fsError('ENOTDIR', 'not a directory: ' + parentPath); |
|
} |
|
if (node[path.basename(dirPath)] == null) { |
|
node[path.basename(dirPath)] = {}; |
|
} |
|
}); |
|
|
|
function fsError(code, message) { |
|
const error = new Error(code + ': ' + message); |
|
error.code = code; |
|
return error; |
|
} |
|
|
|
function isDirNode(node) { |
|
return ( |
|
node && |
|
typeof node === 'object' && |
|
node.SYMLINK == null && |
|
Buffer.isBuffer(node) === false |
|
); |
|
} |
|
|
|
function readlinkSync(filepath) { |
|
const node = getToNode(filepath); |
|
if (node !== null && typeof node === 'object' && !!node.SYMLINK) { |
|
return node.SYMLINK; |
|
} else { |
|
throw new Error(`EINVAL: invalid argument, readlink '${filepath}'`); |
|
} |
|
} |
|
|
|
fs.readlink.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let result; |
|
try { |
|
result = readlinkSync(filepath); |
|
} catch (e) { |
|
callback(e); |
|
return; |
|
} |
|
callback(null, result); |
|
}); |
|
|
|
fs.readlinkSync.mockImplementation(readlinkSync); |
|
|
|
function existsSync(filepath) { |
|
try { |
|
const node = getToNode(filepath); |
|
return node !== null; |
|
} catch (e) { |
|
return false; |
|
} |
|
} |
|
|
|
fs.exists.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let result; |
|
try { |
|
result = existsSync(filepath); |
|
} catch (e) { |
|
callback(e); |
|
return; |
|
} |
|
callback(null, result); |
|
}); |
|
|
|
fs.existsSync.mockImplementation(existsSync); |
|
|
|
function makeStatResult(node) { |
|
const isSymlink = node != null && node.SYMLINK != null; |
|
return { |
|
isBlockDevice: () => false, |
|
isCharacterDevice: () => false, |
|
isDirectory: () => node != null && typeof node === 'object' && !isSymlink, |
|
isFIFO: () => false, |
|
isFile: () => node != null && typeof node === 'string', |
|
isSocket: () => false, |
|
isSymbolicLink: () => isSymlink, |
|
mtime, |
|
}; |
|
} |
|
|
|
function statSync(filepath) { |
|
const node = getToNode(filepath); |
|
if (node != null && node.SYMLINK) { |
|
return statSync(node.SYMLINK); |
|
} |
|
return makeStatResult(node); |
|
} |
|
|
|
fs.stat.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let result; |
|
try { |
|
result = statSync(filepath); |
|
} catch (e) { |
|
callback(e); |
|
return; |
|
} |
|
callback(null, result); |
|
}); |
|
|
|
fs.statSync.mockImplementation(statSync); |
|
|
|
function lstatSync(filepath) { |
|
const node = getToNode(filepath); |
|
return makeStatResult(node); |
|
} |
|
|
|
fs.lstat.mockImplementation((filepath, callback) => { |
|
callback = asyncCallback(callback); |
|
let result; |
|
try { |
|
result = lstatSync(filepath); |
|
} catch (e) { |
|
callback(e); |
|
return; |
|
} |
|
callback(null, result); |
|
}); |
|
|
|
fs.lstatSync.mockImplementation(lstatSync); |
|
|
|
fs.open.mockImplementation(function(filepath) { |
|
const callback = arguments[arguments.length - 1] || noop; |
|
let data, error, fd; |
|
try { |
|
data = getToNode(filepath); |
|
} catch (e) { |
|
error = e; |
|
} |
|
|
|
if (error || data == null) { |
|
error = Error(`ENOENT: no such file or directory: \`${filepath}\``); |
|
} |
|
if (data != null) { |
|
/* global Buffer: true */ |
|
fd = {buffer: new Buffer(data, 'utf8'), position: 0}; |
|
} |
|
|
|
callback(error, fd); |
|
}); |
|
|
|
fs.read.mockImplementation( |
|
(fd, buffer, writeOffset, length, position, callback = noop) => { |
|
let bytesWritten; |
|
try { |
|
if (position == null || position < 0) { |
|
({position} = fd); |
|
} |
|
bytesWritten = fd.buffer.copy( |
|
buffer, |
|
writeOffset, |
|
position, |
|
position + length, |
|
); |
|
fd.position = position + bytesWritten; |
|
} catch (e) { |
|
callback(Error('invalid argument')); |
|
return; |
|
} |
|
callback(null, bytesWritten, buffer); |
|
}, |
|
); |
|
|
|
fs.close.mockImplementation((fd, callback = noop) => { |
|
try { |
|
fd.buffer = fs.position = undefined; |
|
} catch (e) { |
|
callback(Error('invalid argument')); |
|
return; |
|
} |
|
callback(null); |
|
}); |
|
|
|
let filesystem = {}; |
|
|
|
fs.mock = { |
|
clear() { |
|
filesystem = {}; |
|
}, |
|
}; |
|
|
|
fs.createReadStream.mockImplementation(filepath => { |
|
if (!filepath.startsWith('/')) { |
|
throw Error('Cannot open file ' + filepath); |
|
} |
|
|
|
const parts = filepath.split('/').slice(1); |
|
let file = filesystem; |
|
|
|
for (const part of parts) { |
|
file = file[part]; |
|
if (!file) { |
|
break; |
|
} |
|
} |
|
|
|
if (typeof file !== 'string') { |
|
throw Error('Cannot open file ' + filepath); |
|
} |
|
|
|
return new stream.Readable({ |
|
read() { |
|
this.push(file, 'utf8'); |
|
this.push(null); |
|
}, |
|
}); |
|
}); |
|
|
|
fs.createWriteStream.mockImplementation(filePath => { |
|
let node; |
|
const writeStream = new stream.Writable({ |
|
write(chunk, encoding, callback) { |
|
this.__chunks.push(chunk); |
|
node[path.basename(filePath)] = this.__chunks.join(''); |
|
callback(); |
|
}, |
|
}); |
|
writeStream.__file = filePath; |
|
writeStream.__chunks = []; |
|
writeStream.end = jest.fn(writeStream.end); |
|
fs.createWriteStream.mock.returned.push(writeStream); |
|
try { |
|
const dirPath = dirname(filePath); |
|
node = getToNode(dirPath); |
|
if (!isDirNode(node)) { |
|
throw fsError('ENOTDIR', 'not a directory: ' + dirPath); |
|
} |
|
// Truncate the file on opening. |
|
node[path.basename(filePath)] = ''; |
|
} catch (error) { |
|
process.nextTick(() => writeStream.emit('error', error)); |
|
} |
|
return writeStream; |
|
}); |
|
fs.createWriteStream.mock.returned = []; |
|
|
|
fs.__setMockFilesystem = object => (filesystem = object); |
|
|
|
const watcherListByPath = new Map(); |
|
|
|
fs.watch.mockImplementation((filename, options, listener) => { |
|
if (options.recursive) { |
|
throw new Error('recursive watch not implemented'); |
|
} |
|
let watcherList = watcherListByPath.get(filename); |
|
if (watcherList == null) { |
|
watcherList = []; |
|
watcherListByPath.set(filename, watcherList); |
|
} |
|
const fsWatcher = new EventEmitter(); |
|
fsWatcher.on('change', listener); |
|
fsWatcher.close = () => { |
|
watcherList.splice(watcherList.indexOf(fsWatcher), 1); |
|
fsWatcher.close = () => { |
|
throw new Error('FSWatcher is already closed'); |
|
}; |
|
}; |
|
watcherList.push(fsWatcher); |
|
}); |
|
|
|
fs.__triggerWatchEvent = (eventType, filename) => { |
|
const directWatchers = watcherListByPath.get(filename) || []; |
|
directWatchers.forEach(wtc => wtc.emit('change', eventType)); |
|
const dirPath = path.dirname(filename); |
|
const dirWatchers = watcherListByPath.get(dirPath) || []; |
|
dirWatchers.forEach(wtc => |
|
wtc.emit('change', eventType, path.relative(dirPath, filename)), |
|
); |
|
}; |
|
|
|
function getToNode(filepath) { |
|
// Ignore the drive for Windows paths. |
|
if (filepath.match(/^[a-zA-Z]:\\/)) { |
|
filepath = filepath.substring(2); |
|
} |
|
|
|
if (filepath.endsWith(path.sep)) { |
|
filepath = filepath.slice(0, -1); |
|
} |
|
const parts = filepath.split(/[\/\\]/); |
|
if (parts[0] !== '') { |
|
throw new Error('Make sure all paths are absolute.'); |
|
} |
|
let node = filesystem; |
|
parts.slice(1).forEach(part => { |
|
if (node && node.SYMLINK) { |
|
node = getToNode(node.SYMLINK); |
|
} |
|
node = node[part]; |
|
if (node == null) { |
|
const err = new Error( |
|
`ENOENT: no such file or directory: \`${filepath}\``, |
|
); |
|
err.code = 'ENOENT'; |
|
throw err; |
|
} |
|
}); |
|
|
|
return node; |
|
} |
|
|
|
module.exports = fs;
|
|
|