Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/lib/bypasser/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*/
export default class Key {
key: string;
value?: boolean;
value: boolean;

constructor(key: string) {
this.key = key;
this.value = undefined;
this.value = false;
}

/**
Expand All @@ -34,7 +34,7 @@ export default class Key {
/**
* Return current value
*/
getValue(): boolean | undefined {
getValue(): boolean {
return this.value;
}
}
54 changes: 37 additions & 17 deletions src/lib/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ const getHeader = (token: string | undefined) => {
};
};

const trySilent = (options: SwitcherOptions, retryOptions: RetryOptions) => {
if (options && 'silentMode' in options) {
if (options.silentMode) {
const expirationTime = new DateMoment(new Date())
.add(retryOptions.retryTime, retryOptions.retryDurationIn).getDate();

return {
data: {
token: 'SILENT',
exp: expirationTime.getTime() / 1000,
},
};
}
}
};

export const getEntry = (input?: string[][]) => {
if (!input) {
return undefined;
Expand Down Expand Up @@ -45,19 +61,7 @@ export const checkAPIHealth = async (url: string, options: SwitcherOptions, retr
throw new ApiConnectionError('API is offline');
}
} catch (_e) {
if (options && 'silentMode' in options) {
if (options.silentMode) {
const expirationTime = new DateMoment(new Date())
.add(retryOptions.retryTime, retryOptions.retryDurationIn).getDate();

return {
data: {
token: 'SILENT',
exp: expirationTime.getTime() / 1000,
},
};
}
}
return trySilent(options, retryOptions);
}
};

Expand All @@ -78,7 +82,11 @@ export const checkCriteria = async (
},
);

return response.json();
if (response.status == 200) {
return response.json();
}

throw new Error(`[checkCriteria] failed with status ${response.status}`);
} catch (e) {
throw new CriteriaError(
e.errno ? getConnectivityError(e.errno) : e.message,
Expand All @@ -101,7 +109,11 @@ export const auth = async (context: SwitcherContext) => {
},
});

return response.json();
if (response.status == 200) {
return response.json();
}

throw new Error(`[auth] failed with status ${response.status}`);
} catch (e) {
throw new AuthError(e.errno ? getConnectivityError(e.errno) : e.message);
}
Expand Down Expand Up @@ -145,7 +157,11 @@ export const checkSnapshotVersion = async (
headers: getHeader(token),
});

return response.json();
if (response.status == 200) {
return response.json();
}

throw new Error(`[checkSnapshotVersion] failed with status ${response.status}`);
} catch (e) {
throw new SnapshotServiceError(
e.errno ? getConnectivityError(e.errno) : e.message,
Expand Down Expand Up @@ -182,7 +198,11 @@ export const resolveSnapshot = async (
headers: getHeader(token),
});

return JSON.stringify(await response.json(), null, 4);
if (response.status == 200) {
return JSON.stringify(await response.json(), null, 4);
}

throw new Error(`[resolveSnapshot] failed with status ${response.status}`);
} catch (e) {
throw new SnapshotServiceError(
e.errno ? getConnectivityError(e.errno) : e.message,
Expand Down
40 changes: 28 additions & 12 deletions src/switcher-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,20 @@ export class Switcher {
if (Switcher._options.offline && Switcher._snapshot) {
checkSwitchers(Switcher._snapshot, switcherKeys);
} else {
await Switcher._auth();
await services.checkSwitchers(
Switcher._context.url || '',
Switcher._context.token,
switcherKeys,
);
try {
await Switcher._auth();
await services.checkSwitchers(
Switcher._context.url || '',
Switcher._context.token,
switcherKeys,
);
} catch (e) {
if (Switcher._options.silentMode) {
checkSwitchers(Switcher._snapshot!, switcherKeys);
} else {
throw e;
}
}
}
}

Expand Down Expand Up @@ -384,7 +392,7 @@ export class Switcher {
* @param input
* @param showReason Display details when using ExecutionLogger
*/
async isItOn(key?: string, input?: string[][], showReason = false) {
async isItOn(key?: string, input?: string[][], showReason = false): Promise<boolean> {
let result;
this._validateArgs(key, input);

Expand All @@ -398,11 +406,19 @@ export class Switcher {
if (Switcher._options.offline) {
result = await this._executeOfflineCriteria();
} else {
await this.validate();
if (Switcher._context.token === 'SILENT') {
result = await this._executeOfflineCriteria();
} else {
result = await this._executeOnlineCriteria(showReason);
try {
await this.validate();
if (Switcher._context.token === 'SILENT') {
result = await this._executeOfflineCriteria();
} else {
result = await this._executeOnlineCriteria(showReason);
}
} catch (e) {
if (Switcher._options.silentMode) {
return this._executeOfflineCriteria();
}

throw e;
}
}

Expand Down
92 changes: 92 additions & 0 deletions test/switcher-integrated.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ describe('Integrated test - Switcher:', function () {
assertTrue(await switcher.isItOn());
});

it('should NOT be valid - API returned 429 (too many requests)', async function () {
// given API responding properly
given('GET@/check', null);
given('POST@/criteria/auth', null, 429);

// test
Switcher.buildContext(contextSettings);
const switcher = Switcher.factory();

await assertRejects(async () =>
await switcher.isItOn('FLAG_1'),
Error, 'Something went wrong: [auth] failed with status 429');
});

it('should be valid - throttle', async function () {
// given API responding properly
given('GET@/check', null);
Expand Down Expand Up @@ -94,6 +108,84 @@ describe('Integrated test - Switcher:', function () {
});
});

describe('check fail response (e2e):', function () {

let contextSettings: SwitcherContext;

afterAll(function() {
Switcher.unloadSnapshot();
});

beforeEach(function() {
tearDown();
Switcher.setTestEnabled();

contextSettings = {
url: 'http://localhost:3000',
apiKey: '[apiKey]',
domain: '[domain]',
component: '[component]',
environment: 'default'
};
});

it('should NOT be valid - API returned 429 (too many requests) at checkHealth/auth', async function () {
// given API responding properly
given('GET@/check', null, 429);
given('POST@/criteria/auth', { error: 'Too many requests' }, 429);

// test
Switcher.buildContext(contextSettings);
const switcher = Switcher.factory();

await assertRejects(async () =>
await switcher.isItOn('FLAG_1'),
Error, 'Something went wrong: [auth] failed with status 429');
});

it('should NOT be valid - API returned 429 (too many requests) at checkCriteria', async function () {
// given API responding properly
given('GET@/check', null);
given('POST@/criteria/auth', generateAuth('[auth_token]', 5));
given('POST@/criteria', { error: 'Too many requests' }, 429);

// test
Switcher.buildContext(contextSettings);
const switcher = Switcher.factory();

await assertRejects(async () =>
await switcher.isItOn('FLAG_1'),
Error, 'Something went wrong: [checkCriteria] failed with status 429');
});

it('should use silent mode when fail to check switchers', async function() {
//given
given('GET@/check', null, 429);

//test
Switcher.buildContext(contextSettings, { silentMode: true });
await assertRejects(async () =>
await Switcher.checkSwitchers(['FEATURE01', 'FEATURE02']),
Error, 'Something went wrong: [FEATURE01,FEATURE02] not found');

await Switcher.checkSwitchers(['FF2FOR2021', 'FF2FOR2021']);
});

it('should use silent mode when fail to check criteria', async function () {
// given API responding properly
given('GET@/check', null);
given('POST@/criteria/auth', generateAuth('[auth_token]', 5));
given('POST@/criteria', { error: 'Too many requests' }, 429);

// test
Switcher.buildContext(contextSettings, { silentMode: true });
const switcher = Switcher.factory();

assertTrue(await switcher.isItOn('FF2FOR2022'));
});

});

describe('check criteria:', function () {

beforeEach(function() {
Expand Down
29 changes: 29 additions & 0 deletions test/switcher-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ describe('E2E test - Switcher offline - Snapshot:', function () {
Deno.removeSync('generated-snapshots/', { recursive: true });
});

it('should NOT update snapshot - Too many requests at checkSnapshotVersion', testSettings, async function () {
//give
given('POST@/criteria/auth', generateAuth(token, 5));
given('GET@/criteria/snapshot_check/:version', null, 429);

//test
Switcher.setTestEnabled();
await Switcher.loadSnapshot();
await assertRejects(async () =>
await Switcher.checkSnapshot(),
Error, 'Something went wrong: [checkSnapshotVersion] failed with status 429');
});

it('should NOT update snapshot - Too many requests at resolveSnapshot', testSettings, async function () {
//given
given('POST@/criteria/auth', generateAuth(token, 5));
given('GET@/criteria/snapshot_check/:version', generateStatus(false)); // Snapshot outdated
given('POST@/graphql', null, 429);

//test
Switcher.buildContext(contextSettings, {
snapshotLocation: 'generated-snapshots/'
});

await assertRejects(async () =>
await Switcher.loadSnapshot(),
Error, 'Something went wrong: [resolveSnapshot] failed with status 429');
});

it('should update snapshot', testSettings, async function () {
await delay(2000);

Expand Down