mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
fix/projectId cannot change for strategy configs (#1084)
This commit is contained in:
parent
ec60f4485c
commit
053956b45e
@ -252,40 +252,6 @@ test('should lookup projectId from data', async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Need access to UPDATE_FEATURE on the project you change to', async () => {
|
|
||||||
const oldProjectId = 'some-project-34';
|
|
||||||
const newProjectId = 'some-project-35';
|
|
||||||
const featureName = 'some-feature-toggle';
|
|
||||||
const accessService = {
|
|
||||||
hasPermission: jest.fn().mockReturnValue(true),
|
|
||||||
};
|
|
||||||
featureToggleStore.getProjectId = jest.fn().mockReturnValue(oldProjectId);
|
|
||||||
|
|
||||||
const func = rbacMiddleware(config, { featureToggleStore }, accessService);
|
|
||||||
const cb = jest.fn();
|
|
||||||
const req: any = {
|
|
||||||
user: new User({ username: 'user', id: 1 }),
|
|
||||||
params: { featureName },
|
|
||||||
body: { featureName, project: newProjectId },
|
|
||||||
};
|
|
||||||
func(req, undefined, cb);
|
|
||||||
|
|
||||||
await req.checkRbac(perms.UPDATE_FEATURE);
|
|
||||||
expect(accessService.hasPermission).toHaveBeenCalledTimes(2);
|
|
||||||
expect(accessService.hasPermission).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
req.user,
|
|
||||||
perms.UPDATE_FEATURE,
|
|
||||||
oldProjectId,
|
|
||||||
);
|
|
||||||
expect(accessService.hasPermission).toHaveBeenNthCalledWith(
|
|
||||||
2,
|
|
||||||
req.user,
|
|
||||||
perms.UPDATE_FEATURE,
|
|
||||||
newProjectId,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Does not double check permission if not changing project when updating toggle', async () => {
|
test('Does not double check permission if not changing project when updating toggle', async () => {
|
||||||
const oldProjectId = 'some-project-34';
|
const oldProjectId = 'some-project-34';
|
||||||
const featureName = 'some-feature-toggle';
|
const featureName = 'some-feature-toggle';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import {
|
import {
|
||||||
CREATE_FEATURE,
|
CREATE_FEATURE,
|
||||||
UPDATE_FEATURE,
|
|
||||||
DELETE_FEATURE,
|
DELETE_FEATURE,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
|
UPDATE_FEATURE,
|
||||||
} from '../types/permissions';
|
} from '../types/permissions';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
import { IUnleashStores } from '../types/stores';
|
import { IUnleashStores } from '../types/stores';
|
||||||
@ -47,33 +47,10 @@ const rbacMiddleware = (
|
|||||||
let { projectId } = params;
|
let { projectId } = params;
|
||||||
|
|
||||||
// Temporary workaround to figure out projectId for feature toggle updates.
|
// Temporary workaround to figure out projectId for feature toggle updates.
|
||||||
if (permission === DELETE_FEATURE) {
|
// will be removed in Unleash v5.0
|
||||||
|
if ([DELETE_FEATURE, UPDATE_FEATURE].includes(permission)) {
|
||||||
const { featureName } = params;
|
const { featureName } = params;
|
||||||
projectId = await featureToggleStore.getProjectId(featureName);
|
projectId = await featureToggleStore.getProjectId(featureName);
|
||||||
} else if (permission === UPDATE_FEATURE) {
|
|
||||||
// if projectId of feature is different from project in body
|
|
||||||
// need to check that we have UPDATE_FEATURE access on both old and new project
|
|
||||||
// TODO: Look at this to make it smoother once we get around to looking at project
|
|
||||||
// Changing project of a toggle should most likely be a separate endpoint
|
|
||||||
const { featureName } = params;
|
|
||||||
projectId = await featureToggleStore.getProjectId(featureName);
|
|
||||||
const newProjectId = req.body
|
|
||||||
? req.body.project || projectId
|
|
||||||
: projectId;
|
|
||||||
if (newProjectId !== projectId) {
|
|
||||||
return (
|
|
||||||
accessService.hasPermission(
|
|
||||||
user,
|
|
||||||
permission,
|
|
||||||
projectId,
|
|
||||||
) &&
|
|
||||||
accessService.hasPermission(
|
|
||||||
user,
|
|
||||||
permission,
|
|
||||||
newProjectId,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (permission === CREATE_FEATURE) {
|
} else if (permission === CREATE_FEATURE) {
|
||||||
projectId = req.body.project || 'default';
|
projectId = req.body.project || 'default';
|
||||||
}
|
}
|
||||||
@ -84,5 +61,4 @@ const rbacMiddleware = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = rbacMiddleware;
|
|
||||||
export default rbacMiddleware;
|
export default rbacMiddleware;
|
||||||
|
@ -7,7 +7,7 @@ import Controller from '../controller';
|
|||||||
|
|
||||||
import { extractUsername } from '../../util/extract-user';
|
import { extractUsername } from '../../util/extract-user';
|
||||||
import { DELETE_FEATURE, UPDATE_FEATURE } from '../../types/permissions';
|
import { DELETE_FEATURE, UPDATE_FEATURE } from '../../types/permissions';
|
||||||
import FeatureToggleServiceV2 from '../../services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../services/feature-toggle-service';
|
||||||
import { IAuthRequest } from '../unleash-types';
|
import { IAuthRequest } from '../unleash-types';
|
||||||
|
|
||||||
export default class ArchiveController extends Controller {
|
export default class ArchiveController extends Controller {
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
} from '../../types/permissions';
|
} from '../../types/permissions';
|
||||||
import { IUnleashConfig } from '../../types/option';
|
import { IUnleashConfig } from '../../types/option';
|
||||||
import { IUnleashServices } from '../../types/services';
|
import { IUnleashServices } from '../../types/services';
|
||||||
import FeatureToggleServiceV2 from '../../services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../services/feature-toggle-service';
|
||||||
import { featureSchema, querySchema } from '../../schema/feature-schema';
|
import { featureSchema, querySchema } from '../../schema/feature-schema';
|
||||||
import { IFeatureToggleQuery } from '../../types/model';
|
import { IFeatureToggleQuery } from '../../types/model';
|
||||||
import FeatureTagService from '../../services/feature-tag-service';
|
import FeatureTagService from '../../services/feature-tag-service';
|
||||||
@ -150,8 +150,11 @@ class FeatureController extends Controller {
|
|||||||
toggle.strategies.map(async (s) =>
|
toggle.strategies.map(async (s) =>
|
||||||
this.service.createStrategy(
|
this.service.createStrategy(
|
||||||
s,
|
s,
|
||||||
createdFeature.project,
|
{
|
||||||
createdFeature.name,
|
projectId: createdFeature.project,
|
||||||
|
featureName: createdFeature.name,
|
||||||
|
environment: DEFAULT_ENV,
|
||||||
|
},
|
||||||
userName,
|
userName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -190,8 +193,7 @@ class FeatureController extends Controller {
|
|||||||
updatedFeature.strategies.map(async (s) =>
|
updatedFeature.strategies.map(async (s) =>
|
||||||
this.service.createStrategy(
|
this.service.createStrategy(
|
||||||
s,
|
s,
|
||||||
projectId,
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
featureName,
|
|
||||||
userName,
|
userName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@ import { applyPatch, Operation } from 'fast-json-patch';
|
|||||||
import Controller from '../../controller';
|
import Controller from '../../controller';
|
||||||
import { IUnleashConfig } from '../../../types/option';
|
import { IUnleashConfig } from '../../../types/option';
|
||||||
import { IUnleashServices } from '../../../types/services';
|
import { IUnleashServices } from '../../../types/services';
|
||||||
import FeatureToggleServiceV2 from '../../../services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../../services/feature-toggle-service';
|
||||||
import { Logger } from '../../../logger';
|
import { Logger } from '../../../logger';
|
||||||
import { CREATE_FEATURE, UPDATE_FEATURE } from '../../../types/permissions';
|
import { CREATE_FEATURE, UPDATE_FEATURE } from '../../../types/permissions';
|
||||||
import {
|
import {
|
||||||
@ -64,18 +64,20 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
this.featureService = featureToggleServiceV2;
|
this.featureService = featureToggleServiceV2;
|
||||||
this.logger = config.getLogger('/admin-api/project/features.ts');
|
this.logger = config.getLogger('/admin-api/project/features.ts');
|
||||||
|
|
||||||
|
// Environments
|
||||||
this.get(`${PATH_ENV}`, this.getEnvironment);
|
this.get(`${PATH_ENV}`, this.getEnvironment);
|
||||||
this.post(`${PATH_ENV}/on`, this.toggleEnvironmentOn, UPDATE_FEATURE);
|
this.post(`${PATH_ENV}/on`, this.toggleEnvironmentOn, UPDATE_FEATURE);
|
||||||
this.post(`${PATH_ENV}/off`, this.toggleEnvironmentOff, UPDATE_FEATURE);
|
this.post(`${PATH_ENV}/off`, this.toggleEnvironmentOff, UPDATE_FEATURE);
|
||||||
|
|
||||||
|
// activation strategies
|
||||||
this.get(`${PATH_STRATEGIES}`, this.getStrategies);
|
this.get(`${PATH_STRATEGIES}`, this.getStrategies);
|
||||||
this.post(`${PATH_STRATEGIES}`, this.addStrategy, UPDATE_FEATURE);
|
this.post(`${PATH_STRATEGIES}`, this.addStrategy, UPDATE_FEATURE);
|
||||||
|
|
||||||
this.get(`${PATH_STRATEGY}`, this.getStrategy);
|
this.get(`${PATH_STRATEGY}`, this.getStrategy);
|
||||||
this.put(`${PATH_STRATEGY}`, this.updateStrategy, UPDATE_FEATURE);
|
this.put(`${PATH_STRATEGY}`, this.updateStrategy, UPDATE_FEATURE);
|
||||||
this.patch(`${PATH_STRATEGY}`, this.patchStrategy, UPDATE_FEATURE);
|
this.patch(`${PATH_STRATEGY}`, this.patchStrategy, UPDATE_FEATURE);
|
||||||
this.delete(`${PATH_STRATEGY}`, this.deleteStrategy, UPDATE_FEATURE);
|
this.delete(`${PATH_STRATEGY}`, this.deleteStrategy, UPDATE_FEATURE);
|
||||||
|
|
||||||
|
// feature toggles
|
||||||
this.get(PATH, this.getFeatures);
|
this.get(PATH, this.getFeatures);
|
||||||
this.post(PATH, this.createFeature, CREATE_FEATURE);
|
this.post(PATH, this.createFeature, CREATE_FEATURE);
|
||||||
|
|
||||||
@ -244,10 +246,8 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const featureStrategy = await this.featureService.createStrategy(
|
const featureStrategy = await this.featureService.createStrategy(
|
||||||
req.body,
|
req.body,
|
||||||
projectId,
|
{ environment, projectId, featureName },
|
||||||
featureName,
|
|
||||||
userName,
|
userName,
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
res.status(200).json(featureStrategy);
|
res.status(200).json(featureStrategy);
|
||||||
}
|
}
|
||||||
@ -270,14 +270,13 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
req: IAuthRequest<StrategyIdParams, any, StrategyUpdateBody, any>,
|
req: IAuthRequest<StrategyIdParams, any, StrategyUpdateBody, any>,
|
||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { strategyId, environment, projectId } = req.params;
|
const { strategyId, environment, projectId, featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const updatedStrategy = await this.featureService.updateStrategy(
|
const updatedStrategy = await this.featureService.updateStrategy(
|
||||||
strategyId,
|
strategyId,
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
userName,
|
|
||||||
req.body,
|
req.body,
|
||||||
|
{ environment, projectId, featureName },
|
||||||
|
userName,
|
||||||
);
|
);
|
||||||
res.status(200).json(updatedStrategy);
|
res.status(200).json(updatedStrategy);
|
||||||
}
|
}
|
||||||
@ -286,17 +285,16 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
req: IAuthRequest<StrategyIdParams, any, Operation[], any>,
|
req: IAuthRequest<StrategyIdParams, any, Operation[], any>,
|
||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { strategyId, projectId, environment } = req.params;
|
const { strategyId, projectId, environment, featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const patch = req.body;
|
const patch = req.body;
|
||||||
const strategy = await this.featureService.getStrategy(strategyId);
|
const strategy = await this.featureService.getStrategy(strategyId);
|
||||||
const { newDocument } = applyPatch(strategy, patch);
|
const { newDocument } = applyPatch(strategy, patch);
|
||||||
const updatedStrategy = await this.featureService.updateStrategy(
|
const updatedStrategy = await this.featureService.updateStrategy(
|
||||||
strategyId,
|
strategyId,
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
userName,
|
|
||||||
newDocument,
|
newDocument,
|
||||||
|
{ environment, projectId, featureName },
|
||||||
|
userName,
|
||||||
);
|
);
|
||||||
res.status(200).json(updatedStrategy);
|
res.status(200).json(updatedStrategy);
|
||||||
}
|
}
|
||||||
@ -323,10 +321,8 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
this.logger.info(strategyId);
|
this.logger.info(strategyId);
|
||||||
const strategy = await this.featureService.deleteStrategy(
|
const strategy = await this.featureService.deleteStrategy(
|
||||||
strategyId,
|
strategyId,
|
||||||
featureName,
|
{ environment, projectId, featureName },
|
||||||
userName,
|
userName,
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
res.status(200).json(strategy);
|
res.status(200).json(strategy);
|
||||||
}
|
}
|
||||||
@ -340,7 +336,7 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
>,
|
>,
|
||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { strategyId, environment, projectId } = req.params;
|
const { strategyId, environment, projectId, featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const { name, value } = req.body;
|
const { name, value } = req.body;
|
||||||
|
|
||||||
@ -349,9 +345,8 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
strategyId,
|
strategyId,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
{ environment, projectId, featureName },
|
||||||
userName,
|
userName,
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
res.status(200).json(updatedStrategy);
|
res.status(200).json(updatedStrategy);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { Response } from 'express';
|
|||||||
import Controller from '../controller';
|
import Controller from '../controller';
|
||||||
import { IUnleashServices } from '../../types/services';
|
import { IUnleashServices } from '../../types/services';
|
||||||
import { IUnleashConfig } from '../../types/option';
|
import { IUnleashConfig } from '../../types/option';
|
||||||
import FeatureToggleServiceV2 from '../../services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../services/feature-toggle-service';
|
||||||
import { Logger } from '../../logger';
|
import { Logger } from '../../logger';
|
||||||
import { querySchema } from '../../schema/feature-schema';
|
import { querySchema } from '../../schema/feature-schema';
|
||||||
import { IFeatureToggleQuery } from '../../types/model';
|
import { IFeatureToggleQuery } from '../../types/model';
|
||||||
|
@ -48,6 +48,15 @@ import { IFeatureToggleClientStore } from '../types/stores/feature-toggle-client
|
|||||||
import { DEFAULT_ENV } from '../util/constants';
|
import { DEFAULT_ENV } from '../util/constants';
|
||||||
import { applyPatch, deepClone, Operation } from 'fast-json-patch';
|
import { applyPatch, deepClone, Operation } from 'fast-json-patch';
|
||||||
|
|
||||||
|
interface IFeatureContext {
|
||||||
|
featureName: string;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFeatureStrategyContext extends IFeatureContext {
|
||||||
|
environment: string;
|
||||||
|
}
|
||||||
|
|
||||||
class FeatureToggleServiceV2 {
|
class FeatureToggleServiceV2 {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
@ -96,6 +105,35 @@ class FeatureToggleServiceV2 {
|
|||||||
this.featureEnvironmentStore = featureEnvironmentStore;
|
this.featureEnvironmentStore = featureEnvironmentStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async validateFeatureContext({
|
||||||
|
featureName,
|
||||||
|
projectId,
|
||||||
|
}: IFeatureContext): Promise<void> {
|
||||||
|
const id = await this.featureToggleStore.getProjectId(featureName);
|
||||||
|
if (id !== projectId) {
|
||||||
|
throw new InvalidOperationError(
|
||||||
|
'You can not change the projectId for an activation strategy.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateFeatureStrategyContext(
|
||||||
|
strategy: IFeatureStrategy,
|
||||||
|
{ featureName, projectId }: IFeatureStrategyContext,
|
||||||
|
): void {
|
||||||
|
if (strategy.projectId !== projectId) {
|
||||||
|
throw new InvalidOperationError(
|
||||||
|
'You can not change the projectId for an activation strategy.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strategy.featureName !== featureName) {
|
||||||
|
throw new InvalidOperationError(
|
||||||
|
'You can not change the featureName for an activation strategy.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async patchFeature(
|
async patchFeature(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureName: string,
|
featureName: string,
|
||||||
@ -126,11 +164,11 @@ class FeatureToggleServiceV2 {
|
|||||||
|
|
||||||
async createStrategy(
|
async createStrategy(
|
||||||
strategyConfig: Omit<IStrategyConfig, 'id'>,
|
strategyConfig: Omit<IStrategyConfig, 'id'>,
|
||||||
projectId: string,
|
context: IFeatureStrategyContext,
|
||||||
featureName: string,
|
|
||||||
userName: string,
|
userName: string,
|
||||||
environment: string = DEFAULT_ENV,
|
|
||||||
): Promise<IStrategyConfig> {
|
): Promise<IStrategyConfig> {
|
||||||
|
const { featureName, projectId, environment } = context;
|
||||||
|
await this.validateFeatureContext(context);
|
||||||
try {
|
try {
|
||||||
const newFeatureStrategy =
|
const newFeatureStrategy =
|
||||||
await this.featureStrategiesStore.createStrategyFeatureEnv({
|
await this.featureStrategiesStore.createStrategyFeatureEnv({
|
||||||
@ -176,17 +214,20 @@ class FeatureToggleServiceV2 {
|
|||||||
* }
|
* }
|
||||||
* @param id
|
* @param id
|
||||||
* @param updates
|
* @param updates
|
||||||
|
* @param context - Which context does this strategy live in (projectId, featureName, environment)
|
||||||
|
* @param userName - Human readable id of the user performing the update
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: verify projectId is not changed from URL!
|
|
||||||
async updateStrategy(
|
async updateStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
environment: string,
|
|
||||||
project: string,
|
|
||||||
userName: string,
|
|
||||||
updates: Partial<IFeatureStrategy>,
|
updates: Partial<IFeatureStrategy>,
|
||||||
|
context: IFeatureStrategyContext,
|
||||||
|
userName: string,
|
||||||
): Promise<IStrategyConfig> {
|
): Promise<IStrategyConfig> {
|
||||||
|
const { projectId, environment } = context;
|
||||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
if (existingStrategy.id === id) {
|
if (existingStrategy.id === id) {
|
||||||
const strategy = await this.featureStrategiesStore.updateStrategy(
|
const strategy = await this.featureStrategiesStore.updateStrategy(
|
||||||
id,
|
id,
|
||||||
@ -201,7 +242,7 @@ class FeatureToggleServiceV2 {
|
|||||||
};
|
};
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: FEATURE_STRATEGY_UPDATE,
|
type: FEATURE_STRATEGY_UPDATE,
|
||||||
project,
|
project: projectId,
|
||||||
environment,
|
environment,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
@ -211,16 +252,18 @@ class FeatureToggleServiceV2 {
|
|||||||
throw new NotFoundError(`Could not find strategy with id ${id}`);
|
throw new NotFoundError(`Could not find strategy with id ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: verify projectId is not changed from URL!
|
|
||||||
async updateStrategyParameter(
|
async updateStrategyParameter(
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
value: string | number,
|
value: string | number,
|
||||||
|
context: IFeatureStrategyContext,
|
||||||
userName: string,
|
userName: string,
|
||||||
project: string,
|
|
||||||
environment: string,
|
|
||||||
): Promise<IStrategyConfig> {
|
): Promise<IStrategyConfig> {
|
||||||
|
const { projectId, environment } = context;
|
||||||
|
|
||||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
if (existingStrategy.id === id) {
|
if (existingStrategy.id === id) {
|
||||||
existingStrategy.parameters[name] = value;
|
existingStrategy.parameters[name] = value;
|
||||||
const strategy = await this.featureStrategiesStore.updateStrategy(
|
const strategy = await this.featureStrategiesStore.updateStrategy(
|
||||||
@ -235,7 +278,7 @@ class FeatureToggleServiceV2 {
|
|||||||
};
|
};
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: FEATURE_STRATEGY_UPDATE,
|
type: FEATURE_STRATEGY_UPDATE,
|
||||||
project,
|
project: projectId,
|
||||||
environment,
|
environment,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data,
|
data,
|
||||||
@ -251,22 +294,22 @@ class FeatureToggleServiceV2 {
|
|||||||
*
|
*
|
||||||
* }
|
* }
|
||||||
* @param id - strategy id
|
* @param id - strategy id
|
||||||
* @param featureName - Name of the feature the strategy belongs to
|
* @param context - Which context does this strategy live in (projectId, featureName, environment)
|
||||||
* @param userName - Who's doing the change
|
|
||||||
* @param project - Which project does this feature toggle belong to
|
|
||||||
* @param environment - Which environment does this strategy belong to
|
* @param environment - Which environment does this strategy belong to
|
||||||
*/
|
*/
|
||||||
async deleteStrategy(
|
async deleteStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
featureName: string,
|
context: IFeatureStrategyContext,
|
||||||
userName: string,
|
userName: string,
|
||||||
project: string = 'default',
|
|
||||||
environment: string = DEFAULT_ENV,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
|
const { featureName, projectId, environment } = context;
|
||||||
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
await this.featureStrategiesStore.delete(id);
|
await this.featureStrategiesStore.delete(id);
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: FEATURE_STRATEGY_REMOVE,
|
type: FEATURE_STRATEGY_REMOVE,
|
||||||
project,
|
project: projectId,
|
||||||
environment,
|
environment,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: {
|
data: {
|
||||||
@ -274,6 +317,7 @@ class FeatureToggleServiceV2 {
|
|||||||
name: featureName,
|
name: featureName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there are no strategies left for environment disable it
|
// If there are no strategies left for environment disable it
|
||||||
await this.featureEnvironmentStore.disableEnvironmentIfNoStrategies(
|
await this.featureEnvironmentStore.disableEnvironmentIfNoStrategies(
|
||||||
featureName,
|
featureName,
|
||||||
@ -444,10 +488,12 @@ class FeatureToggleServiceV2 {
|
|||||||
createStrategies.push(
|
createStrategies.push(
|
||||||
this.createStrategy(
|
this.createStrategy(
|
||||||
s,
|
s,
|
||||||
projectId,
|
{
|
||||||
newFeatureName,
|
projectId,
|
||||||
|
featureName: newFeatureName,
|
||||||
|
environment: e.name,
|
||||||
|
},
|
||||||
userName,
|
userName,
|
||||||
e.name,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
@ -23,7 +23,7 @@ import ResetTokenService from './reset-token-service';
|
|||||||
import SettingService from './setting-service';
|
import SettingService from './setting-service';
|
||||||
import SessionService from './session-service';
|
import SessionService from './session-service';
|
||||||
import UserFeedbackService from './user-feedback-service';
|
import UserFeedbackService from './user-feedback-service';
|
||||||
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
|
import FeatureToggleService from './feature-toggle-service';
|
||||||
import EnvironmentService from './environment-service';
|
import EnvironmentService from './environment-service';
|
||||||
import FeatureTagService from './feature-tag-service';
|
import FeatureTagService from './feature-tag-service';
|
||||||
import ProjectHealthService from './project-health-service';
|
import ProjectHealthService from './project-health-service';
|
||||||
@ -58,7 +58,7 @@ export const createServices = (
|
|||||||
const versionService = new VersionService(stores, config);
|
const versionService = new VersionService(stores, config);
|
||||||
const healthService = new HealthService(stores, config);
|
const healthService = new HealthService(stores, config);
|
||||||
const userFeedbackService = new UserFeedbackService(stores, config);
|
const userFeedbackService = new UserFeedbackService(stores, config);
|
||||||
const featureToggleServiceV2 = new FeatureToggleServiceV2(stores, config);
|
const featureToggleServiceV2 = new FeatureToggleService(stores, config);
|
||||||
const environmentService = new EnvironmentService(stores, config);
|
const environmentService = new EnvironmentService(stores, config);
|
||||||
const featureTagService = new FeatureTagService(stores, config);
|
const featureTagService = new FeatureTagService(stores, config);
|
||||||
const projectHealthService = new ProjectHealthService(
|
const projectHealthService = new ProjectHealthService(
|
||||||
@ -76,6 +76,7 @@ export const createServices = (
|
|||||||
return {
|
return {
|
||||||
accessService,
|
accessService,
|
||||||
addonService,
|
addonService,
|
||||||
|
featureToggleService: featureToggleServiceV2,
|
||||||
featureToggleServiceV2,
|
featureToggleServiceV2,
|
||||||
featureTypeService,
|
featureTypeService,
|
||||||
healthService,
|
healthService,
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
||||||
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
|
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
|
||||||
import { IProjectStore } from '../types/stores/project-store';
|
import { IProjectStore } from '../types/stores/project-store';
|
||||||
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from './feature-toggle-service';
|
||||||
import { hoursToMilliseconds } from 'date-fns';
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
import Timer = NodeJS.Timer;
|
import Timer = NodeJS.Timer;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-st
|
|||||||
import { IProjectQuery, IProjectStore } from '../types/stores/project-store';
|
import { IProjectQuery, IProjectStore } from '../types/stores/project-store';
|
||||||
import { IRole } from '../types/stores/access-store';
|
import { IRole } from '../types/stores/access-store';
|
||||||
import { IEventStore } from '../types/stores/event-store';
|
import { IEventStore } from '../types/stores/event-store';
|
||||||
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from './feature-toggle-service';
|
||||||
import { CREATE_FEATURE, UPDATE_FEATURE } from '../types/permissions';
|
import { CREATE_FEATURE, UPDATE_FEATURE } from '../types/permissions';
|
||||||
import NoAccessError from '../error/no-access-error';
|
import NoAccessError from '../error/no-access-error';
|
||||||
import IncompatibleProjectError from '../error/incompatible-project-error';
|
import IncompatibleProjectError from '../error/incompatible-project-error';
|
||||||
|
@ -18,7 +18,7 @@ import HealthService from '../services/health-service';
|
|||||||
import SettingService from '../services/setting-service';
|
import SettingService from '../services/setting-service';
|
||||||
import SessionService from '../services/session-service';
|
import SessionService from '../services/session-service';
|
||||||
import UserFeedbackService from '../services/user-feedback-service';
|
import UserFeedbackService from '../services/user-feedback-service';
|
||||||
import FeatureToggleServiceV2 from '../services/feature-toggle-service-v2';
|
import FeatureToggleService from '../services/feature-toggle-service';
|
||||||
import EnvironmentService from '../services/environment-service';
|
import EnvironmentService from '../services/environment-service';
|
||||||
import FeatureTagService from '../services/feature-tag-service';
|
import FeatureTagService from '../services/feature-tag-service';
|
||||||
import ProjectHealthService from '../services/project-health-service';
|
import ProjectHealthService from '../services/project-health-service';
|
||||||
@ -35,7 +35,8 @@ export interface IUnleashServices {
|
|||||||
environmentService: EnvironmentService;
|
environmentService: EnvironmentService;
|
||||||
eventService: EventService;
|
eventService: EventService;
|
||||||
featureTagService: FeatureTagService;
|
featureTagService: FeatureTagService;
|
||||||
featureToggleServiceV2: FeatureToggleServiceV2;
|
featureToggleService: FeatureToggleService;
|
||||||
|
featureToggleServiceV2: FeatureToggleService; // deprecated
|
||||||
featureTypeService: FeatureTypeService;
|
featureTypeService: FeatureTypeService;
|
||||||
healthService: HealthService;
|
healthService: HealthService;
|
||||||
projectHealthService: ProjectHealthService;
|
projectHealthService: ProjectHealthService;
|
||||||
|
@ -98,8 +98,7 @@ test('returns three archived toggles', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('revives a feature by name', async () => {
|
test('revives a feature by name', async () => {
|
||||||
expect.assertions(0);
|
|
||||||
return app.request
|
return app.request
|
||||||
.post('/api/admin/archive/revive/featureArchivedX')
|
.post('/api/admin/archive/revive/featureArchivedX')
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
|
@ -3,6 +3,7 @@ import { FeatureToggleDTO, IStrategyConfig } from 'lib/types/model';
|
|||||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
@ -30,8 +31,7 @@ beforeAll(async () => {
|
|||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createStrategy(
|
await app.services.featureToggleServiceV2.createStrategy(
|
||||||
strategy,
|
strategy,
|
||||||
projectId,
|
{ projectId, featureName: toggle.name, environment: DEFAULT_ENV },
|
||||||
toggle.name,
|
|
||||||
username,
|
username,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -263,6 +263,24 @@ test('can change status of feature toggle that does exist', async () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('cannot change project for feature toggle', async () => {
|
||||||
|
await app.request
|
||||||
|
.put('/api/admin/features/featureY')
|
||||||
|
.send({
|
||||||
|
name: 'featureY',
|
||||||
|
enabled: true,
|
||||||
|
project: 'random', //will be ignored
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
})
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(200);
|
||||||
|
const { body } = await app.request
|
||||||
|
.get('/api/admin/features/featureY')
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body.project).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
test('can not toggle of feature that does not exist', async () => {
|
test('can not toggle of feature that does not exist', async () => {
|
||||||
expect.assertions(0);
|
expect.assertions(0);
|
||||||
return app.request
|
return app.request
|
||||||
@ -285,8 +303,7 @@ test('can toggle a feature that does exist', async () => {
|
|||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createStrategy(
|
await app.services.featureToggleServiceV2.createStrategy(
|
||||||
defaultStrategy,
|
defaultStrategy,
|
||||||
'default',
|
{ projectId: 'default', featureName, environment: DEFAULT_ENV },
|
||||||
featureName,
|
|
||||||
username,
|
username,
|
||||||
);
|
);
|
||||||
return app.request
|
return app.request
|
||||||
|
@ -643,6 +643,145 @@ test('Can add strategy to feature toggle to a "some-env-2"', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Can update strategy on feature toggle', async () => {
|
||||||
|
const envName = 'default';
|
||||||
|
const featureName = 'feature.strategy.update.strat';
|
||||||
|
|
||||||
|
const projectPath = '/api/admin/projects/default';
|
||||||
|
const featurePath = `${projectPath}/features/${featureName}`;
|
||||||
|
|
||||||
|
// create feature toggle
|
||||||
|
await app.request
|
||||||
|
.post(`${projectPath}/features`)
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
// add strategy
|
||||||
|
const { body: strategy } = await app.request
|
||||||
|
.post(`${featurePath}/environments/${envName}/strategies`)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// update strategy
|
||||||
|
await app.request
|
||||||
|
.put(`${featurePath}/environments/${envName}/strategies/${strategy.id}`)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '1234',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const { body } = await app.request.get(`${featurePath}`);
|
||||||
|
const defaultEnv = body.environments[0];
|
||||||
|
expect(body.environments).toHaveLength(1);
|
||||||
|
expect(defaultEnv.name).toBe(envName);
|
||||||
|
expect(defaultEnv.strategies).toHaveLength(1);
|
||||||
|
expect(defaultEnv.strategies[0].parameters).toStrictEqual({
|
||||||
|
userIds: '1234',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can NOT delete strategy with wrong projectId', async () => {
|
||||||
|
const envName = 'default';
|
||||||
|
const featureName = 'feature.strategy.delete.strat.error';
|
||||||
|
|
||||||
|
const projectPath = '/api/admin/projects/default';
|
||||||
|
const featurePath = `${projectPath}/features/${featureName}`;
|
||||||
|
|
||||||
|
// create feature toggle
|
||||||
|
await app.request
|
||||||
|
.post(`${projectPath}/features`)
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
// add strategy
|
||||||
|
const { body: strategy } = await app.request
|
||||||
|
.post(`${featurePath}/environments/${envName}/strategies`)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// delete strategy
|
||||||
|
await app.request
|
||||||
|
.delete(
|
||||||
|
`/api/admin/projects/wrongId/features/${featureName}/environments/${envName}/strategies/${strategy.id}`,
|
||||||
|
)
|
||||||
|
.expect(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add strategy cannot use wrong projectId', async () => {
|
||||||
|
const envName = 'default';
|
||||||
|
const featureName = 'feature.strategy.add.strat.wrong.projectId';
|
||||||
|
|
||||||
|
// create feature toggle
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/projects/default/features')
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
// add strategy
|
||||||
|
await app.request
|
||||||
|
.post(
|
||||||
|
`/api/admin/projects/invalidId/features/${featureName}/environments/${envName}/strategies`,
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update strategy on feature toggle cannot use wrong projectId', async () => {
|
||||||
|
const envName = 'default';
|
||||||
|
const featureName = 'feature.strategy.update.strat.wrong.projectId';
|
||||||
|
|
||||||
|
const projectPath = '/api/admin/projects/default';
|
||||||
|
const featurePath = `${projectPath}/features/${featureName}`;
|
||||||
|
|
||||||
|
// create feature toggle
|
||||||
|
await app.request
|
||||||
|
.post(`${projectPath}/features`)
|
||||||
|
.send({ name: featureName })
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
// add strategy
|
||||||
|
const { body: strategy } = await app.request
|
||||||
|
.post(`${featurePath}/environments/${envName}/strategies`)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// update strategy
|
||||||
|
await app.request
|
||||||
|
.put(
|
||||||
|
`/api/admin/projects/invalidId/features/${featureName}/environments/${envName}/strategies/${strategy.id}`,
|
||||||
|
)
|
||||||
|
.send({
|
||||||
|
name: 'default',
|
||||||
|
parameters: {
|
||||||
|
userIds: '1234',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(403);
|
||||||
|
});
|
||||||
|
|
||||||
test('Environments are returned in sortOrder', async () => {
|
test('Environments are returned in sortOrder', async () => {
|
||||||
const sortedSecond = 'sortedSecond';
|
const sortedSecond = 'sortedSecond';
|
||||||
const sortedLast = 'sortedLast';
|
const sortedLast = 'sortedLast';
|
||||||
|
@ -136,11 +136,11 @@ test('import for 4.1.2 enterprise format fixed works', async () => {
|
|||||||
|
|
||||||
test('Can roundtrip. I.e. export and then import', async () => {
|
test('Can roundtrip. I.e. export and then import', async () => {
|
||||||
const projectId = 'export-project';
|
const projectId = 'export-project';
|
||||||
const environmentId = 'export-environment';
|
const environment = 'export-environment';
|
||||||
const userName = 'export-user';
|
const userName = 'export-user';
|
||||||
const featureName = 'export.feature';
|
const featureName = 'export.feature';
|
||||||
await db.stores.environmentStore.create({
|
await db.stores.environmentStore.create({
|
||||||
name: environmentId,
|
name: environment,
|
||||||
type: 'test',
|
type: 'test',
|
||||||
});
|
});
|
||||||
await db.stores.projectStore.create({
|
await db.stores.projectStore.create({
|
||||||
@ -149,7 +149,7 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
|||||||
description: 'Project for export',
|
description: 'Project for export',
|
||||||
});
|
});
|
||||||
await app.services.environmentService.addEnvironmentToProject(
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
environmentId,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
@ -169,9 +169,8 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
|||||||
],
|
],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
projectId,
|
{ projectId, featureName, environment },
|
||||||
featureName,
|
userName,
|
||||||
environmentId,
|
|
||||||
);
|
);
|
||||||
const data = await app.services.stateService.export({});
|
const data = await app.services.stateService.export({});
|
||||||
await app.services.stateService.import({
|
await app.services.stateService.import({
|
||||||
@ -184,11 +183,11 @@ test('Can roundtrip. I.e. export and then import', async () => {
|
|||||||
|
|
||||||
test('Roundtrip with tags works', async () => {
|
test('Roundtrip with tags works', async () => {
|
||||||
const projectId = 'tags-project';
|
const projectId = 'tags-project';
|
||||||
const environmentId = 'tags-environment';
|
const environment = 'tags-environment';
|
||||||
const userName = 'tags-user';
|
const userName = 'tags-user';
|
||||||
const featureName = 'tags.feature';
|
const featureName = 'tags.feature';
|
||||||
await db.stores.environmentStore.create({
|
await db.stores.environmentStore.create({
|
||||||
name: environmentId,
|
name: environment,
|
||||||
type: 'test',
|
type: 'test',
|
||||||
});
|
});
|
||||||
await db.stores.projectStore.create({
|
await db.stores.projectStore.create({
|
||||||
@ -197,7 +196,7 @@ test('Roundtrip with tags works', async () => {
|
|||||||
description: 'Project for export',
|
description: 'Project for export',
|
||||||
});
|
});
|
||||||
await app.services.environmentService.addEnvironmentToProject(
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
environmentId,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
@ -217,9 +216,12 @@ test('Roundtrip with tags works', async () => {
|
|||||||
],
|
],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
projectId,
|
{
|
||||||
featureName,
|
projectId,
|
||||||
environmentId,
|
featureName,
|
||||||
|
environment,
|
||||||
|
},
|
||||||
|
userName,
|
||||||
);
|
);
|
||||||
await app.services.featureTagService.addTag(
|
await app.services.featureTagService.addTag(
|
||||||
featureName,
|
featureName,
|
||||||
@ -245,11 +247,11 @@ test('Roundtrip with tags works', async () => {
|
|||||||
|
|
||||||
test('Roundtrip with strategies in multiple environments works', async () => {
|
test('Roundtrip with strategies in multiple environments works', async () => {
|
||||||
const projectId = 'multiple-environment-project';
|
const projectId = 'multiple-environment-project';
|
||||||
const environmentId = 'multiple-environment-environment';
|
const environment = 'multiple-environment-environment';
|
||||||
const userName = 'multiple-environment-user';
|
const userName = 'multiple-environment-user';
|
||||||
const featureName = 'multiple-environment.feature';
|
const featureName = 'multiple-environment.feature';
|
||||||
await db.stores.environmentStore.create({
|
await db.stores.environmentStore.create({
|
||||||
name: environmentId,
|
name: environment,
|
||||||
type: 'test',
|
type: 'test',
|
||||||
});
|
});
|
||||||
await db.stores.projectStore.create({
|
await db.stores.projectStore.create({
|
||||||
@ -258,7 +260,7 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
|||||||
description: 'Project for export',
|
description: 'Project for export',
|
||||||
});
|
});
|
||||||
await app.services.environmentService.addEnvironmentToProject(
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
environmentId,
|
environment,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
await app.services.environmentService.addEnvironmentToProject(
|
await app.services.environmentService.addEnvironmentToProject(
|
||||||
@ -282,9 +284,8 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
|||||||
],
|
],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
projectId,
|
{ projectId, featureName, environment },
|
||||||
featureName,
|
userName,
|
||||||
environmentId,
|
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createStrategy(
|
await app.services.featureToggleServiceV2.createStrategy(
|
||||||
{
|
{
|
||||||
@ -294,9 +295,8 @@ test('Roundtrip with strategies in multiple environments works', async () => {
|
|||||||
],
|
],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
projectId,
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
featureName,
|
userName,
|
||||||
DEFAULT_ENV,
|
|
||||||
);
|
);
|
||||||
const data = await app.services.stateService.export({});
|
const data = await app.services.stateService.export({});
|
||||||
await app.services.stateService.import({
|
await app.services.stateService.import({
|
||||||
|
@ -1,30 +1,32 @@
|
|||||||
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
|
||||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
|
||||||
const featureName = 'feature.default.1';
|
const featureName = 'feature.default.1';
|
||||||
|
const username = 'test';
|
||||||
|
const projectId = 'default';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_env_api_client', getLogger);
|
db = await dbInit('feature_env_api_client', getLogger);
|
||||||
app = await setupApp(db.stores);
|
app = await setupApp(db.stores);
|
||||||
|
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
'default',
|
projectId,
|
||||||
{
|
{
|
||||||
name: featureName,
|
name: featureName,
|
||||||
description: 'the #1 feature',
|
description: 'the #1 feature',
|
||||||
},
|
},
|
||||||
'test',
|
username,
|
||||||
);
|
);
|
||||||
|
|
||||||
await app.services.featureToggleServiceV2.createStrategy(
|
await app.services.featureToggleServiceV2.createStrategy(
|
||||||
{ name: 'default', constraints: [], parameters: {} },
|
{ name: 'default', constraints: [], parameters: {} },
|
||||||
'default',
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
featureName,
|
username,
|
||||||
'test',
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,8 +55,7 @@ beforeAll(async () => {
|
|||||||
constraints: [],
|
constraints: [],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
project,
|
{ projectId: project, featureName: feature1, environment: DEFAULT_ENV },
|
||||||
feature1,
|
|
||||||
username,
|
username,
|
||||||
);
|
);
|
||||||
await featureToggleServiceV2.createStrategy(
|
await featureToggleServiceV2.createStrategy(
|
||||||
@ -65,10 +64,8 @@ beforeAll(async () => {
|
|||||||
constraints: [],
|
constraints: [],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
project,
|
{ projectId: project, featureName: feature1, environment },
|
||||||
feature1,
|
|
||||||
username,
|
username,
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// create feature 2
|
// create feature 2
|
||||||
@ -85,10 +82,8 @@ beforeAll(async () => {
|
|||||||
constraints: [],
|
constraints: [],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
project,
|
{ projectId: project, featureName: feature2, environment },
|
||||||
feature2,
|
|
||||||
username,
|
username,
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// create feature 3
|
// create feature 3
|
||||||
@ -105,10 +100,8 @@ beforeAll(async () => {
|
|||||||
constraints: [],
|
constraints: [],
|
||||||
parameters: {},
|
parameters: {},
|
||||||
},
|
},
|
||||||
project2,
|
{ projectId: project2, featureName: feature3, environment },
|
||||||
feature3,
|
|
||||||
username,
|
username,
|
||||||
environment,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service';
|
||||||
import { IStrategyConfig } from '../../../lib/types/model';
|
import { IStrategyConfig } from '../../../lib/types/model';
|
||||||
import { createTestConfig } from '../../config/test-config';
|
import { createTestConfig } from '../../config/test-config';
|
||||||
import dbInit from '../helpers/database-init';
|
import dbInit from '../helpers/database-init';
|
||||||
@ -41,8 +41,7 @@ test('Should create feature toggle strategy configuration', async () => {
|
|||||||
|
|
||||||
const createdConfig = await service.createStrategy(
|
const createdConfig = await service.createStrategy(
|
||||||
config,
|
config,
|
||||||
projectId,
|
{ projectId, featureName: 'Demo', environment: DEFAULT_ENV },
|
||||||
'Demo',
|
|
||||||
username,
|
username,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -53,6 +52,7 @@ test('Should create feature toggle strategy configuration', async () => {
|
|||||||
test('Should be able to update existing strategy configuration', async () => {
|
test('Should be able to update existing strategy configuration', async () => {
|
||||||
const projectId = 'default';
|
const projectId = 'default';
|
||||||
const username = 'existing-strategy';
|
const username = 'existing-strategy';
|
||||||
|
const featureName = 'update-existing-strategy';
|
||||||
const config: Omit<IStrategyConfig, 'id'> = {
|
const config: Omit<IStrategyConfig, 'id'> = {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
@ -62,32 +62,33 @@ test('Should be able to update existing strategy configuration', async () => {
|
|||||||
await service.createFeatureToggle(
|
await service.createFeatureToggle(
|
||||||
projectId,
|
projectId,
|
||||||
{
|
{
|
||||||
name: 'update-existing-strategy',
|
name: featureName,
|
||||||
},
|
},
|
||||||
'test',
|
'test',
|
||||||
);
|
);
|
||||||
|
|
||||||
const createdConfig = await service.createStrategy(
|
const createdConfig = await service.createStrategy(
|
||||||
config,
|
config,
|
||||||
'default',
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
'update-existing-strategy',
|
|
||||||
username,
|
username,
|
||||||
);
|
);
|
||||||
expect(createdConfig.name).toEqual('default');
|
expect(createdConfig.name).toEqual('default');
|
||||||
const updatedConfig = await service.updateStrategy(
|
const updatedConfig = await service.updateStrategy(
|
||||||
createdConfig.id,
|
createdConfig.id,
|
||||||
DEFAULT_ENV,
|
|
||||||
projectId,
|
|
||||||
username,
|
|
||||||
{
|
{
|
||||||
parameters: { b2b: true },
|
parameters: { b2b: true },
|
||||||
},
|
},
|
||||||
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
|
username,
|
||||||
);
|
);
|
||||||
expect(createdConfig.id).toEqual(updatedConfig.id);
|
expect(createdConfig.id).toEqual(updatedConfig.id);
|
||||||
expect(updatedConfig.parameters).toEqual({ b2b: true });
|
expect(updatedConfig.parameters).toEqual({ b2b: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should be able to get strategy by id', async () => {
|
test('Should be able to get strategy by id', async () => {
|
||||||
|
const featureName = 'get-strategy-by-id';
|
||||||
|
const projectId = 'default';
|
||||||
|
|
||||||
const userName = 'strategy';
|
const userName = 'strategy';
|
||||||
const config: Omit<IStrategyConfig, 'id'> = {
|
const config: Omit<IStrategyConfig, 'id'> = {
|
||||||
name: 'default',
|
name: 'default',
|
||||||
@ -95,19 +96,17 @@ test('Should be able to get strategy by id', async () => {
|
|||||||
parameters: {},
|
parameters: {},
|
||||||
};
|
};
|
||||||
await service.createFeatureToggle(
|
await service.createFeatureToggle(
|
||||||
'default',
|
projectId,
|
||||||
{
|
{
|
||||||
name: 'get-strategy-by-id',
|
name: featureName,
|
||||||
},
|
},
|
||||||
'test',
|
userName,
|
||||||
);
|
);
|
||||||
|
|
||||||
const createdConfig = await service.createStrategy(
|
const createdConfig = await service.createStrategy(
|
||||||
config,
|
config,
|
||||||
'default',
|
{ projectId, featureName, environment: DEFAULT_ENV },
|
||||||
'Demo',
|
|
||||||
userName,
|
userName,
|
||||||
DEFAULT_ENV,
|
|
||||||
);
|
);
|
||||||
const fetchedConfig = await service.getStrategy(createdConfig.id);
|
const fetchedConfig = await service.getStrategy(createdConfig.id);
|
||||||
expect(fetchedConfig).toEqual(createdConfig);
|
expect(fetchedConfig).toEqual(createdConfig);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import dbInit, { ITestDb } from '../helpers/database-init';
|
import dbInit, { ITestDb } from '../helpers/database-init';
|
||||||
import getLogger from '../../fixtures/no-logger';
|
import getLogger from '../../fixtures/no-logger';
|
||||||
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service';
|
||||||
import { AccessService } from '../../../lib/services/access-service';
|
import { AccessService } from '../../../lib/services/access-service';
|
||||||
import ProjectService from '../../../lib/services/project-service';
|
import ProjectService from '../../../lib/services/project-service';
|
||||||
import ProjectHealthService from '../../../lib/services/project-health-service';
|
import ProjectHealthService from '../../../lib/services/project-health-service';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import dbInit, { ITestDb } from '../helpers/database-init';
|
import dbInit, { ITestDb } from '../helpers/database-init';
|
||||||
import getLogger from '../../fixtures/no-logger';
|
import getLogger from '../../fixtures/no-logger';
|
||||||
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service';
|
||||||
import ProjectService from '../../../lib/services/project-service';
|
import ProjectService from '../../../lib/services/project-service';
|
||||||
import { AccessService } from '../../../lib/services/access-service';
|
import { AccessService } from '../../../lib/services/access-service';
|
||||||
import {
|
import {
|
||||||
|
@ -6,6 +6,7 @@ let db;
|
|||||||
let featureToggleStore;
|
let featureToggleStore;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
getLogger.setMuteError(true);
|
||||||
db = await dbInit('feature_toggle_store_serial', getLogger);
|
db = await dbInit('feature_toggle_store_serial', getLogger);
|
||||||
stores = db.stores;
|
stores = db.stores;
|
||||||
featureToggleStore = stores.featureToggleStore;
|
featureToggleStore = stores.featureToggleStore;
|
||||||
@ -23,7 +24,6 @@ test('should not crash for unknown toggle', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should not crash for undefined toggle name', async () => {
|
test('should not crash for undefined toggle name', async () => {
|
||||||
getLogger.setMuteError(true);
|
|
||||||
const project = await featureToggleStore.getProjectId(undefined);
|
const project = await featureToggleStore.getProjectId(undefined);
|
||||||
expect(project).toBe(undefined);
|
expect(project).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user