mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-13 11:17:26 +02:00
[](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [unleash-client](https://togithub.com/Unleash/unleash-client-node) | [`3.16.1` -> `3.18.0`](https://renovatebot.com/diffs/npm/unleash-client/3.16.1/3.18.0) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes <details> <summary>Unleash/unleash-client-node</summary> ### [`v3.18.0`](https://togithub.com/Unleash/unleash-client-node/blob/HEAD/CHANGELOG.md#​3180) [Compare Source](https://togithub.com/Unleash/unleash-client-node/compare/v3.17.0...v3.18.0) feat: gracefully handle unsuccessful metrics post ([#​414](https://togithub.com/Unleash/unleash-client-node/issues/414)) feat/flush metrics ([#​415](https://togithub.com/Unleash/unleash-client-node/issues/415)) feat: add metrics jitter support ([#​412](https://togithub.com/Unleash/unleash-client-node/issues/412)) fix: Allow SDK to startup when backup data is corrupt ([#​418](https://togithub.com/Unleash/unleash-client-node/issues/418)) fix: flexible-rollout random stickiness is not random enough ([#​417](https://togithub.com/Unleash/unleash-client-node/issues/417)) fix: build correct version on npm version chore(deps): update dependency eslint-plugin-import to v2.27.5 ([#​416](https://togithub.com/Unleash/unleash-client-node/issues/416)) chore(deps): update dependency [@​typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin) to v5.48.2 ([#​413](https://togithub.com/Unleash/unleash-client-node/issues/413)) chore(deps): update dependency eslint to v8.32.0 ([#​410](https://togithub.com/Unleash/unleash-client-node/issues/410)) chore(deps): update dependency prettier to v2.8.3 ([#​406](https://togithub.com/Unleash/unleash-client-node/issues/406)) chore(deps): update dependency eslint-plugin-import to v2.27.4 ([#​404](https://togithub.com/Unleash/unleash-client-node/issues/404)) ### [`v3.17.0`](https://togithub.com/Unleash/unleash-client-node/blob/HEAD/CHANGELOG.md#​3170) [Compare Source](https://togithub.com/Unleash/unleash-client-node/compare/v3.16.1...v3.17.0) - feat: Only initialize the SDK once. ([#​368](https://togithub.com/Unleash/unleash-client-node/issues/368)) - fix: upgrade semver to 7.3.8 - fix: add resolution for debug - fix: add resolution for minimatch - fix: add resolution for qs - fix: add resolution for json5 - fix: update yarn.lock - docs: Update the readme with info from docs.getunleash ([#​399](https://togithub.com/Unleash/unleash-client-node/issues/399)) - docs: minor fix in README - chore(deps): update dependency debug to v4 ([#​402](https://togithub.com/Unleash/unleash-client-node/issues/402)) - chore(deps): update dependency json5 to v2 ([#​401](https://togithub.com/Unleash/unleash-client-node/issues/401)) - chore(deps): update dependency eslint to v8.31.0 ([#​394](https://togithub.com/Unleash/unleash-client-node/issues/394)) - chore(deps): update dependency nock to v13.3.0 ([#​400](https://togithub.com/Unleash/unleash-client-node/issues/400)) - chore(deps): update dependency [@​typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin) to v5.48.1 ([#​395](https://togithub.com/Unleash/unleash-client-node/issues/395)) - chore(deps): update dependency eslint-config-prettier to v8.6.0 ([#​396](https://togithub.com/Unleash/unleash-client-node/issues/396)) - chore(deps): update dependency prettier to v2.8.2 ([#​398](https://togithub.com/Unleash/unleash-client-node/issues/398)) - chore(deps): update dependency [@​typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/eslint-plugin) to v5.47.1 ([#​346](https://togithub.com/Unleash/unleash-client-node/issues/346)) - chore(deps): update dependency typescript to v4.9.4 ([#​386](https://togithub.com/Unleash/unleash-client-node/issues/386)) - chore(deps): update dependency sinon to v15 ([#​391](https://togithub.com/Unleash/unleash-client-node/issues/391)) - chore(deps): update dependency [@​types/node](https://togithub.com/types/node) to v18 ([#​380](https://togithub.com/Unleash/unleash-client-node/issues/380)) - chore(deps): update dependency [@​types/node](https://togithub.com/types/node) to v14.18.36 ([#​382](https://togithub.com/Unleash/unleash-client-node/issues/382)) - chore(deps): update dependency eslint to v8.30.0 ([#​367](https://togithub.com/Unleash/unleash-client-node/issues/367)) - chore(deps): update dependency prettier to v2.8.1 ([#​387](https://togithub.com/Unleash/unleash-client-node/issues/387)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/Unleash/unleash). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC4xMDUuNCIsInVwZGF0ZWRJblZlciI6IjM0LjExNy4xIn0=--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ivar Conradi Østhus <ivar@getunleash.ai>
444 lines
14 KiB
TypeScript
444 lines
14 KiB
TypeScript
import {
|
|
ClientInitOptions,
|
|
mapFeaturesForClient,
|
|
mapSegmentsForClient,
|
|
offlineUnleashClient,
|
|
} from './offline-unleash-client';
|
|
import {
|
|
Unleash as UnleashClientNode,
|
|
InMemStorageProvider as InMemStorageProviderNode,
|
|
} from 'unleash-client';
|
|
import { once } from 'events';
|
|
import { playgroundStrategyEvaluation } from '../openapi/spec/playground-strategy-schema';
|
|
|
|
export const offlineUnleashClientNode = async ({
|
|
features,
|
|
context,
|
|
logError,
|
|
segments,
|
|
}: ClientInitOptions): Promise<UnleashClientNode> => {
|
|
const client = new UnleashClientNode({
|
|
...context,
|
|
appName: context.appName,
|
|
disableMetrics: true,
|
|
refreshInterval: 0,
|
|
skipInstanceCountWarning: true,
|
|
url: 'not-needed',
|
|
storageProvider: new InMemStorageProviderNode(),
|
|
bootstrap: {
|
|
data: mapFeaturesForClient(features),
|
|
segments: mapSegmentsForClient(segments),
|
|
},
|
|
});
|
|
|
|
client.on('error', logError);
|
|
client.start();
|
|
|
|
await once(client, 'ready');
|
|
|
|
return client;
|
|
};
|
|
|
|
describe('offline client', () => {
|
|
it('considers enabled variants with a default strategy to be on', async () => {
|
|
const name = 'toggle-name';
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
name,
|
|
project: 'default',
|
|
enabled: true,
|
|
strategies: [{ name: 'default' }],
|
|
variants: [],
|
|
type: '',
|
|
stale: false,
|
|
},
|
|
],
|
|
context: { appName: 'other-app', environment: 'default' },
|
|
logError: console.log,
|
|
});
|
|
|
|
expect(client.isEnabled(name).result).toBeTruthy();
|
|
});
|
|
|
|
it('constrains on appName', async () => {
|
|
const enabledFeature = 'toggle-name';
|
|
const disabledFeature = 'other-toggle';
|
|
const appName = 'app-name';
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
name: enabledFeature,
|
|
enabled: true,
|
|
project: 'default',
|
|
strategies: [
|
|
{
|
|
name: 'default',
|
|
constraints: [
|
|
{
|
|
contextName: 'appName',
|
|
operator: 'IN',
|
|
values: [appName],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
variants: [],
|
|
type: '',
|
|
stale: false,
|
|
},
|
|
{
|
|
name: disabledFeature,
|
|
enabled: true,
|
|
project: 'default',
|
|
strategies: [
|
|
{
|
|
name: 'default',
|
|
constraints: [
|
|
{
|
|
contextName: 'appName',
|
|
operator: 'IN',
|
|
values: ['otherApp'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
variants: [],
|
|
type: '',
|
|
stale: false,
|
|
},
|
|
],
|
|
context: { appName, environment: 'default' },
|
|
logError: console.log,
|
|
});
|
|
|
|
expect(client.isEnabled(enabledFeature).result).toBeTruthy();
|
|
expect(client.isEnabled(disabledFeature).result).toBeFalsy();
|
|
});
|
|
|
|
it('considers disabled features with a default strategy to be enabled', async () => {
|
|
const name = 'toggle-name';
|
|
const context = { appName: 'client-test' };
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [
|
|
{
|
|
name: 'default',
|
|
},
|
|
],
|
|
project: 'default',
|
|
stale: false,
|
|
enabled: false,
|
|
name,
|
|
type: 'experiment',
|
|
variants: [],
|
|
},
|
|
],
|
|
context,
|
|
logError: console.log,
|
|
});
|
|
|
|
const result = client.isEnabled(name, context);
|
|
|
|
expect(result.result).toBe(true);
|
|
});
|
|
|
|
it('considers disabled variants with a default strategy and variants to be on', async () => {
|
|
const name = 'toggle-name';
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [
|
|
{
|
|
name: 'default',
|
|
},
|
|
],
|
|
project: 'default',
|
|
stale: false,
|
|
enabled: false,
|
|
name,
|
|
type: 'experiment',
|
|
variants: [
|
|
{
|
|
name: 'a',
|
|
weight: 500,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
},
|
|
{
|
|
name: 'b',
|
|
weight: 500,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
context: { appName: 'client-test' },
|
|
logError: console.log,
|
|
});
|
|
|
|
expect(client.isEnabled(name).result).toBe(true);
|
|
});
|
|
|
|
it("returns variant {name: 'disabled', enabled: false } if the toggle isn't enabled", async () => {
|
|
const name = 'toggle-name';
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [],
|
|
stale: false,
|
|
enabled: false,
|
|
name,
|
|
project: 'default',
|
|
type: 'experiment',
|
|
variants: [
|
|
{
|
|
name: 'a',
|
|
weight: 500,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
},
|
|
{
|
|
name: 'b',
|
|
weight: 500,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
context: { appName: 'client-test' },
|
|
logError: console.log,
|
|
});
|
|
|
|
expect(client.isEnabled(name).result).toBeFalsy();
|
|
expect(client.getVariant(name).name).toEqual('disabled');
|
|
expect(client.getVariant(name).enabled).toBeFalsy();
|
|
});
|
|
|
|
it('returns the disabled variant if there are no variants', async () => {
|
|
const name = 'toggle-name';
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [
|
|
{
|
|
name: 'default',
|
|
constraints: [],
|
|
},
|
|
],
|
|
project: 'default',
|
|
stale: false,
|
|
enabled: true,
|
|
name,
|
|
type: 'experiment',
|
|
variants: [],
|
|
},
|
|
],
|
|
context: { appName: 'client-test' },
|
|
logError: console.log,
|
|
});
|
|
|
|
expect(client.getVariant(name, {}).name).toEqual('disabled');
|
|
expect(client.getVariant(name, {}).enabled).toBeFalsy();
|
|
expect(client.isEnabled(name, {}).result).toBeTruthy();
|
|
});
|
|
|
|
it(`returns '${playgroundStrategyEvaluation.unknownResult}' if it can't evaluate a feature`, async () => {
|
|
const name = 'toggle-name';
|
|
const context = { appName: 'client-test' };
|
|
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [
|
|
{
|
|
name: 'unimplemented-custom-strategy',
|
|
constraints: [],
|
|
},
|
|
],
|
|
project: 'default',
|
|
stale: false,
|
|
enabled: true,
|
|
name,
|
|
type: 'experiment',
|
|
variants: [],
|
|
},
|
|
],
|
|
context,
|
|
logError: console.log,
|
|
});
|
|
|
|
const result = client.isEnabled(name, context);
|
|
|
|
result.strategies.forEach((strategy) =>
|
|
expect(strategy.result.enabled).toEqual(
|
|
playgroundStrategyEvaluation.unknownResult,
|
|
),
|
|
);
|
|
expect(result.result).toEqual(
|
|
playgroundStrategyEvaluation.unknownResult,
|
|
);
|
|
});
|
|
|
|
it(`returns '${playgroundStrategyEvaluation.unknownResult}' for the application hostname strategy`, async () => {
|
|
const name = 'toggle-name';
|
|
const context = { appName: 'client-test' };
|
|
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies: [
|
|
{
|
|
name: 'applicationHostname',
|
|
constraints: [],
|
|
},
|
|
],
|
|
project: 'default',
|
|
stale: false,
|
|
enabled: true,
|
|
name,
|
|
type: 'experiment',
|
|
variants: [],
|
|
},
|
|
],
|
|
context,
|
|
logError: console.log,
|
|
});
|
|
|
|
const result = client.isEnabled(name, context);
|
|
|
|
result.strategies.forEach((strategy) =>
|
|
expect(strategy.result.enabled).toEqual(
|
|
playgroundStrategyEvaluation.unknownResult,
|
|
),
|
|
);
|
|
expect(result.result).toEqual(
|
|
playgroundStrategyEvaluation.unknownResult,
|
|
);
|
|
});
|
|
|
|
it('returns strategies in the order they are provided', async () => {
|
|
const featureName = 'featureName';
|
|
const strategies = [
|
|
{
|
|
name: 'default',
|
|
constraints: [],
|
|
parameters: {},
|
|
},
|
|
{
|
|
name: 'default',
|
|
constraints: [
|
|
{
|
|
values: ['my-app-name'],
|
|
inverted: false,
|
|
operator: 'IN' as 'IN',
|
|
contextName: 'appName',
|
|
caseInsensitive: false,
|
|
},
|
|
],
|
|
parameters: {},
|
|
},
|
|
{
|
|
name: 'applicationHostname',
|
|
constraints: [],
|
|
parameters: {
|
|
hostNames: 'myhostname.com',
|
|
},
|
|
},
|
|
{
|
|
name: 'flexibleRollout',
|
|
constraints: [],
|
|
parameters: {
|
|
groupId: 'killer',
|
|
rollout: '34',
|
|
stickiness: 'userId',
|
|
},
|
|
},
|
|
{
|
|
name: 'userWithId',
|
|
constraints: [],
|
|
parameters: {
|
|
userIds: 'uoea,ueoa',
|
|
},
|
|
},
|
|
{
|
|
name: 'remoteAddress',
|
|
constraints: [],
|
|
parameters: {
|
|
IPs: '196.6.6.05',
|
|
},
|
|
},
|
|
];
|
|
|
|
const context = { appName: 'client-test' };
|
|
|
|
const client = await offlineUnleashClient({
|
|
features: [
|
|
{
|
|
strategies,
|
|
// impressionData: false,
|
|
enabled: true,
|
|
name: featureName,
|
|
project: 'default',
|
|
// description: '',
|
|
// project: 'heartman-for-test',
|
|
stale: false,
|
|
type: 'kill-switch',
|
|
variants: [
|
|
{
|
|
name: 'a',
|
|
weight: 334,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
payload: {
|
|
type: 'json',
|
|
value: '{"hello": "world"}',
|
|
},
|
|
},
|
|
{
|
|
name: 'b',
|
|
weight: 333,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
overrides: [],
|
|
payload: {
|
|
type: 'string',
|
|
value: 'ueoau',
|
|
},
|
|
},
|
|
{
|
|
name: 'c',
|
|
weight: 333,
|
|
weightType: 'variable',
|
|
stickiness: 'default',
|
|
payload: {
|
|
type: 'csv',
|
|
value: '1,2,3',
|
|
},
|
|
overrides: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
context,
|
|
logError: console.log,
|
|
});
|
|
|
|
const evaluatedStrategies = client
|
|
.isEnabled(featureName, context)
|
|
.strategies.map((strategy) => strategy.name);
|
|
|
|
expect(evaluatedStrategies).toEqual(
|
|
strategies.map((strategy) => strategy.name),
|
|
);
|
|
});
|
|
});
|