Skip to content

Commit

Permalink
--fix flag (#248)
Browse files Browse the repository at this point in the history
* Add fix command that applies fix property from rule

* Support --fix flag

* Fix tests

* Reintroduce some sad path tests

* Add tests for new fix option

* Add docs

* Enable more tests

* Add no fix found test

* Add tests for extracted setOutputMode helper

* Update no fix found error message

* Fix tests
  • Loading branch information
ryanlntn authored and GantMan committed Sep 20, 2019
1 parent f742bbe commit f4edb7e
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 119 deletions.
194 changes: 121 additions & 73 deletions __tests__/command_helpers/checkRequirement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('checkRequirement', () => {
}
context.strings = strings
context.system = {
run: jest.fn().mockResolvedValue(true),
spawn: jest.fn().mockReturnValue(
Promise.resolve({
stdout: 'you did it!',
Expand All @@ -61,6 +62,51 @@ describe('checkRequirement', () => {
await expect(checkRequirement(badRule, context)).rejects.toThrow()
})

describe('when fix option is passed', () => {
beforeEach(() => checkCLI.mockClear())

test('when no fix is provided', async () => {
checkCLI.mockImplementation(async () => {
throw new Error("Binary 'yarn' not found")
})

const rule = toPairs({
YARN: [{ rule: 'cli', binary: 'yarn' }],
})[0]
const listrTask = await checkRequirement(rule, context, true)

await expect(listrTask.storedInit[0].task()).rejects.toThrow(
new Error('No fix script provided in .solidarity file')
)
})

test('when a fix should be applied', async () => {
checkCLI.mockImplementation(async () => {
throw new Error("Binary 'yarn' not found")
})

const rule = toPairs({
YARN: [{ rule: 'cli', binary: 'yarn', fix: 'brew install yarn' }],
})[0]
const listrTask = await checkRequirement(rule, context, true)

await expect(listrTask.storedInit[0].task()).rejects.toThrow()
expect(context.system.run).toHaveBeenCalledWith('brew install yarn')
})

test('when a fix should not be applied', async () => {
checkCLI.mockImplementation(async () => false)

const rule = toPairs({
YARN: [{ rule: 'cli', binary: 'yarn', fix: 'brew install yarn' }],
})[0]
const listrTask = await checkRequirement(rule, context, true)
await listrTask.storedInit[0].task()

expect(context.system.run).not.toHaveBeenCalled()
})
})

describe('when rule: cli', () => {
beforeEach(() => checkCLI.mockClear())

Expand Down Expand Up @@ -123,15 +169,17 @@ describe('checkRequirement', () => {
expect(result).toEqual('It worked!')
})

// test('sad path', async () => {
// checkDir.mockImplementation(() => throw new Error('Nope'))
test('sad path', async () => {
checkDir.mockImplementation(() => {
throw new Error('Nope')
})

// const rule = toPairs({
// YARN: [{ rule: 'dir', location: 'yarn' }],
// })[0]
// const listrTask = await checkRequirement(rule, context)
// await expect(listrTask.storedInit[0].task()).rejects.toThrow()
// })
const rule = toPairs({
YARN: [{ rule: 'dir', location: 'yarn' }],
})[0]
const listrTask = await checkRequirement(rule, context)
await expect(listrTask.storedInit[0].task()).rejects.toThrow()
})
})

describe('when rule: env', () => {
Expand All @@ -150,16 +198,16 @@ describe('checkRequirement', () => {
expect(runResult).toEqual(false)
})

// test('sad path', async () => {
// checkENV.mockImplementation(async () => undefined)
test('sad path', async () => {
checkENV.mockImplementation(async () => undefined)

// const rule = toPairs({
// YARN: [{ rule: 'env', variable: 'yarn' }],
// })[0]
// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(undefined)
// })
const rule = toPairs({
YARN: [{ rule: 'env', variable: 'yarn' }],
})[0]
const listrTask = await checkRequirement(rule, context)
const result = await listrTask.storedInit[0].task()
expect(result).toEqual(undefined)
})
})

describe('when rule: file', () => {
Expand Down Expand Up @@ -188,60 +236,60 @@ describe('checkRequirement', () => {
// })
})

// describe('when rule fails with custom error messages', () => {
// const customError = 'customError'

// test('failed CLI rule with custom message', async () => {
// checkCLI.mockClear()
// checkCLI.mockImplementation(() => customError)
// const rule = toPairs({
// YARN: [{ rule: 'cli', binary: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(customError)
// })

// test('failed ENV rule with custom message', async () => {
// checkCLI.mockClear()
// checkCLI.mockImplementation(() => true)
// const rule = toPairs({
// BADENV: [{ rule: 'env', variable: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(undefined)
// })

// test('failed DIR rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'dir', location: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(false)
// })

// test('failed FILE rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'file', location: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(undefined)
// })

// test('failed SHELL rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'shell', match: 'hello', command: 'mocked', error: customError }],
// })[0]
// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(customError)
// })
// })
describe('when rule fails with custom error messages', () => {
const customError = 'customError'

test('failed CLI rule with custom message', async () => {
checkCLI.mockClear()
checkCLI.mockImplementation(() => customError)
const rule = toPairs({
YARN: [{ rule: 'cli', binary: 'gazorpazorp', error: customError }],
})[0]

const listrTask = await checkRequirement(rule, context)
const result = await listrTask.storedInit[0].task()
expect(result).toEqual(customError)
})

test('failed ENV rule with custom message', async () => {
checkCLI.mockClear()
checkCLI.mockImplementation(() => true)
const rule = toPairs({
BADENV: [{ rule: 'env', variable: 'gazorpazorp', error: customError }],
})[0]

const listrTask = await checkRequirement(rule, context)
const result = await listrTask.storedInit[0].task()
expect(result).toEqual(undefined)
})

// test('failed DIR rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'dir', location: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(false)
// })

// test('failed FILE rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'file', location: 'gazorpazorp', error: customError }],
// })[0]

// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(undefined)
// })

// test('failed SHELL rule with custom message', async () => {
// const rule = toPairs({
// YARN: [{ rule: 'shell', match: 'hello', command: 'mocked', error: customError }],
// })[0]
// const listrTask = await checkRequirement(rule, context)
// const result = await listrTask.storedInit[0].task()
// expect(result).toEqual(customError)
// })
})
})
42 changes: 42 additions & 0 deletions __tests__/command_helpers/setOutputMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { SolidarityOutputMode } from '../../src/types'
import setOutputMode from '../../src/extensions/functions/setOutputMode'

test('default output mode', () => {
const params = { options: {} }
const settings = {}
const result = setOutputMode(params, settings)

expect(result).toEqual(SolidarityOutputMode.MODERATE)
})

test('settings output mode', () => {
const params = { options: {} }
const settings = { config: { output: 'SILENT' } }
const result = setOutputMode(params, settings)

expect(result).toEqual(SolidarityOutputMode.SILENT)
})

test('verbose output mode', () => {
const params = { options: { verbose: true } }
const settings = {}
const result = setOutputMode(params, settings)

expect(result).toEqual(SolidarityOutputMode.VERBOSE)
})

test('silent output mode', () => {
const params = { options: { silent: true } }
const settings = {}
const result = setOutputMode(params, settings)

expect(result).toEqual(SolidarityOutputMode.SILENT)
})

test('moderate output mode', () => {
const params = { options: { moderate: true } }
const settings = {}
const result = setOutputMode(params, settings)

expect(result).toEqual(SolidarityOutputMode.MODERATE)
})
24 changes: 12 additions & 12 deletions __tests__/commands/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ describe('without a .solidarity file', () => {
process.chdir(origCwd)
})

// it('should prompt the user', async () => {
// await snapshotCommand.run(context)
// expect(context.prompt.ask.mock.calls).toEqual([
// [
// {
// message: 'No `.solidarity` file found for this project. Would you like to create one?',
// name: 'createFile',
// type: 'confirm',
// },
// ],
// ])
// })
it('should prompt the user', async () => {
await snapshotCommand.run(context)
expect(context.prompt.ask.mock.calls).toEqual([
[
{
message: 'No `.solidarity` file found for this project. Would you like to create one?',
name: 'createFile',
type: 'confirm',
},
],
])
})
})

describe('with a .solidarity file', () => {
Expand Down
1 change: 1 addition & 0 deletions __tests__/commands/solidarity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SolidarityOutputMode } from '../../src/types'
const noConfigSolidarity = {
checkRequirement: jest.fn(),
getSolidaritySettings: jest.fn(() => ({})),
setOutputMode: require('../../src/extensions/functions/setOutputMode'), // don't mock setOutputMode
}

const verboseConfigSolidarity = {
Expand Down
1 change: 1 addition & 0 deletions __tests__/extensions/__snapshots__/extensionCheck.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Object {
"key": "command",
},
},
"setOutputMode": [Function],
"setSolidaritySettings": [Function],
"skipRule": [Function],
"updateRequirement": [Function],
Expand Down
4 changes: 4 additions & 0 deletions docs/cliOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Solidarity
--solidarityFile (-f) Use given path to solidarity file for settings
--module (-d) Search for a solidarity file in the given npm package
--stack (-t) Use a known technology stack, and not the local file
--fix Apply fixes to failing rules if fix is available on rule
Solidarity is open source - https://github.com/infinitered/solidarity
If you need additional help, join our Slack at http://community.infinite.red
Expand Down Expand Up @@ -53,3 +54,6 @@ Passing `--stack` or `-t` flags will make our stack look to GitHub for a well kn
> For example: `solidarity --stack react-native` will check our machine if we are ready to run React Native projects, but not a specific React Native project.
Stacks are community managed and found here: https://github.com/infinitered/solidarity-stacks

## fix
Passing `--fix` flag will run fix scripts on any failing rules providing them.
16 changes: 16 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,19 @@ _e.g._ Rule only performs a check on Mac and Linux
}
]
```

### Rule Fixes

Many times rules can be fixed in an automated manor. You can supply a script by passing the `"fix"` property on any rule. The fix will only be ran if the `--fix` flag is passed and only against failing rules. Any script errors will surface should it fail.

_e.g._ Rule provides fix to install yarn if not found (only ran if passing `--fix` option)

```json
"Yarn": [
{
"rule": "cli",
"binary": "yarn",
"fix": "brew install yarn"
}
]
```
Loading

0 comments on commit f4edb7e

Please sign in to comment.