block starting the app if there's an invalid config (#4132)

Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
Dimitri MitropoulosandOpender Singh authored and GitHub committed 2021-10-19 22:10:48 -04:00
1 parent a23e82dd6e
commit dddacb9eaa
10 files changed
+150 -10

No files matched your search

@@ -16,6 +16,8 @@ const remote = {
getLocale() {
return 'en-US';
},
exit: jest.fn(),
},
net: {
request() {
@@ -55,9 +57,14 @@ const remote = {
},
};
const dialog = {
showErrorBox: jest.fn(),
};
const electron = {
...remote,
remote,
dialog,
ipcMain: {
on: jest.fn(),
@@ -0,0 +1,45 @@
import electron from 'electron';
import { mocked } from 'ts-jest/utils';
import { getConfigSettings as _getConfigSettings } from '../../models/helpers/settings';
import { validateInsomniaConfig } from '../validate-insomnia-config';
jest.mock('electron');
jest.mock('../../models/helpers/settings');
const electronAppExit = mocked(electron.app.exit);
const electronShowErrorBox = mocked(electron.dialog.showErrorBox);
const getConfigSettings = mocked(_getConfigSettings);
describe('validateInsomniaConfig', () => {
it('should show error box and exit if there is an error', () => {
// Arrange
const errorReturn = {
error: {
errors: [],
insomniaConfig: 'abc',
configPath: 'configPath',
},
};
getConfigSettings.mockReturnValue(errorReturn);
// Act
validateInsomniaConfig();
// Assert
expect(electronShowErrorBox).toHaveBeenCalled();
expect(electronAppExit).toHaveBeenCalled();
});
it('should not exit if there are no errors', () => {
// Arrange
const validReturn = { enableAnalytics: true };
getConfigSettings.mockReturnValue(validReturn);
// Act
validateInsomniaConfig();
// Assert
expect(electronShowErrorBox).not.toHaveBeenCalled();
expect(electronAppExit).not.toHaveBeenCalled();
});
});
@@ -66,6 +66,11 @@ export function restartApp() {
app.exit();
}
export const exitApp = () => {
const { app } = electron.remote || electron;
app.exit();
};
export const setMenuBarVisibility = (visible: boolean) => {
const { BrowserWindow } = electron.remote || electron;
BrowserWindow.getAllWindows()
@@ -0,0 +1,23 @@
import electron from 'electron';
import { omit } from 'ramda';
import { getConfigSettings } from '../models/helpers/settings';
import { exitApp } from './electron-helpers';
export const validateInsomniaConfig = () => {
const configSettings = getConfigSettings();
if ('error' in configSettings) {
const errors = configSettings.error.errors?.map(omit(['parentSchema', 'data']));
electron.dialog.showErrorBox('Invalid Insomnia Config',
[
`Invalid Insomnia Config found at "${configSettings.error.configPath}"`,
'',
'errors:',
`${JSON.stringify(errors, null, 2)}`,
].join('\n'),
);
exitApp();
}
};
@@ -8,6 +8,7 @@ import { changelogUrl, getAppVersion, isDevelopment, isMac } from './common/cons
import { database } from './common/database';
import { disableSpellcheckerDownload } from './common/electron-helpers';
import log, { initializeLogging } from './common/log';
import { validateInsomniaConfig } from './common/validate-insomnia-config';
import * as errorHandling from './main/error-handling';
import * as grpcIpcMain from './main/grpc-ipc-main';
import { checkIfRestartNeeded } from './main/squirrel-startup';
@@ -38,8 +39,10 @@ if (!isDevelopment()) {
// So if (window) checks don't throw
global.window = global.window || undefined;
// When the app is first launched
app.on('ready', async () => {
validateInsomniaConfig();
disableSpellcheckerDownload();
if (isDevelopment()) {
@@ -62,6 +65,7 @@ app.on('ready', async () => {
const updatedStats = await _trackStats();
await _updateFlags(updatedStats);
await _launchApp();
// Init the rest
await updates.init();
grpcIpcMain.init();
@@ -0,0 +1,31 @@
import fs from 'fs';
import { getConfigSettings } from '../settings';
describe('getConfigSettings error', () => {
it('returns an error if validation result has errors', () => {
// Arrange
const invalidConfig = {
insomniaConfig: '1.0.0',
settings: {
random: 'abcd',
},
};
const readFileSyncSpy = jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify(invalidConfig));
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation();
// Act
const result = getConfigSettings();
// Assert
expect(result).toHaveProperty('error');
if ('error' in result) {
expect(consoleErrorMock).toHaveBeenCalledWith('invalid insomnia config', result.error);
}
// Cleanup
readFileSyncSpy.mockRestore();
consoleErrorMock.mockRestore();
});
});
@@ -3,7 +3,7 @@ import fs from 'fs';
import { getConfigSettings } from '../settings';
// This test exists outside of settings.test.ts because we need an unmocked `../settings` module
describe('getConfigSettings', () => {
describe('getConfigSettings once', () => {
it('only reads the config once on startup and then never again', () => {
// Arrange
const configOne = {
@@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
import { Settings } from 'insomnia-common';
import { InsomniaConfig, validate } from 'insomnia-config';
import { InsomniaConfig, validate, ValidationResult } from 'insomnia-config/dist';
import { resolve } from 'path';
import { mapObjIndexed, once } from 'ramda';
import { omitBy } from 'ramda-adjunct';
@@ -53,7 +53,7 @@ export const getConfigFile = () => {
// The paths above are in priority order such that if we already found what we're looking for, there's no reason to keep reading other files.
for (const configPath of configPaths) {
const insomniaConfig = readConfigFile(configPath);
if (insomniaConfig !== undefined) {
if (insomniaConfig !== undefined && configPath !== undefined) {
return {
insomniaConfig,
configPath,
@@ -67,22 +67,37 @@ export const getConfigFile = () => {
};
};
interface ConfigError {
error: {
configPath?: string;
insomniaConfig: unknown;
errors: ValidationResult['errors'];
};
}
/**
* gets settings from the `insomnia.config.json`
*
* note that it is a business rule that the config is never read again after startup, hence the `once` usage.
*/
export const getConfigSettings = once(() => {
export const getConfigSettings: () => (NonNullable<InsomniaConfig['settings']> | ConfigError) = once(() => {
const { configPath, insomniaConfig } = getConfigFile();
const { valid, errors } = validate(insomniaConfig as InsomniaConfig);
if (!valid) {
const resolvedConfigPath = resolve(configPath);
console.error('invalid insomnia config', {
configPath,
configPath: resolvedConfigPath,
insomniaConfig,
errors,
});
return {};
return {
error: {
configPath: resolvedConfigPath,
insomniaConfig,
errors,
},
};
}
// This cast is important for testing intentionally bad values (the above validation will catch it, anyway)
return (insomniaConfig as InsomniaConfig).settings || {};
+2 -2
View File
@@ -1,8 +1,8 @@
import { Settings } from 'insomnia-common';
type ConfigVersion = '1.0.0';
export type ConfigVersion = '1.0.0';
type AllowedSettings = Partial<Pick<Settings,
export type AllowedSettings = Partial<Pick<Settings,
| 'allowNotificationRequests'
| 'disableUpdateNotification'
| 'enableAnalytics'
+12 -2
View File
@@ -1,2 +1,12 @@
export { validate } from './validate';
export { InsomniaConfig } from './entities';
export {
validate,
ErrorResult,
ValidResult,
ValidationResult,
} from './validate';
export {
InsomniaConfig,
AllowedSettings,
ConfigVersion,
} from './entities';