mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
fix: do not allow creation/update of feature toggle with invalid strategy name (#4555)
This commit is contained in:
parent
604ec5a9ef
commit
0fb078d4c5
@ -39,6 +39,8 @@ import {
|
|||||||
createFakeSegmentService,
|
createFakeSegmentService,
|
||||||
createSegmentService,
|
createSegmentService,
|
||||||
} from '../segment/createSegmentService';
|
} from '../segment/createSegmentService';
|
||||||
|
import StrategyStore from '../../db/strategy-store';
|
||||||
|
import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
|
||||||
|
|
||||||
export const createFeatureToggleService = (
|
export const createFeatureToggleService = (
|
||||||
db: Db,
|
db: Db,
|
||||||
@ -76,6 +78,7 @@ export const createFeatureToggleService = (
|
|||||||
flagResolver,
|
flagResolver,
|
||||||
);
|
);
|
||||||
const groupStore = new GroupStore(db);
|
const groupStore = new GroupStore(db);
|
||||||
|
const strategyStore = new StrategyStore(db, getLogger);
|
||||||
const accountStore = new AccountStore(db, getLogger);
|
const accountStore = new AccountStore(db, getLogger);
|
||||||
const accessStore = new AccessStore(db, eventBus, getLogger);
|
const accessStore = new AccessStore(db, eventBus, getLogger);
|
||||||
const roleStore = new RoleStore(db, eventBus, getLogger);
|
const roleStore = new RoleStore(db, eventBus, getLogger);
|
||||||
@ -105,6 +108,7 @@ export const createFeatureToggleService = (
|
|||||||
featureTagStore,
|
featureTagStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
contextFieldStore,
|
contextFieldStore,
|
||||||
|
strategyStore,
|
||||||
},
|
},
|
||||||
{ getLogger, flagResolver },
|
{ getLogger, flagResolver },
|
||||||
segmentService,
|
segmentService,
|
||||||
@ -119,6 +123,7 @@ export const createFakeFeatureToggleService = (
|
|||||||
): FeatureToggleService => {
|
): FeatureToggleService => {
|
||||||
const { getLogger, flagResolver } = config;
|
const { getLogger, flagResolver } = config;
|
||||||
const eventStore = new FakeEventStore();
|
const eventStore = new FakeEventStore();
|
||||||
|
const strategyStore = new FakeStrategiesStore();
|
||||||
const featureStrategiesStore = new FakeFeatureStrategiesStore();
|
const featureStrategiesStore = new FakeFeatureStrategiesStore();
|
||||||
const featureToggleStore = new FakeFeatureToggleStore();
|
const featureToggleStore = new FakeFeatureToggleStore();
|
||||||
const featureToggleClientStore = new FakeFeatureToggleClientStore();
|
const featureToggleClientStore = new FakeFeatureToggleClientStore();
|
||||||
@ -152,6 +157,7 @@ export const createFakeFeatureToggleService = (
|
|||||||
featureTagStore,
|
featureTagStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
contextFieldStore,
|
contextFieldStore,
|
||||||
|
strategyStore,
|
||||||
},
|
},
|
||||||
{ getLogger, flagResolver },
|
{ getLogger, flagResolver },
|
||||||
segmentService,
|
segmentService,
|
||||||
|
@ -42,6 +42,7 @@ import {
|
|||||||
WeightType,
|
WeightType,
|
||||||
StrategiesOrderChangedEvent,
|
StrategiesOrderChangedEvent,
|
||||||
PotentiallyStaleOnEvent,
|
PotentiallyStaleOnEvent,
|
||||||
|
IStrategyStore,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import BadDataError from '../error/bad-data-error';
|
import BadDataError from '../error/bad-data-error';
|
||||||
@ -118,6 +119,8 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
private featureStrategiesStore: IFeatureStrategiesStore;
|
private featureStrategiesStore: IFeatureStrategiesStore;
|
||||||
|
|
||||||
|
private strategyStore: IStrategyStore;
|
||||||
|
|
||||||
private featureToggleStore: IFeatureToggleStore;
|
private featureToggleStore: IFeatureToggleStore;
|
||||||
|
|
||||||
private featureToggleClientStore: IFeatureToggleClientStore;
|
private featureToggleClientStore: IFeatureToggleClientStore;
|
||||||
@ -150,6 +153,7 @@ class FeatureToggleService {
|
|||||||
featureTagStore,
|
featureTagStore,
|
||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
contextFieldStore,
|
contextFieldStore,
|
||||||
|
strategyStore,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashStores,
|
IUnleashStores,
|
||||||
| 'featureStrategiesStore'
|
| 'featureStrategiesStore'
|
||||||
@ -160,6 +164,7 @@ class FeatureToggleService {
|
|||||||
| 'featureTagStore'
|
| 'featureTagStore'
|
||||||
| 'featureEnvironmentStore'
|
| 'featureEnvironmentStore'
|
||||||
| 'contextFieldStore'
|
| 'contextFieldStore'
|
||||||
|
| 'strategyStore'
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
getLogger,
|
getLogger,
|
||||||
@ -171,6 +176,7 @@ class FeatureToggleService {
|
|||||||
) {
|
) {
|
||||||
this.logger = getLogger('services/feature-toggle-service.ts');
|
this.logger = getLogger('services/feature-toggle-service.ts');
|
||||||
this.featureStrategiesStore = featureStrategiesStore;
|
this.featureStrategiesStore = featureStrategiesStore;
|
||||||
|
this.strategyStore = strategyStore;
|
||||||
this.featureToggleStore = featureToggleStore;
|
this.featureToggleStore = featureToggleStore;
|
||||||
this.featureToggleClientStore = featureToggleClientStore;
|
this.featureToggleClientStore = featureToggleClientStore;
|
||||||
this.tagStore = featureTagStore;
|
this.tagStore = featureTagStore;
|
||||||
@ -225,24 +231,24 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
validateUpdatedProperties(
|
validateUpdatedProperties(
|
||||||
{ featureName, projectId }: IFeatureContext,
|
{ featureName, projectId }: IFeatureContext,
|
||||||
strategy: IFeatureStrategy,
|
existingStrategy: IFeatureStrategy,
|
||||||
): void {
|
): void {
|
||||||
if (strategy.projectId !== projectId) {
|
if (existingStrategy.projectId !== projectId) {
|
||||||
throw new InvalidOperationError(
|
throw new InvalidOperationError(
|
||||||
'You can not change the projectId for an activation strategy.',
|
'You can not change the projectId for an activation strategy.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strategy.featureName !== featureName) {
|
if (existingStrategy.featureName !== featureName) {
|
||||||
throw new InvalidOperationError(
|
throw new InvalidOperationError(
|
||||||
'You can not change the featureName for an activation strategy.',
|
'You can not change the featureName for an activation strategy.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
strategy.parameters &&
|
existingStrategy.parameters &&
|
||||||
'stickiness' in strategy.parameters &&
|
'stickiness' in existingStrategy.parameters &&
|
||||||
strategy.parameters.stickiness === ''
|
existingStrategy.parameters.stickiness === ''
|
||||||
) {
|
) {
|
||||||
throw new InvalidOperationError(
|
throw new InvalidOperationError(
|
||||||
'You can not have an empty string for stickiness.',
|
'You can not have an empty string for stickiness.',
|
||||||
@ -271,6 +277,19 @@ class FeatureToggleService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validateStrategyType(
|
||||||
|
strategyName: string | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
if (strategyName !== undefined) {
|
||||||
|
const exists = await this.strategyStore.exists(strategyName);
|
||||||
|
if (!exists) {
|
||||||
|
throw new BadDataError(
|
||||||
|
`Could not find strategy type with name ${strategyName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async validateConstraints(
|
async validateConstraints(
|
||||||
constraints: IConstraint[],
|
constraints: IConstraint[],
|
||||||
): Promise<IConstraint[]> {
|
): Promise<IConstraint[]> {
|
||||||
@ -502,6 +521,7 @@ class FeatureToggleService {
|
|||||||
const { featureName, projectId, environment } = context;
|
const { featureName, projectId, environment } = context;
|
||||||
await this.validateFeatureBelongsToProject(context);
|
await this.validateFeatureBelongsToProject(context);
|
||||||
|
|
||||||
|
await this.validateStrategyType(strategyConfig.name);
|
||||||
await this.validateProjectCanAccessSegments(
|
await this.validateProjectCanAccessSegments(
|
||||||
projectId,
|
projectId,
|
||||||
strategyConfig.segments,
|
strategyConfig.segments,
|
||||||
@ -602,7 +622,7 @@ class FeatureToggleService {
|
|||||||
*/
|
*/
|
||||||
async updateStrategy(
|
async updateStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<IFeatureStrategy>,
|
updates: Partial<IStrategyConfig>,
|
||||||
context: IFeatureStrategyContext,
|
context: IFeatureStrategyContext,
|
||||||
userName: string,
|
userName: string,
|
||||||
user?: User,
|
user?: User,
|
||||||
@ -640,13 +660,15 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
async unprotectedUpdateStrategy(
|
async unprotectedUpdateStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<IFeatureStrategy>,
|
updates: Partial<IStrategyConfig>,
|
||||||
context: IFeatureStrategyContext,
|
context: IFeatureStrategyContext,
|
||||||
userName: string,
|
userName: string,
|
||||||
): Promise<Saved<IStrategyConfig>> {
|
): Promise<Saved<IStrategyConfig>> {
|
||||||
const { projectId, environment, featureName } = context;
|
const { projectId, environment, featureName } = context;
|
||||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
|
|
||||||
this.validateUpdatedProperties(context, existingStrategy);
|
this.validateUpdatedProperties(context, existingStrategy);
|
||||||
|
await this.validateStrategyType(updates.name);
|
||||||
await this.validateProjectCanAccessSegments(
|
await this.validateProjectCanAccessSegments(
|
||||||
projectId,
|
projectId,
|
||||||
updates.segments,
|
updates.segments,
|
||||||
|
@ -57,26 +57,28 @@ const createSegment = async (segmentName: string) => {
|
|||||||
const createStrategy = async (
|
const createStrategy = async (
|
||||||
featureName: string,
|
featureName: string,
|
||||||
payload: IStrategyConfig,
|
payload: IStrategyConfig,
|
||||||
|
expectedCode = 200,
|
||||||
) => {
|
) => {
|
||||||
return app.request
|
return app.request
|
||||||
.post(
|
.post(
|
||||||
`/api/admin/projects/default/features/${featureName}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${featureName}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send(payload)
|
.send(payload)
|
||||||
.expect(200);
|
.expect(expectedCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStrategy = async (
|
const updateStrategy = async (
|
||||||
featureName: string,
|
featureName: string,
|
||||||
strategyId: string,
|
strategyId: string,
|
||||||
payload: IStrategyConfig,
|
payload: IStrategyConfig,
|
||||||
|
expectedCode = 200,
|
||||||
) => {
|
) => {
|
||||||
const { body } = await app.request
|
const { body } = await app.request
|
||||||
.put(
|
.put(
|
||||||
`/api/admin/projects/default/features/${featureName}/environments/default/strategies/${strategyId}`,
|
`/api/admin/projects/default/features/${featureName}/environments/default/strategies/${strategyId}`,
|
||||||
)
|
)
|
||||||
.send(payload)
|
.send(payload)
|
||||||
.expect(200);
|
.expect(expectedCode);
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
@ -110,8 +112,6 @@ afterEach(async () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.stores.strategyStore.deleteAll();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -2355,7 +2355,7 @@ test('should handle strategy variants', async () => {
|
|||||||
await app.createFeature(feature.name);
|
await app.createFeature(feature.name);
|
||||||
|
|
||||||
const strategyWithInvalidVariant = {
|
const strategyWithInvalidVariant = {
|
||||||
name: uuidv4(),
|
name: 'flexibleRollout',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
@ -2386,7 +2386,7 @@ test('should handle strategy variants', async () => {
|
|||||||
stickiness: 'default',
|
stickiness: 'default',
|
||||||
};
|
};
|
||||||
const strategyWithValidVariant = {
|
const strategyWithValidVariant = {
|
||||||
name: uuidv4(),
|
name: 'flexibleRollout',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
variants: [variant],
|
variants: [variant],
|
||||||
};
|
};
|
||||||
@ -2438,7 +2438,7 @@ test('should reject invalid constraint values for multi-valued constraints', asy
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mockStrategy = (values: string[]) => ({
|
const mockStrategy = (values: string[]) => ({
|
||||||
name: uuidv4(),
|
name: 'flexibleRollout',
|
||||||
constraints: [{ contextName: 'userId', operator: 'IN', values }],
|
constraints: [{ contextName: 'userId', operator: 'IN', values }],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2497,7 +2497,7 @@ test('should add default constraint values for single-valued constraints', async
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mockStrategy = (constraint: unknown) => ({
|
const mockStrategy = (constraint: unknown) => ({
|
||||||
name: uuidv4(),
|
name: 'flexibleRollout',
|
||||||
constraints: [constraint],
|
constraints: [constraint],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2544,7 +2544,7 @@ test('should allow long parameter values', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const strategy = {
|
const strategy = {
|
||||||
name: uuidv4(),
|
name: 'flexibleRollout',
|
||||||
parameters: { a: 'b'.repeat(500) },
|
parameters: { a: 'b'.repeat(500) },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2582,7 +2582,7 @@ test('should change strategy sort order when payload is valid', async () => {
|
|||||||
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'gradualrollout',
|
name: 'flexibleRollout',
|
||||||
parameters: {
|
parameters: {
|
||||||
userId: 'string',
|
userId: 'string',
|
||||||
},
|
},
|
||||||
@ -2668,7 +2668,7 @@ test('should return strategies in correct order when new strategies are added',
|
|||||||
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'gradualrollout',
|
name: 'flexibleRollout',
|
||||||
parameters: {
|
parameters: {
|
||||||
userId: 'string',
|
userId: 'string',
|
||||||
},
|
},
|
||||||
@ -2705,7 +2705,7 @@ test('should return strategies in correct order when new strategies are added',
|
|||||||
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'gradualrollout',
|
name: 'flexibleRollout',
|
||||||
parameters: {
|
parameters: {
|
||||||
userId: 'string',
|
userId: 'string',
|
||||||
},
|
},
|
||||||
@ -2717,7 +2717,7 @@ test('should return strategies in correct order when new strategies are added',
|
|||||||
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'gradualrollout',
|
name: 'flexibleRollout',
|
||||||
parameters: {
|
parameters: {
|
||||||
userId: 'string',
|
userId: 'string',
|
||||||
},
|
},
|
||||||
@ -2993,7 +2993,7 @@ test('should return disabled strategies', async () => {
|
|||||||
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
`/api/admin/projects/default/features/${toggle.name}/environments/default/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'gradualrollout',
|
name: 'flexibleRollout',
|
||||||
parameters: {
|
parameters: {
|
||||||
userId: 'string',
|
userId: 'string',
|
||||||
},
|
},
|
||||||
@ -3355,3 +3355,42 @@ test('Updating feature strategy sort-order should trigger a an event', async ()
|
|||||||
expect(res.body.events[0].type).toBe('strategy-order-changed');
|
expect(res.body.events[0].type).toBe('strategy-order-changed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to create with invalid strategy type name', async () => {
|
||||||
|
const feature = { name: uuidv4(), impressionData: false };
|
||||||
|
await app.createFeature(feature.name);
|
||||||
|
await createStrategy(
|
||||||
|
feature.name,
|
||||||
|
{
|
||||||
|
name: 'random-type',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to update with invalid strategy type name', async () => {
|
||||||
|
const feature = { name: uuidv4(), impressionData: false };
|
||||||
|
await app.createFeature(feature.name);
|
||||||
|
const { body: strategyOne } = await createStrategy(feature.name, {
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateStrategy(
|
||||||
|
feature.name,
|
||||||
|
strategyOne.id,
|
||||||
|
{
|
||||||
|
name: 'random-type',
|
||||||
|
parameters: {
|
||||||
|
userId: 'string',
|
||||||
|
},
|
||||||
|
segments: [],
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -382,21 +382,6 @@ test(`should not delete api_tokens on import when drop-flag is set`, async () =>
|
|||||||
},
|
},
|
||||||
userName,
|
userName,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createStrategy(
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
constraints: [
|
|
||||||
{ contextName: 'userId', operator: 'IN', values: ['123'] },
|
|
||||||
],
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectId,
|
|
||||||
featureName,
|
|
||||||
environment,
|
|
||||||
},
|
|
||||||
userName,
|
|
||||||
);
|
|
||||||
await app.services.apiTokenService.createApiTokenWithProjects({
|
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||||
tokenName: apiTokenName,
|
tokenName: apiTokenName,
|
||||||
type: ApiTokenType.CLIENT,
|
type: ApiTokenType.CLIENT,
|
||||||
|
@ -29,7 +29,7 @@ test('gets all strategies', async () => {
|
|||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
expect(res.body.strategies).toHaveLength(2);
|
expect(res.body.strategies).toHaveLength(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ test('Can get strategies for specific environment', async () => {
|
|||||||
`/api/admin/projects/default/features/${featureName}/environments/testing/strategies`,
|
`/api/admin/projects/default/features/${featureName}/environments/testing/strategies`,
|
||||||
)
|
)
|
||||||
.send({
|
.send({
|
||||||
name: 'custom1',
|
name: 'default',
|
||||||
})
|
})
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ test('Can get strategies for specific environment', async () => {
|
|||||||
expect(res.body.name).toBe(featureName);
|
expect(res.body.name).toBe(featureName);
|
||||||
expect(res.body.strategies).toHaveLength(1);
|
expect(res.body.strategies).toHaveLength(1);
|
||||||
expect(
|
expect(
|
||||||
res.body.strategies.find((s) => s.name === 'custom1'),
|
res.body.strategies.find((s) => s.name === 'default'),
|
||||||
).toBeDefined();
|
).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ beforeAll(async () => {
|
|||||||
);
|
);
|
||||||
await featureToggleServiceV2.createStrategy(
|
await featureToggleServiceV2.createStrategy(
|
||||||
{
|
{
|
||||||
name: 'custom-testing',
|
name: 'flexibleRollout',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
@ -152,7 +152,7 @@ test('returns feature toggle with testing environment config', async () => {
|
|||||||
|
|
||||||
expect(features).toHaveLength(2);
|
expect(features).toHaveLength(2);
|
||||||
expect(f1.strategies).toHaveLength(1);
|
expect(f1.strategies).toHaveLength(1);
|
||||||
expect(f1.strategies[0].name).toBe('custom-testing');
|
expect(f1.strategies[0].name).toBe('flexibleRollout');
|
||||||
expect(f2.strategies).toHaveLength(1);
|
expect(f2.strategies).toHaveLength(1);
|
||||||
expect(query.project[0]).toBe(project);
|
expect(query.project[0]).toBe(project);
|
||||||
expect(query.environment).toBe(environment);
|
expect(query.environment).toBe(environment);
|
||||||
|
@ -66,7 +66,7 @@ const updateSegment = (
|
|||||||
|
|
||||||
const mockStrategy = (segments: number[] = []) => {
|
const mockStrategy = (segments: number[] = []) => {
|
||||||
return {
|
return {
|
||||||
name: randomId(),
|
name: 'flexibleRollout',
|
||||||
parameters: {},
|
parameters: {},
|
||||||
constraints: [],
|
constraints: [],
|
||||||
segments,
|
segments,
|
||||||
|
@ -463,18 +463,10 @@ test('should filter features by strategies', async () => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
strategies: [],
|
strategies: [],
|
||||||
});
|
});
|
||||||
await createFeatureToggle({
|
|
||||||
name: 'featureWithUnknownStrategy',
|
|
||||||
enabled: true,
|
|
||||||
strategies: [{ name: 'unknown', constraints: [], parameters: {} }],
|
|
||||||
});
|
|
||||||
await createFeatureToggle({
|
await createFeatureToggle({
|
||||||
name: 'featureWithMultipleStrategies',
|
name: 'featureWithMultipleStrategies',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
strategies: [
|
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||||
{ name: 'default', constraints: [], parameters: {} },
|
|
||||||
{ name: 'unknown', constraints: [], parameters: {} },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
await app.request
|
await app.request
|
||||||
.get('/api/frontend')
|
.get('/api/frontend')
|
||||||
|
@ -14,6 +14,30 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flexibleRollout",
|
||||||
|
"description": "Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "rollout",
|
||||||
|
"type": "percentage",
|
||||||
|
"description": "",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stickiness",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Used define stickiness. Possible values: default, userId, sessionId, random",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "groupId",
|
||||||
|
"type": "string",
|
||||||
|
"description": "Used to define a activation groups, which allows you to correlate across feature toggles.",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contextFields": [
|
"contextFields": [
|
||||||
|
Loading…
Reference in New Issue
Block a user