1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-09 00:18:26 +01:00
unleash.unleash/src/lib/util/offline-unleash-client.test.ts
Thomas Heartman 98a6cd05c6
Feat(#1873): return 'unknown' for application hostname strategies (#1889)
The hostname strategy will not work correctly with the playground because it depends on external state. In its constructor, it tries to query the environment or use the os.hostname function to determine what its current hostname is. This means that no matter what the user does in the playground, they can’t affect the results of this strategy. It’s also unlikely that it will be true. And if it is, it probably won’t be true for their clients.

In theory, we could accept a hostname property on the Unleash context and use the provided hostname in the address. However, I’m afraid that it’ll make users think that they can impact the hostname strategy by setting the property on their context, when that doesn’t do anything outside of the playground. It would also make the playground evaluate things differently from a regular SDK and I’m not sure that that’s something we want.

Instead, this change to the API makes the feature evaluate to 'unknown' or `false` (depending on constraints).
2022-08-05 11:09:55 +02:00

433 lines
14 KiB
TypeScript

import {
ClientInitOptions,
mapFeaturesForBootstrap,
mapSegmentsForBootstrap,
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,
url: 'not-needed',
storageProvider: new InMemStorageProviderNode(),
bootstrap: {
data: mapFeaturesForBootstrap(features),
segments: mapSegmentsForBootstrap(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,
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,
strategies: [
{
name: 'default',
constraints: [
{
contextName: 'appName',
operator: 'IN',
values: [appName],
},
],
},
],
variants: [],
type: '',
stale: false,
},
{
name: disabledFeature,
enabled: true,
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',
},
],
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',
},
],
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,
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: [],
},
],
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: [],
},
],
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: [],
},
],
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,
// 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),
);
});
});