Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fabo/fs mock #391

Merged
merged 11 commits into from
Jan 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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