Skip to content

Commit

Permalink
Added support to ResultDetail (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
petruki committed Apr 6, 2024
1 parent 9cefc6d commit 6debebd
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .nycrc
@@ -1,6 +1,6 @@
{
"all": true,
"exclude": ["test", "src/lib/utils/fetchFacade.js"],
"exclude": ["test", "src/lib/utils/timed-match/match-proc.js", "src/lib/utils/fetchFacade.js"],
"output": "reports",
"reporter" : [
"html",
Expand Down
10 changes: 8 additions & 2 deletions src/index.d.ts
Expand Up @@ -125,9 +125,9 @@ declare namespace SwitcherClient {
*
* @param key
* @param input
* @param showReason
* @param showDetail (returns ResultDetail object)
*/
isItOn(key?: string, input?: string[], showReason?: boolean): Promise<boolean>;
isItOn(key?: string, input?: string[], showDetail?: boolean): Promise<boolean | ResultDetail>;

/**
* Configure the time elapsed between each call to the API.
Expand All @@ -145,6 +145,12 @@ declare namespace SwitcherClient {
remote(forceRemote: boolean): Switcher;
}

interface ResultDetail {
result: boolean;
reason: string;
metadata: any;
}

interface SwitcherContext {
url: string;
apiKey: string;
Expand Down
67 changes: 30 additions & 37 deletions src/index.js
Expand Up @@ -5,18 +5,16 @@ import DateMoment from './lib/utils/datemoment.js';
import SnapshotAutoUpdater from './lib/utils/snapshotAutoUpdater.js';
import { loadDomain, validateSnapshot, checkSwitchersLocal } from './lib/snapshot.js';
import { SnapshotNotFoundError } from './lib/exceptions/index.js';
import { setCerts, checkSwitchersRemote, auth, checkAPIHealth, checkCriteria } from './lib/remote.js';
import checkCriteriaOffline from './lib/resolver.js';
import * as services from './lib/remote.js';
import checkCriteriaLocal from './lib/resolver.js';
import { writeFileSync, watchFile, unwatchFile } from 'fs';

import { checkDate, checkNetwork, checkNumeric, checkRegex, checkTime, checkValue, checkPayload } from './lib/middlewares/check.js';

const DEFAULT_ENVIRONMENT = 'default';
const DEFAULT_LOCAL = false;
const DEFAULT_LOGGER = false;
const DEFAULT_TEST_MODE = false;

class Switcher {
export class Switcher {

constructor() {
this._delay = 0;
Expand Down Expand Up @@ -49,7 +47,7 @@ class Switcher {

static _buildOptions(options) {
if ('certPath' in options && options.certPath) {
setCerts(options.certPath);
services.setCerts(options.certPath);
}

if ('silentMode' in options && options.silentMode) {
Expand Down Expand Up @@ -165,7 +163,7 @@ class Switcher {
static async _checkSwitchersRemote(switcherKeys) {
try {
await Switcher._auth();
await checkSwitchersRemote(Switcher.context.url, Switcher.context.token, switcherKeys);
await services.checkSwitchersRemote(Switcher.context.url, Switcher.context.token, switcherKeys);
} catch (e) {
if (Switcher.options.silentMode) {
checkSwitchersLocal(Switcher.snapshot, switcherKeys);
Expand Down Expand Up @@ -199,7 +197,7 @@ class Switcher {
}

static async _auth() {
const response = await auth(Switcher.context);
const response = await services.auth(Switcher.context);
Switcher.context.token = response.token;
Switcher.context.exp = response.exp;
}
Expand All @@ -211,7 +209,7 @@ class Switcher {

if (Switcher._isTokenExpired()) {
Switcher._updateSilentToken();
checkAPIHealth(Switcher.context.url || '').then((isAlive) => {
services.checkAPIHealth(Switcher.context.url || '').then((isAlive) => {
if (isAlive) {
Switcher._auth();
}
Expand Down Expand Up @@ -294,7 +292,7 @@ class Switcher {
}
}

async isItOn(key, input, showReason = false) {
async isItOn(key, input, showDetail = false) {
let result;
this._validateArgs(key, input);

Expand All @@ -306,19 +304,19 @@ class Switcher {

// verify if query from snapshot
if (Switcher.options.local && !this._forceRemote) {
result = await this._executeOfflineCriteria();
result = await this._executeLocalCriteria();
} else {
try {
await this.validate();
if (Switcher.context.token === 'SILENT') {
result = await this._executeOfflineCriteria();
result = await this._executeLocalCriteria();
} else {
result = await this._executeRemoteCriteria(showReason);
result = await this._executeRemoteCriteria(showDetail);
}
} catch (e) {
if (Switcher.options.silentMode) {
Switcher._updateSilentToken();
return this._executeOfflineCriteria();
return this._executeLocalCriteria();
}

throw e;
Expand All @@ -331,8 +329,9 @@ class Switcher {
throttle(delay) {
this._delay = delay;

if (delay > 0)
if (delay > 0) {
Switcher.options.logger = true;
}

return this;
}
Expand All @@ -346,24 +345,29 @@ class Switcher {
return this;
}

async _executeRemoteCriteria(showReason) {
if (!this._useSync())
return this._executeAsyncRemoteCriteria(showReason);
async _executeRemoteCriteria(showDetail) {
if (!this._useSync()) {
return this._executeAsyncRemoteCriteria(showDetail);
}

const responseCriteria = await checkCriteria(
Switcher.context, this._key, this._input, showReason);
const responseCriteria = await services.checkCriteria(
Switcher.context, this._key, this._input, showDetail);

if (Switcher.options.logger) {
ExecutionLogger.add(responseCriteria, this._key, this._input);
}

if (showDetail) {
return responseCriteria;
}

return responseCriteria.result;
}

async _executeAsyncRemoteCriteria(showReason) {
async _executeAsyncRemoteCriteria(showDetail) {
if (this._nextRun < Date.now()) {
this._nextRun = Date.now() + this._delay;
checkCriteria(Switcher.context, this._key, this._input, showReason)
services.checkCriteria(Switcher.context, this._key, this._input, showDetail)
.then(response => ExecutionLogger.add(response, this._key, this._input));
}

Expand All @@ -377,12 +381,12 @@ class Switcher {

Switcher._checkHealth();
if (Switcher._isTokenExpired()) {
await this.prepare(this._key, this._input);
await this.prepare(this._key, this._input);
}
}

async _executeOfflineCriteria() {
const response = await checkCriteriaOffline(
async _executeLocalCriteria() {
const response = await checkCriteriaLocal(
this._key, this._input, Switcher.snapshot);

if (Switcher.options.logger) {
Expand All @@ -401,15 +405,4 @@ class Switcher {
return this._delay == 0 || !ExecutionLogger.getExecution(this._key, this._input);
}

}

export {
Switcher,
checkDate,
checkNetwork,
checkNumeric,
checkRegex,
checkTime,
checkValue,
checkPayload
};
}
6 changes: 3 additions & 3 deletions src/lib/remote.js
Expand Up @@ -53,16 +53,16 @@ export async function checkAPIHealth(url) {
}
}

export async function checkCriteria({ url, token }, key, input, showReason = false) {
export async function checkCriteria({ url, token }, key, input, showDetail = false) {
try {
const entry = getEntry(input);
const response = await FetchFacade.fetch(`${url}/criteria?showReason=${showReason}&key=${key}`, {
const response = await FetchFacade.fetch(`${url}/criteria?showReason=${showDetail}&key=${key}`, {
method: 'post',
body: JSON.stringify({ entry }),
headers: getHeader(token),
agent: httpClient
});

if (response.status == 200) {
return response.json();
}
Expand Down
8 changes: 3 additions & 5 deletions src/lib/resolver.js
Expand Up @@ -103,12 +103,12 @@ async function checkStrategyInput(entry, { strategy, operation, values }) {
}
}

async function checkCriteriaOffline(key, input, snapshot) {
export default async function checkCriteriaLocal(key, input, snapshot) {
if (!snapshot) {
throw new Error('Snapshot not loaded. Try to use \'Switcher.loadSnapshot()\'');
}

const { data } = snapshot;
const { data } = snapshot;
return await resolveCriteria(key, input, data);
}

Expand All @@ -117,6 +117,4 @@ class CriteriaFailed extends Error {
super(reason);
this.name = this.constructor.name;
}
}

export default checkCriteriaOffline;
}
23 changes: 13 additions & 10 deletions switcher-client.js
@@ -1,10 +1,13 @@
export {
Switcher,
checkDate,
checkNetwork,
checkNumeric,
checkRegex,
checkTime,
checkValue,
checkPayload
} from './src/index.js';
import ExecutionLogger from './src/lib/utils/executionLogger.js';

export { Switcher } from './src/index.js';
export { ExecutionLogger };
export {
checkDate,
checkNetwork,
checkNumeric,
checkRegex,
checkTime,
checkValue,
checkPayload
} from './src/lib/middlewares/check.js';
4 changes: 4 additions & 0 deletions test/helper/utils.js
Expand Up @@ -46,4 +46,8 @@ export const generateResult = (result) => {
return {
result
};
};

export const generateDetailedResult = (detailedResult) => {
return detailedResult;
};
5 changes: 3 additions & 2 deletions test/playground/index.js
@@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-console */

import { Switcher, checkValue, checkNumeric } from '../../src/index.js';
import { Switcher, checkValue, checkNumeric } from '../../switcher-client.js';

const SWITCHER_KEY = 'MY_SWITCHER';
const apiKey = 'JDJiJDA4JEFweTZjSTR2bE9pUjNJOUYvRy9raC4vRS80Q2tzUnk1d3o1aXFmS2o5eWJmVW11cjR0ODNT';
Expand Down Expand Up @@ -78,7 +78,8 @@ const _testThrottledAPICall = async () => {
switcher.throttle(1000);

for (let index = 0; index < 10; index++) {
console.log(`Call #${index} - ${await switcher.isItOn(SWITCHER_KEY, [checkNumeric('1')])}}`);
const result = await switcher.isItOn(SWITCHER_KEY, [checkNumeric('1')]);
console.log(`Call #${index} - ${String(result)}`);
}

Switcher.unloadSnapshot();
Expand Down
2 changes: 1 addition & 1 deletion test/switcher-client.test.js
@@ -1,7 +1,7 @@
import { rmdir } from 'fs';
import { assert } from 'chai';

import { Switcher, checkValue, checkNetwork, checkPayload, checkRegex } from '../src/index.js';
import { Switcher, checkValue, checkNetwork, checkPayload, checkRegex } from '../switcher-client.js';
import { StrategiesType } from '../src/lib/snapshot.js';
import { assertReject, assertResolve } from './helper/utils.js';

Expand Down
25 changes: 23 additions & 2 deletions test/switcher-integrated.test.js
Expand Up @@ -3,8 +3,8 @@ import { stub, spy } from 'sinon';
import { unwatchFile } from 'fs';

import FetchFacade from '../src/lib/utils/fetchFacade.js';
import { Switcher, checkValue, checkNetwork, checkDate, checkTime, checkRegex, checkNumeric, checkPayload } from '../src/index.js';
import { given, givenError, throws, generateAuth, generateResult, assertReject, assertResolve } from './helper/utils.js';
import { Switcher, checkValue, checkNetwork, checkDate, checkTime, checkRegex, checkNumeric, checkPayload } from '../switcher-client.js';
import { given, givenError, throws, generateAuth, generateResult, assertReject, assertResolve, generateDetailedResult } from './helper/utils.js';

describe('Integrated test - Switcher:', function () {

Expand Down Expand Up @@ -139,6 +139,27 @@ describe('Integrated test - Switcher:', function () {
assert.equal(executeRemoteCriteria.callCount, 1);
});

it('should return true - including reason and metadata', async function () {
// given API responding properly
given(fetchStub, 0, { json: () => generateAuth('[auth_token]', 5), status: 200 });
given(fetchStub, 1, { json: () => generateDetailedResult({
result: true,
reason: 'Success',
metadata: {
user: 'user1',
}
}), status: 200 });

// test
Switcher.buildContext(contextSettings);

const switcher = Switcher.factory();
const detailedResult = await switcher.isItOn('FF2FOR2030', undefined, true);
assert.isTrue(detailedResult.result);
assert.equal(detailedResult.reason, 'Success');
assert.equal(detailedResult.metadata.user, 'user1');
});

it('should return error when local is not enabled', async function () {
Switcher.buildContext(contextSettings, { regexSafe: false, local: false });

Expand Down

0 comments on commit 6debebd

Please sign in to comment.