'use strict' process.setMaxListeners(1000000); const fs = require('fs') const path = require('path') const {test} = require('tap') const rimraf = require('rimraf') const requireInject = require('require-inject') const workdir = path.join(__dirname, path.basename(__filename, '.js')) let testfiles = 0 function tmpFile () { return path.join(workdir, 'test-' + (++testfiles)) } function readFile (path) { return fs.readFileSync(path).toString() } function didWriteFileAtomic (t, expected, filename, data, options, callback) { if (options instanceof Function) { callback = options options = null } if (!options) options = {} const actual = {} const {writeFile: writeFileAtomic} = requireInject('../dist', { fs: Object.assign({}, fs, { chown (filename, uid, gid, cb) { actual.uid = uid actual.gid = gid process.nextTick(cb) }, stat (filename, cb) { fs.stat(filename, (err, stats) => { if (err) return cb(err) cb(null, Object.assign(stats, expected || {})) }) } }) }) return writeFileAtomic(filename, data, options, err => { t.isDeeply(actual, expected, 'ownership is as expected') callback(err) }) } function didWriteFileAtomicSync (t, expected, filename, data, options) { const actual = {} const {writeFileSync} = requireInject('../dist', { fs: Object.assign({}, fs, { chownSync (filename, uid, gid) { actual.uid = uid actual.gid = gid }, statSync (filename) { const stats = fs.statSync(filename) return Object.assign(stats, expected || {}) } }) }) writeFileSync(filename, data, options) t.isDeeply(actual, expected) } function currentUser () { return { uid: process.getuid(), gid: process.getgid() } } test('setup', t => { rimraf.sync(workdir) fs.mkdirSync(workdir, {recursive: true}) t.done() }) test('writes simple file (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, {}, file, '42', err => { t.ifError(err, 'no error') t.is(readFile(file), '42', 'content ok') }) }) test('writes simple file with encoding (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, {}, file, 'foo', 'utf16le', err => { t.ifError(err, 'no error') t.is(readFile(file), 'f\u0000o\u0000o\u0000', 'content ok') }) }) test('writes buffers to simple file (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, {}, file, Buffer.from('42'), err => { t.ifError(err, 'no error') t.is(readFile(file), '42', 'content ok') }) }) test('writes undefined to simple file (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, {}, file, undefined, err => { t.ifError(err, 'no error') t.is(readFile(file), '', 'content ok') }) }) test('writes to symlinks without clobbering (async)', t => { t.plan(5) const file = tmpFile() const link = tmpFile() fs.writeFileSync(file, '42') fs.symlinkSync(file, link) didWriteFileAtomic(t, currentUser(), link, '43', err => { t.ifError(err, 'no error') t.is(readFile(file), '43', 'target content ok') t.is(readFile(link), '43', 'link content ok') t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link') }) }) test('runs chown on given file (async)', t => { const file = tmpFile() didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }, err => { t.ifError(err, 'no error') t.is(readFile(file), '42', 'content ok') t.done() }) }) test('writes simple file with no chown (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, {}, file, '42', { chown: false }, err => { t.ifError(err, 'no error') t.is(readFile(file), '42', 'content ok') t.done() }) }) test('runs chmod on given file (async)', t => { t.plan(5) const file = tmpFile() didWriteFileAtomic(t, {}, file, '42', { mode: parseInt('741', 8) }, err => { t.ifError(err, 'no error') const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '23', { chown: { uid: 42, gid: 43 } }, err => { t.ifError(err, 'no error') }) }) }) test('run chmod AND chown (async)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { mode: parseInt('741', 8), chown: { uid: 42, gid: 43 } }, err => { t.ifError(err, 'no error') const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) }) }) test('does not change chmod by default (async)', t => { t.plan(5) const file = tmpFile() didWriteFileAtomic(t, {}, file, '42', { mode: parseInt('741', 8) }, err => { t.ifError(err, 'no error') didWriteFileAtomic(t, currentUser(), file, '43', err => { t.ifError(err, 'no error') const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) }) }) }) test('does not change chown by default (async)', t => { t.plan(6) const file = tmpFile() didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }, _setModeOnly) function _setModeOnly (err) { t.ifError(err, 'no error') didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '43', { mode: parseInt('741', 8) }, _allDefault) } function _allDefault (err) { t.ifError(err, 'no error') didWriteFileAtomic(t, { uid: 42, gid: 43 }, file, '43', _noError) } function _noError (err) { t.ifError(err, 'no error') } }) test('writes simple file (sync)', t => { t.plan(2) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, '42') t.is(readFile(file), '42') }) test('writes simple file with encoding (sync)', t => { t.plan(2) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, 'foo', 'utf16le') t.is(readFile(file), 'f\u0000o\u0000o\u0000') }) test('writes simple buffer file (sync)', t => { t.plan(2) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, Buffer.from('42')) t.is(readFile(file), '42') }) test('writes undefined file (sync)', t => { t.plan(2) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, undefined) t.is(readFile(file), '') }) test('writes to symlinks without clobbering (sync)', t => { t.plan(4) const file = tmpFile() const link = tmpFile() fs.writeFileSync(file, '42') fs.symlinkSync(file, link) didWriteFileAtomicSync(t, currentUser(), link, '43') t.is(readFile(file), '43', 'target content ok') t.is(readFile(link), '43', 'link content ok') t.ok(fs.lstatSync(link).isSymbolicLink(), 'link is link') }) test('runs chown on given file (sync)', t => { t.plan(1) const file = tmpFile() didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }) }) test('runs chmod on given file (sync)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, '42', { mode: parseInt('741', 8) }) const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '23', { chown: { uid: 42, gid: 43 } }) }) test('runs chown and chmod (sync)', t => { t.plan(2) const file = tmpFile() didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { mode: parseInt('741', 8), chown: { uid: 42, gid: 43 } }) const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) }) test('does not change chmod by default (sync)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomicSync(t, {}, file, '42', { mode: parseInt('741', 8) }) didWriteFileAtomicSync(t, currentUser(), file, '43') const stat = fs.statSync(file) t.is(stat.mode, parseInt('100741', 8)) }) test('does not change chown by default (sync)', t => { t.plan(3) const file = tmpFile() didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '42', { chown: { uid: 42, gid: 43 } }) didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '43', { mode: parseInt('741', 8) }) didWriteFileAtomicSync(t, { uid: 42, gid: 43 }, file, '44') }) test('cleanup', t => { rimraf.sync(workdir) t.done() })