Skip to content

Commit

Permalink
Merge pull request #391 from cosmos/fabo/fs-mock
Browse files Browse the repository at this point in the history
Fabo/fs mock
  • Loading branch information
nylira authored Jan 26, 2018
2 parents a1e2070 + bc25dd1 commit 4213a53
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ npm-debug.log
npm-debug.log.*
thumbs.db
!.gitkeep
.vscode
2 changes: 1 addition & 1 deletion app/src/renderer/vuex/modules/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default function ({ node }) {
}
},
pollRPCConnection ({state, commit, dispatch}, timeout = 3000) {
if (state.nodeTimeout) return
if (state.nodeTimeout || state.stopConnecting) return

state.nodeTimeout = setTimeout(() => {
// clear timeout doesn't work
Expand Down
2 changes: 1 addition & 1 deletion tasks/vue/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fs.writeFileSync(
routeTemplate
)

fs.mkdirSync(path.join(__dirname, `../../app/src/components/${routeName}View`))
fs.ensureDirSync(path.join(__dirname, `../../app/src/components/${routeName}View`))

fs.writeFileSync(
path.join(__dirname, '../../app/src/routes.js'),
Expand Down
12 changes: 6 additions & 6 deletions test/unit/helpers/console_error_throw.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
process.env.LISTENING_TO_UNHANDLED_REJECTION = true
}

console.error = (...args) => {
throw new Error(args.join(' '))
}
// console.error = (...args) => {
// throw Error(args.join(' '))
// }

console.warn = (...args) => {
throw new Error(args.join(' '))
}
// console.warn = (...args) => {
// throw Error(args.join(' '))
// }
129 changes: 129 additions & 0 deletions test/unit/helpers/fs-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const { Writable } = require('stream')
const { normalize, sep } = require('path')

/*
* this mock implements every function (all used in this project for now) in fs-extra so that the file system is just represented by a json file holding file content as strings
*/
export default function mockFsExtra (fileSystem = {}) {
const fsExtraMock = {
copy: (from, to) => {
let {file} = get(from, fsExtraMock.fs)
if (file === null) {
throwENOENT(from)
}
create(to, fsExtraMock.fs, file)
},
ensureFile: (path) => {
let {file} = get(path, fsExtraMock.fs)
if (file === null) {
create(path, fsExtraMock.fs)
}
},
ensureDir: (path) => {
let {file} = get(path, fsExtraMock.fs)
if (file === null) {
create(path, fsExtraMock.fs)
}
},
createWriteStream: () => new Writable(),
// for simplicity we say if there is a file we can access it
access: (path) => {
let {file} = get(path, fsExtraMock.fs)
if (file === null) {
throwENOENT(path)
return false
}
return !!get(path, fsExtraMock.fs).file
},
remove: (path) => {
let {parent, name} = get(path, fsExtraMock.fs)
delete parent[name]
},
readFile: (path) => {
let {file} = get(path, fsExtraMock.fs)
if (!file) {
throwENOENT(path)
}
return file
},
writeFile: (path, file) => create(path, fsExtraMock.fs, file),
pathExists: (path) => !!get(path, fsExtraMock.fs).file,
exists: (path) => {
let {file} = get(path, fsExtraMock.fs)
return file !== null
}
}

// all methods are synchronous in tests
Object.keys(fsExtraMock).forEach(key => {
fsExtraMock[key + 'Sync'] = fsExtraMock[key]
})

// for debugging
fsExtraMock.MOCKED = true
fsExtraMock.fs = fileSystem

// strip long content from fs
function fsToString (fs) {
// clone
fs = JSON.parse(JSON.stringify(fs))
Object.keys(fs).forEach(key => {
if (typeof fs[key] === 'object') {
fs[key] = fsToString(fs[key])
} else {
fs[key] = '#CONTENT#'
}
})
return fs
}

function throwENOENT (path) {
let error = new Error('Path ' + path + ' doesnt exist.\nFS:' + JSON.stringify(fsToString(fileSystem), null, 2))
error.code = 'ENOENT'
throw error
}

function get (path, fs) {
path = normalize(path)
let pathArr = path.split(sep).filter(x => x !== '')
let current = pathArr.shift()

if (fs[current]) {
if (pathArr.length === 0) {
return {
file: fs[current],
name: current,
parent: fs
}
}
return get(pathArr.join('/'), fs[current])
}
return {
file: null,
name: current,
parent: fs
}
}

function create (path, fs, file = {}) {
path = normalize(path)
let pathArr = path.split(sep).filter(x => x !== '')
let current = pathArr.shift()

if (!fs[current]) {
fs[current] = {}
}
if (pathArr.length === 0) {
if (typeof file === 'object') {
// clone object
fs[current] = JSON.parse(JSON.stringify(file))
} else {
fs[current] = file
}
return true
}
return create(pathArr.join('/'), fs[current], file)
}

return fsExtraMock
}
72 changes: 43 additions & 29 deletions test/unit/specs/main.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
const fs = require('fs-extra')
const {join} = require('path')
const mockFsExtra = require('../helpers/fs-mock').default

function sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

jest.mock('fs-extra', () => {
let fs = require('fs')
let mockFs = mockFsExtra()
mockFs.writeFile('./app/networks/gaia-1/config.toml', fs.readFileSync('./app/networks/gaia-1/config.toml', 'utf8'))
mockFs.writeFile('./app/networks/gaia-1/genesis.json', fs.readFileSync('./app/networks/gaia-1/genesis.json', 'utf8'))
return mockFs
})
let fs = require('fs-extra')

jest.mock('electron', () => {
return {
app: {
Expand Down Expand Up @@ -59,9 +68,6 @@ describe('Startup Process', () => {
})

describe('Initialization', function () {
beforeAll(async function () {
await resetConfigs()
})
mainSetup()

it('should create the config dir', async function () {
Expand Down Expand Up @@ -97,7 +103,7 @@ describe('Startup Process', () => {

// TODO the stdout.on('data') trick doesn't work
xit('should init gaia server accepting the new app hash', async function () {
await resetConfigs()
jest.resetModules()
let mockWrite = jest.fn()
childProcessMock((path, args) => ({
stdin: {
Expand All @@ -119,7 +125,7 @@ describe('Startup Process', () => {

describe('Initialization in dev mode', function () {
beforeAll(async function () {
await resetConfigs()
jest.resetModules()

Object.assign(process.env, {
NODE_ENV: 'development',
Expand All @@ -140,7 +146,7 @@ describe('Startup Process', () => {

// TODO the stdout.on('data') trick doesn't work
xit('should init gaia accepting the new app hash', async function () {
await resetConfigs()
jest.resetModules()
let mockWrite = jest.fn()
childProcessMock((path, args) => ({
stdin: {
Expand All @@ -162,7 +168,7 @@ describe('Startup Process', () => {

describe('Initialization in dev mode', function () {
beforeAll(async function () {
await resetConfigs()
jest.resetModules()

Object.assign(process.env, {
NODE_ENV: 'development',
Expand Down Expand Up @@ -209,7 +215,7 @@ describe('Startup Process', () => {
})

xit('should have set the own node as a validator with 100% voting power', async () => {
await resetConfigs()
jest.resetModules()

await fs.writeFile(join(testRoot, 'priv_validator.json'), {
pub_key: '123'
Expand Down Expand Up @@ -250,27 +256,33 @@ describe('Startup Process', () => {
})

describe('Update app version', function () {
beforeAll(() => {
mainSetup()

it('should backup the genesis.json', async function () {
resetModulesKeepingFS()

// alter the version so the main thread assumes an update
jest.mock(root + 'package.json', () => ({
version: '1.1.1'
}))
})
mainSetup()
await require(appRoot + 'src/main/index.js')

it('should backup the genesis.json', async function () {
expect(fs.existsSync(testRoot.substr(0, testRoot.length - 1) + '_backup_1/genesis.json')).toBe(true)
})
})

describe('Update genesis.json', function () {
beforeAll(async function () {
mainSetup()

it('should backup the genesis.json', async function () {
resetModulesKeepingFS()

// alter the genesis so the main thread assumes a change
let existingGenesis = JSON.parse(fs.readFileSync(testRoot + 'genesis.json', 'utf8'))
existingGenesis.genesis_time = (new Date()).toString()
fs.writeFileSync(testRoot + 'genesis.json', JSON.stringify(existingGenesis))
})
mainSetup()
await require(appRoot + 'src/main/index.js')

it('should backup the genesis.json', async function () {
expect(fs.existsSync(testRoot.substr(0, testRoot.length - 1) + '_backup_1/genesis.json')).toBe(true)
})
})
Expand Down Expand Up @@ -306,7 +318,7 @@ describe('Startup Process', () => {
}).join('\n')
fs.writeFileSync(join(testRoot, 'config.toml'), configText, 'utf8')

jest.resetModules()
resetModulesKeepingFS()
await require(appRoot + 'src/main/index.js')
.then(() => done.fail('Didnt fail'))
.catch(err => {
Expand All @@ -318,13 +330,13 @@ describe('Startup Process', () => {
describe('missing files', () => {
beforeEach(async () => {
// make sure it is initialized
await resetConfigs()
jest.resetModules()
await initMain()
main.shutdown()
})
afterEach(async () => {
await main.shutdown()
await resetConfigs()
jest.resetModules()
})
it('should survive the genesis.json being removed', async () => {
fs.removeSync(join(testRoot, 'genesis.json'))
Expand All @@ -347,7 +359,7 @@ describe('Startup Process', () => {

describe('Error handling on init', () => {
beforeEach(async function () {
await resetConfigs()
jest.resetModules()
})
testFailingChildProcess('gaia', 'init')
})
Expand All @@ -370,6 +382,9 @@ async function initMain () {
// restart main with a now initialized state
jest.resetModules()
childProcess = require('child_process')
// have the same mocked fs as main uses
// this is reset with jest.resetModules
fs = require('fs-extra')
main = await require(appRoot + 'src/main/index.js')
expect(main).toBeDefined()
}
Expand Down Expand Up @@ -418,12 +433,11 @@ function failingChildProcess (mockName, mockCmd) {
}))
}

async function resetConfigs () {
if (fs.existsSync('./test/unit/tmp')) {
// fs.removeSync did produce an ENOTEMPTY error under windows
await fs.removeSync('./test/unit/tmp')
expect(fs.existsSync('./test/unit/tmp')).toBe(false)
} else {
fs.ensureDirSync('./test/unit/tmp')
}
// sometime we want to simulate a sequential run of the UI
// usualy we want to clean up all the modules after each run but in this case, we want to persist the mocked filesystem
function resetModulesKeepingFS () {
let fileSystem = fs.fs
jest.resetModules()
fs = require('fs-extra')
fs.fs = fileSystem
}
18 changes: 15 additions & 3 deletions test/unit/specs/node.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
const LCDConnector = require('renderer/node')

let fetch = global.fetch

describe('LCD Connector', () => {
let LCDConnector

beforeEach(() => {
jest.resetModules()
LCDConnector = require('renderer/node')

jest.mock('tendermint', () => () => ({
on (value, cb) {},
removeAllListeners () {},
ws: {
destroy () {}
}
}))

process.env.COSMOS_UI_ONLY = 'false'
})

beforeAll(() => {
global.fetch = () => Promise.resolve({
text: () => '1.2.3.4'
Expand Down Expand Up @@ -41,7 +53,7 @@ describe('LCD Connector', () => {
}
}))
jest.resetModules()
let LCDConnector = require('renderer/node')
LCDConnector = require('renderer/node')
let node = LCDConnector('1.1.1.1')
expect(node.rpc).toBeDefined()
expect(node.rpcOpen).toBe(false)
Expand Down
Loading

0 comments on commit 4213a53

Please sign in to comment.