diff --git a/readme.md b/readme.md index 7bfcbf9..c6d6c51 100644 --- a/readme.md +++ b/readme.md @@ -24,20 +24,20 @@ https://github.com/switcherapi/switcher-api `npm install switcher-client` ## Module initialization -The context properties stores all information regarding connectivity and strategy settings. +The context properties stores all information regarding connectivity. ```js -const Switcher = require("switcher-client"); +const Switcher = require('switcher-client'); -const apiKey = 'API Key'; -const environment = 'default'; // Production = default +const apiKey = '[API_KEY]'; +const environment = 'default'; const domain = 'My Domain'; const component = 'MyApp'; const url = 'https://switcher-load-balance.herokuapp.com'; ``` - **apiKey**: Switcher-API key generated to your component. -- **environment**: Environment name. Production environment is named as 'default'. +- **environment**: (optional) Environment name. Production environment is named as 'default'. - **domain**: Domain name. - **component**: Application name. - **url**: Swither-API endpoint. @@ -48,32 +48,23 @@ You can also activate features such as offline and silent mode: ```js const offline = true; const logger = true; -const snapshotAutoload = true; const snapshotLocation = './snapshot/'; const silentMode = true; const retryAfter = '5m'; -let switcher = new Switcher(url, apiKey, domain, component, environment, { - offline, logger, snapshotLocation, snapshotAutoload, silentMode, retryAfter +Switcher.buildContext({ url, apiKey, domain, component, environment }, { + offline, logger, snapshotLocation, silentMode, retryAfter }); + +let switcher = Switcher.factory(); ``` - **offline**: If activated, the client will only fetch the configuration inside your snapshot file. The default value is 'false'. - **logger**: If activated, it is possible to retrieve the last results from a given Switcher key using Switcher.getLogger('KEY') - **snapshotLocation**: Location of snapshot files. The default value is './snapshot/'. -- **snapshotAutload**: If activated, snapshot folder and files are going to be created automatically. - **silentMode**: If activated, all connectivity issues will be ignored and the client will automatically fetch the configuration into your snapshot file. - **retryAfter** : Time given to the module to re-establish connectivity with the API - e.g. 5s (s: seconds - m: minutes - h: hours). -## Pre-execution -Before you call the API, there is one single step you need to execute to complete the configuration. -If you are not running the API expecting to use the offline features, you can ignore this step. - -After instantiating the Switcher, you need to load the snapshot engine to watch for changes in your Domain structure. -```js -await switcher.loadSnapshot(); -``` - ## Executing There are a few different ways to call the API using the JavaScript module. Here are some examples: @@ -82,7 +73,7 @@ Here are some examples: Invoking the API can be done by instantiating the switcher and calling *isItOn* passing its key as a parameter. ```js -const switcher = new Switcher(url, apiKey, domain, component, environment); +const switcher = Switcher.factory(); await switcher.isItOn('FEATURE01'); ``` @@ -133,9 +124,16 @@ To enable this feature, it is recommended to place the following on your test se Switcher.setTestEnabled(); ``` +## Loading Snapshot from the API +This step is optional if you want to load a copy of the configuration that can be used to eliminate latency when offline mode is activated. + +```js +Switcher.loadSnapshot(); +``` + ## Snapshot version check For convenience, an implementation of a domain version checker is available if you have external processes that manage snapshot files. ```js -switcher.checkSnapshot(); +Switcher.checkSnapshot(); ``` \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index 3987cd2..9a45ee2 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,105 +1,128 @@ +/** + * Quick start with the following 3 steps. + * + * 1. Use Switcher.buildContext() to define the arguments to connect to the API. + * 2. Use Switcher.factory() to create a new instance of Switcher. + * 3. Use the instance created to call isItOn to query the API. + */ declare class Switcher { /** - * @param url Swither-API endpoint - * @param apiKey Switcher-API key generated to your component. - * @param domain Domain name - * @param component Application name - * @param environment Environment name. Production environment is named as 'default' - * @param options offline: boolean - logger: boolean - snapshotLocation: string - snapshotAutoload: string- silentMode: boolean - retryAfter: string; - */ - constructor( - url: string, - apiKey: string, - domain: string, - component: string, - environment: string, - options?: SwitcherOptions); - - url: string; - token: string; - apiKey: string; - domain: string; - environment: string; - key: string; - input: string[]; - exp: number; - snapshot?: string; - - /** - * Validate the input provided to access the API - */ - validate(): Promise; - - /** - * Pre-set input values before calling the API - * - * @param key - * @param input - */ - prepare(key: string, input?: string[]): Promise; - - /** - * Execute async criteria + * Create the necessary configuration to communicate with the API * - * @param key - * @param input - * @param showReason + * @param context Necessary arguments + * @param options */ - isItOn(key?: string, input?: string[], showReason?: boolean): Promise; + static buildContext(context: SwitcherContext, options: SwitcherOptions): void; - /** - * Read snapshot file locally and store in a parsed JSON object - */ - loadSnapshot(): Promise; + /** + * Creates a new instance of Switcher + */ + static factory(): Switcher; - /** - * Verifies if the current snapshot file is updated. - * Return true if an update has been made. - */ - checkSnapshot(): Promise; + /** + * Read snapshot file locally and store in a parsed JSON object + */ + static loadSnapshot(): Promise; - /** - * Remove snapshot from real-time update - */ - unloadSnapshot(): void; + /** + * Verifies if the current snapshot file is updated. + * Return true if an update has been made. + */ + static checkSnapshot(): Promise; + + /** + * Remove snapshot from real-time update + */ + static unloadSnapshot(): void; /** - * Force a switcher value to return a given value by calling one of both methods - true() false() - * - * @param key - */ - static assume(key: string): Key; + * Strategies available to use as Switcher input + */ + static get StrategiesType(): StrategiesType; + + /** + * Force a switcher value to return a given value by calling one of both methods - true() false() + * + * @param key + */ + static assume(key: string): Key; + + /** + * Remove forced value from a switcher + * + * @param key + */ + static forget(key: string): void; + + /** + * Retrieve execution log given a switcher key + * + * @param key + */ + static getLogger(key: string): any[]; /** - * Remove forced value from a switcher - * - * @param key - */ - static forget(key: string): void; + * Enable testing mode + * It prevents from watching Snapshots that may hold process + */ + static setTestEnabled() : void; + + /** + * Disable testing mode + */ + static setTestDisabled(): void; /** - * Retrieve execution log given a switcher key - * - * @param key - */ - static getLogger(key: string): any[]; + * Validate the input provided to access the API + */ + validate(): Promise; - /** - * Activate testing mode - * It prevents from watching Snapshots that may hold process - */ - static setTestEnabled() : void; - + /** + * Pre-set input values before calling the API + * + * @param key + * @param input + */ + prepare(key: string, input?: string[]): Promise; + + /** + * Execute async criteria + * + * @param key + * @param input + * @param showReason + */ + isItOn(key?: string, input?: string[], showReason?: boolean): Promise; + +} + +declare interface SwitcherContext { + url: string; + apiKey: string; + domain: string; + component: string; + environment: string; + token?: string; + exp?: number; } declare interface SwitcherOptions { - offline: boolean; - logger: boolean; - snapshotLocation: string; - snapshotAutoload: string; - silentMode: boolean; - retryAfter: string; + offline: boolean; + logger: boolean; + snapshotLocation: string; + snapshotAutoload: string; + silentMode: boolean; + retryAfter: string; +} + +declare interface StrategiesType { + NETWORK: string; + VALUE: string; + NUMERIC: string; + TIME: string; + DATE: string; + REGEX: string; } export = Switcher; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 3025785..4510caa 100644 --- a/src/index.js +++ b/src/index.js @@ -7,98 +7,170 @@ const services = require('./lib/services'); const checkCriteriaOffline = require('./lib/resolver'); const fs = require('fs'); +const DEFAULT_ENVIRONMENT = 'default'; const DEFAULT_SNAPSHOT_LOCATION = './snapshot/'; const DEFAULT_RETRY_TIME = '5m'; const DEFAULT_OFFLINE = false; const DEFAULT_LOGGER = false; -var testEnabled = false; +const DEFAULT_TEST_MODE = false; class Switcher { - constructor(url, apiKey, domain, component, environment, options) { - this.url = url; - this.apiKey = apiKey; - this.domain = domain; - this.component = component; - this.environment = environment; + static buildContext(context, options) { + this.testEnabled = DEFAULT_TEST_MODE; + + this.context = {}; + this.context = context; + this.context.environment = context.environment || DEFAULT_ENVIRONMENT; // Default values - this.offline = DEFAULT_OFFLINE; - this.snapshotLocation = DEFAULT_SNAPSHOT_LOCATION; - this.logger = DEFAULT_LOGGER; + this.options = {}; + this.options.offline = DEFAULT_OFFLINE; + this.options.snapshotLocation = DEFAULT_SNAPSHOT_LOCATION; + this.options.logger = DEFAULT_LOGGER; if (options) { if ('offline' in options) { - this.offline = options.offline; + this.options.offline = options.offline; } if ('snapshotLocation' in options) { - this.snapshotLocation = options.snapshotLocation; - } - - if ('snapshotAutoload' in options) { - this.snapshotAutoload = options.snapshotAutoload; + this.options.snapshotLocation = options.snapshotLocation; } if ('silentMode' in options) { - this.silentMode = options.silentMode; + this.options.silentMode = options.silentMode; } if ('logger' in options) { - this.logger = options.logger; + this.options.logger = options.logger; } if ('retryAfter' in options) { - this.retryTime = options.retryAfter.slice(0, -1); - this.retryDurationIn = options.retryAfter.slice(-1); + this.options.retryTime = options.retryAfter.slice(0, -1); + this.options.retryDurationIn = options.retryAfter.slice(-1); } else { - this.retryTime = DEFAULT_RETRY_TIME.charAt(0); - this.retryDurationIn = DEFAULT_RETRY_TIME.charAt(1); + this.options.retryTime = DEFAULT_RETRY_TIME.charAt(0); + this.options.retryDurationIn = DEFAULT_RETRY_TIME.charAt(1); } } } + static factory() { + return new Switcher(); + } + + static async checkSnapshot() { + if (Switcher.snapshot) { + if (!Switcher.context.exp || Date.now() > (Switcher.context.exp*1000)) { + await Switcher._auth(); + + const result = await validateSnapshot(Switcher.context, Switcher.options.snapshotLocation, + Switcher.snapshot.data.domain.version); + + if (result) { + Switcher.loadSnapshot(); + return true; + } + } + return false; + } + } + + static async loadSnapshot() { + const snapshotFile = `${Switcher.options.snapshotLocation}${Switcher.context.environment}.json`; + Switcher.snapshot = loadDomain(Switcher.options.snapshotLocation, Switcher.context.environment); + + if (Switcher.snapshot.data.domain.version == 0 && !Switcher.options.offline) { + await Switcher.checkSnapshot(); + } else if (!Switcher.testEnabled) { + fs.unwatchFile(snapshotFile); + fs.watchFile(snapshotFile, () => { + Switcher.snapshot = loadDomain(Switcher.options.snapshotLocation, Switcher.context.environment); + }); + } + } + + static unloadSnapshot() { + if (!Switcher.testEnabled) { + const snapshotFile = `${Switcher.options.snapshotLocation}${Switcher.context.environment}.json`; + Switcher.snapshot = undefined; + fs.unwatchFile(snapshotFile); + } + } + + static async _auth() { + const response = await services.auth(Switcher.context, { + silentMode: Switcher.options.silentMode, + retryTime: Switcher.options.retryTime, + retryDurationIn: Switcher.options.retryDurationIn + }); + + Switcher.context.token = response.data.token; + Switcher.context.exp = response.data.exp; + } + + static get StrategiesType() { + return StrategiesType; + } + + static assume(key) { + return Bypasser.assume(key); + } + + static forget(key) { + return Bypasser.forget(key); + } + + static getLogger(key) { + return ExecutionLogger.getByKey(key); + } + + static setTestEnabled() { + Switcher.testEnabled = true; + } + + static setTestDisabled() { + Switcher.testEnabled = false; + } + async prepare(key, input) { this.key = key; if (input) { this.input = input; } - if (!this.offline) { - let response = await services.auth(this.url, this.apiKey, this.domain, this.component, this.environment, { - silentMode: this.silentMode, - retryTime: this.retryTime, - retryDurationIn: this.retryDurationIn - }); - - this.token = response.data.token; - this.exp = response.data.exp; + if (!Switcher.options.offline) { + await Switcher._auth(); } } + /** + * Validate context before querying the API + */ async validate() { let errors = []; - if (!this.apiKey) { + if (!Switcher.context.apiKey) { errors.push('Missing API Key field'); } - if (!this.component) { + if (!Switcher.context.component) { errors.push('Missing component field'); } - if (!this.key) { - errors.push('Missing key field'); + if (!Switcher.context.url) { + errors.push('Missing url field'); } - if (!this.url) { - errors.push('Missing url field'); + if (!this.key) { + errors.push('Missing key field'); } - if (!this.exp || Date.now() > (this.exp*1000)) { + if (!Switcher.context.exp || Date.now() > (Switcher.context.exp*1000)) { await this.prepare(this.key, this.input); } - if (!this.token) { + if (!Switcher.context.token) { errors.push('Missing token field'); } @@ -108,14 +180,16 @@ class Switcher { } async isItOn(key, input, showReason = false) { + // verify if query from Bypasser const bypassKey = Bypasser.searchBypassed(this.key ? this.key : key); if (bypassKey) { return bypassKey.getValue(); } - if (this.offline) { + // verify if query from snapshot + if (Switcher.options.offline) { const result = checkCriteriaOffline( - this.key ? this.key : key, this.input ? this.input : input, this.snapshot); + this.key ? this.key : key, this.input ? this.input : input, Switcher.snapshot); ExecutionLogger.add(this.key, result); return result; @@ -125,86 +199,23 @@ class Switcher { if (input) { this.input = input; } await this.validate(); - if (this.token === 'SILENT') { + if (Switcher.context.token === 'SILENT') { const result = checkCriteriaOffline( - this.key ? this.key : key, this.input ? this.input : input, this.snapshot); + this.key ? this.key : key, this.input ? this.input : input, Switcher.snapshot); + + if (Switcher.options.logger) + ExecutionLogger.add(this.key, result); - if (this.logger) ExecutionLogger.add(this.key, result); return result; } else { - const response = await services.checkCriteria( - this.url, this.token, this.key, this.input, showReason); + const response = await services.checkCriteria(Switcher.context, this.key, this.input, showReason); - if (this.logger) ExecutionLogger.add(this.key, response.data); - return response.data.result; - } - } - - async checkSnapshot() { - if (!this.exp || Date.now() > (this.exp*1000)) { - const response = await services.auth(this.url, this.apiKey, this.domain, this.component, this.environment, { - silentMode: this.silentMode, - retryTime: this.retryTime, - retryDurationIn: this.retryDurationIn - }); - - this.token = response.data.token; - this.exp = response.data.exp; - - const result = await validateSnapshot( - this.url, this.token, this.domain, this.environment, this.component, this.snapshotLocation, this.snapshot.data.domain.version); - - if (result) { - this.loadSnapshot(); - return true; - } - } - return false; - } - - async loadSnapshot() { - if (this.snapshotLocation) { - const snapshotFile = `${this.snapshotLocation}${this.environment}.json`; - this.snapshot = loadDomain(this.snapshotLocation, this.environment, this.snapshotAutoload); - - if (this.snapshot.data.domain.version == 0 && !this.offline) { - await this.checkSnapshot(); - } else if (!testEnabled) { - fs.unwatchFile(snapshotFile); - fs.watchFile(snapshotFile, () => { - this.snapshot = loadDomain(this.snapshotLocation, this.environment, this.snapshotAutoload); - }); - } - } - } + if (Switcher.options.logger) + ExecutionLogger.add(this.key, response.data); - unloadSnapshot() { - if (!testEnabled && this.snapshotLocation) { - const snapshotFile = `${this.snapshotLocation}${this.environment}.json`; - this.snapshot = undefined; - fs.unwatchFile(snapshotFile); + return response.data.result; } } - - static get StrategiesType() { - return StrategiesType; - } - - static assume(key) { - return Bypasser.assume(key); - } - - static forget(key) { - return Bypasser.forget(key); - } - - static getLogger(key) { - return ExecutionLogger.getByKey(key); - } - - static setTestEnabled() { - testEnabled = true; - } } diff --git a/src/lib/services.js b/src/lib/services.js index d38f4e6..b8b8710 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -33,7 +33,7 @@ exports.getEntry = (input) => { return entry; }; -exports.checkCriteria = async (url, token, key, input, showReason = false) => { +exports.checkCriteria = async ({ url, token }, key, input, showReason = false) => { try { const entry = this.getEntry(input); return await axios.post(`${url}/criteria?showReason=${showReason}&key=${key}`, @@ -43,7 +43,7 @@ exports.checkCriteria = async (url, token, key, input, showReason = false) => { } }; -exports.auth = async (url, apiKey, domain, component, environment, options) => { +exports.auth = async ({ url, apiKey, domain, component, environment }, options) => { try { return await axios.post(`${url}/criteria/auth`, { domain, @@ -111,7 +111,6 @@ class AuthError extends Error { constructor(message) { super(`Something went wrong: ${message}`); this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor); } } @@ -119,7 +118,6 @@ class CriteriaError extends Error { constructor(message) { super(`Something went wrong: ${message}`); this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor); } } @@ -127,6 +125,5 @@ class SnapshotServiceError extends Error { constructor(message) { super(`Something went wrong: ${message}`); this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor); } } \ No newline at end of file diff --git a/src/lib/snapshot.js b/src/lib/snapshot.js index 3a7637d..5a9d7ce 100644 --- a/src/lib/snapshot.js +++ b/src/lib/snapshot.js @@ -3,18 +3,16 @@ const IPCIDR = require('ip-cidr'); const DateMoment = require('./datemoment'); const { resolveSnapshot, checkSnapshotVersion } = require('./services'); -const loadDomain = (snapshotLocation, environment, snapshotAutoload) => { +const loadDomain = (snapshotLocation, environment) => { try { let dataBuffer; const snapshotFile = `${snapshotLocation}${environment}.json`; if (fs.existsSync(snapshotFile)) { dataBuffer = fs.readFileSync(snapshotFile); - } else if (snapshotAutoload) { + } else { dataBuffer = JSON.stringify({ data: { domain: { version: 0 } } }, null, 4); fs.mkdir(snapshotLocation, { recursive: true }, () => {}); fs.writeFileSync(snapshotFile, dataBuffer); - } else { - throw new Error(); } const dataJSON = dataBuffer.toString(); @@ -24,7 +22,7 @@ const loadDomain = (snapshotLocation, environment, snapshotAutoload) => { } }; -const validateSnapshot = async (url, token, domain, environment, component, snapshotLocation, snapshotVersion) => { +const validateSnapshot = async ({ url, token, domain, environment, component }, snapshotLocation, snapshotVersion) => { const { status } = await checkSnapshotVersion(url, token, snapshotVersion); if (!status) { diff --git a/test/playground/index.js b/test/playground/index.js index 8f1060d..3d0f94c 100644 --- a/test/playground/index.js +++ b/test/playground/index.js @@ -12,41 +12,42 @@ function setupSwitcher(offline) { const environment = 'default'; const url = 'http://localhost:3000'; - switcher = new Switcher(url, apiKey, domain, component, environment, { - offline, logger: true - }); - switcher.loadSnapshot(); + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline, logger: true }); + Switcher.loadSnapshot(); } // Requires online API const testSimpleAPICall = async () => { setupSwitcher(false); + const switcher = Switcher.factory(); await switcher.isItOn('FEATURE01', null, true); await switcher.isItOn('FEATURE02', null, true); console.log(Switcher.getLogger('FEATURE01')); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }; // Requires online API const testSnapshotUpdate = async () => { setupSwitcher(false); + const switcher = Switcher.factory(); let result = await switcher.isItOn('FEATURE2020'); console.log(result); - await switcher.checkSnapshot(); + await Switcher.checkSnapshot(); await new Promise(resolve => setTimeout(resolve, 1000)); result = await switcher.isItOn('FEATURE2020'); console.log(result); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }; const testAsyncCall = async () => { setupSwitcher(true); + const switcher = Switcher.factory(); let result = await switcher.isItOn('FEATURE2020'); console.log(result); @@ -59,11 +60,12 @@ const testAsyncCall = async () => { result = await switcher.isItOn('FEATURE2020'); console.log('Value changed:', result); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }; const testBypasser = async () => { setupSwitcher(true); + const switcher = Switcher.factory(); let result = await switcher.isItOn('FEATURE2020'); console.log(result); @@ -76,7 +78,7 @@ const testBypasser = async () => { result = await switcher.isItOn('FEATURE2020'); console.log(result); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }; // Requires online API @@ -87,16 +89,14 @@ const testSnapshotAutoload = async () => { const environment = 'generated'; const url = 'http://localhost:3000'; - switcher = new Switcher(url, apiKey, domain, component, environment, { - snapshotAutoload: true - }); - - await switcher.loadSnapshot(); + Switcher.buildContext({ url, apiKey, domain, component, environment }); + await Switcher.loadSnapshot(); + const switcher = Switcher.factory(); let result = await switcher.isItOn('FEATURE2020'); console.log(result); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }; testSnapshotAutoload(); \ No newline at end of file diff --git a/test/switcher-client.test.js b/test/switcher-client.test.js index 1a8c527..2cabbd1 100644 --- a/test/switcher-client.test.js +++ b/test/switcher-client.test.js @@ -13,15 +13,17 @@ describe('E2E test - Switcher offline:', function () { const environment = 'default'; const url = 'http://localhost:3000'; - this.beforeAll(function() { - switcher = new Switcher(url, apiKey, domain, component, environment, { + this.beforeAll(async function() { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline: true, logger: true }); - switcher.loadSnapshot(); + + await Switcher.loadSnapshot(); + switcher = Switcher.factory(); }); this.afterAll(function() { - fs.unwatchFile('./snapshot/default.json'); + Switcher.unloadSnapshot(); }); it('should be valid - isItOn', async function () { @@ -60,7 +62,7 @@ describe('E2E test - Switcher offline:', function () { }); it('should be valid assuming key to be false and then forgetting it', async function () { - await switcher.loadSnapshot(); + await Switcher.loadSnapshot(); await switcher.prepare('FF2FOR2020', [Switcher.StrategiesType.VALUE, 'Japan', Switcher.StrategiesType.NETWORK, '10.0.0.3']); assert.isTrue(await switcher.isItOn()); @@ -85,13 +87,13 @@ describe('E2E test - Switcher offline:', function () { }); }); - it('should enable test mode which does not need load a snapshot', async function () { + it('should enable test mode which will prevent a snapshot to be watchable', async function () { //given - switcher = new Switcher(url, apiKey, domain, component, environment, { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline: true, logger: true }); - Switcher.setTestEnabled(); + switcher = Switcher.factory(); //test Switcher.assume('FF2FOR2020').false(); @@ -100,33 +102,33 @@ describe('E2E test - Switcher offline:', function () { assert.isTrue(await switcher.isItOn('FF2FOR2020')); }); - it('should be invalid - Offline mode did not found a snapshot file', async function () { + it('should be invalid - Offline mode cannot load snapshot from an invalid path', async function () { + this.timeout(3000); + try { - const switcher = new Switcher(url, apiKey, domain, component, environment, { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline: true, - snapshotLocation: 'somewhere/' + snapshotLocation: '//somewhere/' }); - await switcher.loadSnapshot(); - assert.isNotNull(switcher.snapshot); + + Switcher.setTestEnabled(); + await Switcher.loadSnapshot(); } catch (error) { - assert.equal('Something went wrong: It was not possible to load the file at somewhere/', error.message); + assert.equal('Something went wrong: It was not possible to load the file at //somewhere/', error.message); } }); - it('should be valid - Offline mode w/ autoload snapshot', async function () { + it('should be valid - Offline mode', async function () { this.timeout(3000); try { - const switcher = new Switcher(url, apiKey, domain, component, environment, { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline: true, - snapshotLocation: 'generated-snapshots/', - snapshotAutoload: true + snapshotLocation: 'generated-snapshots/' }); - await switcher.loadSnapshot(); - assert.isNotNull(switcher.snapshot); - switcher.unloadSnapshot(); - fs.unlinkSync(`generated-snapshots/${environment}.json`); + await Switcher.loadSnapshot(); + assert.isNotNull(Switcher.snapshot); } catch (error) { assert.equal('Something went wrong: It was not possible to load the file at generated-snapshots/', error.message); } @@ -139,6 +141,10 @@ describe('Unit test - Switcher:', function () { fs.unwatchFile('./snapshot/default.json'); }); + this.beforeEach(function() { + Switcher.setTestEnabled(); + }); + describe('check criteria:', function () { let requestStub; @@ -158,7 +164,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: true } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); await switcher.prepare('FLAG_1', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']); assert.isTrue(await switcher.isItOn()); @@ -169,7 +176,9 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: true } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+1000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); const spyPrepare = sinon.spy(switcher, 'prepare'); // Prepare the call generating the token @@ -195,7 +204,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: true } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); assert.isTrue(await switcher.isItOn('MY_FLAG', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1'])); }); @@ -203,13 +213,15 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: true } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); await switcher.prepare('MY_FLAG'); assert.isTrue(await switcher.isItOn(undefined, [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1'])); }); it('should be invalid - Missing url field', async function () { - let switcher = new Switcher(undefined, 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); await switcher.prepare('MY_FLAG', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']); @@ -221,7 +233,8 @@ describe('Unit test - Switcher:', function () { }); it('should be invalid - Missing API Key field', async function () { - let switcher = new Switcher('url', undefined, 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: undefined, domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); await switcher.prepare('MY_FLAG', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']); @@ -236,7 +249,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: undefined } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); await switcher.prepare(undefined, [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']); switcher.isItOn().then(function (result) { @@ -250,7 +264,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: undefined } })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', undefined, 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: undefined, environment: 'default' }); + let switcher = Switcher.factory(); switcher.isItOn('MY_FLAG', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']).then(function (result) { assert.isUndefined(result); }, function (error) { @@ -262,7 +277,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ data: { result: undefined } })); clientAuth.returns(Promise.resolve({ data: { token: undefined, exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); switcher.isItOn('MY_FLAG', [Switcher.StrategiesType.VALUE, 'User 1', Switcher.StrategiesType.NETWORK, '192.168.0.1']).then(function (result) { assert.isUndefined(result); }, function (error) { @@ -274,7 +290,8 @@ describe('Unit test - Switcher:', function () { requestStub.returns(Promise.resolve({ result: undefined })); clientAuth.returns(Promise.resolve({ data: { token: 'uqwu1u8qj18j28wj28', exp: (Date.now()+5000)/1000 } })); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); await switcher.prepare('MY_WRONG_FLAG', ['THIS IS WRONG']); switcher.isItOn().then(function (result) { assert.isUndefined(result); @@ -293,11 +310,14 @@ describe('Unit test - Switcher:', function () { errno: 'ECONNREFUSED' }); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default', { + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }, { silentMode: true, retryAfter: '1s' }); - await switcher.loadSnapshot(); + + Switcher.setTestEnabled(); + await Switcher.loadSnapshot(); + let switcher = Switcher.factory(); const spyPrepare = sinon.spy(switcher, 'prepare'); // First attempt to reach the online API - Since it's configured to use silent mode, it should return true (according to the snapshot) @@ -340,7 +360,8 @@ describe('Unit test - Switcher:', function () { errno: 'ECONNREFUSED' }); - let switcher = new Switcher('url', 'apiKey', 'domain', 'component', 'default'); + Switcher.buildContext({ url: 'url', apiKey: 'apiKey', domain: 'domain', component: 'component', environment: 'default' }); + let switcher = Switcher.factory(); await switcher.isItOn('FF2FOR2030').then(function (result) { assert.isUndefined(result); @@ -354,7 +375,6 @@ describe('Unit test - Switcher:', function () { }); describe('E2E test - Switcher offline - Snapshot:', function () { - let switcher; const apiKey = '$2b$08$S2Wj/wG/Rfs3ij0xFbtgveDtyUAjML1/TOOhocDg5dhOaU73CEXfK'; const domain = 'currency-api'; const component = 'Android'; @@ -384,13 +404,14 @@ describe('E2E test - Switcher offline - Snapshot:', function () { }); beforeEach(function() { - switcher = new Switcher(url, apiKey, domain, component, environment, { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { offline: true }); + Switcher.setTestEnabled(); }); this.afterAll(function() { - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); }); it('should update snapshot', async function () { @@ -404,16 +425,15 @@ describe('E2E test - Switcher offline - Snapshot:', function () { requestPostStub.returns(Promise.resolve(JSON.parse(dataJSON))); // Mock finishes - switcher = new Switcher(url, apiKey, domain, component, environment, { + Switcher.buildContext({ url, apiKey, domain, component, environment }, { snapshotLocation: 'generated-snapshots/', - snapshotAutoload: true, offline: true }); - await switcher.loadSnapshot(); - assert.isTrue(await switcher.checkSnapshot()); + await Switcher.loadSnapshot(); + assert.isTrue(await Switcher.checkSnapshot()); - switcher.unloadSnapshot(); + Switcher.unloadSnapshot(); fs.unlinkSync(`generated-snapshots/${environment}.json`); }); @@ -426,8 +446,8 @@ describe('E2E test - Switcher offline - Snapshot:', function () { requestGetStub.returns(Promise.resolve({ data: { status: true } })); // No available update // Mocking finishes - await switcher.loadSnapshot(); - assert.isFalse(await switcher.checkSnapshot()); + await Switcher.loadSnapshot(); + assert.isFalse(await Switcher.checkSnapshot()); }); it('should NOT update snapshot - check Snapshot Error', async function () { @@ -443,8 +463,9 @@ describe('E2E test - Switcher offline - Snapshot:', function () { }); // Mocking finishes - await switcher.loadSnapshot(); - await switcher.checkSnapshot().then(function (result) { + Switcher.setTestEnabled(); + await Switcher.loadSnapshot(); + await Switcher.checkSnapshot().then(function (result) { assert.isUndefined(result); }, function (error) { assert.equal('Something went wrong: Connection has been refused - ECONNREFUSED', error.message); @@ -464,15 +485,16 @@ describe('E2E test - Switcher offline - Snapshot:', function () { }); // Mocking finishes - await switcher.loadSnapshot(); - await switcher.checkSnapshot().then(function (result) { + Switcher.setTestEnabled(); + await Switcher.loadSnapshot(); + await Switcher.checkSnapshot().then(function (result) { assert.isUndefined(result); }, function (error) { assert.equal('Something went wrong: Connection has been refused - ECONNREFUSED', error.message); }); }); - it('should update snapshot - snapshot autoload activated', async function () { + it('should update snapshot', async function () { // Mocking starts clientAuth = sinon.stub(services, 'auth'); requestGetStub = sinon.stub(axios, 'get'); @@ -487,14 +509,14 @@ describe('E2E test - Switcher offline - Snapshot:', function () { // Mocking finishes try { - const switcher = new Switcher(url, apiKey, domain, component, environment, { - snapshotLocation: 'generated-snapshots/', - snapshotAutoload: true + Switcher.buildContext({ url, apiKey, domain, component, environment }, { + snapshotLocation: 'generated-snapshots/' }); - await switcher.loadSnapshot(); - assert.isNotNull(switcher.snapshot); - switcher.unloadSnapshot(); + await Switcher.loadSnapshot(); + assert.isNotNull(Switcher.snapshot); + + Switcher.unloadSnapshot(); fs.unlinkSync(`generated-snapshots/${environment}.json`); } catch (error) { assert.equal('Something went wrong: It was not possible to load the file at generated-snapshots/', error.message);