mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-24 15:49:08 -04:00
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:
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();
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -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 || {};
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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';
|
||||
Reference in new issue
Block a user