import { expect } from 'chai'; import * as path from 'path'; import { promises as fs } from 'fs'; import { spy, SinonSpy } from 'sinon'; import mock = require('mock-fs'); import * as fsUtils from '~/lib/fs-utils'; import { pathOnRoot } from '~/lib/host-utils'; describe('lib/fs-utils', () => { const testFileName1 = 'file.1'; const testFileName2 = 'file.2'; const testFile1 = pathOnRoot(testFileName1); const testFile2 = pathOnRoot(testFileName2); const mockFs = () => { mock({ [testFile1]: mock.file({ content: 'foo', mtime: new Date('2022-01-04T00:00:00'), }), [testFile2]: mock.file({ content: 'bar', mtime: new Date('2022-01-04T00:00:00'), }), }); }; const unmockFs = () => { mock.restore(); }; describe('writeAndSyncFile', () => { before(mockFs); after(unmockFs); it('should write and sync string data', async () => { await fsUtils.writeAndSyncFile(testFile1, 'foo bar'); expect(await fs.readFile(testFile1, 'utf-8')).to.equal('foo bar'); }); it('should write and sync buffers', async () => { await fsUtils.writeAndSyncFile(testFile1, Buffer.from('bar foo')); expect(await fs.readFile(testFile1, 'utf-8')).to.equal('bar foo'); }); }); describe('writeFileAtomic', () => { before(() => { spy(fs, 'rename'); mockFs(); }); after(() => { (fs.rename as SinonSpy).restore(); unmockFs(); }); it('should write string data atomically', async () => { await fsUtils.writeFileAtomic(testFile1, 'foo baz'); expect(await fs.readFile(testFile1, 'utf-8')).to.equal('foo baz'); expect(fs.rename).to.have.been.calledWith(`${testFile1}.new`, testFile1); }); it('should write buffer data atomically', async () => { await fsUtils.writeFileAtomic(testFile1, 'baz foo'); expect(await fs.readFile(testFile1, 'utf-8')).to.equal('baz foo'); expect(fs.rename).to.have.been.calledWith(`${testFile1}.new`, testFile1); }); }); describe('safeRename', () => { beforeEach(mockFs); afterEach(unmockFs); it('should rename a file', async () => { await fsUtils.safeRename(testFile1, testFile1 + 'rename'); const dirContents = await fs.readdir(pathOnRoot()); expect(dirContents).to.have.length(2); expect(dirContents).to.not.include(testFileName1); expect(dirContents).to.include(testFileName1 + 'rename'); }); it('should replace an existing file', async () => { await fsUtils.safeRename(testFile1, testFile2); const dirContents = await fs.readdir(pathOnRoot()); expect(dirContents).to.have.length(1); expect(dirContents).to.include(testFileName2); expect(dirContents).to.not.include(testFileName1); }); }); describe('exists', () => { before(mockFs); after(unmockFs); it('should return whether a file exists', async () => { expect(await fsUtils.exists(testFile1)).to.be.true; await fs.unlink(testFile1).catch(() => { /* noop */ }); expect(await fsUtils.exists(testFile1)).to.be.false; }); }); describe('mkdirp', () => { before(mockFs); after(unmockFs); it('should recursively create directories', async () => { const directory = path.join(pathOnRoot('test1'), 'test2', 'test3'); await fsUtils.mkdirp(directory); expect(() => fs.readdir(directory)).to.not.throw(); }); }); describe('unlinkAll', () => { beforeEach(mockFs); afterEach(unmockFs); it('should unlink a single file', async () => { await fsUtils.unlinkAll(testFile1); expect(await fs.readdir(pathOnRoot())).to.not.include(testFileName1); }); it('should unlink multiple files', async () => { await fsUtils.unlinkAll(testFile1, testFile2); expect(await fs.readdir(pathOnRoot())).to.have.length(0); }); }); describe('touch', () => { beforeEach(mockFs); afterEach(unmockFs); it('creates the file if it does not exist', async () => { await fsUtils.touch('somefile'); expect(await fsUtils.exists('somefile')).to.be.true; }); it('updates the file mtime if file already exists', async () => { const statsBefore = await fs.stat(testFile1); await fsUtils.touch(testFile1); const statsAfter = await fs.stat(testFile1); // Mtime should be different expect(statsAfter.mtime.getTime()).to.not.equal( statsBefore.mtime.getTime(), ); }); it('allows setting a custom time for existing files', async () => { const customTime = new Date('1981-11-24T12:00:00'); await fsUtils.touch(testFile1, customTime); const statsAfter = await fs.stat(testFile1); expect(statsAfter.mtime.getTime()).to.be.equal(customTime.getTime()); }); it('allows setting a custom time for newly created files', async () => { const customTime = new Date('1981-11-24T12:00:00'); await fsUtils.touch('somefile', customTime); const statsAfter = await fs.stat('somefile'); expect(statsAfter.mtime.getTime()).to.be.equal(customTime.getTime()); }); }); });