diff --git a/src/tests/configuration/dynamic-config-poller.test.ts b/src/tests/configuration/dynamic-config-poller.test.ts new file mode 100644 index 0000000..440a997 --- /dev/null +++ b/src/tests/configuration/dynamic-config-poller.test.ts @@ -0,0 +1,186 @@ +/* eslint-disable global-require */ +import {AppContext} from '../../lib/context'; +import {Dict} from '../../types'; + +const MOCK_INTERVAL = 100; + +jest.useFakeTimers({legacyFakeTimers: true}); + +jest.mock('axios', () => ({ + __esModule: true, + default: { + get: jest + .fn() + .mockImplementation( + () => + new Promise((resolve) => + setTimeout( + () => + resolve({ + data: { + gravity: 9.81, + }, + }), + 1, + ), + ), + ) + .mockImplementationOnce( + () => + new Promise((_resolve, reject) => + setTimeout(() => reject('URL did not respond'), 1), + ), + ) + .mockImplementationOnce( + () => + new Promise((_resolve, reject) => + setTimeout(() => reject('axios timeout'), 100), + ), + ) + .mockImplementationOnce( + () => + new Promise((resolve) => + setTimeout( + () => + resolve({ + data: { + gravity: 9.81, + }, + }), + 1, + ), + ), + ) + .mockImplementationOnce( + () => + new Promise((_resolve, reject) => + setTimeout(() => reject('axios timeout'), 100), + ), + ), + }, +})); + +function logNothing(_message: string, _extra?: Dict) {} + +const createMockAppContext = () => { + const mockCtx = new (AppContext as new (name: string, config: Object) => AppContext)('test', { + parentContext: false, + config: {}, + logger: {}, + utils: {}, + tracer: {}, + }) as jest.Mocked; + mockCtx.log = jest.fn().mockImplementation(logNothing); + mockCtx.logError = jest.fn().mockImplementation(logNothing); + return mockCtx; +}; + +const mockAppContext = createMockAppContext(); + +const spyOnLog = jest.spyOn(mockAppContext, 'log'); +const spyOnLogError = jest.spyOn(mockAppContext, 'logError'); + +const proceedWithTicksAndTimers = async (iterations: number, interval: number = MOCK_INTERVAL) => { + for (let i = 0; i < iterations; i++) { + jest.advanceTimersByTime(interval); + await new Promise(process.nextTick); + } +}; + +const MOCK_DYNAMIC_CONFIG = { + url: 'mockUrl', + interval: MOCK_INTERVAL, +}; + +const baseEnv = process.env; + +beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + process.env = {...baseEnv}; +}); + +afterEach(() => { + process.env = baseEnv; +}); + +test('check if we successfully set ctx.dynamicConfig after several error calls', async () => { + //ARRANGE + const {DynamicConfigPoller} = require('../../lib/dynamic-config-poller'); + const poller = new DynamicConfigPoller(mockAppContext, 'testPoller', MOCK_DYNAMIC_CONFIG); + //ACT + poller.startPolling(); + const POLLING_CALLS = 3; + const SUCCESS_CALLS = 1; + const ERROR_CALLS = 2; + // we subtract 1 because we dont't care if our poller move to startPolling again after onSuccess in this test + await proceedWithTicksAndTimers(POLLING_CALLS + SUCCESS_CALLS + ERROR_CALLS - 1); + //ASSERT + expect( + (mockAppContext.dynamicConfig as Record).testPoller.gravity, + ).toEqual(9.81); +}); + +test('check if we do not rewrite already set ctx.dynamicConfig after error call', async () => { + //ARRANGE + const {DynamicConfigPoller} = require('../../lib/dynamic-config-poller'); + const poller = new DynamicConfigPoller(mockAppContext, 'testPoller', MOCK_DYNAMIC_CONFIG); + //ACT + poller.startPolling(); + const POLLING_CALLS = 4; + const SUCCESS_CALLS = 1; + const ERROR_CALLS = 3; + await proceedWithTicksAndTimers(POLLING_CALLS + SUCCESS_CALLS + ERROR_CALLS); + //ASSERT + expect( + (mockAppContext.dynamicConfig as Record).testPoller.gravity, + ).toEqual(9.81); +}); + +test('check if we continue to poll for config after success', async () => { + //ARRANGE + const {DynamicConfigPoller} = require('../../lib/dynamic-config-poller'); + const poller = new DynamicConfigPoller(mockAppContext, 'testPoller', MOCK_DYNAMIC_CONFIG); + const spyOnStartPolling = jest.spyOn(poller, 'startPolling'); + //ACT + poller.startPolling(); + const POLLING_CALLS = 4; + const SUCCESS_CALLS = 1; + const ERROR_CALLS = 2; + await proceedWithTicksAndTimers(POLLING_CALLS + SUCCESS_CALLS + ERROR_CALLS); + //ASSERT + expect(spyOnStartPolling).toHaveBeenCalledTimes(POLLING_CALLS); +}); + +test('check if we log stuff with APP_DEBUG_DYNAMIC_CONFIG=debug', async () => { + //ARRANGE + process.env.APP_DEBUG_DYNAMIC_CONFIG = 'debug'; + const {DynamicConfigPoller} = require('../../lib/dynamic-config-poller'); + const poller = new DynamicConfigPoller(mockAppContext, 'testPoller', MOCK_DYNAMIC_CONFIG); + //ACT + poller.startPolling(); + const POLLING_CALLS = 3; + const SUCCESS_CALLS = 1; + const ERROR_CALLS = 2; + // we subtract 1 because we dont't care if our poller move to startPolling again after onSuccess in this test + await proceedWithTicksAndTimers(POLLING_CALLS + SUCCESS_CALLS + ERROR_CALLS - 1); + //ASSERT + expect(spyOnLog).toHaveBeenCalledTimes(POLLING_CALLS + SUCCESS_CALLS); + expect(spyOnLogError).toHaveBeenCalledTimes(ERROR_CALLS); +}); + +test('check if we do not log stuff without APP_DEBUG_DYNAMIC_CONFIG flag', async () => { + //ARRANGE + const {DynamicConfigPoller} = require('../../lib/dynamic-config-poller'); + const poller = new DynamicConfigPoller(mockAppContext, 'testPoller', MOCK_DYNAMIC_CONFIG); + //ACT + poller.startPolling(); + const POLLING_CALLS = 3; + const SUCCESS_CALLS = 1; + const ERROR_CALLS = 2; + // we subtract 1 because we dont't care if our poller move to startPolling again after onSuccess in this test + await proceedWithTicksAndTimers(POLLING_CALLS + SUCCESS_CALLS + ERROR_CALLS - 1); + //ASSERT + expect(spyOnLog).not.toBeCalled(); + expect(spyOnLogError).toHaveBeenCalledTimes(ERROR_CALLS); +}); diff --git a/src/tests/configuration/file-configs.test.ts b/src/tests/configuration/file-configs.test.ts new file mode 100644 index 0000000..6123900 --- /dev/null +++ b/src/tests/configuration/file-configs.test.ts @@ -0,0 +1,156 @@ +import path from 'path'; + +import {loadFileConfigs} from '../../lib/file-configs'; + +const TEST_MOCK_CONFIG = { + common: { + athmosphere: true, + gravity: 9.81, + }, + mars: { + common: { + gravity: 3.71, + }, + test: { + surface: 'marshmallow', + gravity: 1.23, + }, + production: { + surface: 'sand', + }, + }, + venus: { + common: { + gravity: 8.87, + }, + test: { + surface: 'nougat', + gravity: 4.56, + }, + production: { + surface: 'stone', + }, + }, +} as Record>; + +const MOCK_CONFIG_PATH = './mockConfigs'; + +jest.doMock(path.resolve(MOCK_CONFIG_PATH, `common`), () => TEST_MOCK_CONFIG.common, { + virtual: true, +}); + +['mars', 'venus'].forEach((installation) => + ['common', 'test', 'production'].forEach((environment) => + jest.doMock( + path.resolve(MOCK_CONFIG_PATH, `${installation}/${environment}`), + () => TEST_MOCK_CONFIG[installation][environment], + {virtual: true}, + ), + ), +); + +test('check if we load right common config by default', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs'); + //ASSERT + expect(gravity).toEqual(9.81); +}); + +test('check if we load specific common config without env', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'mars'); + //ASSERT + expect(gravity).toEqual(3.71); +}); + +test('check if we load default common config if we messed with installation parameter', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'phaeton', 'test'); + //ASSERT + expect(gravity).toEqual(9.81); +}); + +test('check if we load specific common config if we messed with env parameter', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'mars', 'model'); + //ASSERT + expect(gravity).toEqual(3.71); +}); + +test('check if we do not overwrite existing parameters with non existing ones', () => { + //ARRANGE + //ACT + const {athmosphere} = loadFileConfigs('./mockConfigs', 'mars', 'test'); + //ASSERT + expect(athmosphere).toEqual(true); +}); + +test('check if we correctly overwrite existing parameters', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'mars', 'production'); + //ASSERT + expect(gravity).toEqual(3.71); +}); + +test('check if we correctly load existing parameters from specific configs', () => { + //ARRANGE + //ACT + const {surface} = loadFileConfigs('./mockConfigs', 'mars', 'test'); + //ASSERT + expect(surface).toEqual('marshmallow'); +}); + +test('check if we load different configs for different envs', () => { + //ARRANGE + //ACT + const {surface: testSurface} = loadFileConfigs('./mockConfigs', 'mars', 'test'); + const {surface: productionSurface} = loadFileConfigs('./mockConfigs', 'mars', 'production'); + //ASSERT + expect(testSurface === productionSurface).toEqual(false); +}); + +test('check if we load different configs for different installations', () => { + //ARRANGE + //ACT + const {gravity: marsSurface} = loadFileConfigs('./mockConfigs', 'mars', 'production'); + const {gravity: venusSurface} = loadFileConfigs('./mockConfigs', 'venus', 'production'); + //ASSERT + expect(marsSurface === venusSurface).toEqual(false); +}); + +test('check if we load specific common config if env and installation parameters are OK', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'mars', 'test'); + //ASSERT + expect(gravity).toEqual(1.23); +}); + +test('check if we load default common config if we messed with env and installation parameters order', () => { + //ARRANGE + //ACT + const {gravity} = loadFileConfigs('./mockConfigs', 'test', 'mars'); + //ASSERT + expect(gravity).toEqual(9.81); +}); + +test('check if we load empty config if we messed with configs path', () => { + //ARRANGE + //ACT + const config = loadFileConfigs('./noConfigs', 'mars', 'test'); + //ASSERT + expect(Object.keys(config).length).toEqual(0); +}); + +test('check if we load empty config if we skip configs path', () => { + //ARRANGE + //ACT + const config = loadFileConfigs(); + //ASSERT + expect(Object.keys(config).length).toEqual(0); +});