-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1049 from api3dao/validator-cli
Create validator CLI
- Loading branch information
Showing
17 changed files
with
424 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@api3/airnode-validator': minor | ||
--- | ||
|
||
Create validator CLI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
#!/usr/bin/env node | ||
|
||
require('../src/commands/validateCmd'); | ||
require('../src/cli'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,20 @@ | ||
const config = require('../../jest.config.base'); | ||
|
||
module.exports = { | ||
...config, | ||
projects: [ | ||
{ | ||
...config, | ||
// Add custom settings below | ||
name: 'e2e', | ||
displayName: 'e2e', | ||
testMatch: ['**/?(*.)+(feature).[tj]s?(x)'], | ||
}, | ||
{ | ||
...config, | ||
// Add custom settings below | ||
displayName: 'unit', | ||
name: 'unit', | ||
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'], | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { join } from 'path'; | ||
import * as cli from './cli'; | ||
|
||
describe('validateConfiguration', () => { | ||
let succeedSpy: any; | ||
let failSpy: any; | ||
|
||
const configPath = join(__dirname, '../../test/fixtures/valid-config.json'); | ||
const secretsPath = join(__dirname, '../../test/fixtures/valid-secrets.env'); | ||
|
||
beforeEach(() => { | ||
succeedSpy = jest.spyOn(cli, 'succeed').mockImplementation(jest.fn()); | ||
failSpy = jest.spyOn(cli, 'fail').mockImplementation(jest.fn() as any); | ||
}); | ||
|
||
describe('calls fail', () => { | ||
it('when config file does not exist', () => { | ||
cli.validateConfiguration('non-existent-config.json', secretsPath); | ||
|
||
expect(failSpy).toHaveBeenCalledTimes(1); | ||
expect(failSpy).toHaveBeenCalledWith( | ||
expect.stringContaining( | ||
'Unable to read config file at "non-existent-config.json". Reason: Error: Error: ENOENT: no such file or directory' | ||
) | ||
); | ||
}); | ||
|
||
it('when secrets file does not exist', () => { | ||
cli.validateConfiguration(configPath, 'non-existent-secrets.env'); | ||
|
||
expect(failSpy).toHaveBeenCalledTimes(1); | ||
expect(failSpy).toHaveBeenCalledWith( | ||
expect.stringContaining( | ||
'Unable to read secrets file at "non-existent-secrets.env". Reason: Error: Error: ENOENT: no such file or directory' | ||
) | ||
); | ||
}); | ||
|
||
it('when secrets have invalid format', () => { | ||
cli.validateConfiguration(configPath, join(__dirname, '../../test/fixtures/invalid-secrets.env')); | ||
|
||
expect(failSpy).toHaveBeenCalledTimes(1); | ||
expect(failSpy).toHaveBeenCalledWith( | ||
'The configuration is not valid. Reason: Error: Error interpolating secrets. Make sure the secrets format is correct' | ||
); | ||
}); | ||
|
||
it('when configuration is invalid', () => { | ||
cli.validateConfiguration(configPath, join(__dirname, '../../test/fixtures/missing-secrets.env')); | ||
|
||
expect(failSpy).toHaveBeenCalledTimes(1); | ||
expect(failSpy).toHaveBeenCalledWith( | ||
'The configuration is not valid. Reason: Error: Error interpolating secrets. Make sure the secrets format is correct' | ||
); | ||
}); | ||
}); | ||
|
||
it('calls success when for valid configuration', () => { | ||
cli.validateConfiguration(configPath, secretsPath); | ||
|
||
expect(succeedSpy).toHaveBeenCalledTimes(1); | ||
expect(succeedSpy).toHaveBeenCalledWith('The configuration is valid'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import path from 'path'; | ||
import { readFileSync } from 'fs'; | ||
import yargs from 'yargs'; | ||
import { hideBin } from 'yargs/helpers'; | ||
import { goSync } from '@api3/promise-utils'; | ||
import ora from 'ora'; | ||
import * as dotenv from 'dotenv'; | ||
import { parseConfigWithSecrets } from '../api'; | ||
|
||
export const succeed = (s: string) => ora(s).succeed(); | ||
export const fail = (s: string) => { | ||
ora(s).fail(); | ||
process.exit(1); | ||
}; | ||
|
||
const examples = [ | ||
'--config pathTo/config.json --secrets pathTo/secrets.env', | ||
'-c pathTo/config.json -s pathTo/secrets.env', | ||
]; | ||
|
||
export const validateConfiguration = (configPath: string, secretsPath: string) => { | ||
const goRawConfig = goSync(() => readFileSync(path.resolve(configPath), 'utf-8')); | ||
if (!goRawConfig.success) return fail(`Unable to read config file at "${configPath}". Reason: ${goRawConfig.error}`); | ||
|
||
const goConfig = goSync(() => JSON.parse(goRawConfig.data)); | ||
if (!goConfig.success) return fail(`The configuration is not a valid JSON.`); | ||
|
||
const goRawSecrets = goSync(() => readFileSync(path.resolve(secretsPath), 'utf-8')); | ||
if (!goRawSecrets.success) { | ||
return fail(`Unable to read secrets file at "${secretsPath}". Reason: ${goRawSecrets.error}`); | ||
} | ||
|
||
const goSecrets = goSync(() => dotenv.parse(goRawSecrets.data)); | ||
if (!goSecrets.success) return fail(`The secrets have incorrect format.`); | ||
|
||
const parseResult = parseConfigWithSecrets(goConfig.data, goSecrets.data); | ||
if (!parseResult.success) return fail(`The configuration is not valid. Reason: ${parseResult.error}`); | ||
|
||
return succeed('The configuration is valid'); | ||
}; | ||
|
||
export const cli = () => { | ||
const cliArguments = yargs(hideBin(process.argv)) | ||
.option('config', { | ||
description: 'Path to "config.json" file to validate', | ||
alias: 'c', | ||
type: 'string', | ||
demandOption: true, | ||
}) | ||
.option('secrets', { | ||
description: 'Path to "secrets.env" file to interpolate in the config', | ||
alias: 's', | ||
type: 'string', | ||
// Making the secrets file required. If the users do not use a secrets file they can pass any empty file. However, | ||
// not passing a secrets file is not recommended and usually is a mistake. | ||
demandOption: true, | ||
}) | ||
.strict() | ||
.help() | ||
.wrap(120) | ||
.example(examples.map((e) => [e])) | ||
.parseSync(); | ||
|
||
const { config, secrets } = cliArguments; | ||
validateConfiguration(config, secrets); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { cli } from './cli'; | ||
|
||
cli(); |
This file was deleted.
Oops, something went wrong.
14 changes: 14 additions & 0 deletions
14
packages/airnode-validator/test/__snapshots__/cli.feature.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`validator CLI shows help 1`] = ` | ||
"Options: | ||
--version Show version number [boolean] | ||
-c, --config Path to \\"config.json\\" file to validate [string] [required] | ||
-s, --secrets Path to \\"secrets.env\\" file to interpolate in the config [string] [required] | ||
--help Show help [boolean] | ||
Examples: | ||
--config pathTo/config.json --secrets pathTo/secrets.env | ||
-c pathTo/config.json -s pathTo/secrets.env | ||
" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { spawnSync } from 'child_process'; | ||
import { join } from 'path'; | ||
|
||
const runValidator = (args: string[]) => { | ||
const command = ['node', join(__dirname, '../dist/bin/validator.js'), ...args].join(' '); | ||
|
||
return spawnSync(command, { shell: true }); | ||
}; | ||
|
||
describe('validator CLI', () => { | ||
it('shows help', () => { | ||
const cliHelp = runValidator(['--help']).stdout.toString(); | ||
|
||
expect(cliHelp).toMatchSnapshot(); | ||
}); | ||
|
||
it('validates valid configuration', () => { | ||
const args = [ | ||
`--config ${join(__dirname, './fixtures/valid-config.json')}`, | ||
`--secrets ${join(__dirname, './fixtures/valid-secrets.env')}`, | ||
]; | ||
|
||
const output = runValidator(args); | ||
|
||
expect(output.status).toBe(0); | ||
// We use "expect.stringContaining" because the output begins with "✔" | ||
expect(output.stderr.toString()).toEqual(expect.stringContaining('The configuration is valid\n')); | ||
}); | ||
|
||
it('validates invalid configuration', () => { | ||
const args = [ | ||
`--config ${join(__dirname, './fixtures/valid-config.json')}`, | ||
`--secrets ${join(__dirname, './fixtures/missing-secrets.env')}`, | ||
]; | ||
|
||
const output = runValidator(args); | ||
|
||
expect(output.status).toBe(1); | ||
expect(output.stderr.toString()).toEqual( | ||
// We use "expect.stringContaining" because the output begins with "✖" | ||
expect.stringContaining( | ||
'The configuration is not valid. Reason: Error: Error interpolating secrets. Make sure the secrets format is correct\n' | ||
) | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"key1": "value1", | ||
"key2": "value2" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
AIRNODE_WALLET_MNEMONIC=tube spin artefact salad slab lumber foot bitter wash reward vote cook |
Oops, something went wrong.